3.1 Tile划分和并行处理

作者:chai51
出处:https://www.cnblogs.com/chai51
版权:本文版权归作者和博客园共有
转载:欢迎转载,但未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任

引言

Tile(瓦片)是AV1并行解码的基本单元,它将一帧图像划分为多个独立的矩形区域,每个Tile可以独立解码,从而实现并行处理。Tile划分和并行处理是AV1解码器实现高效解码的关键技术之一。

源码说明: 本文档基于作者自己编写的AV1解码器Python实现,所有代码示例和实现细节均来自实际可运行的源码。源码仓库:GitHub - av1_learning


Tile概述

Tile的基本概念

Tile是AV1帧内可独立解码的矩形区域,具有以下特点:

  1. 独立性:每个Tile可以独立解码,不依赖其他Tile的数据
  2. 并行性:多个Tile可以并行解码,提高解码效率
  3. 边界限制:Tile边界必须对齐到Superblock边界
  4. 尺寸限制:每个Tile的最大宽度和面积有规范限制

Tile与Superblock的关系

  • Superblock:AV1的基本编码单元,可以是64x64或128x128像素
  • Tile:由多个Superblock组成的矩形区域
  • Tile边界必须对齐到Superblock边界,确保解码的独立性

Tile信息解析

位置: src/frame/frame_header.py - __tile_info()

规范文档: 5.9.15 Tile info syntax

解析流程

  1. 计算Superblock数量

    sbCols = ((MiCols + 31) >> 5) if use_128x128_superblock else ((MiCols + 15) >> 4)
    sbRows = ((MiRows + 31) >> 5) if use_128x128_superblock else ((MiRows + 15) >> 4)
    
    • 根据Superblock尺寸(64x64或128x128)计算帧内的Superblock列数和行数
  2. 计算Tile划分参数

    maxTileWidthSb = MAX_TILE_WIDTH >> sbSize
    maxTileAreaSb = MAX_TILE_AREA >> (2 * sbSize)
    minLog2TileCols = self.__tile_log2(maxTileWidthSb, sbCols)
    maxLog2TileCols = self.__tile_log2(1, min(sbCols, MAX_TILE_COLS))
    maxLog2TileRows = self.__tile_log2(1, min(sbRows, MAX_TILE_ROWS))
    
    • 计算Tile的最大宽度和面积限制
    • 计算Tile列数和行数的对数范围
  3. Tile间距模式

    uniform_tile_spacing_flag = read_f(reader, 1)
    if uniform_tile_spacing_flag:
        # 均匀Tile间距
        frame_header.TileColsLog2 = minLog2TileCols
        frame_header.TileRowsLog2 = minLog2TileRows
        # 计算Tile列数和行数
        frame_header.TileCols = 1 << frame_header.TileColsLog2
        frame_header.TileRows = 1 << frame_header.TileRowsLog2
    else:
        # 非均匀Tile间距
        # 解码每个Tile的边界位置
    
    • 均匀间距:所有Tile尺寸相同,只需解码列数和行数的对数
    • 非均匀间距:每个Tile的边界位置需要单独解码
  4. 计算Tile边界

    # 计算每个Tile的起始和结束位置
    for i in range(frame_header.TileCols + 1):
        frame_header.MiColStarts[i] = ...
    for i in range(frame_header.TileRows + 1):
        frame_header.MiRowStarts[i] = ...
    
    • 计算每个Tile在MI(最小单元)坐标系中的起始和结束位置
    • 存储到 MiColStartsMiRowStarts 数组中

Tile Group OBU结构

位置: src/tile/tile_group.py - tile_group_obu()

规范文档: 5.11.1 General tile group OBU syntax

OBU结构

Tile Group OBU包含一个或多个Tile的数据,结构如下:

