Qt与FFmpeg开发指南(二)-- 本地视频的解码

一、开发前的准备工作(见文末链接)

开发工具为Qt5.8,目录结构:

  • bin:工作和测试目录
  • include:ffmpeg头文件配置目录
  • lib:ffmpeg静态库配置目录
  • ......

二、编解码基础知识

(1)封装格式

指音视频的组合格式,常见的封装格式(带后缀的音视频文件)有wmv,avi,rmvb,mp4,mp3,flv,mkv等等

(2)编码格式

就像wmv这种格式而言,一般包含有视频和音频。视频的编码格式为YUV420P,音频的编码格式为PCM。所以通常我们说的编码过程就包括:画面采集、转码、编码再封装。

(3)视频解码和音频解码有什么区别

一些高速摄像机的采集速度能够达到12000帧/秒,那么在播放这类影片的时候我们是否也需要以12000帧/秒播放呢?当然不是,通常我们会按照25帧/秒或者60帧/秒设定图像的FPS值。但是由于视频中关键帧保存了完整的画面而过渡帧只是保存了与前一帧画面的变化部分,需要通过关键帧计算获得。因此我们需要对每一帧都进行解码,即获取画面的YUV数据。同时只对我们真正需要显示的画面进行转码,即将YUV数据转换成RGB数据,包括计算画面的宽高等。

而音频的播放必须和采集保持同步。提高或降低音频的播放速度都会让音质发生变化,这也是变声器的原理。因此在实际开发中为了保证播放的音视频同步,我们往往会按照音频的播放速度来控制视频的解码转码速度。

( 4 ) 框架图

 三、步骤及代码

1、注册所有组件:av_register_all()

2、打开视频文件

    //参数1:封装格式上下文->AVFormatContext结构体->包含了视频信息(视频格式、大小等)
    //参数2:要打开流的路径(文件名)
    AVFormatContext  * formatContent = avformat_alloc_context();
    char * filename="Warcraft3_End.avi";
    /*2、打开视频文件*/
    int res = avformat_open_input(&formatContent,filename,nullptr,nullptr);
    if(res!=0)
    {
        char *err = new char[32];//显示异常的信息
        av_strerror(res,err,1024);
        qDebug()<<QString("错误的信息:%1").arg(err);
        //qDebug()<<"打开流媒体信息";
        //return ;

    }

3、获取视频文件(视频流)信息

    //参数一:封装格式文件上下文->AVFormatContext。
    //参数二:字典,一些配置选项。
    res = avformat_find_stream_info(pFormatContext,NULL);//获取视频文件信息(视频流信息)
    if(res<0)//进行判断的原因是有可能打开普通文件
    {
        char *err = new char[1024];//显示异常的信息
        av_strerror(res,err,1024);
        qDebug()<<QString("错误的信息:%1").arg(err);
    }

4、查找解码器

    //第一点:获取当前解码器是属于什么类型解码器->找到视频流
    //音频、视频、字幕解码器等等...
    int videoType = -1;
    for (int i = 0; i < formatContent->nb_streams; ++i) {
        //循环遍历每一流,包括视频流、音频流、字幕流等等...
        //streams->AVStream->codec
        if (formatContent->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO){
            //找到视频流(只有一个)
            videoType = i;//标识视频流这个类型
            break;
        }
    }
    if (videoType== -1){
        qDebug()<<QString("没有找到视频流相关信息");
        return ;
    }
    //第二点:根据视频流->查找到视频解码器上下文->视频压缩数据(宽高格式等)
    AVCodecContext* codec= formatContent->streams[videoType]->codec;
 
    //第三点:有视频流,则查找对应视频流的解码器
    AVCodec *decoder = avcodec_find_decoder(codec->codec_id);//需要解码器的id
    if(decoder ==nullptr)
    {
        qDebug()<<"没有找到对应的视频解码器";
        return;
    }

5、打开解码器

    //参数:1.初始化的上下文对象 2.打开的解码器 3.类似目录的东西(没有)
    res = avcodec_open2(codec,decoder,nullptr);
    if(res!=0)
    {
        //qDebug()<<"解码器打开失败";
        //return;
        char* error_info = new char[32];
        av_strerror(avformat_find_stream_info_result, error_info, 1024);
        qDebug()<<QString("异常信息 %1").arg(error_info);
        return false;
    }    

以上基本就是打开多媒体文件的主要步骤,解码和转码的所有参数都可以在这里获取。接下来我们就需要循环进行读取、解码、转码直到播放完成。

6、循环读取视频帧,进行循环解码,转码输出YUV420P视频

    FILE *fp = fopen("saveH264.h264","wb+");
    FILE *fpYUV = fopen("saveYUV.yuv","wb+");
    AVPacket *pkt=nullptr;//pkt这时没有指向,要我们给他分配内存空间
    pkt = (AVPacket *)malloc(sizeof(AVPacket));//读取帧数据换成到哪里?->缓存到packet里面
    //开空间不知道一帧的码流数据是多少?其实编解码器告诉了宽高,以此可以计算出给码流数据开多大空间
    int bufSize = codec->width*codec->height;//计算一帧(类似一张图)数据的大小
    av_new_packet(pkt,bufSize);

    AVFrame *pictureYUV = nullptr;//保存解码及剔除后的像素数据(做准备)
    pictureYUV = av_frame_alloc();
    pictureYUV->width = codec->width;
    pictureYUV->height = codec->height;
    pictureYUV->format = codec->pix_fmt;//格式的设置
    //要存解码后的像素数据到pictureYUV,那这个数据有多大呢?
    //获取解码后的一帧像素数据有多大
    int numByte = avpicture_get_size(AV_PIX_FMT_YUV420P,codec->width,codec->height);
    //开的空间用来保存像素数据的大小
    uint8_t *buffer = (uint8_t *)av_malloc(numByte*sizeof(uint8_t));
    //像素数据填充到AVFrame的pictureYUV里
    avpicture_fill((AVPicture *)pictureYUV,buffer,AV_PIX_FMT_YUV420P,codec->width,codec->height);
    //因为解码之后要伸展,所以先进行转换规则的设置,转换完进入第七步解码
    SwsContext *swsContent = nullptr;
    swsContent = sws_getContext(codec->width,codec->height,codec->pix_fmt,
                                codec->width,codec->height,AV_PIX_FMT_YUV420P,
                                SWS_BICUBIC,nullptr,nullptr,nullptr);


           
    while(av_read_frame(formatContent,pkt) >= 0)//>=0:说明有数据,继续读取   <0:说明读取完毕,结束
    {
        /*判断读到的每一帧的码流数据是不是视频流*/
        if(pkt->stream_index == videoType)
        {
            //是视频流则写到文件中(saveH264.h264)
            fwrite(pkt->data,pkt->size,1,fp);//每次写一个结构体
            //读到一帧是视频流就进行解码的动作
            /*avcodec_decode_video2:解码得到YUV,保存在AVFrame结构体里*/
                //参数:1编解码器上下文对象的结构体 2存放解码后的像素数据(AVFrame类型)
                        //3判断有没有数据可以进行解码:指针类型的变量 4对谁进行解码:一帧的码流数据
            //功能:把得到的一帧码流数据用编解码器上下文对象去解,存放在AVFrame结构体里
            int got_picture_ptr = -1;
            AVFrame *picture = av_frame_alloc();//保存原始yuv像素数据
            avcodec_decode_video2(codec,picture,&got_picture_ptr,pkt);
            if(got_picture_ptr != 0)
            {
                //把解码得到的损坏的像素数据剔除存到pictureYUV中
                //data是数组,把Y U V 三份数据分开存储  
                sws_scale(swsContent,picture->data,picture->linesize,0,picture->height,
                          pictureYUV->data,pictureYUV->linesize);
                //因为所有的像素数据都有完整的亮点数据,所有Y保存的是bufSize的大小,其他的都是1/4
                fwrite(pictureYUV->data[0],bufSize,1,fpYUV);//写入->Y
                fwrite(pictureYUV->data[1],bufSize/4,1,fpYUV);//写入->U
                fwrite(pictureYUV->data[2],bufSize/4,1,fpYUV);//写入->V

            }


        }

7、关闭解码组件

    av_packet_unref(pkt);//此处不是free
    //关闭流
    fclose(fp);
    fclose(fpYUV);
    av_frame_free(&picture);
    av_frame_free(&pictureYUV);
    avcodec_close(codec);
    avformat_free_context(formatContent);

四、实现效果

解码后用YUV Player Deluxe 软件查看存储到本地的yuv视频文件

开发前的准备工作(详见Qt与FFmpeg开发指南(一) 

Qt与FFmpeg开发指南(一)--Windows下环境搭建_hml111666的博客-CSDN博客

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ze言

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值