ffmpeg轉碼步驟源碼實現的一點點淺析
ffmpeg轉碼步驟源碼實現的一點點淺析
ffmpeg轉碼過程對解碼的處理封裝在process_input()中(process_input()->decode_video()->decode()->avcodec_send_packet()),轉碼過程中ffmpeg會通過avformat庫一包一包的讀取avpacket經過avcodec_send_packet()往內部解碼器送原始音影片壓縮包、這裡也提一下,我們都知道
avpacket 和 avframe 是ffmpeg的通用幀封裝 ,
avpacket是壓縮幀,avframe是原始影像幀,
在解碼端,avpacket會送到解碼器,產出avframe
在編碼端,avframe會送進編碼器,產出avpacket
在濾鏡端,avframe 入,avframe 出
4.2.1ffmpeg內部做了並行解碼,簡圖要如下:
avcodec->internal內部維繫了一個環形執行緒列表,默認工作執行緒數量為nb_cpu + 1 個,主執行緒通過avcodec_send_pkt()—>…->submit_packet()通過條件變數提交一個任務給一個空閑的工作執行緒,空閑執行緒收到通知後調用對應的解碼器回調函數code->decode()開始解碼,同時此執行緒的狀態機會切換到工作狀態(假定為灰色格子)
next_decoded總是指向下一個空閑執行緒,
next_finnished總是指向第一個工作執行緒,這樣解碼器幀出來的順序即幀送進解碼器的順序,
next_finnished指向的工作執行緒解碼完成後,會存儲在avcodec->internal->buffer_frame中,avcodec_receive_frame()中會判斷它是否有數據有則取走,沒有則走一遍內部調用取幀,internal一般都是ffmpeg內部結構,不建議開發人員訪問.
關於ffmpeg的多執行緒編解碼分為 frame級和slice級 兩類, 當然應該不是所有的編解碼器都支援.
我在測試過程中發現 ffmpeg n4.2.1的版本 解碼 h264 默認是打開了幀級多執行緒解碼的, 類 -threads 0 -thread_type frame -i xxx.mp4
ffmpeg -y -threads 0 -thread_type frame -i xxx.mp4 -f null -an -
同時掃了一下x264編碼部分,實現方式和解碼形同,但測試過程中卻發現-threads x -thread_type frame 即x264幀級多執行緒編碼並不支援, 而是轉成了x264內部的多執行緒編碼參數,可見是因編解碼器而異.
另外ffmpeg transcode_step 中,即便不加filter也會畢走了一個null filter,
add_buffersrc會將avframe添加進內部filter graph link鏈的一個fifo隊列中、
buffsersink_get會從自己buffsink的link fifo裡面取已經產出的frame,如果還沒有,則會激活跑一遍filter graph的濾鏡鏈圖再來取.
簡化如下,未經調試,實際也許有區別.
static int get_frame_internal(AVFilterContext *ctx, AVFrame *frame, int flags, int samples)
{
BufferSinkContext *buf = ctx->priv;
AVFilterLink *inlink = ctx->inputs[0];
int status, ret;
AVFrame *cur_frame;
int64_t pts;
if (buf->peeked_frame)
return return_or_keep_frame(buf, frame, buf->peeked_frame, flags);
while (1) {
ret = samples ? ff_inlink_consume_samples(inlink, samples, samples, &cur_frame) :
ff_inlink_consume_frame(inlink, &cur_frame);
if (ret < 0) {
return ret;
} else if (ret) {
/* TODO return the frame instead of copying it */
return return_or_keep_frame(buf, frame, cur_frame, flags);
} else if (ff_inlink_acknowledge_status(inlink, &status, &pts)) {
return status;
} else if ((flags & AV_BUFFERSINK_FLAG_NO_REQUEST)) {
return AVERROR(EAGAIN);
} else if (inlink->frame_wanted_out) {
ret = ff_filter_graph_run_once(ctx->graph);
if (ret < 0)
return ret;
} else {
ff_inlink_request_frame(inlink);
}
}
}