车海快讯

FFmpeg解码的软解及硬解(cuda和qsv)使用方法

admin 140

本次使用的ffmpeg版本是4.2,解码的调用方式为:

int32_tiRet=-1;//最后一个包解码完成后,需要取完解码器中剩余的缓存帧;//调用avcodec_s_packet时塞空包进去,;//解码器就会知道所有包解码完成,再调用avcodec_receive_frame时,将会取出缓存帧;//AVPacketpacket;//av_init_packet(packet);//=NULL;//=0;//avcodec_s_packet(ctx,pkt);iRet=avcodec_s_packet(ctx,pkt);if(iRet!=0iRet!=AVERROR(EAGAIN)){get_ffmepg_err_str(iRet);if(iRet==AVERROR_EOF)iRet=0;returniRet;}while(true){//每解出来一帧,丢到队列中;iRet=avcodec_receive_frame(ctx,frame);if(iRet!=0){if(iRet==AVERROR(EAGAIN)){return0;}elsereturniRet;}PushRerBuffer();//音频解码后,如果需要重采样,也可以在此处进行resample;}

以前的版本解码方式为:

int32_tiRet=-1;iRet=avcodec_s_packet(ctx,pkt);if(iRet!=0iRet!=AVERROR(EAGAIN)){get_ffmepg_err_str(iRet);if(iRet==AVERROR_EOF)iRet=0;returniRet;}avcodec_decode_video2(pCodecCtx,frame,iGotPicture,pkt);

新旧版本更新时,注意接口的使用方法,新版本avcodec_s_packet一次,需要循环调用avcodec_receive_frame多次,返回EAGAIN后,结束当前这次的解码,音频解码也是一样

一、软件解码:

2、avcodec_find_decoder和avcodec_alloc_context3创建的指针,不需要覆盖stream中的指针,这是错误的用法,stream只是记录当前文件的流信息,不要用来保存context,创建的context由自己来保管;

3、avcodec_parameters_to_context,一定要做,这是把解析到的留信息设置到context,不需要在自己一个参数一个参数的设置;

thread_count为0,表示由ffmpeg调用最大线程数来进行软解,我的电脑的6核12线程,被创建了13个线程进行解码,实际上这会资源过剩,可以根据实际使用情况设置指定线程数量进行解码,这里我设置的是5,

切记!在设置thread_count大于1的值时,一定要把thread_safe_callbacks设置为1,否则的话必定会产生崩溃;

默认时,thread_safe_callbacks是为0,thread_count是1或者0的时候,都不需要这个标记

5、自行分配avcodec_receive_frame中frame的数据内存

uint8_t*AllocBufferByFrame(constAVFrame*frame){if(!frame)returnnullptr;int32_ts=av_image_get_buffer_size((AVPixelFormat)frame-format,frame-width,frame-height,1);if(s=0)returnnullptr;returnnewuint8_t[s]{0};}int32_tFillBufferFromFrame(uint8_t*alloced_buffer,constAVFrame*frame){if(!alloced_buffer||!frame)return-1;uint8_t*dst_data[4]{};intdst_linesize[4]{};int32_tret=av_image_fill_arrays(dst_data,dst_linesize,alloced_buffer,(AVPixelFormat)frame-format,frame-width,frame-height,1);if(ret=0)returnret;av_image_copy(dst_data,dst_linesize,(constuint8_t**)frame-data,frame-linesize,(AVPixelFormat)frame-format,frame-width,frame-height);returnret;}uint8_t*buffer=AllocBufferByFrame(frame);FillBufferFromFrame(buffer,frame);//使用完buffer的地方,释放buffer的内存;

按照这种使用方法,同一帧的数据会申请两份,解码器中申请了一份,拷贝到buffer中一份,增加了内存消耗以及数据拷贝,ffmpeg提供了解码时frame的内存由自己管理的回调接口,get_buffer2,这个回调接口在调用avcodec_receive_frame时,如果自定义了函数指针,将会调用自定义的函数接口,在接口内完成frame的data,linesize,buf的内容填充,在回调时,frame中的format,width,height已经被填充,可以直接拿来使用,注意:在get_buffer2的回调里,flag是AV_GET_BUFFER_FLAG_REF(1),我们申请的内存会被其他frame复用,不能直接释放,应该通过绑定的free接口来释放,我这里申请的是连续内存,回调free的接口每个平面都会调用,只有第一个平面也就是data[0]的时候,才能delete,方法如下:

