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

相关推荐
blog.pytool.com1 天前
LVGL 驱动地址自动变更为32 位的问题
ffmpeg
知南x1 天前
【物联网视频监控系统----韦东山老师视频总结】(4)流媒体方案的实现之ffmpeg
ffmpeg·音视频
kkoral1 天前
FFmpeg 零基础入门教程
ffmpeg
小Tomkk3 天前
⭐️ StarRocks Web 使用介绍与实战指南
前端·ffmpeg
aqi003 天前
FFmpeg开发笔记(九十八)基于FFmpeg的跨平台图形用户界面LosslessCut
android·ffmpeg·kotlin·音视频·直播·流媒体
带土14 天前
2. Linux下FFmpeg C++音视频解码+推流开发
linux·c++·ffmpeg
aqi004 天前
FFmpeg开发笔记(九十七)国产的开源视频剪辑工具AndroidVideoEditor
android·ffmpeg·音视频·直播·流媒体
Sleepless_斑马4 天前
RTMP/RTSP流媒体服务器搭建、ffmpeg推流桌面、vlc拉流
ffmpeg·rtmp·rtsp
炼金术4 天前
SkyPlayer v1.1.0 - 在线视频播放功能更新
android·ffmpeg
喜欢吃豆4 天前
深度解析:FFmpeg 远程流式解复用原理与工程实践
人工智能·架构·ffmpeg·大模型·音视频·多模态