【FFmpeg】ffmpeg中zig-zag的扫描方式

目录

在看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,计算的原理也是类似的。

相关推荐
q***040510 小时前
从MySQL5.7平滑升级到MySQL8.0的最佳实践分享
ffmpeg
一叶飘零_sweeeet11 小时前
FFmpeg 实战全解析:从底层原理到企业级应用落地
ffmpeg
别动哪条鱼1 天前
MP4转AAC转换器C++
c++·ffmpeg·音视频·aac
别动哪条鱼1 天前
FFmpeg 核心数据结构关系图
数据结构·ffmpeg
aqi002 天前
FFmpeg开发笔记(九十一)基于Kotlin的Android直播开源框架RootEncoder
android·ffmpeg·kotlin·音视频·直播·流媒体
寻找华年的锦瑟2 天前
Qt-FFmpeg案例(0基础,包含环境配置)
开发语言·qt·ffmpeg
大新新大浩浩2 天前
amazoncorretto:17镜像中安装ffmpeg
ffmpeg
Industio_触觉智能3 天前
瑞芯微RK3562平台FFmpeg硬件编解码移植及性能测试实战攻略
ffmpeg·视频编解码·瑞芯微·rk3562·触觉智能
八月的雨季 最後的冰吻3 天前
FFmepg--25-h265解码yuv格式
ffmpeg