SVT-AV1 Mini-Gop决策算法详解+源码分析.md

【元宝搜索开发者征文大赛】长辈不信你时,我靠元宝搜索拿出 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 ** 源码描述

二、概念

  1. 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 被截断
  1. 进行场景切换检测,如果有场景切换将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;
    }
  2. 场景切换帧的 cra_flag=true ,所以 intra_count 递增;

    C 复制代码
    enc_ctx->pre_assignment_buffer_intra_count += (pcs->idr_flag || pcs->cra_flag);
  3. 提前触发 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);
        ...
    }
  1. 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行)
       └────────────────────────────────────────┘
相关推荐
Kobebryant-Manba1 小时前
IEKT向量矩阵流程&评估
人工智能·算法·矩阵
Js.Tx1 小时前
【具体数学 Concrete Mathematics】2.1.7 无限和式
算法·具体数学
FPGA信号处理1 小时前
【 凸优化】第六讲:求解器与支持向量机
算法·机器学习·支持向量机
专注仿真1 小时前
支持向量机(SVM)算法详解与代码实例
算法·机器学习·支持向量机
Pniubi1 小时前
力扣42接雨水(动态规划法)
算法·leetcode·职场和发展
CQU_JIAKE2 小时前
10.31[A]
算法·深度优先
zwc32104320552 小时前
Day 5:threshold与inRange阈值分割
c++·图像处理·人工智能·opencv·算法·计算机视觉
云-缩影2 小时前
实际项目中怎么选排序算法:从面试题到工程落地
java·算法·排序算法
Pniubi2 小时前
力扣46全排列(回溯(递归))
算法·leetcode·职场和发展