voidDemuxStream::CustomFrameFreeBuffer(void*opaque,uint8_t*data){//申请的空间是连续的,只有第一个data,也就是data[0]的时候再删除,否则会崩溃;inti=(int)opaque;if(i==0)deletedata;}intDemuxStream::CustomFrameAllocBuffer(structAVCodecContext*s,AVFrame*frame,intflags){int32_tsize=av_image_get_buffer_size((AVPixelFormat)frame-format,frame-width,frame-height,1);if(size=0)return-1;//这是由自己申请的内存,avcodec_receive_frame使用完后要自己释放;uint8_t*buffer=newuint8_t[size]{0};uint8_t*dst_data[AV_NUM_DATA_POINTERS]{};intdst_linesize[AV_NUM_DATA_POINTERS]{};int32_tret=av_image_fill_arrays(dst_data,dst_linesize,buffer,(AVPixelFormat)frame-format,frame-width,frame-height,1);if(ret0){delete[]buffer;returnret;}for(inti=0;iAV_NUM_DATA_POINTERS;i++){frame-linesize[i]=dst_linesize[i];frame-data[i]=dst_data[i];frame-buf[i]=av_buffer_create(frame-data[i],frame-linesize[i]*frame-height,DemuxStream::CustomFrameFreeBuffer,(void*)i,0);}return0;}//解码时伪代码;avcodec_s_packet(ctx,pkt);while(true){//每解出来一帧,丢到队列中;iRet=avcodec_receive_frame(ctx,frame);if(iRet!=0){if(iRet==AVERROR(EAGAIN)){return0;}elsereturniRet;}push_rer_buffer(frame);//使用完成后,调用av_frame_unref,内存回收会在CustomFrameFreeBuffer执行;//因为自行申请的buffer,交给ffmpeg后,有可能会被复用,不能直接删除buffer;}
二、硬件解码

硬件解码需要编译的ffmpeg库支持,具体编译方法就不再此赘述,本次用到的硬件解码为英伟达cuda和英特尔的qsv,硬解解码器的创建跟软解有所不同,使用的过程也只是存在一点差异

1、英伟达,cuda

AVPixelFormatDemuxStream::GetHwFormat(AVCodecContext*ctx,constAVPixelFormat*pix_fmts){constenumAVPixelFormat*p;DemuxStream*pThis=(DemuxStream*)ctx-opaque;for(p=pix_fmts;*p!=-1;p++){if(*p==pThis-m__pix_fmt){return*p;}}fprintf(stderr,"FailedtogetHWsurfaceformat.\n");returnAV_PIX_FMT_NONE;}AVCodecContext*DemuxStream::GetCudaDecoder(AVStream*stream){int32_tret=avformat_open_input(m_pAVFormatIC,pPath,NULL,NULL);if(ret0)returnnullptr;if(avformat_find_stream_info(m_pAVFormatIC,NULL)0)returnnullptr;ret=av_find_best_stream(m_pAVFormatIC,AVMEDIA_TYPE_VIDEO,-1,-1,find_codec,0);if(ret0)returnnullptr;for(inti=0;;i++){constAVCodecHWConfig*config=avcodec_get_hw_config(find_codec,i);if(!config){//没找到cuda解码器,不能使用;returnnullptr;}if(config-device_type==AV_HWDEVICE_TYPE_CUDA){//找到了cuda解码器,记录对应的AVPixelFormat,后面get_format需要使用;m__pix_fmt=config-pix_fmt;m__type=AV_HWDEVICE_TYPE_CUDA;break;}}AVCodecContext*decoder_ctx=avcodec_alloc_context3(find_codec);avcodec_parameters_to_context(decoder_ctx,m_pAVFormatIC-stream[video_index]-codecpar);decoder_ctx-opaque=this;decoder_ctx-get_format=DemuxStream::GetHwFormat;if(m__device_ctx){av_buffer_unref(m__device_ctx);m__device_ctx=NULL;}if(av_hwdevice_ctx_create(m__device_ctx,m__type,NULL,NULL,0)0){fprintf(stderr,"FailedtocreatespecifiedHWdevice.\n");//创建硬解设备context失败,不能使用;returnnullptr;}decoder_ctx-hw_device_ctx=av_buffer_ref(m__device_ctx);avcodec_open2(decoder_ctx,find_codec,NULL);}

avcodec_get_hw_config,从当前硬解的配置中,遍历寻找适用于当前AvCodec的cuda配置,找到后记录cuda解码后的AVPixelFormat,后面解码器初始化时会使用到这个pix_fmt;

设置get_format的回调接口,在回调接口里面,会遍历cuda解码器支持的输出格式,匹配记录的pix_fmt才可以使用,在get_format如果没有找到匹配的硬解设置,可以返回指定类型的软解输出格式,解码器会自动切换到软解进行解码

初始化cuda解码器之前,先创建硬解设备context,赋值AVCodecContext-hw_device_ctx为其引用

2、英特尔硬解,qsv

另外在查找codec_id的时候,需要加上“_qsv”的后缀,这个所支持的qsv的编码格式,可以通过查看到

其他部分与cuda的创建基本一致

AVPixelFormatDemuxStream::GetHwFormat(AVCodecContext*ctx,constAVPixelFormat*pix_fmts){constenumAVPixelFormat*p;DemuxStream*pThis=(DemuxStream*)ctx-opaque;for(p=pix_fmts;*p!=-1;p++){if(*p==pThis-m__pix_fmt*p==AV_PIX_FMT_QSV){if(pThis-HwQsvDecoderInit(ctx)0)returnAV_PIX_FMT_NONE;return*p;}}fprintf(stderr,"FailedtogetHWsurfaceformat.\n");returnAV_PIX_FMT_NONE;}intDemuxStream::HwQsvDecoderInit(AVCodecContext*ctx){DemuxStream*pThis=(DemuxStream*)ctx-opaque;AVHWFramesContext*frames_ctx;AVQSVFramesContext*frames_hwctx;/*createapoolofsurfacestobeusedbythedecoder*/ctx-hw_frames_ctx=av_hwframe_ctx_alloc(pThis-m_qsv_device_ref);if(!ctx-hw_frames_ctx)return-1;frames_ctx=(AVHWFramesContext*)ctx-hw_frames_ctx-data;frames_hwctx=(AVQSVFramesContext*)frames_ctx-hwctx;frames_ctx-format=AV_PIX_FMT_QSV;frames_ctx-sw_format=ctx-sw_pix_fmt;frames_ctx-width=FFALIGN(ctx-coded_width,32);frames_ctx-height=FFALIGN(ctx-coded_height,32);frames_ctx-initial_pool_size=16;frames_hwctx-frame_type=MFX_MEMTYPE_VIDEO_MEMORY_DECODER_TARGET;returnav_hwframe_ctx_init(ctx-hw_frames_ctx);}AVCodecContext*DemuxStream::GetQsvDecoder(AVStream*stream){AVCodecContext*decoder_ctx=nullptr;intret=av_hwdevice_ctx_create(m_qsv_device_ref,AV_HWDEVICE_TYPE_QSV,"auto",NULL,0);if(ret0){gotofailed;}AVCodec*find_decoder=avcodec_find_decoder(stream-codec-codec_id);if(!find_decoder){gotofailed;}find_decoder=avcodec_find_decoder_by_name((std::string(find_decoder-name)+"_qsv").c_str());if(!find_decoder){gotofailed;}for(inti=0;;i++){constAVCodecHWConfig*config=avcodec_get_hw_config(find_decoder,i);if(!config){fprintf(stderr,"Decoder%sdoesnotsupportdevicetype%s.\n",find_decoder-name,av_hwdevice_get_type_name(AV_HWDEVICE_TYPE_QSV));gotofailed;}if(config-device_type==AV_HWDEVICE_TYPE_QSV){m__pix_fmt=config-pix_fmt;m__type=AV_HWDEVICE_TYPE_QSV;break;}}if(m__type==AV_HWDEVICE_TYPE_NONE||m__pix_fmt==AV_PIX_FMT_NONE)gotofailed;decoder_ctx=avcodec_alloc_context3(find_decoder);if(!decoder_ctx)gotofailed;if(avcodec_parameters_to_context(decoder_ctx,stream-codecpar)0)gotofailed;decoder_ctx-opaque=this;decoder_ctx-get_format=DemuxStream::GetHwFormat;ret=avcodec_open2(decoder_ctx,NULL,NULL);if(ret0)gotofailed;returndecoder_ctx;failed:m__pix_fmt=AV_PIX_FMT_NONE;m__type=AV_HWDEVICE_TYPE_NONE;av_buffer_unref(m_qsv_device_ref);m_qsv_device_ref=nullptr;avcodec_free_context(decoder_ctx);returnnullptr;}