这第二章主要说明一下流程和使用函数、结构体变量的功能参数。
至于源代码看这位W2Y大神的代码,之前我还想好好研究的,奈何看到了这位大神的代码,直接满足了工作需求,但还是将我从中研究到的FFmpeg知识分享出来。
第一节 解码流程图
解码上下文(环境)初始化
帧格式初始化:
解码步骤:
至此解码结束,但是某些细节和释放空间等并没有写在上面的流程图上。
至于将其播放,只要将最终得到的帧通过信号与槽传到新界面上,然后转为QImage格式不停刷上去就行。
第二节 变量及函数简单解析
//本人提醒
//要注意别在解码过程中突然改变解码中需要用到的结构体或者内存,会崩溃。
//先暂停视频流,再等待解码结束就即时释放结构体和内存,然后进行下一个设备
//这是下面的输出内存申请,如果低于frame_header->width*frame_header->height * sizeof(uint8_t)*3将会崩溃
outRGBBuf = (uint8_t *)av_malloc(frame_header->width*frame_header->height * sizeof(uint8_t)*3+1);
//编解码器,类似于接口的存在
struct AVCodec *pAVCodec_decoder;
//编解码器上下文,主要存储解码器的相关设置内容
struct AVCodecContext *pAVCodecCtx_decoder = NULL;
//压缩的帧,适用于编码后解码前的帧
struct AVPacket mAVPacket_decoder;
//原始数据,适用于编码前解码后的帧
//pAVFrame_decoder 存储的初始解码后的数据,即YUV格式
struct AVFrame *pAVFrame_decoder = NULL;
//pFrameYUV_decoder 存储的转化后的数据,即RGB格式
struct AVFrame *pFrameYUV_decoder = NULL;
//转化格式上下文,主要存储根据参数生成的转化需要的相关设置内容
struct SwsContext* pImageConvertCtx_decoder = NULL;
初始化
{
//属性弃用,不需要使用,因为在新版本中会自行注册
//avcodec_register_all();
//avcodec_find_decoder,查找解码器,使用相对应的解码器应该在此判断并调用相对应的解码器
//AVCodec代表着一个查找到的解码器,相当于对应接口
AVCodec *pAVCodec_decoder = avcodec_find_decoder(AV_CODEC_ID_H264);
//AVCodec *pAVCodec_decoder= avcodec_find_decoder(AV_CODEC_ID_H265);
//avcodec_alloc_context3分配一个以pAVCodec对应解码/编码器为主要内容的AVCodecContext空间
//AVCodecContext对应编解码器的内容
AVCodecContext *pAVCodecCtx_decoder= avcodec_alloc_context3(pAVCodec_decoder);
//avcodec_parameters_to_context从AVCodecParameters *中获取关于对应编解码器的参数到AVCodecContext *中
avcodec_parameters_to_context(pAVCodecCtx_decoder, codecParameters)
//根据AVCodec *正式初始化AVCodecContext *,如为各结构体分配内存等
avcodec_open2(pAVCodecCtx_decoder, pAVCodec_decoder, NULL)
//解码帧,解码后帧的初始化
av_init_packet(&mAVPacket_decoder);
pAVFrame_decoder = av_frame_alloc();
pFrameYUV_decoder = av_frame_alloc();
}
FFmpeg_H264Decode(unsigned char *inbuf, int inbufSize, int *framePara, unsigned char *outRGBBuf, unsigned char **outYUVBuf)
{
if (!pAVCodecCtx_decoder || !pAVFrame_decoder || !inbuf || inbufSize<=0 || !framePara || (!outRGBBuf && !outYUVBuf)) {
return -1;
}
//重置解码输出后的帧格式
av_frame_unref(pAVFrame_decoder);
av_frame_unref(pFrameYUV_decoder);
framePara[0] = framePara[1] = 0;
//将输入的用uchar*储存的数据,导入进解码帧里
mAVPacket_decoder.data = inbuf;
mAVPacket_decoder.size = inbufSize;
//将需要解码的帧mAVPacket_decoder以导进AVCodecContext *初始化好对应的解码器内容来解码
int ret = avcodec_send_packet(pAVCodecCtx_decoder, &mAVPacket_decoder);
if (ret == 0) {
//以AVCodecContext *内容解码出来的YUV帧导出到pAVFrame_decoder中
ret = avcodec_receive_frame(pAVCodecCtx_decoder, pAVFrame_decoder);
if (ret == 0) {
framePara[0] = pAVFrame_decoder->width;
framePara[1] = pAVFrame_decoder->height;
//这是YUV数据帧
if (outYUVBuf) {
//导进YUVBuf里
*outYUVBuf = (unsigned char *)pAVFrame_decoder->data;
framePara[2] = pAVFrame_decoder->linesize[0];
framePara[3] = pAVFrame_decoder->linesize[1];
framePara[4] = pAVFrame_decoder->linesize[2];
} else if (outRGBBuf) {
//将需要导出的内存空间放进从YUV数据帧转化的RGB帧即pFrameYUV_decoder里
pFrameYUV_decoder->data[0] = outRGBBuf;
pFrameYUV_decoder->data[1] = NULL;
pFrameYUV_decoder->data[2] = NULL;
pFrameYUV_decoder->data[3] = NULL;
//导出的相关格式设置
int linesize[4] = { pAVCodecCtx_decoder->width * 3, pAVCodecCtx_decoder->height * 3, 0, 0 };
//sws_getContext获取对应的SwsContext *转化格式上下文,类似于解码的解码器格式AVCodecContext
pImageConvertCtx_decoder = sws_getContext(pAVCodecCtx_decoder->width, pAVCodecCtx_decoder->height, AV_PIX_FMT_YUV420P, pAVCodecCtx_decoder->width, pAVCodecCtx_decoder->height, AV_PIX_FMT_RGB24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
//sws_scale作帧转换
sws_scale(pImageConvertCtx_decoder, (const uint8_t* const *) pAVFrame_decoder->data, pAVFrame_decoder->linesize, 0, pAVCodecCtx_decoder->height, pFrameYUV_decoder->data, linesize);
sws_freeContext(pImageConvertCtx_decoder);
return 1;
}
} else if (ret == AVERROR(EAGAIN)) {
return 0;
} else {
return -1;
}
}
return 0;
}
以上的代码内容绝大部分源自于开头时写的那位大神代码,其他内容是自己研究后得出的一些学习总结。
老实说,“上下文”我觉得应该解释为“环境”或者“内容”更为贴切。
网上关于FFmpeg解码的内容过多,基本都能找到,rtsp的,读取视频文件等的其实也可以使用这套代码,只要将AVCodecParameters从视频流中取出来就行。
avformat_open_input
avformat_find_stream_info
AVStream * = AVFormatContext->streams[i];
上述只针对单独解码器,主在视频而非音频,音频在原代码上修改下需求即可
注:不能只初始化一个编码格式就同时解码两个格式的,
比如说一开始初始化解码器解码H264,那么这个解码器的数据就是针对H264的了,不能简单的更改一下avcodec_find_decoder的编解码器就实现切换不同的格式解码;
要对每一种streams格式,单独初始化一次并保留在对应的对象里,判断后就调用那一种的编解码器解码即可。