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

相关推荐
luoqice8 小时前
RTMP视频流的帧格式分析
网络·ffmpeg
老姚---老姚1 天前
编译支持HEVC/H.265 over RTMP / Enhanced RTMP 的 ffmpeg
ffmpeg·h.265·hevc·rtmp·enhanced
码流怪侠2 天前
FFmpeg 开发实战全解析:从入门到精通(附完整代码示例)
ffmpeg·音视频开发·视频编码
圆弧YH2 天前
FFmpeg
ffmpeg
luoqice2 天前
FLV文件格式详解
ffmpeg
happybasic3 天前
在CMD下使用FFmpeg将.wav文件转换成指定的格式~
ffmpeg
shao9185163 天前
第10章 Streaming(上):初级音频应用(1)——项目三:自建服务器的Mini-Omni实时语音聊天机器人
ffmpeg·whisper·asr·mini-omni·自建语音服务器
Leon_Chenl4 天前
【已开源】【嵌入式 Linux 音视频+ AI 实战项目】瑞芯微 Rockchip 系列 RK3588-基于深度学习的人脸门禁+ IPC 智能安防监控系统
深度学习·opencv·yolo·ffmpeg·音视频·边缘计算·人脸识别+检测
antzou4 天前
视频图片/文字水印
ffmpeg·视频水印·批量水印
AC赳赳老秦5 天前
DBA 专属方案:用 OpenClaw 实现 SQL 语句优化、慢查询分析、数据库备份巡检全自动化
服务器·前端·数据库·ffmpeg·自动化·deepseek·openclaw