AV1代码学习:av1_foreach_transformed_block_in_plane函数

本文深入解析了AV1编码标准中的变换块处理,包括变换块的尺寸、亮度与色度分量的不同处理方式,以及如何通过av1_foreach_transformed_block_in_plane函数遍历并处理每个变换块。通过对编码块尺寸的判断和隐式划分,实现不同大小的变换操作,确保编码效率和图像质量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在AV1中,进行预测变换都是基于Transform Block(变换块)进行的,变换块一共19种尺寸,并且其尺寸通常是小于或者等于编码块尺寸的,如下代码所示。

enum {
  TX_4X4,             // 4x4 transform
  TX_8X8,             // 8x8 transform
  TX_16X16,           // 16x16 transform
  TX_32X32,           // 32x32 transform
  TX_64X64,           // 64x64 transform
  TX_4X8,             // 4x8 transform
  TX_8X4,             // 8x4 transform
  TX_8X16,            // 8x16 transform
  TX_16X8,            // 16x8 transform
  TX_16X32,           // 16x32 transform
  TX_32X16,           // 32x16 transform
  TX_32X64,           // 32x64 transform
  TX_64X32,           // 64x32 transform
  TX_4X16,            // 4x16 transform
  TX_16X4,            // 16x4 transform
  TX_8X32,            // 8x32 transform
  TX_32X8,            // 32x8 transform
  TX_16X64,           // 16x64 transform
  TX_64X16,           // 64x16 transform
  TX_SIZES_ALL,       // Includes rectangular transforms
  TX_SIZES = TX_4X8,  // Does NOT include rectangular transforms
  TX_SIZES_LARGEST = TX_64X64,
  TX_INVALID = 255  // Invalid transform size
} UENUM1BYTE(TX_SIZE);

如果编码块小于或等于64x64,则变换块划分只能应用于亮度分量,对于色度块,变换块的大小与编码块的大小相同,不使用变换块划分。否则,如果编码块的宽度或高度大于64,则亮度和色度编码块都将隐式拆分为min(W,64)x min(H,64)和min(W,32)x min(H,32)的变换块。

av1_foreach_transformed_block_in_plane函数就是循环遍历编码块中全部的变换块,对每一个变换块进行预测、变换和量化等操作的。

首先,通过av1_get_tx_size函数获取变换块尺寸,并获得每个变换块的4x4单元数;这里,亮度的变换块尺寸是由上层函数决定传递下来的,此处可以直接通过mbmi获取到

static INLINE TX_SIZE av1_get_tx_size(int plane, const MACROBLOCKD *xd) {
  const MB_MODE_INFO *mbmi = xd->mi[0];
  if (xd->lossless[mbmi->segment_id]) return TX_4X4; 
  if (plane == 0) return mbmi->tx_size;//返回亮度的变换块尺寸
  const MACROBLOCKD_PLANE *pd = &xd->plane[plane];
  return av1_get_max_uv_txsize(mbmi->bsize, pd->subsampling_x,
                               pd->subsampling_y); //返回色度最大变换块尺寸
}

获取当前编码块尺寸,此时会判断当前块的右侧/下侧是否在边界外,如果是在边界外,则会减去边界外的区域;后面也不会访问完全位于边界外的子块。

static INLINE int max_block_wide(const MACROBLOCKD *xd, BLOCK_SIZE bsize,
                                 int plane) {
  assert(bsize < BLOCK_SIZES_ALL);
  int max_blocks_wide = block_size_wide[bsize];

  if (xd->mb_to_right_edge < 0) { //当前编码块右侧到帧边界的距离,为负表示超出边界
    const struct macroblockd_plane *const pd = &xd->plane[plane];
    max_blocks_wide += xd->mb_to_right_edge >> (3 + pd->subsampling_x);
    // 右移3原因是mb_to_right_edge是以1/8像素精度存储的
  }

  // Scale the width in the transform block unit.
  return max_blocks_wide >> MI_SIZE_LOG2;
}
static INLINE int max_block_high(const MACROBLOCKD *xd, BLOCK_SIZE bsize,
                                 int plane) {
  int max_blocks_high = block_size_high[bsize];

  if (xd->mb_to_bottom_edge < 0) {
    const struct macroblockd_plane *const pd = &xd->plane[plane];
    max_blocks_high += xd->mb_to_bottom_edge >> (3 + pd->subsampling_y);
  }

  // Scale the height in the transform block unit.
  return max_blocks_high >> MI_SIZE_LOG2;
}

