一 函数的功能说明
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;/将场景切换标志保存到上下文中`
`}`
`