在進行h264解碼過程中,有兩個最重要的結構體,分別為H264Picture、H264SliceContext。
H264Picture
H264Picture用於維護一幀圖像以及與該圖像相關的語法元素。其中占用大片內存的結構體成員有以下幾個:
typedef struct H264Picture { AVFrame *f; int8_t *qscale_table; int16_t (*motion_val[2])[2]; uint32_t *mb_type; int8_t *ref_index[2]; } H264Picture;
Menber/Size[2] | Description |
f W x H (frame in pixels) x YUV |
維護視頻的一幀,主要的存儲空間由AVBufferRef提供,存儲的是這一幀的像素數據,由於視頻中每個像素分有YUV三個分量,因此會有三塊大內存,分別存儲這三個分量的像素數據[1]。如果視頻是以interlaced來進行編碼的,則會對一幀分為上下場進行編碼,不過在解碼的時候這兩個場會被合並,由這一成員維護。![]() |
qscale_table W x H (frame in MBs) |
記錄一幀中所有宏塊的QP。每個宏塊都有獨立的QP,QP值由SPS、PPS、slice以及宏塊中的QP相關語法元素計算得來。QP除了用於對殘差系數進行逆量化之外還在去塊濾波中起到判別真假濾波邊界的作用。![]() |
motion_val W x H (frame in 4x4 blocks) x 2 x 2 |
記錄一幀中所有4x4塊的運動向量。4x4塊是運動向量作用的最小單位,該表格會記錄inter宏塊中各個4x4塊的運動向量。如果該塊在進行編碼時采用的是雙向預測,那么在解碼的時候就會得到前向以及后向共兩個運動向量,因此motion_val是個長度為2的數組,分別指向前向以及后向運動向量表,表中的每一項表示一個運動向量。一個運動向量分為x與y兩個分量。![]() |
mb_type W x H (frame in MBs) |
記錄一幀中所有宏塊的類型。即每個宏塊解碼出來的語法元素mb_type。![]() |
ref_index W x H (frame in 8x8 blocks) x 2 |
記錄一幀中所有8x8塊的參考圖像索引。8x8塊是參考圖像作用的最小單位,該表格會記錄inter宏塊中各個8x8塊的參考圖像索引。如果該塊在編碼時采用的是雙向預測,那么在解碼的時候就會得到前向以及后向共兩個參考圖像索引,因此ref_index是個長度為2的數組,分別指向前向以及后向參考圖像索引表,表中的每一項存儲一個索引。![]() |
H264SliceContext
h.264解碼時,各個slice之間相對來說較為獨立,因此對於從一個slice解碼出來的各個語法元素,會用一個結構體來進行維護,這個結構體就是H264SliceContext。在對slice解碼過程中涉及到的大多數據的存取都是通過該結構體來完成。其中占用較大內存,並且會被頻繁使用的語法元素相關的結構體成員有以下幾個:
typedef struct H264SliceContext { int8_t intra4x4_pred_mode_cache[5 * 8]; int8_t(*intra4x4_pred_mode); DECLARE_ALIGNED(8, uint8_t, non_zero_count_cache)[15 * 8]; DECLARE_ALIGNED(16, int16_t, mv_cache)[2][5 * 8][2]; DECLARE_ALIGNED(8, int8_t, ref_cache)[2][5 * 8]; DECLARE_ALIGNED(16, uint8_t, mvd_cache)[2][5 * 8][2]; uint8_t direct_cache[5 * 8]; ///< as a DCT coefficient is int32_t in high depth, we need to reserve twice the space. DECLARE_ALIGNED(16, int16_t, mb)[16 * 48 * 2]; DECLARE_ALIGNED(16, int16_t, mb_luma_dc)[3][16 * 2]; uint8_t (*mvd_table[2])[2]; }
Menber | Description |
intra4x4_pred_mode_cache | 存儲當前宏塊及其Left,Top方向的每個4x4塊的intra4x4預測模式,有如下用途: 1. 對當前宏塊進行幀內預測,也就是通過intra4x4預測模式來構建宏塊的像素數據[7]。 2. 在進行當前宏塊的intra4x4預測模式的預測時,需要根據每一個4x4塊其A(左)、B(上)塊的intra4x4預測模式來進行當前預測模式的預測[6]。 ![]() |
intra4x4_pred_mode | 存儲當前宏塊所在的行以及前一行宏塊的intra4x4預測模式[17]。但是要注意的是,這里只對每個宏塊提供8個intra4x4預測模式的存儲位置,而實際所用到的區域只有7個,這7個intra4x4預測模式分別位於當前宏塊的最底下一行(Bottom)以及最右邊一列(Right)[4]。 當前宏塊的這7個intra4x4預測模式將會作為后面所解碼的宏塊的Left、Top方向的intra4x4預測模式使用,即會用intra4x4_pred_mode來填充intra4x4_pred_mode_cache的Left、Top的位置[3]。 ![]() |
non_zero_count_cache | 存儲當前宏塊及其Left、Top方向的每個4x4塊中非零系數的個數,有YUV三個分量。有如下用途: 1. 在cabac解碼語法元素coded_block_flag時,需要當前塊的A(左)以及B(上)的非零系數數目來選取上下文索引[16]。 2. 在進行去塊濾波時,會根據邊界兩邊的塊是否含有非零系數來確定濾波強度[13]。 3. 4x4塊non_zero_count的值會根據當前宏塊的CBP的值來進行設定,如果一個4x4塊沒有非零系數,則沒有必要進行系數的逆量化逆變換了。 ![]() |
mv_cache | 存儲當前宏塊及其Left、Top、Top-Right、Top-Left方向的mv。有如下用途: 1. 在進行mv預測時,會根據當前塊的A、B、C、D的mv來得到當前塊的mvp[10]。 2. 如果當前塊是B_Direct,並且采用的是spatial預測,則會根據當前塊的A、B、C來確定當前塊的ref以及mv[9]。 3. 在進行運動補償時,需要通過當前塊的mv來生成像素數據[11]。 4. 在進行去塊濾波時,會根據邊界兩邊的mv的差值來確定濾波強度[13]。 ![]() |
ref_cache | 存儲當前宏塊及其Left、Top、Top-Right、Top-Left方向的ref。有如下用途: 1. 在進行ref的cabac解碼時需要根據其A以及B方塊的ref來選取上下文索引值[14]。 2. 在進行mv預測的時候,會根據解碼出來的當前塊的ref以及A、B、C、D的ref來得到mvp[10]。 3. 在進行運動補償時,需要通過當前塊的ref來生成像素數據[11]。 4. 在進行去塊濾波時,會根據邊界兩邊是否為同一個ref,或者是否有同樣的參考幀數目來確定濾波強度[13]。 5. 如果當前塊是B_Direct,並且采用的是spatial預測,則會根據A、B、C塊的ref來確定當前塊的ref以及mv[9]。 ![]() |
mvd_cache | 存儲當前宏塊以及其Left、Top的mvd。有如下用途: 1. 通過當前塊的mvd以及預測所得的mvp得到正確的mv[8]。 2. 在進行當前塊的mvd的cabac解碼時需要根據其A、B塊的mvd來選取上下文索引值[15]。 ![]() |
direct_cache | 存儲當前宏塊的Left、Top的塊的sub_mb_type(以8x8塊為單位),主要用於判斷這些塊是否為B_Direct。在進行ref的cabac解碼時需要根據當前塊的A、B的塊是否為B_Direct來選取上下文索引值[14]。![]() |
mb | 存儲當前宏塊的YUV的像素殘差的變換系數。在編碼的時候宏塊像素殘差的編碼順序為變換、量化、然后熵編碼就能得到碼流數據;而在解碼時,宏塊的碼流在經過熵解碼后,然后執行逆量化,會得到宏塊殘差像素的變換系數,這些系數會被存在mb當中,對這些殘差系數執行逆變換后,就能得到像素殘差。![]() |
mb_luma_dc | 存儲當前宏塊的DC系數。在編碼時,如果當前宏塊采用的預測模式為intra16x16,那么像素殘差在進行4x4的變換后會得到16個DC系數以及15x16個AC系數,在進行量化后,這16個DC系數會排列在一起先進行熵編碼,然后熵編碼這16x15個AC系數;那么在解碼時,如果當前宏塊的預測模式為intra16x16,那么在執行熵解碼后會得到16個DC系數,這些DC系數會被寫入mb_luma_dc當中。在mb_luma_dc當中的這些DC系數在進行逆量化后就會被寫入mb,形成16個4x4的像殘差系數[12]。![]() |
mvd_table | 存儲當前宏塊所在的行以及前一行宏塊的mvd[17]。但是要注意的是,這里只對每個宏塊提供8個mvd的存儲位置,而實際所用到的區域只有7個,這7個mvd分別位於當前宏塊的最底下一行(Bottom)以及最右邊一列(Right)[5] 。 當前宏塊的這7個mvd將會作為后面所解碼的宏塊的Left、Top方向的mvd使用,即會用於填充mvd_cache的Left、Top的位置[3]。 ![]() |
其中名稱中含有“cache”這一名稱的結構體成員都需要當前宏塊的周邊塊的信息,這些信息都是在fill_decode_cache中寫入到成員的數組中的,而當前宏塊中的信息則是在熵解碼后直接或者間接存儲到cache結構體成員中。
這些包含cache字段的成員中基本都有DECLARE_ALIGNED修飾,這個宏主要用於向編譯器聲明這些成員為8或者16byte對齊。原因是為了提升處理速度,這些成員大多需要用SIMD指令進行處理,而SIMD指令在執行時,如果內存操作數不是對齊的,則有可能會出現性能下降[18]。
這些結構體成員被命名為cache也是有原因的。在計算機原理中,當進行內存訪問時,為了提高數據訪問速度,一般都會對所訪問的內存及其周邊內存區域(即一個cache line)一同取入cache當中,如果某個代碼段會頻繁訪問數據,並且大部分數據都在cache當中,即cache命中率高,那么這個代碼段的執行效率就會得到很好的提升;如果大部分數據不在cache中,即cache命中率低,就會在數據訪問上浪費大量時間。一般的處理器的L1 cache僅幾十k字節的容量,因此在執行數據處理的時候,如果不是頻繁訪問的內存區域,有可能很快就會被從cache中清除。基於這些理論,現在返回來觀察h264頻繁訪問的數據,可以發現:
- 這些以cache命名的結構體成員除了包含當前宏塊的數據之外,還包含其周邊塊的數據,特別是上一行的數據。在實際進行數據的排列的時候,是以宏塊行為單位從左到右進行排列的,因此即使宏塊在空間位置上是上下相鄰,但是在內存中也會間隔較遠,很有可能不在同一cache line中。
- 解碼一個宏塊所需要訪問的數據繁多,解碼器為每一幀的每種數據都分配了各自的內存塊,這些內存塊都占用相當大的內存空間,因此不同的數據不可能在同一cache line中。
- 解碼一個宏塊需要多次訪問各個內存塊中的不同數據,並且訪問的代碼段較為分散。由於cache空間有限,如果直接處理內存塊內的數據,就有可能會導致cache line被頻繁替換,使得在進行數據訪問的時候cache命中率較低,從而在數據訪問上耗費較多時間。
為了針對上述問題進行優化,ffmpeg把在進行宏塊解碼時頻繁訪問到的數據集中到了H264SliceContext結構體中,並且用名稱包含cache字段的成員存儲宏塊及其周邊的數據。如此一來,就使得宏塊解碼過程中的數據訪問的內存范圍大大縮小,只有在開頭的填充這些成員以及末尾的數據寫回的時候才會訪問到各個分散的內存塊,以此來提升內存的cache命中率。
還有一些未被介紹的緩沖區,指向這些緩沖區的指針是H264Context結構體的成員,主要在ff_h264_alloc_tables中進行內存分配。
Reference:
- update_frame_pool, video_get_buffer
- alloc_picture, init_table_pools
- fill_decode_cache
- write_back_intra_pred_mode
- write_back_motion_list
- 8.3.1.1 Derivation process for Intra4x4PredMode/ Intra Luma Prediction (pred_intra_mode)
- 8.3.1.2 Intra_4x4 sample prediction / Intra Luma Prediction (hl_decode_mb_predict_luma, pred4x4)
- 8.4.1 Derivation process for motion vector components and reference indices (DECODE_CABAC_MB_MVD)
- 8.4.1.2.2 Derivation process for spatial direct luma motion vector and reference index prediction mode (pred_spatial_direct_motion)
- 8.4.1.3 Derivation process for luma motion vector prediction / h.264 mvp求解過程 (pred_motion)
- 8.4.2.2 Fractional sample interpolation process (mc_part_std,mc_dir_part)
- 8.5.2 Specification of transform decoding process for luma samples of Intra_16x16 macroblock prediction
mode (decode_cabac_luma_residual, hl_decode_mb_predict_luma, h264_luma_dc_dequant_idct) - 8.7.2.1 Derivation process for the luma content dependent boundary filtering strength / 估算邊界強度 (check_mv, filter_mb_dir)
- 9.3.3.1.1.6 Derivation process of ctxIdxInc for the syntax elements ref_idx_l0 and ref_idx_l1 (decode_cabac_mb_ref)
- 9.3.3.1.1.7 Derivation process of ctxIdxInc for the syntax elements mvd_l0 and mvd_l1 (DECODE_CABAC_MB_MVD)
- 9.3.3.1.1.9 Derivation process of ctxIdxInc for the syntax element coded_block_flag (get_cabac_cbf_ctx)
- ff_h264_alloc_tables
- Intel® 64 and IA-32 Architectures Optimization Reference Manual 4.4 STACK AND DATA ALIGNMENT