SVT-AV1帧类型决策-场景切换检测

一 函数的功能说明
svt_aom_picture_decision_kernel

功能:图像决策核心函数,这是Picture Decision处理阶段的主入口函数

这个函数负责

1 接收来自Picture Analysis阶段的图片分析结果

2 管理图片重排序队列,因为多线程处理可能导致图片乱序到达

3 按现实顺序处理图片

4 进行场景切换检测

5 设置帧类型 IPB 和预测结构

6 生成参考帧集 RPS

7 初始化图片编码参数

8 将处理好的图片发送到后续处理阶段Motion Estimation等

参数:input_ptr 线程上下文指针,包含线程私有数据

返回值:

void * 线程函数返回值,这里返回NULL

check_window_availability

复制代码
/*`
`check_window_availability`
`函数功能总结`
`主要功能`
`检查场景切换检测窗口是否可用,判断是否有足够的lookahead帧进行场景切换检查。`
`窗口结构`
`函数构建一个场景切换检测窗口,包含:`
`pd_window[0] 前一帧`
`pd_window[1] 当前帧`
`pd_window[2] 到pd_window[2 + scd_delay - 1] 未来帧`
`检查逻辑`
`1 序列结束检查:检查当前帧是否为序列结束帧`
`2 前一帧检查,如果不是第一帧,检查前一帧是否已经到达`
`3 未来帧检查,检查所需的scd_delay个未来帧是否都已经到达`
`4 窗口填充:如果所有帧都可用,将帧指针填充到pd_window数组中`
`输出参数`
`window_avail 窗口是否可用,所有需要的帧是否都已经到达`
`eos_reached 是否到达序列结束`
`关键点:`
`第一帧POC=0不需要前一帧,跳过前一帧检查`
`如果任何需要的帧未到达,窗口不可用`
`如果遇到序列结束帧,窗口不可用并标记序列结束`
`使用循环队列处理,通过宏queue_get_previous_spot和`
`queue_get_next_spot计算索引`

`check_window_availability`
`窗口结构:`
`pd_window[0] 前一帧`
`pd_window[1] 当前帧`
`pd_window[2]未来第1帧`
`pd_window[3] 未来第2帧`
`pd_window[2 + scd_delay - 1] = 未来第scd_delay帧`
`参数:`
`scs - 序列控制集指针,包含序列级别的配置 scd_delay场景切换检测延迟`
`enc_ctx 编码上下文指针,包含重排序队列和队列头索引`
`pcs 图像父控制集指针,用于存储窗口中的帧指针,pd_window 数组`
`queue_entry 重排序队列条目指针,指向当前要检查的队列条目`
`window_avail 输出参数,指向布尔值,标识窗口是否可用`
`eos_reached 输出参数,指向布尔值,表示是否到达序列结束`
`*/`
`static void check_window_availability(SequenceControlSet* scs, EncodeContext* enc_ctx,`
`PictureParentControlSet* pcs, PictureDecisionReorderEntry* queue_entry,`
`bool *window_avail, bool* eos_reached)`
`{`
`//检查当前队列条目指向的图片是否是序列结束帧`
    `*eos_reached = (PictureParentControlSet*)(queue_entry->ppcs_wrapper->object_ptr)->end_of_sequence_flag == TRUE;//如果当前帧的序列结束标志为TRUE,则设置eos_reached为true`
    `*window_avail = true;//初始假设窗口可用,后续检查可能会将其设置为false`
    `//计算前一个队列条目的索引,循环队列的前一个位置`
    `//QUEUE_GET_PERVIOUS_STOP宏;如果头索引为0,返回队列最大深度-1, 否则返回头索引-1`
    `unsigned int pervious_entry_index = QUEUE_GET_PERVIOUS_SPOT(enc_ctx->picture_decision_reorder_queue_head_index);//获取队列中前一个条目的索引,用于获取前一帧`
    `//清空场景切换检测窗口数组,准备重新填充`
    `//pd_window 数组大小为:2 前一帧 + 当前帧 + scd_delay未来帧数量`
    `if (queue_entry->picture_number > 0 && enc_ctx->picture_decision_reorder_queue[previous_entry_index]->ppcs_wrapper == NULL) //如果当前帧不是第一帧,picture_number > 0 且前一帧的队列条目唯恐,前一帧还未到达`
        `*window_avail = false; //设置窗口不可用,因为缺少前一帧,无法进行场景切换检测`
    `else { //如果当前帧是第一帧,或者前一帧已经到达`
        `//TODO 访问前一帧时存在竞态 条件的风险,pcs0 被释放,而pcs1仍在进行场景切换检测`
        `//实际上我们不需要保留前一帧,只需要保留前一帧的直方图副本`
        `//设置窗口中的前一帧指针,`
        `pcs->pd_window[0] = queue_entry->picture_number > 0 ? (PictureParentControlSet*)enc_ctx->picture_decision_reorder_queue[previous_entry_index]->ppcs_wrapper->object_ptr: NULL;//如果不是第一帧,获取前一帧的pcs指针,如果是第一帧,设置为NULL`
            `//设置窗口中的当前帧指针`
        `pcs->pd_window[1] = (PictureParentControlSet*)enc_ctx->picture_decision_reorder_queue[enc_ctx->picture_decision_reorder_queue_head_index]->ppcs_wrapper->object_ptr;//从队列头部获取当前帧的PCS指针`
        `//检查未来帧lookahead 是否可用`
        `//场景切换检测需要SCD_DELAY个未来帧的信息`
        `for (unsigned int window_index = 0; window_index < scs->scd_delay; window_index++) {//遍历所需的未来帧数量,scd_delay`
            `//计算未来帧在队列中的索引位置`
            `//QUEUE_GET_NEXT_SPOT宏:计算从头索引开始,偏移window_index+1个位置的队列索引,处理循环队列`
            `unsigned int entry_index = QUEUE_GET_NEXT_SPOT(enc_ctx->picture_decision_reorder_queue_head_index, window_index + 1); //获取未来第 window_index + 1帧在队列中的索引`
            `if (enc_ctx->picture_decision_reorder_queue[entry_index]->ppcs_wrapper == NULL) { //如果该队列条目为空,未来帧还未到达`
                `*window_avail = false;//设置窗口不可用,缺少未来帧`
                `break;//跳出循环,不再检查后续的未来帧`
            `} else if ((PictureParentControlSet*)(enc_ctx->picture_decision_reorder_queue[entry_index]->ppcs_wrapper->object_ptr))->end_of_sequence_flag == TRUE) {//如果未来帧时序列结束帧`
                `*window_avail = false;//设置窗口不可用,因为序列已结束,无法获取更多未来帧`
                `*eos_reached = true;//设置序列结束标志为true`
                `break;//跳出循环`    
            `} else { //如果未来帧已经到达且不是序列结束帧`
                `//将未来帧的PCS指针存储到窗口数组中`
                `//pd_window[2 + window_index] 存储未来第(window_index + 1)帧`
                `pcs->pd_window[2 + window_index] = (PictureParentControlSet*)enc_ctx->picture_decision_reorder_queue[netry_index]->ppcs_wrapper->object_ptr;//从队列条目中提取未来帧的PCS指针并存储到窗口数组中`
            `}`
        `}`
    `}`
`}`
`

svt_aom_picture_decision_kernel

复制代码
/*`
`svt_aom_picture_decision_kernel`
`功能:图像决策核心函数,这是Picture Decision 处理阶段的主入口函数`
`这个函数负责:`
`1 接受来自Picture Analysis阶段的图片分析结果`
`2 管理图片重排序队列,因为多线程处理可能导致图片乱序到达`
`3 按现实顺序处理图片`
`4 进行场景切换检测`
`5 设置帧类型,IPB 和预测结构`
`6 生成参考帧集 RPS`
`7 初始化图片编码参数`
`8 将处理的图片发送到后续处理阶段 Motion Estimation等`
`参数: input_ptr 线程上下文指针,包含线程私有数据PictureDecisionContext`
`返回值:`
`void* 线程函数返回值,这里返回NULL`
`*/`
`void* svt_aom_picture_decision_kernel(void *input_ptr)`
`{`
    `EbThreadContext *thread_ctx = (EbThreadContext*)input_ptr;//将输入指针转换为线程上下文结构体指针`
    `PictureDecisionContext *ctx = (PictureDecisionContext*)thread_ctx->priv;//从线程上下文中获取Picture Decision 的私有上下文数据`
    `PictureParentControlSet *pcs;//声明图像父控制集指针,用于存储当前处理的图片的所有编码参数`
    `EncodeContext *enc_ctx;//声明编码上下文指针,包含编码过程中的全局状态信息`
    `SequenceControlSet *scs;//声明序列控制集指针,包含序列级别的配置信息`
    `EbObjectWrapper *in_results_wrapper_ptr;//声明输入结果包装器指针,用于从FIFO队列接收图片分析结果`
    `PictureAnalysisResults *in_results_ptr;//声明图片分析结果指针,包含图片分析阶段产生的统计信息`
    
    `PredictionStructureEntry *pred_position_ptr;//声明预测结构条目指针,指向当前帧在预测结构中的位置信息`
    
    `PictureDecisionReorderEntry *queue_entry_ptr;//声明重排队列条目指针,用于管理图片的重排序队列`
    `unsigned int pic_idx;//声明图片索引变量,用于循环便利图片`
    `int64_t current_input_poc = -1;//初始化当前输入图片的POC Picture Order Count 为-1, 用于跟踪图片顺序`
    `for(;;) { //无限循环,持续处理输入的图片,直到线程被终止`
        `//Get input full object`
        `/从FIFO队列中获取一个完整的图片分析结果对象,阻塞等待直到有数据可用`
`        EB_GET_FULL_OBJECT(`
`            ctx->picture_analysis_results_input_fifo_ptr,  // 图片分析结果输入FIFO队列指针`
`            &in_results_wrapper_ptr);  // 输出参数,接收从队列中取出的对象包装器`
        `in_results_ptr = (PictureAnalysisResults*)in_results_wrapper_ptr->object_ptr;/从对象包装器中提取图片分析结果结构体指针`
        `pcs = (PictureParentControlSet*)in_results_ptr->pcs_wrapper->object_ptr;//从图片分析结果中提取图像父控制集指针`
        `scs = pcs->scs;//从PCS中获取序列控制集指针`
        `enc_ctx = (EncodeContext*)scs->enc_ctx;//从序列控制集中获取编码上下文指针`
        `//将图片分析结果输入到图片决策重排序队列中`
        `//由于之前的图片分析处理阶段时多线程的,输入的图片决策处理阶段的图片`
        `//可能以非显示顺序到达,因此使用图片决策重排序队列来强制按显示顺序处理`
        `//图片按现实顺序处理`
        `if (!pcs->is_overlay) { //如果当前图片不是overlay图片`
            `//计算当前图片在重排序队列中的索引位置`
            `//索引 = (当前图片编号 - 队列头图片编号) + 队列头索引`
            `int queue_entry_index = (int)(pcs->picture_number - enc_ctx->picture_decision_reorder_queue[enc_ctx->picture_decision_reorder_queue_head_index]->picture_number);//计算相对于队列头的偏移量`
            `queue_entry_index += enc_ctx->picture_decision_reorder_queue_head_index;//加上队列头索引,得到绝对索引`
            `queue_entry_index = (queue_entry_index > PICTURE_DECISION_REORDER_QUEUE_MAX_DEPTH - 1) ? queue_entry_index - PICTURE_DECISION_REORDER_QUEUE_MAX_DEPTH : queue_entry_index;  // 处理循环队列的溢出,如果超出最大深度则循环回到开始`
            `queue_entry_ptr = enc_ctx->picture_decision_reorder_queue[queue_entry_index];//获取计算出的队列条目指针`
            `if (queue_entry_ptr->ppcs_wrapper != NULL) {`
            `//如果该队列条目已经被占用,说明有重复的图片编码货队列溢出`
                `CHECK_REPORT_ERROR_NC( //检查并报告错误,非条件错误`
                    `enc_ctx->app_callback_ptr, //应用回调函数指针`
                    `EB_ENC_PD_ERROR); //错误代码,队列条目自己被占用`
            `} else { //如果队列条目为空,可以正常使用`
                `queue_entry_ptr->ppcs_wrapper = in_results_ptr->pcs_wrapper;//将图片控制集包装器存储到队列条目中`
                `queue_entry_ptr->picture_number = pcs->picture_number;//记录图片编号到队列条目中`
            `}`
            `pcs->pic_decision_reorder_queue_idx = queue_entry_index;//将队列索引保存到PCS中,用于后续跟踪`
            `pcs->first_pass__done = 0;//标记第一遍处理未完成0 表示未完成`
        `}`
        `//处理图片决策重排序队列的头部,条目N`
        `//图片决策重排序队列应该按现实顺序解析,以便能够构建预测结构`
        `queue_entry_ptr = enc_ctx->picture_decision_reorder_queue[enc_ctx->picture_decision_reorder_queue_head_index];//获取队列头部的条目指针`
        `while` `(queue_entry_ptr->ppcs_wrapper != NULL)` `{`
        `//当队列头的条目不为空循环处理`
            `if` `(scs->lap_rc)` `{` `//如果启用了lookahead速率控制` `LAP` `RC`
                  `process_first_pass(scs, enc_ctx);//执行第一遍处理,进行速率控制的预处理`          
            `}`
            `pcs` `=` `(PictureParentControlSet*)queue_entry_ptr->ppcs_wrapper->object_ptr;//从队列条目中提取图片父控制集指针`
            `//注意:此时pcs中国呢的slice` `type字段还没有被设置,帧类型决策将在后面代码完成`
            `//这里只是从重排序队列中取出PCS,准备进行帧类型决策`
            `bool` `window_avail,` `eos_reached;//声明两个布尔变量,窗口可用标志和序列结束标志`
            `check_window_availability(scs, enc_ctx, pcs, queue_entry_ptr, &window_avail, &eos_reached);//检查处理窗口是否可用,是否有足够的lookahead帧,以及是否达到序列结束`
            `//如果相关帧可用,执行场景切换检测`
            `if` `(window_avail == TRUE && queue_entry_ptr->picture_number > 0)` `{` `//如果窗口可用且不是第一帧,第一帧不需要场景切换检测`
                `perform_scene_change_detection(scs, pcs, ctx);//执行常见切换检测,判断当前帧是否是场景切换帧`
            `}`
            `//如果所需的lookahead帧不可用,尚未达到序列结束,必须等待更多帧才能继续处理`
            `if (!window_avail && !eos_reached)`
                `break;//跳出循环,等待更多帧到达`
            `//将图片控制机放入预分配缓冲区`
            `//预分配缓冲区用于存储整个预测结构` `一个Mini` `gop的所有帧`
            `enc_ctx=>pre_assignment_buffer[enc_ctx->pre_assignment_buffer_count]` `= queue_entry_ptr->ppcs_wrapper;//将队列条目中的pcs包装器存储到分配缓冲区的当前位置`
            `//设置POC` `编号`
            `pcs->picture_number` `=` `++current_input_poc;//递增当前输入POC赋值给当前帧的图片编号`
            `pcs->pred_structure` `= scs->static_config.pred_structure;//从序列配置中复制预测结构类型到PCS`
            `pcs->hierarchical_layers_diff = 0;//初始化层次级别差值为0,用于检测mini-GOP切换`
            `pcs->init_pred_struct_position_flag = FALSE;//初始化预测结构位置标志为FALSE,表示不需要重要位置`
            `pcs->tpl_group_size = 0;//初始化TPL,` `时域传播层`
            `if (pcs->picture_number == 0)` `//如果是第一帧,图片编号为0`
                `ctx->prev_delayed_intra` `= NULL;//清空前一个延迟I帧指针`
          `release_prev_picture_from_reorder_queue(enc_ctx);//从重排序队列中释放前一个已经处理的图片`
          `//如果帧内周期长度为0,则每一帧都引入帧内帧`
          `if (scs->static_config.intra_period_length == 0)` 
          `//如果帧内周期长度为0,则每一帧都引入帧内帧`
            `pcs->cra_flag` `= TRUE;`
           `else if (scs->static_config.intra_period_length != -1)` `{` `//如果帧内周期长度部位-1,` `-1表示无限,不实用周期性I帧`
               `//设置CRA标志,如果刷新类型不是`
               `pcs->cra_flag =`
`                    (scs->static_config.intra_refresh_type != SVT_AV1_FWDKF_REFRESH) ?  // 如果刷新类型不是前向关键帧刷新`
`                    pcs->cra_flag :  // 保持原有的CRA标志值`
`                    ((enc_ctx->intra_period_position == (uint32_t)scs->static_config.intra_period_length) || (pcs->scene_change_flag == TRUE)) ?  // 否则,如果达到周期长度或检测到场景切换`
`                    TRUE :  // 设置CRA标志为TRUE`
`                    pcs->cra_flag;  // 否则保持原值`
                    `//GOP间隔到了,直接设置为IDR帧`
                ` pcs->idr_flag =`
`                    (scs->static_config.intra_refresh_type != SVT_AV1_KF_REFRESH) ?  // 如果刷新类型不是关键帧刷新`
`                    pcs->idr_flag :  // 保持原有的IDR标志值`
`                    enc_ctx->intra_period_position == (uint32_t)scs->static_config.intra_period_length ?  // 否则,如果达到周期长度`

`                    TRUE :  // 设置IDR标志为TRUE`
`                    pcs->idr_flag;  // 否则保持原值`
           `}`
           `//检查是否有输入设置好的IDR帧`
           `// 再次检查IDR标志:如果刷新类型是KF,且检测到场景切换或输入图片类型是关键图片,设置IDR为TRUE`
`            pcs->idr_flag =`
`                (scs->static_config.intra_refresh_type != SVT_AV1_KF_REFRESH) ?  // 如果刷新类型不是关键帧刷新`
`                pcs->idr_flag :  // 保持原有的IDR标志值`
`                (pcs->scene_change_flag == TRUE ||  pcs->input_ptr->pic_type == EB_AV1_KEY_PICTURE) ?  // 否则,如果检测到场景切换或输入图片类型是关键图片`
`                TRUE :  // 设置IDR标志为TRUE`
`                pcs->idr_flag;  // 否则保持原值`
`            enc_ctx->pre_assignment_buffer_eos_flag = (pcs->end_of_sequence_flag) ? (uint32_t)TRUE : enc_ctx->pre_assignment_buffer_eos_flag;  // 如果当前帧是序列结束帧,设置预分配缓冲区的EOS标志`
            
        `//直方图数据将在下一个输入时使用`
            `if` `(scs->calc_hist)` `{` `//如果启用了直方图计算`
                `copy_histograms(pcs, ctx);  // 复制直方图数据(用于场景切换检测)`
            `}`
            `//递增预分配缓冲区的帧内帧计数`
            `enc_ctx->pre_assignment_buffer_intra_count += (pcs->idr_flag || pcs->cra_flag);  // 如果当前帧是IDR或CRA,增加帧内帧计数`
`            enc_ctx->pre_assignment_buffer_idr_count += pcs->idr_flag;  // 如果当前帧是IDR,增加IDR计数`
`            enc_ctx->pre_assignment_buffer_count += 1;  // 预分配缓冲区计数加1`
            `// 递增帧内周期位置`
`            enc_ctx->intra_period_position =`
`                ((enc_ctx->intra_period_position == (uint32_t)scs->static_config.intra_period_length) ||  // 如果当前位置已达到周期长度,或`
`                (pcs->scene_change_flag == TRUE) ||  // 检测到场景切换,或`
`                    pcs->input_ptr->pic_type == EB_AV1_KEY_PICTURE) ?  // 输入图片类型是关键图片`
`                0 : enc_ctx->intra_period_position + 1;  // 重置为0,否则位置加1`
        `}`
        `//确定下一个mini-gop的层次级别数`
        `uint32_t` `next_mg_hierarchical_levels = scs->static_config.hierarchical_levels;//默认使用配置的层次级别数`
        `if (ctx->enable_startup_mg)` `{//如果启用了启动Mini-GOP` `用于序列开始时的特殊处理`
            `next_mg_hierarchical_levels =` `scs->static_config.startup_mg_size;//使用启动mini-gop的大小作为层次级别数`
        `}`
        `//确定是否可以从预分配缓冲区释放图片`
        `//满足以下任一条件时,可以开始处理预分配缓冲区中的图片`
        `//1` `缓冲区中有帧内帧,IDR或CRA`
        `//2` `缓冲区中的帧数达到mini-GOP的大小,2层次级别数`
        `//3` `到达序列结束EOS`
        `//4` `预测结构是低延迟P或低延迟B,低延迟模式立即处理`
        `if (enc_ctx->pre_assignment_buffer_intra_count > 0)` `||` `//如果缓冲区中有帧内帧`
            `(enc_ctx->pre_assignment_buffer_count == (uint32_t)(1 << next_mg_hierarchical_levels)) ||` `//或缓冲区帧数达到mini-gop大小`
            `(enc_ctx->pre_assignment_buffer_eos_flag == TRUE) || ` `//或到达序列结束`
            `(pcs->pred_structure == SVT_AV1_PRED_LOW_DELAY_P)` `||` `//或者低延迟P结构`
            `(pcs->pred_structure == SVT_AV1_PRED_LOW_DELAY_B)` `//或是低延迟B结构`
        `{`
            `//一旦预分配缓冲区中有足够的帧,我们可以设置mini-GOP结构`
            `set_mini_gop_structure(scs, enc_ctx, pcs, ctx);//设置mini-GOP` `结构,确定每个mini-GOP的起始和结束索引`
            `//遍历所有mini-GOP中的第一个PCS`
            `pcs = (PictureParentControlSet*)enc_ctx->pre_assignment_buffer[ctx->mini_gop_start_index[mini_gop_index]]->object_ptr; //从预分配缓冲区中获取mini-GOP起始位置的PCS`
            `//计算当前mini-GOP和上一个mini-GOP之间的时间层差异`
            `pcs->hierarchical_layers_diff = (int32_t)enc_ctx->previous_mini_gop_hierarchical_levels - (int32_t)pcs->hierarchical_levels; //计算层次级别差值,用于检测mini-GOP切换`
            `//如果mini-gop切换,设置init_pred_struct_position_flag为TRUE`
            `pcs->init_pred_struct_position_flag` `= enc_ctx->is_mini_gop_chaned` `= (pcs->hierarchical_layers_diff != 0);` `//如果层次级别差值部位0,说明mini-gop切换了。`
            `//跟踪最新实现的mini-gop的层次级别数`
            `enc_ctx->previous_mini_gop_hierarchical_levels = ctx->mini_gop_hierarchical_levels[mini_gop_index];//更新上一个mini-gop的层次级别数`
            `ctx->cut_short_ra_mg = 0;` `//重置随机访问Mini-gop被提前结束的标志`
            `//第一遍循环:遍历预分配缓冲区中的图片`
            `//为mini-gop中的所有帧设置预测结构和图片类型,包括overlay图片`
            `for (pic_idx = ctx->mini_gop_start_index[mini_gop_index]; pic_idx <= ctx->mini_gop_end_index[mini_gop_index]; ++pic_idx)` `{`
            `//遍历minigop中的所有图片`
                `pcs = (PictureParentControlSet*)enc_ctx->pre_assignment_buffer[pic_idx]->object_ptr; //从预分配缓冲区中获取当前索引的PCS`
                `scs =` `pcs->scs;//获取序列控制集指针`
                `//调用帧类型决策函数,设置预测结构和图片类型IPB`
                `update_pred_struct_and_pic_type(scs, enc_ctx,`
`                            pcs, ctx, mini_gop_index, pre_assignment_buffer_first_pass_flag,`
`                            &pcs->slice_type, &pred_position_ptr);  // 输出参数:slice_type(帧类型)和pred_position_ptr(预测结构位置  `
            `}`
        `}`
    `}`
`}`
`

perform_scene_change_detection

功能:场景切换检测的主入口,根据配置决定是否执行检测,并设置相关的标志

主要逻辑:

1 检查是否启用场景切换检测

2 调用scene_transition_detector进行检测

3 如果检测到场景切换,设置CRA标志(在场景切换处插入I帧)

4 将检测结果保存到上下文中

scene_transition_detector 核心检测函数

功能:通过分析连续帧的直方图和亮度差异检测场景切换。

检测原理:

1 区域划分:将图片分成多个区域,独立分析

2 直方图分析:计算当前帧与前帧的累积直方图绝对差值AHD

3 突然变化检测:比较AHD与运行平均值,判断是否发生突然变化。

4 类型区分:

闪光:未来帧-过去帧,但当前帧差异大

  • 淡入淡出(Fade):当前帧与未来帧、过去帧都接近
  • 场景切换(Scene Change):既不是闪光也不是淡入淡出

关键阈值

SCENE_TH:每个64x64块的场景阈值

FLASH_TH:闪光检测阈值

FADE_TH:淡入淡出检测阈值

region_count_threshhold 区域计算阈值

复制代码
perform_scene_change_detection`
`功能:执行场景切换检测并更新相关信号`
`这个函数是场景切换入口函数,决定是否执行场景切换检测`
`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);  // 图片窗口数组(包含过去、当前、未来帧的PCS指针)`
    `}`
    `else` `{ //如果未启用场景切换检测功能`
        `pcs->scene_change_flag = FALSE;//直接设置场景切换标志为FALSE`
`        //如果启用了视觉质量控制的锐度控制中的场景转换检测,且尚未检测到转换或检测结果为0`
        `if (scs->vq_ctrls.sharpness_ctrls.scene_transition && (ctx->transition_detected == -1 || ctx->transition_detected == 0))` `{ //如果启用了锐度控制的场景转换检测,且转换状态为未检测-1, 或者未转换0`
`        //仍然执行场景转换检测,结果存储在上下文中,用于其他用途,锐度控制`
            `ctx->transition_detected = scene_transition_detector(`
`                ctx,  // 图片决策上下文`
`                scs,  // 序列控制集`
`                (PictureParentControlSet**)pcs->pd_window);  // 图片窗口数组`    
        `}`    
    `}`
`    //如果检测到场景切换,设置CRA clean random access标志为TRUE`
`    //CRA帧是场景切换时插入I帧,用于提供随机访问点`
    `pcs->cra_flag = (pcs->scene_change_flag == TRUE) ?  // 如果场景切换标志为TRUE`
`        TRUE :  // 设置CRA标志为TRUE(在场景切换处插入CRA帧)`
`        pcs->cra_flag;  // 否则保持原有的CRA标志值(可能已经由其他逻辑设置)`
`    //Store scene change in context`
`    //在上下文中存储场景切换检测结果,供其他模块使用`
    `ctx->is_scene_change_detected = pcs->scene_change_flag;/将场景切换标志保存到上下文中`
`}`
`
相关推荐
LYFlied2 小时前
前端工程化核心面试题与详解
前端·面试·工程化
开开心心_Every2 小时前
无广告干扰:简单好用文字LOGO设计工具
xml·java·网络·数据库·华为od·华为云·excel
小程故事多_802 小时前
用Agent与大模型实现Web项目全自动化生成:从需求到部署的完整落地方案
运维·前端·人工智能·自动化·aigc
不染尘.2 小时前
传输层协议头分析
服务器·网络·tcp/ip·计算机网络·udp·tcp
千里马-horse2 小时前
AsyncContext
开发语言·前端·javascript·c++·napi·asynccontext
勇往直前plus2 小时前
Jackson 反序列化首字母大写字段映射失败的底层原因与解决方案
java·开发语言·前端
转转技术团队2 小时前
基于微前端 qiankun 多实例保活的工程实践
前端·javascript·前端工程化
松涛和鸣2 小时前
37、UDP网络编程入门
linux·服务器·前端·网络·udp·php
数字护盾(和中)2 小时前
BAS模拟入侵攻击系统:前置防控核心,守护企业网络安全
网络·安全·web安全