目录
在看h264_cabac.c的代码过程中,发现ffmpeg使用了scan8x8和scan两个映射矩阵,来存储解析后的残差,这里的存储方式比较巧妙。
在libavcodec/h264_cabac.c中,有
c
if(IS_INTERLACED(mb_type)){
scan8x8 = sl->qscale ? h->field_scan8x8 : h->field_scan8x8_q0;
scan = sl->qscale ? h->field_scan : h->field_scan_q0;
}else{
scan8x8 = sl->qscale ? h->zigzag_scan8x8 : h->zigzag_scan8x8_q0;
scan = sl->qscale ? h->zigzag_scan : h->zigzag_scan_q0;
}
使用decode_cabac_residual_internal()进行解析时,会使用scan8x8或者scan进行存储。对于IntraMB而言,如果当前模式为I_16x16或者I_4x4,用的就是scan,如果是I_8x8,用的就是scan8x8
c
#define STORE_BLOCK(type) \
do { \
uint8_t *ctx = coeff_abs_level1_ctx[node_ctx] + abs_level_m1_ctx_base; \
int j= scantable[index[--coeff_count]]; \ /* 这里的scantable是scan8x8或scan */
if( get_cabac( CC, ctx ) == 0 ) { \
node_ctx = coeff_abs_level_transition[0][node_ctx]; \
if( is_dc ) { \
((type*)block)[j] = get_cabac_bypass_sign( CC, -1); \
}else{ \
((type*)block)[j] = (get_cabac_bypass_sign( CC, -qmul[j]) + 32) >> 6; \
} \
...
if( is_dc ) { \
((type*)block)[j] = get_cabac_bypass_sign( CC, -coeff_abs ); \
}else{ \
((type*)block)[j] = ((int)(get_cabac_bypass_sign( CC, -coeff_abs ) * qmul[j] + 32)) >> 6; \
} \
scan8x8和scan的定义位于libavcodec/h264_slice.c中,init_scan_tables()中
c
static void init_scan_tables(H264Context *h)
{
int i;
for (i = 0; i < 16; i++) {
#define TRANSPOSE(x) ((x) >> 2) | (((x) << 2) & 0xF)
h->zigzag_scan[i] = TRANSPOSE(ff_zigzag_scan[i]);
#undef TRANSPOSE
}
for (i = 0; i < 64; i++) {
#define TRANSPOSE(x) ((x) >> 3) | (((x) & 7) << 3)
h->zigzag_scan8x8[i] = TRANSPOSE(ff_zigzag_direct[i]);
#undef TRANSPOSE
}
...
其中,ff_zigzag_scan和ff_zigzag_direct的定义为
c
const uint8_t ff_zigzag_direct[64] = {
0, 1, 8, 16, 9, 2, 3, 10,
17, 24, 32, 25, 18, 11, 4, 5,
12, 19, 26, 33, 40, 48, 41, 34,
27, 20, 13, 6, 7, 14, 21, 28,
35, 42, 49, 56, 57, 50, 43, 36,
29, 22, 15, 23, 30, 37, 44, 51,
58, 59, 52, 45, 38, 31, 39, 46,
53, 60, 61, 54, 47, 55, 62, 63
};
/**
* (0,0), (1,0), (0,1), (0,2)
* (1,1), (2,0), (3,0), (2,1)
* (1,2), (0,3), (1,3), (2,2)
* (3,1), (3,2), (2,3), (3,3)
*/
const uint8_t ff_zigzag_scan[16+1] = {
0 + 0 * 4, 1 + 0 * 4, 0 + 1 * 4, 0 + 2 * 4,
1 + 1 * 4, 2 + 0 * 4, 3 + 0 * 4, 2 + 1 * 4,
1 + 2 * 4, 0 + 3 * 4, 1 + 3 * 4, 2 + 2 * 4,
3 + 1 * 4, 3 + 2 * 4, 2 + 3 * 4, 3 + 3 * 4,
};
经过TRANSPOSE之后,得到h->zigzag_scan和h->zigzag_scan8x8为
c
/**
* (0,0), (0,1), (1,0), (2,0)
* (1,1), (0,2), (0,3), (1,2)
* (2,1), (3,0), (3,1), (2,2)
* (1,3), (2,3), (3,2), (3,3)
*/
h->zigzag_scan[16] = {
0, 4, 1, 2,
5, 8, 12, 9,
6, 3, 7, 10,
13, 14, 11, 15
};
h->zigzag_scan8x8[64] = {
0, 8, 1, 2, 10, 4, 12, 5,
3, 16, 17, 25, 9, 13, 6, 14,
20,21,26,33,34,48,49,50,
24,18,22, 7,15,23,29,30,
38,39,40,56,57,58,41,42,
27,19,31,32,35,43,44,51,
59,60,61,45,46,36,47,52,
53,62,63,54,55,28,37,63
};
假设IntraMb的类型为I_4x4,当前小的4x4块解析出来的index[] = {0, 1, ,2, 3, 4, 5, 7},共有7个参数,分别命名为{a, b, c, d, e, f, g}。解析时,是从后开始向前解析残差系数的,第一次解析出来的参数放置在最末尾。参考h->zigzag_scan[16],计算的结果是
c
j = scantable[index[6]] = scantable[7] = (1,2)
j = scantable[index[5]] = scantable[5] = (0,2)
j = scantable[index[4]] = scantable[4] = (1,1)
j = scantable[index[3]] = scantable[3] = (2,0)
j = scantable[index[2]] = scantable[2] = (1,0)
j = scantable[index[1]] = scantable[1] = (0,1)
j = scantable[index[0]] = scantable[0] = (0,0)
上述j计算的坐标对应于下列4x4表格之中,其中g为第一个解析出来的参数,a为最后一个解析出来的参数,可以看到这是一个逆序的zig-zag存储方式,即ffmpeg在写入残差数据时,就已经考虑到了zig-zag的扫描。
c
| a | c | d | |
| b | e | | |
| f | g | | |
| | | | |
存储的上层结构为sl->mb,以decode_cabac_luma_residual()中的部分代码举例。
c
for( i4x4 = 0; i4x4 < 4; i4x4++ ) {
const int index = 16*p + 4*i8x8 + i4x4;
decode_cabac_residual_nondc(h, sl, sl->mb + (16*index << pixel_shift), ctx_cat[2][p], index, scan, qmul, 16);
}
传入的mb的offset是以16为单位的,参考https://blog.csdn.net/weixin_30289831/article/details/96686209这里关于mb的存储结构来看,应该是每一行存储一个4x4子块的数据,这与通常理解的不符合,然后在各行中,按照上面的4x4的方式来存储。
c
第1行:存储第1个4x4子块
第2行:存储第2个4x4子块
...
如果是I_8x8模式的话,使用的就是scan8x8,计算的原理也是类似的。