Qt5.12 使用FFmpeg实时解码播放H264/H265摄像头记录(直传数据法)

这第二章主要说明一下流程和使用函数、结构体变量的功能参数。
至于源代码看这位W2Y大神的代码,之前我还想好好研究的,奈何看到了这位大神的代码,直接满足了工作需求,但还是将我从中研究到的FFmpeg知识分享出来。

第一节 解码流程图

解码上下文(环境)初始化

AVStream *->codecpar
enum AVCodecID
AVCodec *
AVCodecContext *
AVCodecParameters *
AVCodec *
AVCodecContext *
AVStream *
AVCodecParameters *
avcodec_find_decoder
avcodec_alloc_context3
avcodec_parameters_to_context
avcodec_open2

帧格式初始化:

需要解码的帧
解码后YUV储存的帧
解码后RGB储存的帧
AVPacket
av_init_packet
AVFrame *
av_frame_alloc
AVFrame *
av_frame_alloc

解码步骤:

上面初始化好的
解码后的帧
以同样的格式取出来
YUV格式
结构体里面的一些变量作为参数
SwsContext*
目标RGB帧
AVCodecContext *
avcodec_send_packet
AVPacket
avcodec_receive_frame
AVFrame *
sws_getContext
sws_scale
AVFrame *

至此解码结束,但是某些细节和释放空间等并没有写在上面的流程图上。
至于将其播放,只要将最终得到的帧通过信号与槽传到新界面上,然后转为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格式,单独初始化一次并保留在对应的对象里,判断后就调用那一种的编解码器解码即可。

你可能感兴趣的:(#,FFmpeg,#,Qt功能模块,qt,ffmpeg,qtcreator,qt5)