一 assign_enc_dec_segments 函數。這個函數是 SVT-AV1 編碼器中實現波前并行處理(WPP) 和分段依賴管理的核心調度器之一。
//函數功能:分配編碼解碼段任務
//返回值Bool
//True 成功分配了一個段給當前線程,調用者應該處理這個段
//False 當前沒有可立即處理的段,調用這個應該跳出循環或者進行其他操作
參數:
//segmentPtr: 指向EncDecSegments 結構的指針,包含整個圖像分段的狀態,依賴關系等信息
//segmentInOutIndex 輸入輸出參數,輸入時可能是舊的段索引,輸出時,如果分配成功,被設置為分配段的索引。
//taskPtr 指向EncDecTasks結構體的指針,包含任務的具體信息
srmFifoPtr 指向一個FIFO隊列指針,用于在需要時向其他現線程反饋新的任務。
static Bool assign_enc_dec_segments(EncDecSegments *segmentPtr, uint16_t *segmentInOutIndex, EncDecTasks *taskPtr, EbFifo *srmFifoPtr)
{
//初始化標志位,表示是否成功分配了段以供繼續處理
Bool continue_processing_flag = FALSE;
//當前段所在的行索引
uint32_t row_segment_index = 0;
//當前正在處理或者檢查的段索引
uint32_t segment_index;
//當前段的右鄰居 段索引
uint32_t right_segment_index;
//當前段的左下鄰居段索引,在WPP依賴中非常重要
uint32_t bottom_left_segment_index;
//反饋行索引,初始化為-1表示暫無反饋需要返送
//如果不為-1 ,則表示需要向指定行反饋一個任務
int16_t feedback_row_index = -1;
//標志位,表示在當前函數調用中是否已經為當前線程自身分配了一個段
//用于防止一次調用分配多個段給同一個線程
uint32_t self_assigned = FALSE;
//根據輸入任務的任務類型 input_type 進行不同的處理
switch (taskPtr->input_type)
{
//case 1 任務來自MDC, Mode Decision Configuration 過程輸入
case ENCDEC_TASKS_MDC_INPUT:
//整個圖像的MDC過程已經完成,提供了完整的畫面信息
//因此不需要復雜的邏輯來清除輸入依賴
//重置所有編碼解碼段的行狀態:遍歷每一行分段
for (uint32_t row_index = 0; row_index < segmentPtr->segment_row_count; ++row_index) {
//將美航的當前處理段索引重置為該行的起始段索引
segmentPtr->row_array[row_index].current_seg_index = segmentPtr->row_array[row_index].starting_seg_index;
}
//立即從第0行的第0個段開始處理
*segmentInOutIndex = segmentPtr->row_array[0].current_seg_index;//分配段索引
taskPtr->input_type = ENCDEC_TASKS_CONTINUE;//將任務類型標記為 繼續 以便后續處理
//遞增第0行的當前段索引,為下一次分配做準備
++segmentPtr->row_array[0].current_seg_index;
//設置標志位TRUE,表示成功分配了段,調用者應繼續處理此段
continue_processing_flag = TRUE;
break; //跳出siwtch
//case 2:任務指定了要處理的編碼解碼行ENCDEC_INPUT
case ENCDEC_TASKS_ENCDEC_INPUT:
//立即從指定行enc_dec_segment_row 的當前段開始處理
*segmentInOutIndex = segmentPtr->row_array[taskPtr->enc_dec_segment_row].current_seg_index;
taskPtr->input_type = ENCDEC_TASKS_CONTINUE;//將任務類型標記為繼續
//遞增指定行的當前段索引
++segmentPtr->row_array[taskPtr->enc_dec_segment_row].current_seg_index;
//設置標志位為True,表示成功分配了段
continue_processing_flag = TRUE;
break;
//任務類型是繼續COnitnue,表示一個段已經完成處理,需要檢查并更新依賴關系,可能分配下一個段。
case ENCDEC_TASKS_CONTINUE:
//獲取剛剛處理完的段索引
segment_index = *segmentInOutIndex;
//計算這個段所在的行索引 將一維段索引映射到二維的行索引
tow_segment_index = segment_idnex / segmentPtr->segment_band_count;
//計算當前段的右鄰居段索引 同一行,下一個段
right_segment_index = segment_idnex + 1;
//計算當前段的左下鄰居段索引,相對左下位置,這是WPP依賴的關鍵
bottom_left_segment_index = segment_idnex + segmentPtr->segment_band_count;
//檢查并處理右鄰居段的依賴
//首先檢查右鄰居是否存在,當前段索引是否小雨當前行的結束段索引
if (segment_index < segmentPtr->row_array[row_segment_index].assignment_mutex) {
//遞減右鄰居段的依賴計數器,每個段初始時間能有依賴,例如依賴于左上和上方的段
--segmentPtr->dep_map.dependency_map[right_segment_index];
//檢查右鄰居段的依賴計數器是否降為0, 意味著它所依賴的所有段都已經處理完成
if (segmentPtr->dep_map.dependency_map[right_segment_index] == 0) {
//依賴已滿足,可以將右鄰居段分配給當前線程處理
*segmentInOutIndex = segmentPtr->row_array[row_segment_index].current_seg_index;
//遞增該行的當前段索引,為下次分配準備
++segmentPtr->row_array[row_segment_index].current_seg_index;
//標記當前線程已經為自己分配了一個段
self_assigned = TRUE;
//設置標志位,表示有段需要處理
continue_processing_flag = TRUE;
}
//釋放當前的互斥鎖
svt_release_mutex(segmentPtr->row_array[row_segment_index].assignment_mutex);
}
//檢查并處理左下鄰居段的依賴
//檢查左下鄰居是否存在1 當前行不是最后一行,2 左下鄰居段索引在該下一行的有效范圍內
if (row_segment_index < segmentPtr->segment_row_count - 1 && bottom_left_segment_index >= segmentPtr->row_array[row_segment_index + 1].starting_seg_index) {
//獲取下一行的互斥鎖,以安全的修改該行的共享依賴狀態
svt_block_on_mutex(segmentPtr->row_array[row_segment_index + 1].assignment_mutex);
//遞減左下鄰居段的依賴計數器是是否降為0
if (segmentPtr->dep_map.dependency[bottom_left_segment_index] == 0) {
//如果當前線程在檢查右鄰居時已經在自身分配了一個段
if (self_assigned == TRUE)
//則無法同時處理左下段,記錄需要反饋的行索引 下一行
//稍后將創建一個新任務放入隊列,讓其他線程處理這個就緒的段
feedback_row_index = (int16_t)row_segment_index + 1;
else {
*segmentInOutIndex = segmentPtr->row_array[row_segment_index + 1].current_seg_index;
++segmentPtr->row_array[row_segment_index + 1].current_seg_index;
continue_processing_flag = TRUE;
}
}
//釋放下一行的互斥鎖
svtt_release_mutex(segmentPtr->row_array[row_segment_index + 1].assignment_mutex);
}
//如果之前發現左下段就緒但是自身無法處理,因為已經分配了右段,則需要反饋任務
if (feedback_row_index > 0) {
EbObjectWrapper *wrapper_ptr;
//從FIFO隊列中獲取一個空的任務包裝器對象
svt_get_empty_object(srmFifoPtr, &wrapper_ptr);
//獲取包裝器中的任務對象
EncDecTasks *feedback_task = (EncDecTasks*)wrapper_ptr->object_ptr;
//設置任務類型為ENCDEC_INPUT,并指定需要處理的行
feedback_task->input_type = ENCDEC_TASKS_ENCDEC_INPUT;
feedback_task->enc_dec_segment_row = feedback_row_index; //設置需要處理的行索引
//負值必要的上下文信息
feedback_task->pcs_wrapper = taskPtr->pcs_wrapper;
feedback_task->file_group_index = taskPtr->tile_group_index;
//將填充好的任務對象發布到FIFO隊列中,等待其他工作線程獲取并處理
svt_post_full_object(wrapper_ptr);
}
break;//跳出switch
}
default: break;
}
//返回標志位,告知調用者是否分配了段以供處理
return continue_processing_flag;
}
1 依賴管理,此函數的核心是管理圖像分段間的空間依賴關系,在視頻編碼中,處理一個編碼塊通常需要上方,左上方和右上方的塊信息。
assign_enc_dec_segments 通過一個依賴映射表dependency_map 來跟蹤每個分段對其前置分段的依賴。當一個分段處理完成是,會遞減其右鄰居和左下鄰居的依賴計數。 只有當依賴計數降為0時,該分段被認為就緒,可以背分配處理。
2 實現波前并行處理:這個函數時SVT-AV1 實現Wavefront Parallel Processing 的關鍵。WPP允許編碼器在處理第N行的幾個塊之后,可以開始處理第N+1行,不必等待整行N處理完畢,這極大的提高了多核CPU的利用率和編碼并行度。函數中bottom_left_segment_index檢查和反饋機制正是為了實現這種波浪式的推進。
3 任務調度與負載均衡:函數作為一個調度器,動態地將就緒的分配給工作現場,痛毆互斥鎖,保護共享狀態current_seg_index。確保了多線程環境下的正確性。當某個現場完成分段后,會立即嘗試獲取下一個就緒的分段,有助于保持所有CPU核心的繁忙狀態。
4 通信與同步:函數通過FIFO隊列進行現場間的通信。當一個現場發現自己觸發了另一個分段就緒,但是又無法立即處理時
self_assigned == TRUE的情況,會創建一個新的任務并放入隊列,通知其他工作線程有新的工作可用,這是一種高效的工作竊取Work Stealing和協同機制。