第三章 XVID CODEC
3.3 Decode for I-Frame (Intra Frame)
3.3.3 decoder_mbintra()
此程式為解出 intra block 的核心函數,圖27 為 decoder_mbintra() 的部分程式碼,
而圖28 為程式的流程圖,從流程圖可以看出,先前得到的 cbp 參數,是用來決定是否 需要進行Run-level 解碼的旗標。
圖 27. decoder_mbintra() 片段程式碼
圖 28. decoder_mbintra() 流程圖
static void decoder_mbintra(dec, pMB, acpred_flag, cbp, bs,quant) { for (i = 0; i < 6; i++) {
uint32_t iDcScaler = get_dc_scaler(quant, i<4);
int16_t predictors[8]; // 係數預測值的暫存區 predict_acdc( .., predictors ,.. );
if (!acpred_flag) pMB->acpred_directions[i] = 0;
if (cbp & (1 << (5 - i)))
get_intra_block(bs,&data[i*64],direction, .. );
add_acdc( .., &data[i*64], iDcScaler, predictors, .. );
dequant_h263_intra(.,&data[i*64],quant,iDcScaler,.);
idct((short * const)&data[i*64]);
}
transfer_16to8copy(pY_Cur , &data[0*64] , stride);
transfer_16to8copy(pY_Cur + 8 , &data[1*64] , stride);
transfer_16to8copy(pY_Cur + next_block , &data[2*64] , stride);
transfer_16to8copy(pY_Cur + next_block + 8 , &data[3*64] , stride);
transfer_16to8copy(pU_Cur , &data[4*64] , stride2);
transfer_16to8copy(pV_Cur , &data[5*64] , stride2);
}
1.get_dc_scaler()
傳入值 quant 為量化參數 (Quantizer scale parameter : QP),而 i = 0 ~ 3 時,為 Luminance,i = 4 ~ 5 時,為 Chrominance,接著由表6 取得所對應的 DC 係數之量化參 數iDcScaler。
表 6. QP Ù dc_scaler 轉換表
Block type QP ≤ 4 5 ≤ QP ≤ 8 9 ≤ QP ≤ 24 25 ≤ QP Luma 8 2× QP QP + 8 (2× QP)-16 Chroma 8 (QP + 13)/2 (QP + 13)/2 QP-6
2. predict_acdc()
此函數用來計算DC 係數以及部分 AC 係數之預測值。首先,先針對左、上及左上 區塊的係數預測值做初始化,其默認值 (default values) 為 DC 係數 1024,其餘的 14 個 AC 係數設為 0,然後開始判斷左、上及左上的 block 此時是否位於邊界上,若位於邊界,
其相鄰block 的係數預測值則使用默認值,若不在邊界上,則使用相鄰 block 的係數預 測值,由於亮度 (Luma) 是由四個 block 所組成,所以選擇的相鄰 block 之係數預測值,
特別要經過一些指標上的偏移,以圖29 為例,當想要取得 Y2的相鄰block 之係數預測 值時,左邊block 的係數預測值位於 left MB 的 Y3位置,上面block 的係數預測值則為
圖 29. 相鄰 block 的示意圖
current MB 的 Y0位置,而程式碼的實現如圖30 的 case2 所示,而 case 5 的程式碼,為 取得V 的相鄰 block 之係數預測值。若想取得 current V 的相鄰 block 之係數預測值時,
由於V 只有一個 block,且每一個 MB 的係數預測值是依照 Y0、Y1、Y2、Y3、U、V 的 Y0
Y2
Y1
Y3
diag top
left current D2
L2
MBPRED_SIZE = 15
圖 30. predict_acdc() 片段程式碼
順序所排列,因此從程式碼可以看出,均是固定跳過五個block 的指標偏移來取得 V 的 相鄰block 之係數預測值。
取得相鄰的左、上及左上 block 的係數預測值之後,如圖31 左邊的 pseudo code,
經由選擇較小的DC 差,來決定預測方向,如果左邊和左上 block 的 DC 係數預測值相
DCB
if |DCA DCB| < |DCB - DCC| predict from block C else
predict from block A
圖 31. DC、AC 係數的預測
減較小,預測方向為垂直方向 (dec->mbs[x + y * mb_width].acpred_directions[i] = 1,
其中i 代表 4y1u1v 的 block 旗標) ,此時 block X 將選擇 block C 為預測目標,表示
case 2: 若左邊是邊界,此時 left 是 NULL if(left){
pLeft = left + 3 * MBPRED_SIZE; // 如圖 28 的 L2
pDiag = left + MBPRED_SIZE; // 如圖 28 的 D2
}
pTop = current;
top_quant = current_quant;
break;
. . . case 5:
if(left) pLeft = left + 5 * MBPRED_SIZE; // 跳過五個 block if(top) pTop = top + 5 * MBPRED_SIZE;
if(diag) pDiag = diag + 5 * MBPRED_SIZE;
break;
block X 的 DC 以及 AC 的第一橫列之係數預測值將由 block C 取得,反之,當預測方向 為水平方向時 (dec->mbs[x + y * mb_width].acpred_direction[i] = 2),block X 的 DC 以及 AC 的第一直欄之係數預測值則是從 block A 取得,因此,此函數的目的,是取得八個 係數預測值 (一個 DC、七個 AC)。
3. get_intra_block()
經由查看 cbp 參數之後,決定是否要做 Run-level Decode (RLD),解碼的一開始,
會先決定存放 block 係數的掃描順序,選擇掃描方式的依據在於何種方式可以達到較佳 的壓縮效率,而掃描方式這邊分為三種,Z 字形 (Zig-Zag)、交錯式水平 (Alternate- Horizontal) 及交錯式垂直 (Alternate-Vertical) 掃描,一般都是選用 Z 字形掃描
(dec->mbs[x + y * mb_width].acpred_directions[i] = 0),因為 DCT 之後的係數大小會按 照頻率的高低來作排列,而且,越是高頻訊號,值為零的機率越高,因此,並可以利用 此特性配合Z 字形掃描使得出現零值的訊號連續出現,接著使用變動長度編碼
(Variable-Length Coding : VLC) 來達到最高的壓縮率,但是,如果在計算預測值的函數 中 (predict_acdc),預測方向為水平時 (dec->mbs[x + y * mb_width].acpred_directions[i]
= 1),此時的掃描方式就為交錯式垂直掃描,圖32 為不同掃描方式的示意圖。
圖 32. 係數掃描方式
決定好掃描方式之後,便開始從位元流中取出 RLD 所需要的參數,係數的解碼利 用查表來實現,其相關的表為 DCT3D [ 2 ] [ 4096 ],且表所儲存的資料結構為
REVERSE_EVENT,其結構成員如表 7,從結構成員可以看出,有四個最主要的參數,
last 代表是否為最後一個不為零的係數,0 代表不是、1 代表是,run 的值代表該係數的
前面有幾個零,level 就是此係數真正的值,而 len 值代表該 VLC 的 Huffman Code 碼 長,len 的用途在於更新位元流的位置,藉由這四個參數就可以解出整個 block 的所有 係數值。DCT3D陣列前面的2 代表 intra 或 inter,後面的 4096 儲存了 0 ~ 212 - 1 的所有 值,因為最長的 VLC 長度為 12-bits,所以最長的 VLC 為最大限制,且每個 VLC 均外 加1-bit 儲存正負號。
表 7. EVENT 和 REVERSE_EVENT 的結構成員
資料結構名稱 type name 資料結構名稱 type name EVENT uint8_t last REVERSE_EVENT uint8_t len
uint8_t run EVENT event
int8_t level
和係數相關的 (run , level , last) 共有 102 種組合,這些組合儲存在 coeff_tab 陣列 中,圖33 為 coeff_tab 的部分陣列元素,因此,DCT3D 透過這 102 種組合對陣列作初
圖 33. coeff_tab 部分陣列元素
始化,初始化的程式碼如圖34 所示,下一段將詳細說明如何利用 102 種組合來建立 DCT3D 陣列的所有元素,而DCT3D為 RLD 解碼所需要的 VLC table。
VLC_TABLE const coeff_tab[2][102] = { // intra = 0
VLC EVENT typedef struct { code len last run level VLC vlc;
{ { { 2, 2}, { 0, 0, 1} }, EVENT event;
{ {15, 4}, { 0, 0, 2} }, } VLC_TABLE;
{ {21, 6}, { 0, 0, 3} }, . . },
// intra = 1 typedef struct { { { { 2, 2}, { 0, 0, 1} }, uint32_t code;
{ {15, 4}, { 0, 0, 3} }, uint8_t len;
{ {21, 6}, { 0, 0, 6} }, . . }, } VLC;
};
以圖33 圈起來的部分為例,此時 intra = 0,code = 2 (即 Huffman Code = 10),len = 2 (Huffman Code length) 時,已知最長的 Huffman Code 碼長為 12,但是目前碼長為 2,
因此剩下的 10-bits 便可任意存放 0 或 1,轉換成整數即為 0 ~ 210-1,因此,DCT3D [0]
[index] .len = 2 且 DCT3D [0] [index] .event = {0 , 0 , 1},其中 index = 10xxxxxxxxxx,
換成整數為 2048 + (0 ~ 210-1),程式實現部分如圖34。
圖 34. 建立DCT3D 陣列的部分程式碼
DCT3D陣列建立好之後,解 RLD 的步驟,如同先前解 mcbpc (get_mcbpc_intra) 函 數提到的查表法,例如:當目前為 intra,且從位元流中取出的 12-bits 位元資訊為 010101-xxxxxx 時 (xxxxxx 中的 x 表示任意填入 0 或 1),將 12-bits 轉成陣列位置 index,
透過DCT3D 查表得到 DCT3D [1][index].len = 6,DCT3D [1][index].event={ 0, 0, 6 },其 中 index = 010101xxxxxx,接著,就可以透過這些參數來進行解碼。
4. add_acdc()
將先前計算出來的係數預測值 predictors [8],加上 RLD 之後的第一行或第一欄的 係數,並且將加完之後的DC 係數乘上 iDcScaler 參數,然後把 DC 係數以及 AC 係數 的第一行和第一欄 (總共 15 個值),儲存在 blocks 的 dec->mbs.pred_values[6][15] 結構 中,以提供給接下來未解碼的block,計算係數預測值之用。
5. dequant_h263_intra()
此函數為進行反量化 (Inverse Quantization : IQ) 運算的部份,至於反量化的原理是 將各個原始係數除上量化參數後,取最接近的整數,而此量化後的整數就是我們需要傳
for(intra = 0 ; intra < 2 ; intra++){
for(i = 0 ; i < 102 ; i++){
for(j = 0 ; j < (1<<(12- coeff_tab[intra][i].vlc.len)) ; j++){
DCT3D[intra][(coeff_tab[intra][i].vlc.code <<
(12-coeff_tab[intra][i].vlc.len))| j ].len
= coeff_tab[intra][i].vlc.len;
DCT3D[intra][(coeff_tab[intra][i].vlc.code <<
(12-coeff_tab[intra][i].vlc.len)) | j ].event
= coeff_tab[intra][i].event;
} } }
index = 10 - xxxxxxxxxx
送的資料,而量化的目的是希望透過量化這個步驟,在影像品質能夠接受的情況下,將 else if ( coefficient < -2048 ) coefficient = - 2048 where DCQ and ACQ are quantized DC and AC coefficient
+ +
特性,因此,這邊所採用的 DCT 則是利用此特性的 Chen’s Algorithm,此演算法是一 個非常有效率的快速演算法,圖 36 為 Chen’s Algorithm 的示意圖。
由於它的可分離之特性,此解碼步驟則拆成兩部份,porting 在兩顆不同的 SPE 上 運作。
7. transfer_16to8copy()
結束反 DCT 轉換之後,就結束整個完整的 intra block 係數值的解碼,因此,該 函數便將已解碼的係數值,更新到目前畫面 (Current Frame) 的暫存區中,但是,更新 之前,還須對所有係數值經過一些轉換處理。每個 block 的係數都會從 16-bit 轉換成 8-bit 大小,如果係數大於 255 就取 255,如果係數小於 0 就取 0,其他的值就保持 原值,轉換結束之後,便存放該係數到所對應的暫存區,而 Y、U、V 的暫存區分別位 於 dec->cur.y , dec->cur.u , dec->cur.v 中,等所有係數都轉換且更新結束之後,整個 I-Frame 的解碼就結束了,因此,該程式為 porting 至 SPE 的最後一個解碼部份。