FFMPEG.H264解碼解析-轉自雷神
阿新 • • 發佈:2019-01-01
本文分析FFmpeg的H.264解碼器的主幹部分。“主幹部分”是相對於“熵解碼”、“巨集塊解碼”、“環路濾波”這些細節部分而言的。它包含了H.264解碼器直到decode_slice()前面的函式呼叫關係(decode_slice()後面就是H.264解碼器的細節部分,主要包含了“熵解碼”、“巨集塊解碼”、“環路濾波”3個部分)。
函式呼叫關係圖
解碼器主幹部分的原始碼在整個H.264解碼器中的位置如下圖所示。
解碼器主幹部分的原始碼的呼叫關係如下圖所示。
從圖中可以看出,H.264解碼器(Decoder)在初始化的時候呼叫了ff_h264_decode_init(),ff_h264_decode_init()又呼叫了下面幾個函式進行解碼器彙編函式的初始化工作(僅舉了幾個例子):ff_h264dsp_init():初始化DSP相關的彙編函式。包含了IDCT、環路濾波函式等。H.264解碼器在關閉的時候呼叫了h264_decode_end(),h264_decode_end()又呼叫了ff_h264_remove_all_refs(),ff_h264_free_context()等幾個函式進行清理工作。
ff_h264qpel_init():初始化四分之一畫素運動補償相關的彙編函式。
ff_h264_pred_init():初始化幀內預測相關的彙編函式。
H.264解碼器在解碼影象幀的時候呼叫了h264_decode_frame(),h264_decode_frame()呼叫了decode_nal_units(),decode_nal_units()呼叫了兩類函式——解析函式和解碼函式,如下所示。
(1)解析函式(獲取資訊):
ff_h264_decode_nal():解析NALU Header。(2)解碼函式(解碼獲得影象):
ff_h264_decode_seq_parameter_set():解析SPS。
ff_h264_decode_picture_parameter_set():解析PPS。
ff_h264_decode_sei():解析SEI。
ff_h264_decode_slice_header():解析Slice Header。
ff_h264_execute_decode_slices():解碼Slice。其中ff_h264_execute_decode_slices()呼叫了decode_slice(),而decode_slice()中呼叫瞭解碼器中細節處理的函式(暫不詳細分析):
ff_h264_decode_mb_cabac():CABAC熵解碼函式。本文針對H.264解碼器decode_slice()前面的函式呼叫關係進行分析。
ff_h264_decode_mb_cavlc():CAVLC熵解碼函式。
ff_h264_hl_decode_mb():巨集塊解碼函式。
loop_filter():環路濾波函式。
ff_h264_decoder
ff_h264_decoder是FFmpeg的H.264解碼器對應的AVCodec結構體。它的定義位於libavcodec\h264.c,如下所示。- 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 = ff_h264_decode_init,
- .close = h264_decode_end,
- .decode = h264_decode_frame,
- .capabilities = /*CODEC_CAP_DRAW_HORIZ_BAND |*/ CODEC_CAP_DR1 |
- CODEC_CAP_DELAY | CODEC_CAP_SLICE_THREADS |
- CODEC_CAP_FRAME_THREADS,
- .flush = flush_dpb,
- .init_thread_copy = ONLY_IF_THREADS_ENABLED(decode_init_thread_copy),
- .update_thread_context = ONLY_IF_THREADS_ENABLED(ff_h264_update_thread_context),
- .profiles = NULL_IF_CONFIG_SMALL(profiles),
- .priv_class = &h264_class,
- };
從ff_h264_decoder的定義可以看出:解碼器初始化的函式指標init()指向ff_h264_decode_init()函式,解碼的函式指標decode()指向h264_decode_frame()函式,解碼器關閉的函式指標close()指向h264_decode_end()函式。
ff_h264_decode_init()
ff_h264_decode_init()用於FFmpeg H.264解碼器的初始化。該函式的定義位於libavcodec\h264.c,如下所示。- //H.264解碼器初始化函式
- av_cold int ff_h264_decode_init(AVCodecContext *avctx)
- {
- H264Context *h = avctx->priv_data;
- int i;
- int ret;
- h->avctx = avctx;
- //8顏色位深8bit
- h->bit_depth_luma = 8;
- //1代表是YUV420P
- h->chroma_format_idc = 1;
- h->avctx->bits_per_raw_sample = 8;
- h->cur_chroma_format_idc = 1;
- //初始化DSP相關的彙編函式。包含了IDCT、環路濾波函式等
- ff_h264dsp_init(&h->h264dsp, 8, 1);
- av_assert0(h->sps.bit_depth_chroma == 0);
- ff_h264chroma_init(&h->h264chroma, h->sps.bit_depth_chroma);
- //初始化四分之一畫素運動補償相關的彙編函式
- ff_h264qpel_init(&h->h264qpel, 8);
- //初始化幀內預測相關的彙編函式
- ff_h264_pred_init(&h->hpc, h->avctx->codec_id, 8, 1);
- h->dequant_coeff_pps = -1;
- h->current_sps_id = -1;
- /* needed so that IDCT permutation is known early */
- if (CONFIG_ERROR_RESILIENCE)
- ff_me_cmp_init(&h->mecc, h->avctx);
- ff_videodsp_init(&h->vdsp, 8);
- memset(h->pps.scaling_matrix4, 16, 6 * 16 * sizeof(uint8_t));
- memset(h->pps.scaling_matrix8, 16, 2 * 64 * sizeof(uint8_t));
- h->picture_structure = PICT_FRAME;
- h->slice_context_count = 1;
- h->workaround_bugs = avctx->workaround_bugs;
- h->flags = avctx->flags;
- /* set defaults */
- // s->decode_mb = ff_h263_decode_mb;
- if (!avctx->has_b_frames)
- h->low_delay = 1;
- avctx->chroma_sample_location = AVCHROMA_LOC_LEFT;
- //初始化熵解碼器
- //CAVLC
- ff_h264_decode_init_vlc();
- //CABAC
- ff_init_cabac_states();
- //8-bit H264取0, 大於 8-bit H264取1
- h->pixel_shift = 0;
- h->sps.bit_depth_luma = avctx->bits_per_raw_sample = 8;
- h->thread_context[0] = h;
- h->outputed_poc = h->next_outputed_poc = INT_MIN;
- for (i = 0; i < MAX_DELAYED_PIC_COUNT; i++)
- h->last_pocs[i] = INT_MIN;
- h->prev_poc_msb = 1 << 16;
- h->prev_frame_num = -1;
- h->x264_build = -1;
- h->sei_fpa.frame_packing_arrangement_cancel_flag = -1;
- ff_h264_reset_sei(h);
- if (avctx->codec_id == AV_CODEC_ID_H264) {
- if (avctx->ticks_per_frame == 1) {
- if(h->avctx->time_base.den < INT_MAX/2) {
- h->avctx->time_base.den *= 2;
- } else
- h->avctx->time_base.num /= 2;
- }
- avctx->ticks_per_frame = 2;
- }
- //AVCodecContext中是否包含extradata?包含的話,則解析之
- if (avctx->extradata_size > 0 && avctx->extradata) {
- ret = ff_h264_decode_extradata(h, avctx->extradata, avctx->extradata_size);
- if (ret < 0) {
- ff_h264_free_context(h);
- return ret;
- }
- }
- if (h->sps.bitstream_restriction_flag &&
- h->avctx->has_b_frames < h->sps.num_reorder_frames) {
- h->avctx->has_b_frames = h->sps.num_reorder_frames;
- h->low_delay = 0;
- }
- avctx->internal->allocate_progress = 1;
- ff_h264_flush_change(h);
- return 0;
- }
從函式定義中可以看出,ff_h264_decode_init()一方面給H.264 解碼器中一些變數(例如bit_depth_luma、chroma_format_idc等)設定了初始值,另一方面呼叫了一系列彙編函式的初始化函式(初始化函式的具體內容在後續文章中完成)。初始化彙編函式的的步驟是:首先將C語言版本函式賦值給相應模組的函式指標;然後檢測平臺的特性,如果不支援彙編優化(ARM、X86等),則不再做任何處理,如果支援彙編優化,則將相應的彙編優化函式賦值給相應模組的函式指標(替換掉C語言版本的效率較低的函式)。下面幾個函式初始化了幾個不同模組的彙編優化函式:
ff_h264dsp_init():初始化DSP相關的彙編函式。包含了IDCT、環路濾波函式等。
ff_h264qpel_init():初始化四分之一畫素運動補償相關的彙編函式。
ff_h264_pred_init():初始化幀內預測相關的彙編函式。
可以舉例看一下個ff_h264_pred_init()的程式碼。
ff_h264_pred_init()
函式用於初始化幀內預測相關的彙編函式,定位於libavcodec\h264pred.c,如下所示。- /**
- * Set the intra prediction function pointers.
- */
- //初始化幀內預測相關的彙編函式
- av_cold void ff_h264_pred_init(H264PredContext *h, int codec_id,
- constint bit_depth,
- int chroma_format_idc)
- {
- #undef FUNC
- #undef FUNCC
- #define FUNC(a, depth) a ## _ ## depth
- #define FUNCC(a, depth) a ## _ ## depth ## _c
-