电子商务网站建设项目书,网站制作 企业网站建设哪家好,内网网站怎么建设,网络检修摘要#xff1a;本文描述了FFmpeg中videotoobox解码器如何进行解码工作#xff0c;如何将一个编码的码流解码为最终的裸流。 关键字#xff1a;videotoobox,decoder,ffmpeg VideoToolbox 是一个低级框架#xff0c;提供对硬件编码器和解码器的直接访问。 它提供视频… 摘要本文描述了FFmpeg中videotoobox解码器如何进行解码工作如何将一个编码的码流解码为最终的裸流。 关键字videotoobox,decoder,ffmpeg VideoToolbox 是一个低级框架提供对硬件编码器和解码器的直接访问。 它提供视频压缩和解压缩服务以及存储在 CoreVideo 像素缓冲区中的光栅图像格式之间的转换服务。 这些服务以会话对象压缩、解压缩和像素传输的形式提供并作为 Core Foundation (CF) 类型输出。 VideoToolbox支持H.263, H.264, HEVC, MPEG-1, MPEG-2, MPEG-4 Part 2, ProRes解码H.264, HEVC, ProRes编码最新的版本似乎也支持了VP9解码。
1 主流程
1.1 涉及的Context FFmpeg中每个解码器都有自己的Context描述该描述按照约定的格式描述对应的解码器参数和解码器的处理函数指针。FFmpeg中的VideoToolbox解码器主要实现代码在libavcodec/videotoobox.{h,c}中其中针对每一种支持的解码格式定义了一个独立的Context比如ff_h263_videotoolbox_hwaccel,ff_h263_videotoolbox_hwaccel,ff_h264_videotoolbox_hwaccel,...等只是实现上有差异我们主要关注其中一个即可这里主要关注ff_h264_videotoolbox_hwaccel。
const AVHWAccel ff_h264_videotoolbox_hwaccel {.name h264_videotoolbox,.type AVMEDIA_TYPE_VIDEO,.id AV_CODEC_ID_H264,.pix_fmt AV_PIX_FMT_VIDEOTOOLBOX,.alloc_frame ff_videotoolbox_alloc_frame,.start_frame ff_videotoolbox_h264_start_frame,.decode_slice ff_videotoolbox_h264_decode_slice,.decode_params videotoolbox_h264_decode_params,.end_frame videotoolbox_h264_end_frame,.frame_params ff_videotoolbox_frame_params,.init ff_videotoolbox_common_init,.uninit ff_videotoolbox_uninit,.priv_data_size sizeof(VTContext),
}; 该结构中定义了
解码器的名称解码数据的类型解码器ID硬件解码的格式申请一个硬件相关的帧结构的函数指针解码开始前针对帧进行内存拷贝之类的操作解码数据解析解码器需要的参数比如sps等送帧结束后的后处理初始化硬件解码器销毁硬件解码器当前硬件解码器的描述结构。 ff_h264_videotoolbox_hwaccel是存储在hw_configs中的运行时遍历该列表寻找期望的硬件解码器。所以解码工作是先经过FFmpeg内的ff_h264_decoder解码器再进入硬件解码器的。
const AVCodec ff_h264_decoder {.name h264,.long_name NULL_IF_CONFIG_SMALL(H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10),.type AVMEDIA_TYPE_VIDEO,.id AV_CODEC_ID_H264,.priv_data_size sizeof(H264Context),.init h264_decode_init,.close h264_decode_end,.decode h264_decode_frame,.capabilities /*AV_CODEC_CAP_DRAW_HORIZ_BAND |*/ AV_CODEC_CAP_DR1 |AV_CODEC_CAP_DELAY | AV_CODEC_CAP_SLICE_THREADS |AV_CODEC_CAP_FRAME_THREADS,.hw_configs (const AVCodecHWConfigInternal *const []) {
#if CONFIG_H264_DXVA2_HWACCELHWACCEL_DXVA2(h264),
#endif
#if CONFIG_H264_D3D11VA_HWACCELHWACCEL_D3D11VA(h264),
#endif
#if CONFIG_H264_D3D11VA2_HWACCELHWACCEL_D3D11VA2(h264),
#endif
#if CONFIG_H264_NVDEC_HWACCELHWACCEL_NVDEC(h264),
#endif
#if CONFIG_H264_VAAPI_HWACCELHWACCEL_VAAPI(h264),
#endif
#if CONFIG_H264_VDPAU_HWACCELHWACCEL_VDPAU(h264),
#endif
#if CONFIG_H264_VIDEOTOOLBOX_HWACCELHWACCEL_VIDEOTOOLBOX(h264),
#endifNULL},.caps_internal FF_CODEC_CAP_INIT_THREADSAFE | FF_CODEC_CAP_EXPORTS_CROPPING |FF_CODEC_CAP_ALLOCATE_PROGRESS | FF_CODEC_CAP_INIT_CLEANUP,.flush h264_decode_flush,.update_thread_context ONLY_IF_THREADS_ENABLED(ff_h264_update_thread_context),.update_thread_context_for_user ONLY_IF_THREADS_ENABLED(ff_h264_update_thread_context_for_user),.profiles NULL_IF_CONFIG_SMALL(ff_h264_profiles),.priv_class h264_class,
};VTContextVT解码过程中描述VT的Context。
typedef struct VTContext {// The current bitstream buffer.uint8_t *bitstream;// The current size of the bitstream.int bitstream_size;// The reference size used for fast reallocation.int allocated_size;// The core video bufferCVImageBufferRef frame;// Current dummy frames context (depends on exact CVImageBufferRef params).struct AVBufferRef *cached_hw_frames_ctx;// Non-NULL if the new hwaccel API is used. This is only a separate struct// to ease compatibility with the old API.struct AVVideotoolboxContext *vt_ctx;// Current H264 parameters (used to trigger decoder restart on SPS changes).uint8_t sps[3];bool reconfig_needed;void *logctx;
} VTContext;1.2 主要流程 2 每个步骤的具体实现
2.1ff_videotoolbox_common_init ff_videotoolbox_common_init在初始化解码器时调用一般是在avcodec_open2时初始化硬件解码器。一般FFmpeg为了更加准确的探测当前视频的媒体信息在avformat_find_stream_info时就会初始化解码器解码少部分的帧来进行流媒体信息探测。 初始化时首先就时申请VT的Context内存并设置一些参数实际上只设置了VT的callback函数和PixFormat。之后及时根据需要初始化AVHWFramesContext主要就是申请内存并设置帧格式比如宽高格式等等。 最后就是调用videotoolbox_start创建VT的Session创建的过程比较简单就是直接调用Apple的API创建Session需要重点关注的是如何设置的。具体的实现函数为videotoolbox_decoder_config_create其中设置硬件加速的配置时写死的无法进行配置。另外就是从当前的CodecCteonxt中取出sps等信息送给解码器如果没有这些信息解码器是无法准确识别出时间戳信息的。sps和pps的解析是由FFmpeg完成的。 switch (codec_type) {case kCMVideoCodecType_MPEG4Video :if (avctx-extradata_size)data videotoolbox_esds_extradata_create(avctx);if (data)CFDictionarySetValue(avc_info, CFSTR(esds), data);break;case kCMVideoCodecType_H264 :data ff_videotoolbox_avcc_extradata_create(avctx);if (data)CFDictionarySetValue(avc_info, CFSTR(avcC), data);break;case kCMVideoCodecType_HEVC :data ff_videotoolbox_hvcc_extradata_create(avctx);if (data)CFDictionarySetValue(avc_info, CFSTR(hvcC), data);break;
#if CONFIG_VP9_VIDEOTOOLBOX_HWACCELcase kCMVideoCodecType_VP9 :data ff_videotoolbox_vpcc_extradata_create(avctx);if (data)CFDictionarySetValue(avc_info, CFSTR(vpcC), data);break;
#endifdefault:break;}解码callback的实现比较简单就是Retain一下CVPixelBuffer。
static void videotoolbox_decoder_callback(void *opaque,void *sourceFrameRefCon,OSStatus status,VTDecodeInfoFlags flags,CVImageBufferRef image_buffer,CMTime pts,CMTime duration)
{VTContext *vtctx opaque;if (vtctx-frame) {CVPixelBufferRelease(vtctx-frame);vtctx-frame NULL;}if (!image_buffer) {av_log(vtctx-logctx, AV_LOG_DEBUG,vt decoder cb: output image buffer is null: %i\n, status);return;}vtctx-frame CVPixelBufferRetain(image_buffer);
}2.2 videotoolbox_h264_decode_params和ff_videotoolbox_frame_params esmp;videotoolbox_h264_decode_params主要的工作就是将上层解码出来额sps和pps信息拷贝到VTContext中。
case H264_NAL_SPS: {GetBitContext tmp_gb nal-gb;if (avctx-hwaccel avctx-hwaccel-decode_params) {ret avctx-hwaccel-decode_params(avctx,nal-type,nal-raw_data,nal-raw_size);if (ret 0)goto end;}if (ff_h264_decode_seq_parameter_set(tmp_gb, avctx, h-ps, 0) 0)break;av_log(h-avctx, AV_LOG_DEBUG,SPS decoding failure, trying again with the complete NAL\n);init_get_bits8(tmp_gb, nal-raw_data 1, nal-raw_size - 1);if (ff_h264_decode_seq_parameter_set(tmp_gb, avctx, h-ps, 0) 0)break;ff_h264_decode_seq_parameter_set(nal-gb, avctx, h-ps, 1);break;ff_videotoolbox_frame_params比较简单就是将CodecContext中的参数传递给HWFramesContext。
ff_videotoolbox_alloc_frame,ff_videotoolbox_h264_start_frame,ff_videotoolbox_h264_decode_slice,videotoolbox_h264_end_frame 这几个函数每一帧都会调用顺序是alloc_frame-start_frame-decode_frame-end_frame。 ff_videotoolbox_alloc_frame用来申请一块内存此时的内存只是一块儿裸内存只是将release函数指针设置成了VT的release指针还未与CVPixelBuffer绑定绑定是在解码器的Callback中进行的。 ff_videotoolbox_h264_start_frame主要就是将上层传下来的stream数据流拷贝到VTContext中。 videotoolbox_common_decode_slice也是拷贝数据流。 videotoolbox_h264_end_frame才是具体将数据送给解码器的地方核心的地方就是videotoolbox_session_decode_frame这里送给解码器的数据流就上上面拷贝的数据流需要注意的是在初始化时的callback中只是做了拷贝内存其他什么也没有做。这是因为在这里调用了VTDecompressionSessionWaitForAsynchronousFrames等待异步解码完成能够保证上一帧解码完成后才送下一帧数据。
2.3 ff_videotoolbox_uninit ff_videotoolbox_uninit比较简单就是释放解码器的Context和缓存中的内存。 Apple Documentation——VideoToolbox