最近关注的流媒体方向技术,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));
接下来就是循环读取数据并解码了
while (av_read_frame(pFormatCtx, packet) >= 0)
{
if (packet->stream_index == videoindex)
{
// 视频帧进行解码,注意这里很不一样了,之前一个函数调用即可,现在需要自己再次写一个解码函数,具体看下面函数实现
ret = Decode(pCodecCtx, pFrame, packet);
if (ret < 0)
{
printf("Decode Error.\n");
av_packet_unref(packet);
continue;
}
// 这里即为解码出来的数据进行YUV转换存储
y_size = pCodecCtx->width*pCodecCtx->height;
fwrite(pFrame->data[0], 1, y_size, fp_yuv); //Y
fwrite(pFrame->data[1], 1, y_size / 4, fp_yuv); //U
fwrite(pFrame->data[2], 1, y_size / 4, fp_yuv); //V
printf("Write one frame...\n");
}
// 用完了别忘了释放packet
av_packet_unref(packet);
}
上面的没啥好说的,主要是Decode函数的实现不一样了,改动挺大的,下面贴出实现代码
int Decode(AVCodecContext *dec_ctx, AVFrame *frame, AVPacket *pkt)
{
int ret;
// 先发送包数据到解码上下文中
ret = avcodec_send_packet(dec_ctx, pkt);
if (ret < 0)
{
printf("Error sending a packet for decoding\n");
return ret;
}
// 然后从解码上下文中读取帧数据到frame对象中
return avcodec_receive_frame(dec_ctx, frame);
}
不知道官方基于何种意愿做此修改的,个人觉着麻烦多了,因为我要多写不少代码。。。
至此解码工作基本完成,当然用完了资源别忘了还给人家:
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);
好的,想到的就这么多了。。。