使用FFMPEG3.4.2版本进行视频的解码为YUV格式

卢德惠
2023-12-01

本文章是转载开源团队路过之君的博客,最他移植ffmpeg解码器到我们的视频项目中。全文如下:

最近关注的流媒体方向技术,FFMPEG学习是绕不过去的,不过网上关于FFMPEG的应用基本是基于2015年左右的版本,最新的FFMPEG视频解码部分变动还是挺大的,特此记录一下解码过程:

首先当然是FFMPEG的解码器等的初始化:

// 一定要先调用该注册FFMPEG,否则下面的都无法使用

av_register_all();

// 调用该初始化函数才能读取RTSP/RTMP等网络流的协议数据,否则干瞪眼,只解码本地文件的可以无视

avformat_network_init();

初始化完后需要申请一个AVFormatContext对象  *pFormatCtx = avformat_alloc_context();用于解码的上下文,有点FFMPEG基础的都明白这是老套路了。

然后呢还是老套路,调用以下两个函数:

// decodeUrl即为你要进行解码的链接,可以是本地文件路径,也可以是RTSP/RTMP等网络视频流的URL路径

avformat_open_input(&pFormatCtx, decodeUrl, NULL, NULL);

avformat_find_stream_info(pFormatCtx, NULL);

然后就在pFormatCtx->nb_streams找对应的解码类型为视频格式的解码器,这里跟老版本是有点不一样的,codecpar是新推荐的成员变量:

videoindex = -1;
    for (uint32_t i = 0; i < pFormatCtx->nb_streams; i++)
    {
        if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            videoindex = i;
            break;
        }

    }

接下来还是有点区别的,不过一般人还是可以靠自己解决的

AVCodec  *pCodec = avcodec_find_decoder(pFormatCtx->streams[videoindex]->codecpar->codec_id);
    if (pCodec == NULL)
    {
        return -1;
    }

AVCodecContext  *pCodecCtx = avcodec_alloc_context3(pCodec);

获取视频解码器并创建解码器上下文,很好理解,套路一样就是细节改了改;

下面就开始打开解码器了,这个没变:

avcodec_open2(pCodecCtx, pCodec, NULL);

然后创建视频帧及YUV帧对象跟数据包对象

AVFrame *pFrame = av_frame_alloc();
AVFrame *pFrameYUV = av_frame_alloc();
AVPacket *packet = (AVPacket *)av_malloc(sizeof(AVPacket));

接下来就是循环读取数据并解码了

[cpp]  view plain  copy
  1. while (av_read_frame(pFormatCtx, packet) >= 0)  
  2. {  
  3.  if (packet->stream_index == videoindex)  
  4. {  
  5. // 视频帧进行解码,注意这里很不一样了,之前一个函数调用即可,现在需要自己再次写一个解码函数,具体看下面函数实现  
  6.  ret = Decode(pCodecCtx, pFrame, packet);  
  7.             if (ret < 0)  
  8.             {  
  9.                 printf("Decode Error.\n");  
  10.                 av_packet_unref(packet);  
  11.                 continue;  
  12.             }  
  13.   
  14. // 这里即为解码出来的数据进行YUV转换存储  
  15.             y_size = pCodecCtx->width*pCodecCtx->height;  
  16.             fwrite(pFrame->data[0], 1, y_size, fp_yuv);      //Y   
  17.             fwrite(pFrame->data[1], 1, y_size / 4, fp_yuv);  //U  
  18.             fwrite(pFrame->data[2], 1, y_size / 4, fp_yuv);  //V  
  19.             printf("Write one frame...\n");  
  20.         }  
  21. // 用完了别忘了释放packet  
  22.  av_packet_unref(packet);  
  23. }  

上面的没啥好说的,主要是Decode函数的实现不一样了,改动挺大的,下面贴出实现代码

[cpp]  view plain  copy
  1. int Decode(AVCodecContext *dec_ctx, AVFrame *frame, AVPacket *pkt)  
  2. {  
  3.     int ret;  
  4.   
  5.         // 先发送包数据到解码上下文中  
  6.     ret = avcodec_send_packet(dec_ctx, pkt);  
  7.     if (ret < 0)  
  8.     {  
  9.         printf("Error sending a packet for decoding\n");  
  10.         return ret;  
  11.     }  
  12.   
  13.         // 然后从解码上下文中读取帧数据到frame对象中  
  14.     return avcodec_receive_frame(dec_ctx, frame);  
  15. }  

不知道官方基于何种意愿做此修改的,个人觉着麻烦多了,因为我要多写不少代码。。。

至此解码工作基本完成,当然用完了资源别忘了还给人家:

av_frame_free(&pFrameYUV);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx);

avformat_close_input(&pFormatCtx);

然后测试了下本地视频文件的解码,测试OK,做完后觉着很不错嘛,然而,然而真的那么如你所愿么?显然不是的。。。

在我一时兴起测试RTSP流的时候发现FFMPEG会报错,无法获取,提示什么FFMPEG编译没有添加-lpthread之类的选项,这是什么鬼?懵逼了,因为我是从官方下载的动态链接库,这是你会发现还是GG好使的。。。话不多说,贴解决办法:

在avformat_open_input函数调用中最后一个options参数进行设置RTSP传输基于TCP即可。。。

AVDictionary* options = NULL;
av_dict_set(&options, "rtsp_transport", "tcp", 0);

avformat_open_input(&pFormatCtx, c_TestFile, NULL, &options);

是的,这么设置过后就可以解码RTSP流数据了,知道了后是不是很简单,当然用完了资源记得一定要还:

av_dict_free(&options);

好的,想到的就这么多了。。。

https://blog.csdn.net/xinxinsky/article/details/79823971

car-eye 开源网站:www.car-eye.cn



 类似资料: