FFmpeg的H.264解碼器源代碼簡單分析:概述

轉載:http://blog.csdn.net/leixiaohua1020/article/details/44864509

本文簡單記錄FFmpeglibavcodecH.264解碼器(H.264 Decoder)的源代碼。這個H.264解碼器十分重要,可以說FFmpeg項目今天可以幾乎"壟斷"視音頻編解碼技術,很大一部分貢獻就來自於這個H.264解碼器。這個H.264解碼器一方面功能強大,性能穩定;另一方面源代碼也比較複雜,難以深入研究。本文打算梳理一下這個H.264解碼器的源代碼結構,以方便以後深入學習H.264使用。

PS:這部分代碼挺複雜的,還有不少地方還比較模糊,還需要慢慢學習......

函數調用關係圖

H.264解碼器的函數調用關係圖如下所示。

單擊查看更清晰的大圖

 

下面解釋一下圖中關鍵標記的含義。

 

作爲接口的結構體

FFmpegH.264解碼器之間作爲接口的結構體有2個:

ff_h264_parser:用於解析H.264碼流的AVCodecParser結構體。

ff_h264_decoder:用於解碼H.264碼流的AVCodec結構體。

 

函數背景色

函數在圖中以方框的形式表現出來。不同的背景色標誌了該函數不同的作用:

白色背景的函數:普通內部函數。

粉紅色背景函數:解析函數(Parser)。這些函數用於解析SPSPPS等信息。

紫色背景的函數:熵解碼函數(Entropy Decoding)。這些函數讀取碼流數據並且進行CABAC或者CAVLC熵解碼。

綠色背景的函數:解碼函數(Decode)。這些函數通過幀內預測、幀間預測、DCT反變換等方法解碼壓縮數據。

黃色背景的函數:環路濾波函數(Loop Filter)。這些函數對解碼後的數據進行濾波,去除方塊效應。

藍色背景函數:彙編函數(Assembly)。這些函數是做過彙編優化的函數。圖中主要畫出了這些函數的C語言版本,此外這些函數還包含MMX版本、SSE版本、NEON版本等。

 

箭頭線

箭頭線標誌了函數的調用關係:

黑色箭頭線:不加區別的調用關係。

粉紅色的箭頭線:解析函數(Parser)之間的調用關係。

紫色箭頭線:熵解碼函數(Entropy Decoding)之間的調用關係。

綠色箭頭線:解碼函數(Decode)之間的調用關係。

黃色箭頭線:環路濾波函數(Loop Filter)之間的調用關係。

  

函數所在的文件

每個函數標識了它所在的文件路徑。

 

 

幾個關鍵部分

下文簡單記錄幾個關鍵的部分。

 

FFmpegH.264解碼器之間作爲接口的結構體

FFmpegH.264解碼器之間作爲接口的結構體有2個:ff_h264_parserff_h264_decoder

ff_h264_parser
ff_h264_parser
是用於解析H.264碼流的AVCodecParser結構體。AVCodecParser中包含了幾個重要的函數指針:

parser_init():初始化解析器。
parser_parse()
:解析。
parser_close()
:關閉解析器。

ff_h264_parser結構體中,上述幾個函數指針分別指向下面幾個實現函數:

init():初始化H.264解析器。
h264_parse()
:解析H.264碼流。
close()
:關閉H.264解析器。

ff_h264_decoder
ff_h264_decoder
是用於解碼H.264碼流的AVCodec結構體。AVCodec中包含了幾個重要的函數指針:

init():初始化解碼器。
decode()
:解碼。
close()
:關閉解碼器。

ff_h264_decoder結構體中,上述幾個函數指針分別指向下面幾個實現函數:

ff_h264_decode_init():初始化H.264解碼器。
h264_decode_frame()
:解碼H.264碼流。
h264_decode_end()
:關閉H.264解碼器。

 

普通內部函數

普通內部函數指的是H.264解碼器中還沒有進行分類的函數。下面舉幾個例子。

ff_h264_decoderff_h264_decode_init()調用的初始化函數:

ff_h264dsp_init():初始化DSP相關的函數。包含了IDCT、環路濾波函數等。
ff_h264qpel_init()
:初始化四分之一像素運動補償相關的函數。
ff_h264_pred_init()
:初始化幀內預測相關的函數。
ff_h264_decode_extradata()
:解析AVCodecContext中的extradata

ff_h264_decoderh264_decode_frame()逐層調用的和解碼Slice相關的函數:

decode_nal_units()ff_h264_execute_decode_slices()decode_slice()等。

ff_h264_decoderh264_decode_end()調用的清理函數:

ff_h264_remove_all_refs():移除所有參考幀。
ff_h264_free_context()
:釋放在初始化H.264解碼器的時候分配的內存。

ff_h264_parserh264_parse()逐層調用的和解析Slice相關的函數:

h264_find_frame_end():查找NALU的結尾。
parse_nal_units()
:解析一個NALU

 

解析函數(Parser

    解析函數(Parser)用於解析H.264碼流中的一些信息(例如SPSPPSSlice Header等)。在parse_nal_units()decode_nal_units()中都調用這些解析函數完成了解析。下面舉幾個解析函數的例子。

ff_h264_decode_nal():解析NALU。這個函數是後幾個解析函數的前提。
ff_h264_decode_slice_header()
:解析Slice Header
ff_h264_decode_sei()
:解析SEI
ff_h264_decode_seq_parameter_set()
:解析SPS
ff_h264_decode_picture_parameter_set()
:解析PPS

 

熵解碼函數(Entropy Decoding

    熵解碼函數(Entropy Decoding)讀取碼流數據並且進行CABAC或者CAVLC熵解碼。CABAC解碼函數是ff_h264_decode_mb_cabac()CAVLC解碼函數是ff_h264_decode_mb_cavlc()。熵解碼函數中包含了很多的讀取指數哥倫布編碼數據的函數,例如get_ue_golomb_long()get_ue_golomb()get_se_golomb()get_ue_golomb_31()等等。
    
在獲取殘差數據的時候需要進行CAVLC/CABAC解碼。例如解碼CAVLC的時候,會調用decode_residual()函數,而decode_residual()會調用get_vlc2()函數,get_vlc2()會調用OPEN_READER()UPDATE_CACHE()GET_VLC()CLOSE_READER()幾個函數讀取CAVLC格式的數據。
    
此外,在獲取運動矢量的時候,會調用pred_motion()以及類似的幾個函數獲取運動矢量相關的信息。

解碼函數(Decode

    解碼函數(Decode)通過幀內預測、幀間預測、DCT反變換等方法解碼壓縮數據。解碼函數是ff_h264_hl_decode_mb()。其中跟宏塊類型的不同,會調用幾個不同的函數,最常見的就是調用hl_decode_mb_simple_8()
    hl_decode_mb_simple_8()
的定義是無法在源代碼中直接找到的,這是因爲它實際代碼的函數名稱是使用宏的方式寫的(以後再具體分析)。hl_decode_mb_simple_8()的源代碼實際上就是FUNC(hl_decode_mb)()函數的源代碼。
    FUNC(hl_decode_mb)()
根據宏塊類型的不同作不同的處理:如果宏塊類型是INTRA,就會調用hl_decode_mb_predict_luma()進行幀內預測;如果宏塊類型不是INTRA,就會調用FUNC(hl_motion_422)()或者FUNC(hl_motion_420)()進行四分之一像素運動補償。
    
隨後FUNC(hl_decode_mb)()會調用hl_decode_mb_idct_luma()等幾個函數對數據進行DCT反變換工作。

環路濾波函數(Loop Filter

    環路濾波函數(Loop Filter)對解碼後的數據進行濾波,去除方塊效應。環路濾波函數是loop_filter()。其中調用了ff_h264_filter_mb()ff_h264_filter_mb_fast()ff_h264_filter_mb_fast()中又調用了h264_filter_mb_fast_internal()。而h264_filter_mb_fast_internal()中又調用了下面幾個函數進行濾波:

filter_mb_edgeh():亮度水平濾波
filter_mb_edgev()
:亮度垂直濾波
filter_mb_edgech()
:色度水平濾波

filter_mb_edgecv():色度垂直濾波

 

彙編函數(Assembly

    彙編函數(Assembly)是做過彙編優化的函數。爲了提高效率,整個H.264解碼器中(主要在解碼部分和環路濾波部分)包含了大量的彙編函數。實際解碼的過程中,FFmpeg會根據系統的特性調用相應的彙編函數(而不是C語言函數)以提高解碼的效率。如果系統不支持彙編優化的話,FFmpeg纔會調用C語言版本的函數。例如在幀內預測的時候,對於16x16亮度DC模式,有以下幾個版本的函數:

C語言版本的pred16x16_dc_8_c()
NEON
版本的ff_pred16x16_dc_neon()
MMXEXT
版本的ff_pred16x16_dc_8_mmxext()
SSE2
版本的ff_pred16x16_dc_8_sse2()

 

附錄

在網上找到一張圖(出處不詳),分析了FFmpegH.264解碼器每個函數運行的耗時情況,比較有參考意義,在這裏附上。

單擊查看更清晰的圖片

 

從圖中可以看出,熵解碼、宏塊解碼、環路濾波耗時比例分別爲:23.64%51.85%22.22%

 

 

至此FFmpegH.264解碼器的結構就大致梳理完畢了。

 

H264_parser.c (libavcodec)

  1. AVCodecParser ff_h264_parser = {  
  2.     .codec_ids      = { AV_CODEC_ID_H264 },  
  3.     .priv_data_size = sizeof(H264ParseContext),  
  4.     .parser_init    = init,  
  5.     .parser_parse   = h264_parse,  
  6.     .parser_close   = h264_close,  
  7.     .split          = h264_split,  
  8. };  

 

H264dec.c (libavcodec)    

  1. AVCodec ff_h264_decoder = {  
  2.     .name                  = "h264",  
  3.     .long_name             = NULL_IF_CONFIG_SMALL("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),  
  4.     .type                  = AVMEDIA_TYPE_VIDEO,  
  5.     .id                    = AV_CODEC_ID_H264,  
  6.     .priv_data_size        = sizeof(H264Context),  
  7.     .init                  = h264_decode_init,  
  8.     .close                 = h264_decode_end,  
  9.     .decode                = h264_decode_frame,  
  10.     .capabilities          = /*AV_CODEC_CAP_DRAW_HORIZ_BAND |*/ AV_CODEC_CAP_DR1 |  
  11.                              AV_CODEC_CAP_DELAY | AV_CODEC_CAP_SLICE_THREADS |  
  12.                              AV_CODEC_CAP_FRAME_THREADS,  
  13.     .caps_internal         = FF_CODEC_CAP_INIT_THREADSAFE,  
  14.     .flush                 = flush_dpb,  
  15.     .init_thread_copy      = ONLY_IF_THREADS_ENABLED(decode_init_thread_copy),  
  16.     .update_thread_context = ONLY_IF_THREADS_ENABLED(ff_h264_update_thread_context),  
  17.     .profiles              = NULL_IF_CONFIG_SMALL(ff_h264_profiles),  
  18.     .priv_class            = &h264_class,  
  19. };  

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章