­

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);
        }
    }
}