如果块尺寸是大于64x64的,会进行隐式划分,对于亮度块会以min(W,64)x min(H,64)进行预测变换操作,对于色度块会以min(W,32)x min(H,32)进行预测变换量化操作。

最后,遍历编码块中的变换块,进行预测变换等操作。

代码及其注释如下:

void av1_foreach_transformed_block_in_plane(
    const MACROBLOCKD *const xd, BLOCK_SIZE plane_bsize, int plane,
    foreach_transformed_block_visitor visit, void *arg) {
  const struct macroblockd_plane *const pd = &xd->plane[plane];
  // block and transform sizes, in number of 4x4 blocks log 2 ("*_b")
  // 4x4=0, 8x8=2, 16x16=4, 32x32=6, 64x64=8
  // transform size varies per plane, look it up in a common way.
  const TX_SIZE tx_size = av1_get_tx_size(plane, xd); //变换块尺寸
  const uint8_t txw_unit = tx_size_wide_unit[tx_size]; // 变换块宽度的单元数tx_size_wide/4
  const uint8_t txh_unit = tx_size_high_unit[tx_size]; // 变换块高度的单元数tx_size_high/4
  const int step = txw_unit * txh_unit; // 按照变换块尺寸遍历BLOCK

  // If mb_to_right_edge is < 0 we are in a situation in which
  // the current block size extends into the UMV and we won't
  // visit the sub blocks that are wholly within the UMV.
  //如果mb_to_right_edge<0,则当前块大小扩展到UMV中,并且我们不会访问完全在UMV中的子块
  const int max_blocks_wide = max_block_wide(xd, plane_bsize, plane); //宽度的最大单元数 width/4
  const int max_blocks_high = max_block_high(xd, plane_bsize, plane); // 高度的最大单元数 height/4
  // 这里,使用BLOCK_64X64,是为了由于进行变换量化时,其最大尺寸为64x64
  // 如果当前Block尺寸大于64时,在这里会进行隐式划分
  const BLOCK_SIZE max_unit_bsize =
      get_plane_block_size(BLOCK_64X64, pd->subsampling_x, pd->subsampling_y); // 64
  const int mu_blocks_wide =
      AOMMIN(mi_size_wide[max_unit_bsize], max_blocks_wide);
  const int mu_blocks_high =
      AOMMIN(mi_size_high[max_unit_bsize], max_blocks_high);

  // Keep track of the row and column of the blocks we use so that we know
  // if we are in the unrestricted motion border.
  // 跟踪我们使用的块的行和列,以便知道我们是否在不受限制的运动边界中。
  int i = 0;
  for (int r = 0; r < max_blocks_high; r += mu_blocks_high) {
    const int unit_height = AOMMIN(mu_blocks_high + r, max_blocks_high);
    // Skip visiting the sub blocks that are wholly within the UMV.
    // 跳过访问整个UMV中的子块。
    for (int c = 0; c < max_blocks_wide; c += mu_blocks_wide) {
      const int unit_width = AOMMIN(mu_blocks_wide + c, max_blocks_wide);
      // 前两个循环是相当于隐式划分
      for (int blk_row = r; blk_row < unit_height; blk_row += txh_unit) {
        for (int blk_col = c; blk_col < unit_width; blk_col += txw_unit) {
          // 变换全部的变换块
          // 以变换块尺寸为单元进行预测变换量化
          visit(plane, i, blk_row, blk_col, plane_bsize, tx_size, arg);
          i += step;
        }
      }
    }
  }
}