def tile_group_obu(self, av1: AV1Decoder, sz: int) -> TileGroup:
    # 1. 解析Tile Group头部
    NumTiles = frame_header.TileCols * frame_header.TileRows
    if NumTiles > 1:
        tile_start_and_end_present_flag = read_f(reader, 1)
    
    # 2. 确定Tile范围
    if NumTiles == 1 or not tile_start_and_end_present_flag:
        tg_start = 0
        tg_end = NumTiles - 1
    else:
        tileBits = frame_header.TileColsLog2 + frame_header.TileRowsLog2
        tg_start = read_f(reader, tileBits)
        tg_end = read_f(reader, tileBits)
    
    # 3. 解码每个Tile
    frame_header.TileNum = tg_start
    while frame_header.TileNum <= tg_end:
        # 计算Tile位置
        tileRow = frame_header.TileNum // frame_header.TileCols
        tileCol = frame_header.TileNum % frame_header.TileCols
        
        # 解析Tile大小(最后一个Tile除外)
        if not lastTile:
            tile_size_minus_1 = read_le(reader, frame_header.TileSizeBytes)
            tileSize = tile_size_minus_1 + 1
        
        # 设置Tile边界
        tile_group.MiRowStart = frame_header.MiRowStarts[tileRow]
        tile_group.MiRowEnd = frame_header.MiRowStarts[tileRow + 1]
        tile_group.MiColStart = frame_header.MiColStarts[tileCol]
        tile_group.MiColEnd = frame_header.MiColStarts[tileCol + 1]
        
        # 解码Tile
        decoder.init_symbol(av1, tileSize)
        self.__decode_tile(av1)
        decoder.exit_symbol(av1)
        
        frame_header.TileNum += 1

Tile Group头部字段

  • tile_start_and_end_present_flag:是否包含Tile起始和结束索引
  • tg_start:Tile Group中第一个Tile的索引
  • tg_end:Tile Group中最后一个Tile的索引
  • tile_size_minus_1:每个Tile的大小(最后一个Tile除外)

Tile解码过程

位置: src/tile/tile_group.py - __decode_tile()

规范文档: 5.11.2 Decode tile syntax

解码流程

def __decode_tile(self, av1: AV1Decoder):
    # 1. 初始化上下文
    clear_above_context(av1)
    tile_group.DeltaLF = [0] * FRAME_LF_COUNT
    
    # 2. 初始化参考参数
    for plane in range(NumPlanes):
        for pass_val in range(2):
            tile_group.RefSgrXqd[plane][pass_val] = Sgrproj_Xqd_Mid[pass_val]
            for i in range(WIENER_COEFFS):
                tile_group.RefLrWiener[plane][pass_val][i] = Wiener_Taps_Mid[i]
    
    # 3. 遍历Superblock
    sbSize = SUB_SIZE.BLOCK_128X128 if use_128x128_superblock else SUB_SIZE.BLOCK_64X64
    sbSize4 = Num_4x4_Blocks_Wide[sbSize]
    
    for r in range(tile_group.MiRowStart, tile_group.MiRowEnd, sbSize4):
        clear_left_context(av1)
        for c in range(tile_group.MiColStart, tile_group.MiColEnd, sbSize4):
            # 初始化块解码标志
            tile_group.ReadDeltas = frame_header.delta_q_present
            self.__clear_cdef(av1, r, c)
            self.__clear_block_decoded_flags(av1, r, c, sbSize4)
            self.__read_lr(av1, r, c, sbSize)
            
            # 解码块划分
            self.__decode_partition(av1, r, c, sbSize)

