【元宝搜索开发者征文大赛】长辈不信你时,我靠元宝搜索拿出 23 条可点开的官方通报
一、SVT-AV1 Mini-Gop决策算法详解
SVT-AV1 支持多种层级预测结构(3L/4L/5L/6L),在 Random Access 模式下,编码器会根据时域复杂度自适应地调整 mini-GoP 的层级结构,这一机制称为动态 Mini-GoP(Dynamic Mini-GoP)。此外,S-Frame 对齐、启动 mini-GoP、场景切换、RA 截断等因素也会影响最终的 mini-GoP 结构。
注:本文根据 **v4.1.0 ** 源码描述
二、概念
- Mini-Gop:在SVT-AV1中的概念可以理解为两个只能前向参考 的帧以及之间的所有帧组成一个Mini-Gop,在SVT-AV1中最大的Mini-Gop就是6L,最小为1L(2^0=1),一个6L的Mini-Gop结构如下,共有32帧(2^5=32):

三、核心算法
SVT-AV1 默认会将 Mini-Gop 初始化设置为 6L(从0开始) ,即32帧,后续可能会在此基础上分割为2的n次方个帧,但一定小于32帧。
1.S-Frame 对 mini-GoP 大小的调整
S-Frame(Switching Frame)是 AV1 中用于码流切换的特殊帧。当 S-Frame 落在下一个 mini-GoP 内部时,需要 降级 mini-GoP 的层级数 ,使 mini-GoP 边界与 S-Frame 对齐。
Plain
if (ctx->sframe_hier_lvls != (int32_t)scs->static_config.hierarchical_levels) {
next_mg_hierarchical_levels = ctx->sframe_hier_lvls;
}
2.Startup Mini-GoP(启动 mini-GoP)
编码开始时(IDR/CRA 之后),可以使用较小的 mini-GoP 大小来加速编码启动,避免第一个 mini-GoP 过大导致延迟。
- 含义:
C++
/**
* @brief startup_mg_size
*
* When enabled, a MG with specified size will be inserted after the key frame.
* The MG size is determined by 2^startup_mg_size.
*
* 0: off
* 2: set hierarchical levels to 2 (MG size 4)
* 3: set hierarchical levels to 3 (MG size 8)
* 4: set hierarchical levels to 4 (MG size 16)
* Default is 0.
*/
// 启动 MG(mini-gop) 大小,0 表示不启用,2、3、4 表示设置层级为 2、3、4,
// 在开启此功能后会在关键帧(Key Frame / I帧)之后会强制插入一个指定尺寸的mini-gop
uint8_t startup_mg_size;
- 启用逻辑:
get_pred_struct_for_all_frames() 函数:
Java
// 启动 MG(mini-gop) 大小,0 表示不启用,2、3、4 表示设置层级为 2、3、4,在开启此功能后会在关键帧(Key Frame / I帧)之后会强制插入一个指定尺寸的mini-gop
if (scs->static_config.startup_mg_size != 0) {
if (pcs->idr_flag || pcs->cra_flag) { // 如果是关键帧或CR帧
ctx->enable_startup_mg = true; // 启动 MG(mini-gop) 大小功能
} else if (ctx->enable_startup_mg) { // 否则,关闭 MG(mini-gop) 大小功能
ctx->enable_startup_mg = false;
}
}
// 标记当前 mini-GoP 是否为"启动 mini-GoP"(序列开始或关键帧后的第一个 mini-GoP)
if (pcs->idr_flag && pcs->picture_number == 0) {
ctx->is_startup_gop = true;
} else if (pcs->idr_flag || pcs->cra_flag) {
ctx->is_startup_gop = false;
}
- 应用
Plain
if (ctx->enable_startup_mg) {
next_mg_hierarchical_levels = scs->static_config.startup_mg_size;
}
3.动态 Mini-GoP (Dynamic Mini-GoP / enable_dg)
基于早期运动分析决定是否将 6L 拆分为 5L。
- 含义:默认开启
Plain
/* Dynamic gop
*
* 0 = disable Dynamic GoP
* 1 = enable Dynamic GoP
* Default is 1. */
bool enable_dg;
详细算法参考:SVT-AV1 动态 Mini-GoP (Dynamic Mini-GoP)算法详解
4.不完整 mini-GoP 处理 (Incomplete Mini-GoP)
当预分配缓冲区中的帧数不足以构成一个完整的 mini-GoP 时(例如 GOP 末尾、场景切换、IDR 插入),需要处理不完整的 mini-GoP。
initialize_mini_gop_activity_array()中的缓冲区帧数判断:根据 pre_assignment_buffer_count 的大小,逐级标记哪些 mini-GoP 是完整的( activity = false 表示不拆分)
Java
// Loop over all mini GOPs to initialize the activity 遍历编码器内所有预定义的 Mini-GOP 模板
// 层级数 > 1 的 mini-GoP 设为 true (活跃/需拆分),层级数 == 1 的设为 false (叶节点,不拆分)
for (uint32_t gopindex = 0; gopindex < MINI_GOP_MAX_COUNT; ++gopindex) {
ctx->mini_gop_activity_array[gopindex] = svt_aom_get_mini_gop_stats(gopindex)->hierarchical_levels >
MIN_HIERARCHICAL_LEVEL;
}
// Assign the MGs to be used; if the MG is incomplete, the pre-assignment buffer will hold
// fewer than (1 << scs->static_config.hierarchical_levels) pics
// 分配要使用的Mini-GOP;如果该Mini-GOP不完整,预分配缓冲区中容纳的图像数量将会少于 (1 << scs->static_config.hierarchical_levels) 帧
// 关键规则 :一个 mini-GoP 被标记为 false (完整/不拆分),当且仅当缓冲区中有 足够的帧 填满该 mini-GoP, 且该 mini-GoP 的最后一帧不是 IDR (IDR 会截断预测结构)
// IDR 特殊处理 : !(count == N && pcs->idr_flag) 这个条件确保------如果缓冲区恰好有 N 帧且最后一帧是 IDR,则 不 将该 mini-GoP 标记为完整。原因是 IDR 帧会截断预测结构,不能作为 mini-GoP 的正常终止帧。
// count >= 32 且不是"恰好32帧+IDR"
if (enc_ctx->pre_assignment_buffer_count >= 32 && !(enc_ctx->pre_assignment_buffer_count == 32 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L6_INDEX] = false;
} else if (enc_ctx->pre_assignment_buffer_count >= 16 &&
!(enc_ctx->pre_assignment_buffer_count == 16 && pcs->idr_flag)) { // count >= 16 且不是"恰好16帧+IDR"
ctx->mini_gop_activity_array[L5_0_INDEX] = false; // 前16帧构成完整5L
// 然后继续检查剩余帧( count - 16 )能构成什么:
if ((enc_ctx->pre_assignment_buffer_count - 16) >= 8 &&
!((enc_ctx->pre_assignment_buffer_count - 16) == 8 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L4_2_INDEX] = false; // 前8帧构成完整4L
if ((enc_ctx->pre_assignment_buffer_count - 16 - 8) >= 4 &&
!((enc_ctx->pre_assignment_buffer_count - 16 - 8) == 4 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L3_6_INDEX] = false; // 前4帧构成完整3L
if ((enc_ctx->pre_assignment_buffer_count - 16 - 8 - 4) >= 2 &&
!((enc_ctx->pre_assignment_buffer_count - 16 - 8 - 4) == 2 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L2_14_INDEX] = false; // 前2帧构成完整2L
}
} else if ((enc_ctx->pre_assignment_buffer_count - 16 - 8) >= 2 &&
!((enc_ctx->pre_assignment_buffer_count - 16 - 8) == 2 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L2_12_INDEX] = false; // 前2帧构成完整2L
}
} else if ((enc_ctx->pre_assignment_buffer_count - 16) >= 4 &&
!((enc_ctx->pre_assignment_buffer_count - 16) == 4 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L3_4_INDEX] = false; // 前4帧构成完整3L
if ((enc_ctx->pre_assignment_buffer_count - 16 - 4) >= 2 &&
!((enc_ctx->pre_assignment_buffer_count - 16 - 4) == 2 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L2_10_INDEX] = false; // 前2帧构成完整2L
}
} else if ((enc_ctx->pre_assignment_buffer_count - 16) >= 2 &&
!((enc_ctx->pre_assignment_buffer_count - 16) == 2 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L2_8_INDEX] = false; // 前2帧构成完整2L
}
} else if (enc_ctx->pre_assignment_buffer_count >= 8 &&
!(enc_ctx->pre_assignment_buffer_count == 8 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L4_0_INDEX] = false;
if ((enc_ctx->pre_assignment_buffer_count - 8) >= 4 &&
!((enc_ctx->pre_assignment_buffer_count - 8) == 4 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L3_2_INDEX] = false;
if ((enc_ctx->pre_assignment_buffer_count - 8 - 4) >= 2 &&
!((enc_ctx->pre_assignment_buffer_count - 8 - 4) == 2 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L2_6_INDEX] = false;
}
} else if ((enc_ctx->pre_assignment_buffer_count - 8) >= 2 &&
!((enc_ctx->pre_assignment_buffer_count - 8) == 2 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L2_4_INDEX] = false;
}
} else if (enc_ctx->pre_assignment_buffer_count >= 4 &&
!(enc_ctx->pre_assignment_buffer_count == 4 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L3_0_INDEX] = false;
if ((enc_ctx->pre_assignment_buffer_count - 4) >= 2 &&
!((enc_ctx->pre_assignment_buffer_count - 4) == 2 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L2_2_INDEX] = false;
}
} else if ((enc_ctx->pre_assignment_buffer_count) >= 2 &&
!((enc_ctx->pre_assignment_buffer_count) == 2 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L2_0_INDEX] = false;
}
handle_incomplete_picture_window_map():处理尾部不完整的 mini-GoP,将其层级设为 MIN_HIERARCHICAL_LEVEL (最小值)
Markdown
/***************************************************************************************************
* Handles an incomplete picture window map 处理缓冲区末尾"剩余帧"无法构成完整 mini-GoP 的情况。
*
*
***************************************************************************************************/
static EbErrorType handle_incomplete_picture_window_map(uint32_t hierarchical_level, PictureDecisionContext* pd_ctx,
EncodeContext* enc_ctx) {
EbErrorType return_error = EB_ErrorNone;
if (pd_ctx->total_number_of_mini_gops == 0) { // 完全没有完整 mini-GoP
// 直接将缓冲区所有帧作为mini-gop
// 为什么要强制层级为 1?剩余帧数量通常少于 2 帧的完整结构所需,为了避免引用缺失/结构不完整,使用最小的层级结构(最保守、最安全)。
hierarchical_level = MIN(MIN_HIERARCHICAL_LEVEL, hierarchical_level);
pd_ctx->mini_gop_start_index[pd_ctx->total_number_of_mini_gops] = 0; // 设置当前mini-gop的起始索引为0
pd_ctx->mini_gop_end_index[pd_ctx->total_number_of_mini_gops] = enc_ctx->pre_assignment_buffer_count - 1; // 设置当前mini-gop的结束索引为缓冲区帧数量减1
pd_ctx->mini_gop_length[pd_ctx->total_number_of_mini_gops] = enc_ctx->pre_assignment_buffer_count -
pd_ctx->mini_gop_start_index[pd_ctx->total_number_of_mini_gops]; // 设置当前mini-gop区帧数量减区帧数量减起mini-gop的起始索引
pd_ctx->mini_gop_hierarchical_levels[pd_ctx->total_number_of_mini_gops] = hierarchical_level; // 设置当前mini-gop的层级
pd_ctx->total_number_of_mini_gops++; // 增加有效mini-gop的数量
} else if (pd_ctx->mini_gop_end_index[pd_ctx->total_number_of_mini_gops - 1] <
enc_ctx->pre_assignment_buffer_count - 1) { // 最后一个mini-gop的结束索引小于缓冲区帧数量减1,说明还有一些帧没有分配mini-gop
// 直接将缓冲区剩余的所有帧作为mini-gop
pd_ctx->mini_gop_start_index[pd_ctx->total_number_of_mini_gops] =
pd_ctx->mini_gop_end_index[pd_ctx->total_number_of_mini_gops - 1] + 1; // 设置当前mini-gop的起始索引为上一个mini-gop的结束索引加1
pd_ctx->mini_gop_end_index[pd_ctx->total_number_of_mini_gops] = enc_ctx->pre_assignment_buffer_count - 1; // 设置当前mini-gop的结束索引为缓冲区帧数量减1
pd_ctx->mini_gop_length[pd_ctx->total_number_of_mini_gops] = enc_ctx->pre_assignment_buffer_count -
pd_ctx->mini_gop_start_index[pd_ctx->total_number_of_mini_gops]; // 设置当前mini-gop区帧数量减区帧数量减起mini-gop的起始索引
pd_ctx->mini_gop_hierarchical_levels[pd_ctx->total_number_of_mini_gops] = MIN_HIERARCHICAL_LEVEL; // 设置当前mini-gop的层级
pd_ctx->mini_gop_intra_count[pd_ctx->total_number_of_mini_gops - 1] = 0; // 设置当前mini-gop的帧内编码帧数为0
pd_ctx->mini_gop_idr_count[pd_ctx->total_number_of_mini_gops - 1] = 0; // 设置当前mini-gop的IDR编码帧数为0
pd_ctx->total_number_of_mini_gops++; // 增加有效mini-gop的数量
}
pd_ctx->mini_gop_intra_count[pd_ctx->total_number_of_mini_gops - 1] = enc_ctx->pre_assignment_buffer_intra_count; // 设置当前mini-gop的帧内编码帧数为缓冲区中统计的 I 帧数量
pd_ctx->mini_gop_idr_count[pd_ctx->total_number_of_mini_gops - 1] = enc_ctx->pre_assignment_buffer_idr_count; // 设置当前mini-gop的IDR编码帧数为缓冲区中统计的 IDR 帧数量
return return_error;
}
4.1 场景切换对 mini-gop的影响
默认情况下此开关被强制关闭,SVT-AV1 不推荐通过插入关键帧处理场景切换。
Plain
/* Flag to enable the scene change detection algorithm.
*
* Default is 0. */
uint32_t scene_change_detection;
C
场景切换检测 → cra_flag=true → intra_count++ → 提前触发 set_mini_gop_structure → mini-GoP 被截断
-
进行场景切换检测,如果有场景切换将cra_flag=true;
JavaScript// Perform scene change detection and update relevant signals static void perform_scene_change_detection(SequenceControlSet* scs, PictureParentControlSet* pcs, PictureDecisionContext* ctx) { if (scs->static_config.scene_change_detection) { pcs->scene_change_flag = scene_transition_detector(ctx, scs, (PictureParentControlSet**)pcs->pd_window); } else { pcs->scene_change_flag = false; if (scs->vq_ctrls.sharpness_ctrls.scene_transition && (ctx->transition_detected == -1 || ctx->transition_detected == 0)) { ctx->transition_detected = scene_transition_detector(ctx, scs, (PictureParentControlSet**)pcs->pd_window); } } pcs->cra_flag = (pcs->scene_change_flag == true) ? true : pcs->cra_flag; // Store scene change in context ctx->is_scene_change_detected = pcs->scene_change_flag; } -
场景切换帧的 cra_flag=true ,所以 intra_count 递增;
Cenc_ctx->pre_assignment_buffer_intra_count += (pcs->idr_flag || pcs->cra_flag); -
提前触发 set_mini_gop_structure()函数:
-
正常情况 :缓冲区积攒到 1 << hierarchical_levels 帧(如 6L=32 帧)时才触发 set_mini_gop_structure 。
-
场景切换时 :缓冲区中出现了 I 帧( intra_count > 0 ), 即使帧数不足 32,也会立即触发 。
C// Determine if Pictures can be released from the Pre-Assignment Buffer 确定是否可以从预分配缓冲区释放图片 /* 1.预分配缓冲区中出现了需要强制编码为 Intra 帧(I 帧/键帧) 的图像; * 2.缓冲区中的图像数量,正好等于当前分层结构所需的满载数量; * 3.收到了 EOS(End of Sequence,序列结束) 标志; * 4.当前的预测结构被配置为 低延迟(Low Delay,通常只有 P/B 帧向前预测,没有双向重排) 或 全帧内(All Intra,全 I 帧)*/ if ((enc_ctx->pre_assignment_buffer_intra_count > 0) || (enc_ctx->pre_assignment_buffer_count == (uint32_t)(1 << next_mg_hierarchical_levels)) || (enc_ctx->pre_assignment_buffer_eos_flag == true) || (pcs->pred_structure == LOW_DELAY || pcs->pred_structure == ALL_INTRA)) { ... set_mini_gop_structure(scs, enc_ctx, pcs, ctx); ... }
- mini-GoP 被截断:进入
set_mini_gop_structure后,由于pre_assignment_buffer_count< 32 ,initialize_mini_gop_activity_array中的帧数判断会将不完整的 mini-GoP 标记出来,最终由generate_picture_window_split和handle_incomplete_picture_window_map生成 截断的 mini-GoP 。
5 RA Mini-GoP 截断 (Cutting Short RA Mini-GoP)
在 Random Access 模式下,当 mini-GoP 长度小于完整预测结构条目数,或者 mini-GoP 中包含 IDR 帧时,需要 截断 RA 预测结构 ,切换为 Low Delay 预测结构。
-
is_pic_cutting_short_ra_mg :判断是否需要截断
-
update_pred_struct_and_pic_type 中应用截断:将预测结构从 RANDOM_ACCESS 切换为 LOW_DELAY
6 Mini-GoP 层级切换检测
当相邻两个 mini-GoP 的层级数不同时,标记为 mini-GoP 切换,需要重置预测结构位置。
位置:Source/Lib/Codec/pd_process.c/svt_aom_picture_decision_kernel()
C++
// Derive the temporal layer difference between the current mini GOP and the previous mini GOP
pcs->hierarchical_layers_diff = (int32_t)enc_ctx->previous_mini_gop_hierarchical_levels -
(int32_t)pcs->hierarchical_levels;
// Set init_pred_struct_position_flag to true if mini-GOP switch
pcs->init_pred_struct_position_flag = enc_ctx->is_mini_gop_changed =
(pcs->hierarchical_layers_diff != 0);
四、相关代码详解
1 set_mini_gop_structure() ------ 决定要使用的mini-gop大小并初始化相关字段
位置:Source/Lib/Codec/pd_process.c
Java
// Decide what mini-gop sizes to use and init the relevant fields 决定要使用的mini-gop大小并初始化相关字段
static void set_mini_gop_structure(SequenceControlSet* scs, EncodeContext* enc_ctx, PictureParentControlSet* pcs,
PictureDecisionContext* ctx) {
// 首先读取全局静态配置的 B 帧分层层数 hierarchical_levels
uint32_t next_mg_hierarchical_levels = scs->static_config.hierarchical_levels;
// Overwrite next_mg_hierarchical_levels when an S-Frame needs to modify the mini-GOP size.
// S-Frame(开关帧/流切换帧)改写:如果当前遇到了 S 帧(常用于流媒体自适应码率切换),它需要改变当前的 Mini-GOP 大小以实现快速切流,此时用 sframe_hier_lvls 覆盖默认层级。
if (ctx->sframe_hier_lvls != (int32_t)scs->static_config.hierarchical_levels) {
next_mg_hierarchical_levels = ctx->sframe_hier_lvls;
}
// 启动 Mini-GOP 改写:如果开启了 enable_startup_mg(视频刚开始播放时的快速启动机制),为了让第一帧图像尽快显示,会使用一个较小的启动 Mini-GOP 尺寸(startup_mg_size,通常是 1 或 2 层)
if (ctx->enable_startup_mg) {
next_mg_hierarchical_levels = scs->static_config.startup_mg_size;
}
// Initialize Picture Block Params
// 假设所有pre_assignment_buffer都可以作为一个mini-gop(索引为0)
ctx->mini_gop_start_index[0] = 0; // 起始索引
ctx->mini_gop_end_index[0] = enc_ctx->pre_assignment_buffer_count - 1; // 结束索引
ctx->mini_gop_length[0] = enc_ctx->pre_assignment_buffer_count; // mini-gop 数量
ctx->mini_gop_hierarchical_levels[0] = next_mg_hierarchical_levels; // mini-gop 分层层数
ctx->mini_gop_intra_count[0] = enc_ctx->pre_assignment_buffer_intra_count; // mini-gop intra帧数量
ctx->mini_gop_idr_count[0] = enc_ctx->pre_assignment_buffer_idr_count; // mini-gop idr帧数量
ctx->total_number_of_mini_gops = 1; // mini-gop 数量暂时设置为1
// 果是视频的第一帧(picture_number == 0),初始化前一个 Mini-GOP 的层级
enc_ctx->previous_mini_gop_hierarchical_levels = (pcs->picture_number == 0)
? next_mg_hierarchical_levels
: enc_ctx->previous_mini_gop_hierarchical_levels;
// Mini-GOP 计数器:如果当前缓冲区里有 IDR 帧(意味着一个全新的大 GOP 开始了),则将当前 GOP 内的 Mini-GOP 计数器清零;否则计数器加 1
enc_ctx->mini_gop_cnt_per_gop = (enc_ctx->pre_assignment_buffer_idr_count) ? 0 : enc_ctx->mini_gop_cnt_per_gop + 1;
// 若缓冲区里"全都是 I 帧"(intra_count == count),那么缓冲区的总帧数"必须只能是 1 帧"(count == 1)。因为在非全 I 帧配置下,不可能连续把多帧同时塞进缓冲区并都设为 I 帧
assert(IMPLIES(enc_ctx->pre_assignment_buffer_intra_count == enc_ctx->pre_assignment_buffer_count,
enc_ctx->pre_assignment_buffer_count == 1));
// TODO: Why special case? Why no check on enc_ctx->pre_assignment_buffer_count > 1
if (next_mg_hierarchical_levels == 1) { // 当层级为1时,即min-gop长度为2,强制在 mini_gop[1] 的位置也做了一次边界初始化。
//minigop 2 case
ctx->mini_gop_start_index[ctx->total_number_of_mini_gops] = 0;
ctx->mini_gop_end_index[ctx->total_number_of_mini_gops] = enc_ctx->pre_assignment_buffer_count - 1;
ctx->mini_gop_length[ctx->total_number_of_mini_gops] = enc_ctx->pre_assignment_buffer_count -
ctx->mini_gop_start_index[ctx->total_number_of_mini_gops];
ctx->mini_gop_hierarchical_levels[ctx->total_number_of_mini_gops] = 2;
}
// In RA, if the only picture is an I_SLICE, use default settings (set above). If treat the solo I_SLICE
// as a regular MG, you will change the hierarchical_levels to the minimum.
// For low-delay pred strucutres, pre_assignment_buffer_count will be 1, but no need to change the default
// hierarchical levels.
// 如果缓冲区有多余1帧的图像或者在随机访问模式(RANDOM_ACCESS)下,虽然可能只有 1 帧,但它不是 I 帧
else if (enc_ctx->pre_assignment_buffer_count > 1 ||
(!enc_ctx->pre_assignment_buffer_intra_count && scs->static_config.pred_structure == RANDOM_ACCESS)) {
// 初始化 Mini-GOP 活跃度/特征数组。检查这些帧里哪些是场景切换,哪些是关键帧
initialize_mini_gop_activity_array(scs, pcs, enc_ctx, ctx);
// 构建最终 mini-GoP 列表:执行真正的切分。如果原本预期一个 Mini-GOP 要 16 帧,但中间第 6 帧发生了场景切换(Scene Change),
// 这个函数就会把这 16 帧"咔嚓"切成两半,变成一个长度为 6 的不完整 Mini-GOP 和一个长度为 10 的 Mini-GOP。此时 total_number_of_mini_gops 会大于 1
generate_picture_window_split(ctx, enc_ctx);
// 处理那些因为特殊原因(如 EOS 序列结束、场景切换截断)导致的不完整/非 2^n 幂次长度的 Mini-GOP,为它们单独适配非标准的预测层级
handle_incomplete_picture_window_map(next_mg_hierarchical_levels, ctx, enc_ctx);
}
get_pred_struct_for_all_frames(ctx, enc_ctx);
}
2 initialize_mini_gop_activity_array() ------ 初始化并评估 mini-GoP 拆分
位置:Source/Lib/Codec/pd_process.c
Markdown
/***************************************************************************************************
* Initializes mini GOP activity array 初始化并评估 mini-GoP 拆分
*
***************************************************************************************************/
static void initialize_mini_gop_activity_array(SequenceControlSet* scs, PictureParentControlSet* pcs,
EncodeContext* enc_ctx, PictureDecisionContext* ctx) {
(void)scs;
// Loop over all mini GOPs to initialize the activity 遍历编码器内所有预定义的 Mini-GOP 模板
// 层级数 > 1 的 mini-GoP 设为 true (活跃/需拆分),层级数 == 1 的设为 false (叶节点,不拆分)
for (uint32_t gopindex = 0; gopindex < MINI_GOP_MAX_COUNT; ++gopindex) {
ctx->mini_gop_activity_array[gopindex] = svt_aom_get_mini_gop_stats(gopindex)->hierarchical_levels >
MIN_HIERARCHICAL_LEVEL;
}
// Assign the MGs to be used; if the MG is incomplete, the pre-assignment buffer will hold
// fewer than (1 << scs->static_config.hierarchical_levels) pics
// 分配要使用的Mini-GOP;如果该Mini-GOP不完整,预分配缓冲区中容纳的图像数量将会少于 (1 << scs->static_config.hierarchical_levels) 帧
// 关键规则 :一个 mini-GoP 被标记为 false (完整/不拆分),当且仅当缓冲区中有 足够的帧 填满该 mini-GoP, 且该 mini-GoP 的最后一帧不是 IDR (IDR 会截断预测结构)
// IDR 特殊处理 : !(count == N && pcs->idr_flag) 这个条件确保------如果缓冲区恰好有 N 帧且最后一帧是 IDR,则 不 将该 mini-GoP 标记为完整。原因是 IDR 帧会截断预测结构,不能作为 mini-GoP 的正常终止帧。
// count >= 32 且不是"恰好32帧+IDR"
if (enc_ctx->pre_assignment_buffer_count >= 32 && !(enc_ctx->pre_assignment_buffer_count == 32 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L6_INDEX] = false;
} else if (enc_ctx->pre_assignment_buffer_count >= 16 &&
!(enc_ctx->pre_assignment_buffer_count == 16 && pcs->idr_flag)) { // count >= 16 且不是"恰好16帧+IDR"
ctx->mini_gop_activity_array[L5_0_INDEX] = false; // 前16帧构成完整5L
// 然后继续检查剩余帧( count - 16 )能构成什么:
if ((enc_ctx->pre_assignment_buffer_count - 16) >= 8 &&
!((enc_ctx->pre_assignment_buffer_count - 16) == 8 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L4_2_INDEX] = false; // 前8帧构成完整4L
if ((enc_ctx->pre_assignment_buffer_count - 16 - 8) >= 4 &&
!((enc_ctx->pre_assignment_buffer_count - 16 - 8) == 4 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L3_6_INDEX] = false; // 前4帧构成完整3L
if ((enc_ctx->pre_assignment_buffer_count - 16 - 8 - 4) >= 2 &&
!((enc_ctx->pre_assignment_buffer_count - 16 - 8 - 4) == 2 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L2_14_INDEX] = false; // 前2帧构成完整2L
}
} else if ((enc_ctx->pre_assignment_buffer_count - 16 - 8) >= 2 &&
!((enc_ctx->pre_assignment_buffer_count - 16 - 8) == 2 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L2_12_INDEX] = false; // 前2帧构成完整2L
}
} else if ((enc_ctx->pre_assignment_buffer_count - 16) >= 4 &&
!((enc_ctx->pre_assignment_buffer_count - 16) == 4 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L3_4_INDEX] = false; // 前4帧构成完整3L
if ((enc_ctx->pre_assignment_buffer_count - 16 - 4) >= 2 &&
!((enc_ctx->pre_assignment_buffer_count - 16 - 4) == 2 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L2_10_INDEX] = false; // 前2帧构成完整2L
}
} else if ((enc_ctx->pre_assignment_buffer_count - 16) >= 2 &&
!((enc_ctx->pre_assignment_buffer_count - 16) == 2 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L2_8_INDEX] = false; // 前2帧构成完整2L
}
} else if (enc_ctx->pre_assignment_buffer_count >= 8 &&
!(enc_ctx->pre_assignment_buffer_count == 8 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L4_0_INDEX] = false;
if ((enc_ctx->pre_assignment_buffer_count - 8) >= 4 &&
!((enc_ctx->pre_assignment_buffer_count - 8) == 4 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L3_2_INDEX] = false;
if ((enc_ctx->pre_assignment_buffer_count - 8 - 4) >= 2 &&
!((enc_ctx->pre_assignment_buffer_count - 8 - 4) == 2 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L2_6_INDEX] = false;
}
} else if ((enc_ctx->pre_assignment_buffer_count - 8) >= 2 &&
!((enc_ctx->pre_assignment_buffer_count - 8) == 2 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L2_4_INDEX] = false;
}
} else if (enc_ctx->pre_assignment_buffer_count >= 4 &&
!(enc_ctx->pre_assignment_buffer_count == 4 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L3_0_INDEX] = false;
if ((enc_ctx->pre_assignment_buffer_count - 4) >= 2 &&
!((enc_ctx->pre_assignment_buffer_count - 4) == 2 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L2_2_INDEX] = false;
}
} else if ((enc_ctx->pre_assignment_buffer_count) >= 2 &&
!((enc_ctx->pre_assignment_buffer_count) == 2 && pcs->idr_flag)) {
ctx->mini_gop_activity_array[L2_0_INDEX] = false;
}
// 6L vs. 5L 动态mini-gop评估
// 如果开启动态mini-gop评估,且6L mini-gop 被标记为完整不拆分,则评估6L mini-gop是否可以拆分为5L mini-gop
if (scs->enable_dg && ctx->mini_gop_activity_array[L6_INDEX] == false) {
// Minigop第一帧的psc
PictureParentControlSet* start_pcs = (PictureParentControlSet*)enc_ctx->pre_assignment_buffer[0]->object_ptr;
// Minigop中间帧的psc
PictureParentControlSet* mid_pcs =
(PictureParentControlSet*)enc_ctx
->pre_assignment_buffer[((1 << scs->static_config.hierarchical_levels) >> 1) - 1]
->object_ptr;
// Minigop最后一帧的psc
PictureParentControlSet* end_pcs = (PictureParentControlSet*)enc_ctx
->pre_assignment_buffer[enc_ctx->pre_assignment_buffer_count - 1]
->object_ptr;
// 核心评估函数,评估6L mini-gop是否可以拆分为5L mini-gop
eval_sub_mini_gop(ctx, enc_ctx, L6_INDEX, L5_0_INDEX, L5_1_INDEX, start_pcs, mid_pcs, end_pcs);
}
ctx->list0_only = 0;
if (scs->list0_only_base) { // 如果基层帧只使用list0编码
ctx->list0_only = 1; // 标记为list0编码
}
}
3 eval_sub_mini_gop() ------ 核心评估函数,评估6L 是否可以拆分为5L
位置:Source/Lib/Codec/pd_process.c
这个函数是实际调用早期运动搜索的位置。
C++
// 执行三次早期 HME 搜索(运动估计),只对第0层进行评估
static void eval_sub_mini_gop(PictureDecisionContext* ctx, EncodeContext* enc_ctx, uint64_t top_layer_idx,
uint64_t sub_layer_idx0, uint64_t sub_layer_idx1, PictureParentControlSet* start_pcs,
PictureParentControlSet* mid_pcs, PictureParentControlSet* end_pcs) {
// 帧级早期 HME 编排器,计算归一化指标
early_hme(ctx, end_pcs, start_pcs); // 帧31相对帧0的运动 → 6L 整体失真
uint64_t dist_end_start = ctx->norm_dist; // tot_dist / (b64 总数)
uint8_t perc_cplx_end_start = ctx->perc_cplx; // 复杂块百分比
uint8_t perc_active_end_start = ctx->perc_active; // 活跃块百分比
int16_t mv_in_out_count_end_start = ctx->mv_in_out_count; // MV 内外计数百分比
early_hme(ctx, end_pcs, mid_pcs); // 帧31相对帧15的运动 → 第二个5L子块失真
uint64_t dist_end_mid = ctx->norm_dist;
uint8_t perc_cplx_end_mid = ctx->perc_cplx;
uint8_t perc_active_end_mid = ctx->perc_active;
int16_t mv_in_out_count_end_mid = ctx->mv_in_out_count;
early_hme(ctx, mid_pcs, start_pcs); // 帧15相对帧0的运动 → 第一个5L子块失真
uint64_t dist_mid_start = ctx->norm_dist;
uint8_t perc_cplx_mid_start = ctx->perc_cplx;
uint8_t perc_active_mid_start = ctx->perc_active;
int16_t mv_in_out_count_mid_start = ctx->mv_in_out_count;
// 然后调用 calc_mini_gop_activity 判断是否拆分
calc_mini_gop_activity(ctx,
enc_ctx,
top_layer_idx,
dist_end_start,
perc_active_end_start,
perc_cplx_end_start,
sub_layer_idx0,
dist_mid_start,
perc_active_mid_start,
perc_cplx_mid_start,
sub_layer_idx1,
dist_end_mid,
perc_active_end_mid,
perc_cplx_end_mid,
mv_in_out_count_end_start,
mv_in_out_count_end_mid,
mv_in_out_count_mid_start);
}
4 calc_mini_gop_activity() ------ 判断是否拆分
位置:Source/Lib/Codec/pd_process.c
C++
static void calc_mini_gop_activity(PictureDecisionContext* ctx, EncodeContext* enc_ctx, uint64_t top_layer_idx,
uint64_t top_layer_dist, uint8_t top_layer_perc_active, uint8_t top_layer_perc_cplx,
uint64_t sub_layer_idx0, uint64_t sub_layer_dist0, uint8_t sub_layer0_perc_active,
uint8_t sub_layer0_perc_cplx, uint64_t sub_layer_idx1, uint64_t sub_layer_dist1,
uint8_t sub_layer1_perc_active, uint8_t sub_layer1_perc_cplx,
int16_t top_layer_mv_in_out_count, int16_t sub_layer_mv_in_out_count1,
int16_t sub_layer_mv_in_out_count2) {
(void)top_layer_mv_in_out_count;
// The bias is function of the previous mini-gop structure towards less switch(es) within the same gop
// 6L will be maintained unless the presence of a significant change compared to the previous mini-gop
// To do: make the bias function of the preset; higher is the preset, higher is the bias towards less 6L
// 如果当前GOP中有多个mini-gop并且前一个mini-gop为5L,那么bias为25(倾向保持6L(更难满足失真条件)),否则为75(倾向拆分(更容易满足失真条件))
int bias = (enc_ctx->mini_gop_cnt_per_gop > 1 && enc_ctx->previous_mini_gop_hierarchical_levels == 5) ? 25 : 75;
// cond1:6L整体有 ≥95% 的块存在运动(运动剧烈)并且不会出现左5L运动剧烈但右5L运动很少并且不会出现左5L运动很少但右5L运动剧烈
const bool cond1 = top_layer_perc_active >= 95 && !(sub_layer0_perc_active >= 95 && sub_layer1_perc_active < 75) &&
!(sub_layer0_perc_active < 75 && sub_layer1_perc_active >= 95);
// cond2:6L失真 > 512 && 左5L失真 < 4608 && 右5L失真 < 4608 && 6L有复杂块 && 左5L复杂块 < 25%
// && 右5L复杂块 < 25% && 5L平均率失真小于6L的率失真乘以bias(bias越大越倾向于拆分)
const bool cond2 = top_layer_dist > LOW_DIST_TH && sub_layer_dist0 < HIGH_DIST_TH &&
sub_layer_dist1 < HIGH_DIST_TH && top_layer_perc_cplx > 0 && sub_layer0_perc_cplx < 25 &&
sub_layer1_perc_cplx < 25 && (((sub_layer_dist0 + sub_layer_dist1) / 2) < ((bias * top_layer_dist) / 100));
// cond3:左5L和右5L的内外计数百分比都大于 40% 的块存在运动(运动剧烈)&&
// 左5L和右5L的内外计数百分比至少一个大于 55%
// 说明左5L和右5L的运动差异较大,需要拆分
const bool cond3 = MIN(sub_layer_mv_in_out_count1, sub_layer_mv_in_out_count2) > 40 &&
MAX(sub_layer_mv_in_out_count1, sub_layer_mv_in_out_count2) > 55;
// 如果动态 GoP 决定拆分,会将 L6_INDEX 从 false 改为 true (表示6L被拆分了),同时将两个5L子节点设为 false (各自完整)。
/* cond1 (运动活跃且均匀)
/
cond2 (失真/复杂度) cond3 (缩放活动)
\ /
cond2 || cond3 */
if (cond1 && (cond2 || cond3)) {
ctx->mini_gop_activity_array[top_layer_idx] = true;
ctx->mini_gop_activity_array[sub_layer_idx0] = false;
ctx->mini_gop_activity_array[sub_layer_idx1] = false;
}
}
5 generate_picture_window_split() ------ 构建最终 mini-GoP 列表
位置:Source/Lib/Codec/pd_process.c
Markdown
/***************************************************************************************************
* Generates block picture map 构建最终 mini-GoP 列表
*
*
***************************************************************************************************/
static EbErrorType generate_picture_window_split(PictureDecisionContext* pd_ctx, EncodeContext* enc_ctx) {
pd_ctx->total_number_of_mini_gops = 0;
// Loop over all mini GOPs 遍历所有mini-gop,根据第一个基层是否需要拆分来判断步长, mini_gop_offset[lvl - 1] 就是"以该节点为根的子树总节点数
for (uint32_t gopindex = 0; gopindex < MINI_GOP_MAX_COUNT; gopindex += pd_ctx->mini_gop_activity_array[gopindex]
? 1
: mini_gop_offset[svt_aom_get_mini_gop_stats(gopindex)->hierarchical_levels - MIN_HIERARCHICAL_LEVEL]) {
// Only for a valid mini GOP 仅对有效mini-gop进行处理
// 有效mini-gop:mini-gop的结束索引小于缓冲区帧数量,且mini-gop的活动索引为false,说明mini-gop没有被标记为拆分
if (svt_aom_get_mini_gop_stats(gopindex)->end_index < enc_ctx->pre_assignment_buffer_count &&
!pd_ctx->mini_gop_activity_array[gopindex]) {
pd_ctx->mini_gop_start_index[pd_ctx->total_number_of_mini_gops] =
svt_aom_get_mini_gop_stats(gopindex)->start_index; // 设置当前mini-gop的起始索引
pd_ctx->mini_gop_end_index[pd_ctx->total_number_of_mini_gops] =
svt_aom_get_mini_gop_stats(gopindex)->end_index; // 设置当前mini-gop的结束索引
pd_ctx->mini_gop_length[pd_ctx->total_number_of_mini_gops] = svt_aom_get_mini_gop_stats(gopindex)->length; // 设置当前mini-gop的长度
pd_ctx->mini_gop_hierarchical_levels[pd_ctx->total_number_of_mini_gops] =
svt_aom_get_mini_gop_stats(gopindex)->hierarchical_levels; // 设置当前mini-gop的层级
pd_ctx->mini_gop_intra_count[pd_ctx->total_number_of_mini_gops] = 0; // 设置当前mini-gop的帧内编码帧数为0
pd_ctx->mini_gop_idr_count[pd_ctx->total_number_of_mini_gops] = 0; // 设置当前mini-gop的IDR编码帧数为0
pd_ctx->total_number_of_mini_gops++; // 增加有效mini-gop的数量
}
}
// Only in presence of at least 1 valid mini GOP 仅在有至少一个有效mini-gop时处理
// 把当前预分配缓冲区中统计的 I 帧/IDR 帧数量,赋给 最后一个 mini-GoP 。这些计数用于后续 RA 截断判断
if (pd_ctx->total_number_of_mini_gops != 0) {
pd_ctx->mini_gop_intra_count[pd_ctx->total_number_of_mini_gops - 1] =
enc_ctx->pre_assignment_buffer_intra_count;
pd_ctx->mini_gop_idr_count[pd_ctx->total_number_of_mini_gops - 1] = enc_ctx->pre_assignment_buffer_idr_count;
}
return EB_ErrorNone;
}
6 handle_incomplete_picture_window_map ------ 处理缓冲区末尾"剩余帧"无法构成完整 mini-GoP 的情况
位置:Source/Lib/Codec/pd_process.c
Markdown
/***************************************************************************************************
* Handles an incomplete picture window map 处理缓冲区末尾"剩余帧"无法构成完整 mini-GoP 的情况。
*
*
***************************************************************************************************/
static EbErrorType handle_incomplete_picture_window_map(uint32_t hierarchical_level, PictureDecisionContext* pd_ctx,
EncodeContext* enc_ctx) {
EbErrorType return_error = EB_ErrorNone;
if (pd_ctx->total_number_of_mini_gops == 0) { // 完全没有完整 mini-GoP
// 直接将缓冲区所有帧作为mini-gop
// 为什么要强制层级为 1?剩余帧数量通常少于 2 帧的完整结构所需,为了避免引用缺失/结构不完整,使用最小的层级结构(最保守、最安全)。
hierarchical_level = MIN(MIN_HIERARCHICAL_LEVEL, hierarchical_level);
pd_ctx->mini_gop_start_index[pd_ctx->total_number_of_mini_gops] = 0; // 设置当前mini-gop的起始索引为0
pd_ctx->mini_gop_end_index[pd_ctx->total_number_of_mini_gops] = enc_ctx->pre_assignment_buffer_count - 1; // 设置当前mini-gop的结束索引为缓冲区帧数量减1
pd_ctx->mini_gop_length[pd_ctx->total_number_of_mini_gops] = enc_ctx->pre_assignment_buffer_count -
pd_ctx->mini_gop_start_index[pd_ctx->total_number_of_mini_gops]; // 设置当前mini-gop区帧数量减区帧数量减起mini-gop的起始索引
pd_ctx->mini_gop_hierarchical_levels[pd_ctx->total_number_of_mini_gops] = hierarchical_level; // 设置当前mini-gop的层级
pd_ctx->total_number_of_mini_gops++; // 增加有效mini-gop的数量
} else if (pd_ctx->mini_gop_end_index[pd_ctx->total_number_of_mini_gops - 1] <
enc_ctx->pre_assignment_buffer_count - 1) { // 最后一个mini-gop的结束索引小于缓冲区帧数量减1,说明还有一些帧没有分配mini-gop
// 直接将缓冲区剩余的所有帧作为mini-gop
pd_ctx->mini_gop_start_index[pd_ctx->total_number_of_mini_gops] =
pd_ctx->mini_gop_end_index[pd_ctx->total_number_of_mini_gops - 1] + 1; // 设置当前mini-gop的起始索引为上一个mini-gop的结束索引加1
pd_ctx->mini_gop_end_index[pd_ctx->total_number_of_mini_gops] = enc_ctx->pre_assignment_buffer_count - 1; // 设置当前mini-gop的结束索引为缓冲区帧数量减1
pd_ctx->mini_gop_length[pd_ctx->total_number_of_mini_gops] = enc_ctx->pre_assignment_buffer_count -
pd_ctx->mini_gop_start_index[pd_ctx->total_number_of_mini_gops]; // 设置当前mini-gop区帧数量减区帧数量减起mini-gop的起始索引
pd_ctx->mini_gop_hierarchical_levels[pd_ctx->total_number_of_mini_gops] = MIN_HIERARCHICAL_LEVEL; // 设置当前mini-gop的层级
pd_ctx->mini_gop_intra_count[pd_ctx->total_number_of_mini_gops - 1] = 0; // 设置当前mini-gop的帧内编码帧数为0
pd_ctx->mini_gop_idr_count[pd_ctx->total_number_of_mini_gops - 1] = 0; // 设置当前mini-gop的IDR编码帧数为0
pd_ctx->total_number_of_mini_gops++; // 增加有效mini-gop的数量
}
pd_ctx->mini_gop_intra_count[pd_ctx->total_number_of_mini_gops - 1] = enc_ctx->pre_assignment_buffer_intra_count; // 设置当前mini-gop的帧内编码帧数为缓冲区中统计的 I 帧数量
pd_ctx->mini_gop_idr_count[pd_ctx->total_number_of_mini_gops - 1] = enc_ctx->pre_assignment_buffer_idr_count; // 设置当前mini-gop的IDR编码帧数为缓冲区中统计的 IDR 帧数量
return return_error;
}
7 get_pred_struct_for_all_frames ------ 为每帧分配预测结构
位置:Source/Lib/Codec/pd_process.c
Markdown
/***************************************************************************************************
* Gets the pred struct for each frame in the mini-gop(s) that we have available 为每帧分配预测结构
***************************************************************************************************/
static void get_pred_struct_for_all_frames(PictureDecisionContext* ctx, EncodeContext* enc_ctx) {
// 遍历所有 mini-gop
for (unsigned int mini_gop_index = 0; mini_gop_index < ctx->total_number_of_mini_gops; ++mini_gop_index) {
// Loop over picture within the mini GOP 遍历当前 mini-gop 中的所有帧
for (unsigned int pic_idx = ctx->mini_gop_start_index[mini_gop_index];
pic_idx <= ctx->mini_gop_end_index[mini_gop_index];
pic_idx++) {
// 获取当前帧的图片控制集
PictureParentControlSet* pcs =
(PictureParentControlSet*)enc_ctx->pre_assignment_buffer[pic_idx]->object_ptr;
SequenceControlSet* scs = pcs->scs;
#if DEBUG_STARTUP_MG_SIZE
if (pcs->idr_flag || pcs->cra_flag) {
SVT_LOG("Frame %d, key-frame\n", (int)pcs->picture_number);
}
if (pic_idx == ctx->mini_gop_start_index[mini_gop_index]) {
SVT_LOG("mGOP start %d, mGOP length %d, startup mini-GOP %d\n",
(int)pcs->picture_number,
ctx->mini_gop_length[mini_gop_index],
ctx->enable_startup_mg);
}
if (pic_idx == ctx->mini_gop_end_index[mini_gop_index]) {
SVT_LOG(
"mGOP end %d, mGOP length %d\n", (int)pcs->picture_number, ctx->mini_gop_length[mini_gop_index]);
}
#endif
// 全局预测结构类型,即Gop的预测结构,RANDOM_ACCESS表示可以双向预测
pcs->pred_structure = scs->static_config.pred_structure;
// IDR 帧用默认层级,否则用 mini-GoP 的层级
pcs->hierarchical_levels = pcs->idr_flag ? scs->static_config.hierarchical_levels
: (uint8_t)ctx->mini_gop_hierarchical_levels[mini_gop_index];
// 指向具体的预测结构表
pcs->pred_struct_ptr = svt_aom_get_prediction_structure(
enc_ctx->prediction_structure_group_ptr, pcs->pred_structure, pcs->hierarchical_levels);
// 启动 MG(mini-gop) 大小,0 表示不启用,2、3、4 表示设置层级为 2、3、4,在开启此功能后会在关键帧(Key Frame / I帧)之后会强制插入一个指定尺寸的mini-gop
if (scs->static_config.startup_mg_size != 0) {
if (pcs->idr_flag || pcs->cra_flag) { // 如果是关键帧或CR帧
ctx->enable_startup_mg = true; // 启动 MG(mini-gop) 大小功能
} else if (ctx->enable_startup_mg) { // 否则,关闭 MG(mini-gop) 大小功能
ctx->enable_startup_mg = false;
}
}
// 标记当前 mini-GoP 是否为"启动 mini-GoP"(序列开始或关键帧后的第一个 mini-GoP)
if (pcs->idr_flag && pcs->picture_number == 0) {
ctx->is_startup_gop = true;
} else if (pcs->idr_flag || pcs->cra_flag) {
ctx->is_startup_gop = false;
}
pcs->is_startup_gop = ctx->is_startup_gop;
}
}
}
8 Segments划分
Plain
帧图像 1920x1080P(30 b64列 × 17 b64行):
b64列: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
┌──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┬──────┐
0 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
│◄────────────── seg[0] ──────────►│◄────────────── seg[1] ──────────►│◄────────────── seg[2] ──────────►│◄────────────── seg[3] ──────────►│◄────────────── seg[4] ──────────►│◄────────────── seg[5] ──────────►│
1 │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
├──────┴──────┴──────┴──────┴──────┴──────┤
│ seg[6] ~ seg[11]
2 │ (row y=1)
│
├──────┬──────┬──────┬──────┬──────┬──────┤
│ │
3 │ (row y=1 continued)
│
├──────┴──────┴──────┴──────┴──────┴──────┤
│ seg[12] ~ seg[17]
4 │ (row y=2)
│
├──────┬──────┬──────┬──────┬──────┬──────┤
│ │
5 │ (row y=2 continued)
│
├──────┴──────┴──────┴──────┴──────┴──────┤
│ seg[18] ~ seg[23]
6 │ (row y=3)
│
├──────┬──────┬──────┬──────┬──────┬──────┤
│ │
7 │ (row y=3 continued)
│
├──────┴──────┴──────┴──────┴──────┴──────┤
│ seg[24] ~ seg[29]
8 │ (row y=4)
│
├──────┬──────┬──────┬──────┬──────┬──────┤
│ │
9 │ (row y=4 continued)
│
├──────┴──────┴──────┴──────┴──────┴──────┤
│ seg[30] ~ seg[35]
10 │ (row y=5)
│
├──────┬──────┬──────┬──────┬──────┬──────┤
│ │
11 │ (row y=5 continued)
│
├──────┴──────┴──────┴──────┴──────┴──────┤
│ seg[36] ~ seg[41]
12 │ (row y=6)
│
├──────┬──────┬──────┬──────┬──────┬──────┤
│ │
13 │ (row y=6 continued, 只有1行!)
│
├──────┴──────┴──────┴──────┴──────┴──────┤
│ seg[42] ~ seg[47]
14 │ (row y=7)
│
├──────┬──────┬──────┬──────┬──────┬──────┤
│ │
15 │ (row y=7 continued)
│
├──────┴──────┴──────┴──────┴──────┴──────┤
│ (row y=7 continued)
16 │ (最后1行)
└────────────────────────────────────────┘