一 代码逻辑流程
帧内块划分和模式决策核心逻辑位置
1 块划分决策核心函数
主要入口函数:
svt_aom_mode_decision_sb() 超级块模式决策
处理整个SUperBlock的模式决策
遍历所有块,调用process_block() 处理每个块
块划分决策函数:
svt_aom_d1_non_square_block_decision()
非方形块NSQ划分决策
计算划分成分,决定是否采用非方形划分
svt_aom_d2_inter_depth_block_decision()
深度间划分决策
比较不同深度的成本,决定是否继续划分
update_d1_data()
更新d1数据,包括d1划分决策
2 模式决策核心函数
单个块模式决策
md_encode_block()
单个块的模式决策主函数
包含多阶段模式决策流程 (MDS0->MDS1->MDS2->MDS3)
帧内候选生成
inject_intra_condidates()
注入帧内预测候选模式
生成DC,方向,角度等帧内模式候选
generate_md_stage_0_cand
生成模式决策阶段0的候选
包括帧内和帧间候选
模式决策阶段
md_stage_0() 快速评估阶段
md_stage_1() 第一次全循环评估
md_stage_2() 第二次循环评估
md_stage_3() 最终全循环评估
3 关键文件位置
product_coding_loop.c 主要模式决策流程
svt_aom_decision_sb() #SuperBlock模式决策入口
md_encode_block() 单个模式决策
process_block() 块处理入口
full_loop.c 块划分决策
svt_aom_d1_non_square_block_decision() 非方形块决策
svt_aom_d2_inter_depth_block_decision() 深度间决策
mode_decision.c #模式决策核心逻辑
inject_intra_candidates() 帧内候选注入
generate_md_stage_0_cand() 候选生成
md_stage_0/1/2/3() 多阶段决策
enc_mode_config.c 模式决策配置
set_intra_ctrls() 帧内控制参数设置
4 执行流程
svt_aom_mode_decision_sb()
process_block()
md_encode_block()
generate_md_stage_0_cand() #生成候选 包括帧内
inject_intra_candidates() 注入帧内候选
md_stage_0() 快速评估
md_stage_1() 第一次全循环
md_staget_2() 第二次全循环
md_stage_3() 最终评估
块划分决策在svt_aom_mode_decision_sb() 中通过 update_d1_data()和update_d2_decision()完成
二 核心函数 svt_aom_mode_decision_sb 解析
函数功能:遍历SuperBlock中所有需要处理的块,对每个块执行模式决策,然后输出该SB的最优模式分布/划分结果`
`处理流程:`
`对每个块,通过多个MD阶段选择最佳模式,随着阶段推进,精度提高但是候选模式数量减少`
`基于块成本,为父块选择最佳划分,如果存在NSQ形状`
`最后执行深度间决策,确定最终的划分结构`
`scs:` `序列控制集指针,包含序列级别的配置信息`
`pcs:图像控制集指针,包含当前图像的控制信息`
`ctx:` `模式决策上下文指针,包含当前SB的模式决策状态和结果`
`mdc_sb_data:` `SUperBlock的模式决策数据指针,包含需要处理的块列表和划分标志。`
`void` `svt_aom_mode_decision_sb(SequenceControlSet *scs, PictureCOntrolSet *pcs, ModeDecisionCOntext *ctx, `
`const MdcSbData *const mdc_sb_data)`
`{`
`//更新SuperBlock的邻居数组,用于帧内预测和上下文建模`
`//邻居数组存储了已经编码块的重建像素,供当前块参考使用`
`update_neighbour_arrays(pcs, ctx);`
`//获取输入图像指针,从图像控制集中获取增强后的图像,可能经过预处理`
`EbPictureBufferDesc*` `input_pic` `= pcs->ppcs->enhanced_pic;`
`//如果需要16位图像,对输入图像进行填充处理,仅仅在SB级别执行一次`
`//高比特深度模式决策需要16位数据,即使编码器使用8位MD也可能需要16位图像用于后续处理`
`if (ctx->hbd_md)` `{`
`//如果使用高比特深度模式决策,将输入图像填充为16位格式并更新指针`
`input_pic = pad_hbd_pictures(scs, pcs, ctx, input_pic);`
`}` `else if (ctx->encoder_bit_depth > EB_EIGHT_BIT && ctx->bypass_encdec && ctx->pd_pass == PD_PASS_1)` `{`
`//如果编码器比特深度大于8位,且使用8位MD但绕过编码解码过程,且处于PD_PASS_1阶段`
`//虽然现在不需要16位图像,但是后续会需要,所以提前填充,但不改变input_pic指针`
`pad_hbd_pictures(scs, pcs, ctx, input_pic);`
`}`
`//初始化用于跟踪块的变量`
`//leaf_count,` `当前SB中需要处理的叶子块,最终划分块` `数量`
`uint32_t` `leaf_count = mdc_sb_data->leaf_count;`
`//leaf_data_array` `叶子块数据数组指针,包含每个块的位置,形状等信息`
`const EbMdcLeafData` `*const leaf_data_array = mdc_sb_data->leaf_data_array;`
`//md_early_exit_sq: 方形块早期退出标志,如果为1则跳过当前方形块的所有后续处理`
`Bool` `md_early_exit_sq =` `0;/`
`//next_non_skip_blk_idx_mds:` `下一个不跳过的块的MDS索引,用于早期退出优化`
`uint32_t` `next_non_skip_blk_idx_mds = 0;`
`//coded_area_sb` `SB中已经编码的亮度区域,像素数,用于统计和优化`
`ctx->coded_area_sb` `= 0;`
`//coded_area_sb_uv` `SB中已经编码的色度区域` `像素数,用于统计和优化`
`ctx->coded_area_sb_uv` `= 0;`
`//params_status` `参数状态标志,1表示需要重置参数,某些特性会修改设置`
`ctx->params_status` `= 0;`
`//copied_neigh_arrays` `是否已经复制邻居数组标志,用于判断是否需要恢复邻居数组`
`ctx->copied_neigh_arrays` `= 0;`
`//遍历所有标记为需要处理的块,叶子块`
`for (uint32_t blk_idx = 0; blk_idx < leaf_count; blk_idx++)` `{`
`//base_blk_idx_mds` `当前块的基础MDS索引,方形块的索引`
`uint32_t` `base_blk_idx_mds = leaf_data_array[blk_idx].mds_idx;`
`//leaf_data_ptr 当前叶子块的数组指针,包含该块的所有形状和配置信息`
`const` `EbMdcLeafData` `*const leaf_data_ptr = &leaf_data_array[blk_idx];`
`//blk_split_flag` `块划分标志,表示该块是否应该继续划分`
`const uint8_t` `blk_split_flag = mdc_sb_data->split_flag[blk_idx];`
`//获取当前块的集合信息,尺寸位置,形状等`
`ctx->blk_geom` `= get_blk_geom_mds(base_blk_idx_mds);`
`//获取当前块的模式决策数据结构指针,用于存储该块的决策结果`
`ctx->blk_ptr = &ctx->md_blk_arr_nsq[base_blk_idx_mds];`
`//重置设置,以防被前一个块覆盖`
`//只有当使用会改变设置的特性时才需要重置设置`
`if (ctx->params_status == 1)` `{`
`//重新推导信号参数,这些参数可能被前一个块修改`
`svt_aom_sig_deriv_enc_dec(scs, pcs, ctx);`
`//重置参数状态标志`
`ctx->params_status = 0;`
`}`
`//断言检查,基础块索引必须等于其几何信息的方形块索引`
`//这确保我们处理的是正确的块`
`//初始化块数据,设置块的初始化状态,QP,lambda等参数`
`init_block_data(pcs, ctx, blk_split_flag, base_blk_idx_mds);`
`//更新方形块的左侧和上方划分邻居信息`
`//这些信息用于推导划分的码率成本,上下文建模`
`update_part_neighs(ctx);`
`//在高能量区域使用更保守的NSQ,设置`
`//高能量区域通常包含复杂纹理,需要更仔细的划分决策`
`if (!ctx->md_disallow_nsq_search && ctx->nsq_search_ctrls.high_energy_weight && ctx->detect_high_freq_ctrls.enabled)` `{`
`//根据高能量检测结果更新NSQ搜索设置`
`update_nsq_settings(pcs, ctx);`
`}`
`//检查当前深度的成本,如果大于父块成本,提前退出`
`//如果只使用预测深度,pred_depth_only 不会跳过,所以不需要检查`
`if (!ctx->pd_pass == PD_PASS_1 && ctx->pred_depth_only)` `{`
`//比较当前深度和父深度的长呢改变呢,如果当前成本更高则设置早期退出`
`check_curr_to_parent_cost(scs, pcs, ctx, &next_non_skip_blk_idx_mds, &md_early_exit_sq); `
`}`
`//如果需要测试超过1个NSQ形状或将要划分,则将邻居数组复制到临时缓冲区以便后续重用`
`//方形块,SQ不需要更新邻居数组,所以不需要复制`
`//条件,形状数量` `> 2, 或2` `形状数量` `> 1且第一个形状不是方形,或3,当前块会继续划分`
`const bool copy_neigh_arrays` `(leaf_data_array[blk_idx].tot_shapes > 2 || (leaf_data_array[blk_idx].tot_shapes > 1 && leaf_data_array[blk_idx].shapes[0] != PART_N)) || ctx->md_blk_arr_nsq[ctx->blk_geom->sqi_mds].split_flag;`
`//遍历在当前深度设置的所有要测试的形状`
`//shape_idx` `形状索引,从0开始遍历所有可能的形状`
`//如果设置了早期退出标志,md_early_exit_sq, 则停止遍历`
`}`
`for (uint32_t shape_idx = 0; shape_idx < leaf_data_array[blk_idx].tot_shapes && !md_early_exit_sq;`
`shape_idx++)` `{`
`//shape,` `当前要测试的形状类型,PART_N` `方形,PART_H` `水平划分,PART_V` `垂直划分等`
`Part` `shape = leaf_data_array[blk_idx].shapes[shape_idx];`
`//shape_block_cnt` `该形状包含的子块数量,例如H划分有2个子块,SPLIT划分有4个子块`
`uint8_t shape_block_cnt` `= num_ns_per_shape[shape];`
`//blk_idx_mds, 计算当前形状中第一个子块的MDS索引`
`//根据SB大小,128或64` `使用不同的偏移表`
`uint32_t blk_idx_mds = base_blk_idx_mds` `+` `(ctx->blk_geom->sq_size == 128 ? ns_blk_offset_128_md[shape] : ns_blk_offset_md[shape]);`
`//遍历该形状中的所有子块`
`//nsi` `子块在该形状中的索引` `nsi = non-squar index`
`//每次循环后,blk_idx_mds` `递增以指向下一个子块`
`for (uint32_t nsi = 0; nsi < shape_block_cnt; nsi++, blk_idx_mds++)` `{`
`//获取正在测试的形状中当前块的集合信息和块指针`
`//blk_geom` `当前子块的几何信息,尺寸位置等`
`ctx->blk_geom = get_blk_geom_mds(blk_idx_mds);`
`//blk_ptr 当前子块的模式决策数据结构指针`
`ctx->blk_ptr =` `&ctx->md_blk_arr_nsq[blk_idx_mds];`
`//初始化当前子块的数据,设置初始状态,参数等`
`init_block_data(pcs, ctx, blk_split_flag, blk_idx_mds);`
`//处理当前块,执行完整的模式决策流程`
`//包括,候选生成,多阶段评估,最佳模式选择等`
`process_blok(pcs, ctx, leaf_data_ptr, input_pic);`
`//md_early_exit_nsq` `NSQ早期退出标志,初始化为0`
`Bool md_early_exit_nsq = 0;`
`//更新d1` `深度1` `数据,计算当前形状的成本,更新父块信息,决定是否提前退出`
`//如果当前形状的成本已经足够好,可以跳过后续形状的测试`
`update_d1_data(pcs, ctx, blk_idx_mds, &md_early_exit_nsq, shape_idx, copy_neigh_arrays);`
`//如果设置了NSQ早期退出标志,跳出内层循环,不再测试该形状的其他子块`
`if (md_early_exit_nsq) {`
`break;`
`}`
`}`
`}`
`//现在已经检查完所有d1块,更新d2,深度2,既深度间信息`
`//如果已经复制邻居数组且当前方形块会继续划分,需要恢复邻居数组`
`//将临时缓冲区[1]中的内容恢复到` `0 中,因为最后一个NS块已经处理完成`
`if (ctx->copied_neigh_arrays && ctx->md_blk_arr_nsq[ctx->blk_geom->sqi_mds].split_flag)`
`svt_aom_copy_neighbour_arrays(` `//恢复,将1恢复到0,处理完最后一个NS块后`
`pcs, ` `//图像控制集`
`ctx, ` `///模式决策上下文`
`1, ` `//源缓冲区索引,从1复制`
`0, ` `//目标缓冲区索引,复制到0`
`ctx->blk_geom->sqi_mds);` `/方形块的MDS索引`
`//在最终d1块之后执行d2深度间决策`
`//比较不同深度的成本,决定是否继续划分到更深的层次`
`update_d2_decision(pcs, ctx);`
`//如果启用了自身度跳过控制,且当前块可以进一步划分,且是有效块`
`if (ctx->skip_sub_depth_ctrls.enable &&`
`ctx->md_blk_aee_nsq[ctx->blk_geom->sqi_mds].split_flag &&` `//可以进一步划分`
`ctx->avail_blk_flag[ctx->blk_geom->sqi_mds])` `{`
`//有效块`
`//如果块尺寸小于等于最大跳过尺寸,且满足跳过条件1`
`//则设置下一个不跳过的块索引,并设置方形块早期退出标志`
`if (ctx->blk_geom->sq_size <= ctx->skip_sub_depth_ctrls.max_size && eval_sub_depth_skip_cond1(ctx))` `{`
`//计算下一个不跳过的块索引,跳过当前块的子块`
`next_non_skip_blk_idx_mds = ctx->blk_geom->sqi_mds + ctx->blk_geom->ns_depth_offset;`
`//设置早期退出标志,跳过当前方形块的后续处理`
`md_early_exit_sq =` `1;`
`}`
`}`
`/重置已经复制邻居数组标志,为下一个块做准备`
`ctx->copied_neigh_arrays` `= 0;`
`}`
`