关键步骤

  1. 上下文初始化

    • 清除上方上下文(clear_above_context
    • 初始化Delta LF参数
    • 初始化Loop Restoration参考参数
  2. Superblock遍历

    • 按光栅扫描顺序遍历Tile内的所有Superblock
    • 每行开始时清除左侧上下文(clear_left_context
  3. 块解码标志初始化

    • 清除CDEF索引
    • 清除块解码标志
    • 读取Loop Restoration参数

并行处理机制

Tile的独立性

Tile的独立性体现在以下几个方面:

  1. 数据独立性:每个Tile包含完整的解码所需数据
  2. 上下文独立性:Tile边界处的上下文被重置
  3. 熵解码独立性:每个Tile有独立的熵解码上下文

并行解码策略

虽然AV1规范支持Tile的并行解码,但实际实现中需要考虑:

  1. 依赖关系:某些帧级操作(如CDF更新)需要在所有Tile解码完成后进行
  2. 内存管理:并行解码需要合理管理内存访问
  3. 同步机制:帧级操作需要等待所有Tile完成

上下文管理

Tile边界处的上下文管理:

  • 上方上下文:每行Superblock开始时清除
  • 左侧上下文:每个Superblock开始时清除
  • CDEF上下文:每个Superblock开始时清除
  • Loop Restoration上下文:每个Superblock开始时读取

Tile划分流程图

Tile信息解析流程

Tile信息解析是在帧头中完成的,主要目的是确定如何将一帧图像划分成多个Tile。这个过程分为两种情况:

情况1:均匀划分 - 所有Tile大小相同,只需要计算边界位置
情况2:非均匀划分 - Tile大小可以不同,需要从比特流中读取每个Tile的边界位置

graph TD A[帧头解析开始] --> B[计算Superblock数量<br/>sbCols sbRows] B --> D[计算Tile列数和行数范围<br/>最小和最大Tile数量] D --> E{均匀Tile间距?} E -->|是| F1[计算Tile列边界<br/>计算TileCols] F1 -->|是| F2[计算Tile行边界<br/>计算TileRows] F2 --> I[Tile信息解析完成] E -->|否| G1[解码每个Tile列边界<br/>计算TileCols] G1 --> G2[解码每个Tile行边界<br/>计算TileRows] G2 --> I style A fill:#e1f5ff style E fill:#fff9c4 style I fill:#c8e6c9

Tile Group OBU解析流程

Tile Group OBU包含了实际的图像数据,解析流程如下:

  1. 确定要解码哪些Tile:一个Tile Group OBU可能包含多个Tile,需要确定起始和结束位置
  2. 逐个解码每个Tile:对每个Tile,先读取大小信息,然后解码Tile内的数据
  3. 每个Tile独立解码:每个Tile有独立的熵解码上下文,互不干扰
graph TD A[Tile Group OBU解析开始] --> B[计算Tile总数<br/>NumTiles] B --> C{只有一个Tile?} C -->|否| F{是否指定了Tile范围<br/>tile_start_and_end_present_flag?} C -->|是| E[直接使用全部Tile] F -->|是| G[解码Tile范围] F -->|否| E E --> I[还有Tile要处理吗?] G --> I I -->|否| END[Tile Group解析完成] I -->|是| J[计算Tile位置<br/>tileRow tileCol] J --> K[解码tileSize] K --> N[设置Tile边界<br/>MiRowStart MiRowEnd<br/>MiColStart MiColEnd] N --> O[初始化熵解码器<br/>init_symbol] O --> P[解码Tile<br/>__decode_tile] P --> Q[退出熵解码器<br/>exit_symbol] Q -->|下一个| I style A fill:#e1f5ff style C fill:#fff9c4 style F fill:#fff9c4 style I fill:#fff9c4 style K fill:#fff9c4 style END fill:#c8e6c9

流程说明

  • Tile范围:一个Tile Group OBU可以只包含部分Tile,这样可以实现更灵活的数据组织
  • Tile大小:除了最后一个Tile,其他Tile的大小都会明确记录在比特流中
  • 独立解码:每个Tile解码前都会初始化熵解码器,解码后退出,确保Tile之间的独立性

关键数据结构

FrameHeader中的Tile信息

class FrameHeader:
    # Tile划分参数
    TileColsLog2: int          # Tile列数的对数
    TileRowsLog2: int          # Tile行数的对数
    TileCols: int              # Tile列数
    TileRows: int              # Tile行数
    
    # Tile边界
    MiColStarts: List[int]     # 每个Tile列的起始MI位置
    MiRowStarts: List[int]     # 每个Tile行的起始MI位置
    
    # Tile大小
    TileSizeBytes: int         # Tile大小字段的字节数
    TileNum: int               # 当前Tile编号

TileGroup中的Tile信息

class TileGroup:
    # Tile边界
    MiRowStart: int            # 当前Tile的起始行
    MiRowEnd: int              # 当前Tile的结束行
    MiColStart: int            # 当前Tile的起始列
    MiColEnd: int              # 当前Tile的结束列
    
    # 上下文管理
    BlockDecoded: List[List[List[int]]]  # 块解码标志
    DeltaLF: List[int]        # Delta LF参数
    RefSgrXqd: List[List[List[int]]]  # Loop Restoration参考参数
    RefLrWiener: List[List[List[List[int]]]]  # Wiener滤波参考参数

总结

Tile划分和并行处理是AV1解码器实现高效解码的关键技术。通过将一帧图像划分为多个独立的Tile,AV1可以实现:

  1. 并行解码:多个Tile可以同时解码,提高解码效率
  2. 内存管理:每个Tile有独立的上下文,便于内存管理
  3. 错误隔离:一个Tile的错误不会影响其他Tile

Tile划分在帧头中完成,Tile Group OBU包含Tile的实际数据。每个Tile可以独立解码,但帧级操作(如CDF更新)需要在所有Tile完成后进行。


参考资源:

下一篇: Superblock解码

posted @ 2026-01-24 09:37  chai51  阅读(1)  评论(0)    收藏  举报