<think>好的,我现在需要分析用户提供的这段C语言代码,并解释它的功能。首先,我看到这是一个switch语句,根据tx_type的不同值返回不同的枚举类型。让我先仔细看看每个case的情况。 首先,switch的条件是tx_type,这说明这个变量可能代表某种变换类型。然后有多个case标签,比如V_DCT、V_ADST、V_FLIPADST,这些可能都是枚举常量。接下来是H_DCT、H_ADST、H_FLIPADST,最后是default返回TX_CLASS_2D。 看起来代码的作用是根据不同的tx_type值,判断其属于垂直类(VERT)、水平类(HORIZ)还是默认的二维类(2D)。可能的上下文是视频编码或图像处理中的变换类型分类,比如DCT(离散余弦变换)、ADST(反正弦变换)等。 接下来,我需要确认每个case的分组是否正确。V_开头的三个case都返回TX_CLASS_VERT,而H_开头的三个返回TX_CLASS_HORIZ。这说明V_前缀代表垂直方向的变换类型,H_前缀代表水平方向。剩下的情况则默认归类为二维变换。 我需要检查是否有遗漏的case,但代码中没有break语句,因为每个case直接返回,所以不需要break。每个case标签后面跟着return,所以执行完相应return后就会退出switch语句。 可能的疑问是,为什么V_和H_分别对应VERT和HORIZ?通常,垂直变换可能应用在水平方向,反之亦然,这在视频编码中有时会有这种命名习惯,但需要进一步确认。不过根据代码逻辑,这里只是根据前缀进行分类,不管具体变换类型的方向如何,只要前缀是V或H,就归类到对应的VERT或HORIZ类。 总结一下,这段代码的作用是将不同的变换类型分为垂直、水平和二维类别,用于后续处理,比如选择不同的处理流程或参数设置。</think>这段代码是一个C语言中的`switch`语句,用于根据不同的变换类型(`tx_type`)返回对应的变换分类。它的主要功能是对变换类型进行分类,判断其属于垂直方向、水平方向还是默认的二维类。以下是逐步解析: --- ### **代码结构分析** ```c switch (tx_type) { case V_DCT: case V_ADST: case V_FLIPADST: return TX_CLASS_VERT; // 垂直方向变换 case H_DCT: case H_ADST: case H_FLIPADST: return TX_CLASS_HORIZ; // 水平方向变换 default: return TX_CLASS_2D; // 默认二维变换 } ``` --- ### **分类逻辑详解** #### 1. **垂直方向变换 (TX_CLASS_VERT)** - **匹配条件**:当`tx_type`为以下任意值时: - `V_DCT` - `V_ADST` - `V_FLIPADST` - **行为**:返回`TX_CLASS_VERT`,表示该变换属于垂直方向。 - **应用场景**:这类变换可能用于处理垂直方向的数据(例如图像/视频编码中的垂直频率分量)。 #### 2. **水平方向变换 (TX_CLASS_HORIZ)** - **匹配条件**:当`tx_type`为以下任意值时: - `H_DCT` - `H_ADST` - `H_FLIPADST` - **行为**:返回`TX_CLASS_HORIZ`,表示该变换属于水平方向。 - **应用场景**:这类变换可能用于处理水平方向的数据(例如图像/视频编码中的水平频率分量)。 #### 3. **默认二维变换 (TX_CLASS_2D)** - **匹配条件**:所有未明确列出的`tx_type`值。 - **行为**:返回`TX_CLASS_2D`,表示该变换是二维的,无法简单归类为垂直或水平。 - **应用场景**:可能需要二维处理的复杂变换(例如全图像块的变换)。 --- ### **关键设计思想** 1. **命名约定**: - `V_`前缀表示垂直方向变换(如`V_DCT`)。 - `H_`前缀表示水平方向变换(如`H_ADST`)。 - 这种命名方式使得代码逻辑清晰,易于扩展。 2. **分类简化**: - 通过合并多个`case`标签(如`case V_DCT: case V_ADST: ...`),减少重复代码。 - 默认值处理未知或复杂的变换类型。 3. **应用背景推测**: - 可能用于视频编码标准(如AV1、H.266)中的变换类型选择。 - 常见的变换类型包括DCT(离散余弦变换)、ADST(反正弦变换)、FLIPADST(翻转ADST)等。 --- ### **示例场景** 假设在视频编码中需要根据块的方向选择变换: - 如果当前块是垂直方向纹理,使用`V_DCT`或`V_ADST`,代码返回`TX_CLASS_VERT`。 - 如果当前块是水平方向纹理,使用`H_FLIPADST`,代码返回`TX_CLASS_HORIZ`。 - 对于非方向性纹理(如平坦区域),返回`TX_CLASS_2D`。 --- ### **总结** 这段代码通过`switch`语句将不同的变换类型映射到三个分类中,核心逻辑是通过`tx_type`的前缀(`V_`或`H_`)判断方向,其余情况视为二维变换。这种设计在视频编码中常用于优化变换核的选择。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值