ffmpeg中的並行解碼分爲兩種:html
咱們以前討論過Frame-level Parallelism。在以前的文章中,咱們說過在進行幀級的並行處理時,因爲I、P幀是做爲參考幀(B幀也能做爲參考幀),所以不能對其進行並行處理,只有非參考B幀纔是最適宜進行並行處理的幀。不過其實若是咱們能正確地處理好各個幀之間的依賴關係,不管是I、P仍是B幀都能進行並行處理。FFmpeg爲了達到這一目的,對咱們以前所討論的Frame-level Parallelism進行了改進:添加幀間依賴。多線程
h264的幀間依賴主要有兩個:函數
FFmpeg對於上述依賴的解決方案是:編碼
在討論FFmpeg的實現以前,咱們須要先了解packet(AVPacket)與frame(AVFrame)之間的關係。不一樣的編碼格式也許會有所不一樣,不過h264在FFmpeg中的一個packet中所包含的數據就是一個frame(一幀)。通常狀況下一幀就是一個slice,這樣的話一個packet中只有一個slice;固然,一幀也有可能會分爲多個slice,若是是這種狀況的話,一個packet中會包含這一幀全部的slice。線程
咱們之因此在這裏討論這二者之間的關係,是由於FFmpeg每次都是以一個packet爲單位向解碼器傳入須要解碼的數據,也就是說每次會向h264解碼器傳入一幀的數據。3d
FFmpeg實現方案以下:code
Thread List,線程列表,線程列表中的每一項都映射一個解碼線程。主線程會從線程列表中按照序號由小到大(循環)提取解碼線程,並把解碼任務提交到該解碼線程。同時主線程在提交完解碼任務後也會從線程列表中按照序號由小到大(循環)提取解碼線程,並嘗試從該解碼線程獲取解碼完成的幀。orm
M,主線程,主要目的有兩個:視頻
T,解碼線程,接收解碼任務並進行解碼。解碼線程是以frame爲單位進行處理的。解碼線程解碼主線程所提交的packet,執行與單線程時同樣的解碼做業,固然在解碼做業期間會碰到咱們上面所述的幀間依賴並進行處理。htm
幀間依賴除了上面所述的明顯存在的幀間依賴以外,還有一處較爲隱蔽的幀間依賴。
解碼所需的參考圖像列表依賴於POC,而在計算圖像POC時,須要對相鄰兩個frame(或者說slice)頭部的pic_order_cnt_lsb或者frame_num進行比較。這就代表在開始一個frame的解碼以前,須要把上一個frame的這些參數傳入當前frame。有了上一個frame頭部的這些參數,當前的frame就能按照單線程解碼那樣準確地計算出當前frame的POC。
FFmpeg把這參數傳入操做實如今了ff_h264_update_thread_context當中,該函數會在提交解碼任務前被調用[5]。
如咱們以前討論過的Slice-level Parallelism,ffmpeg的slice級並行只能在幀內並行。所以,若是在某個視頻在編碼時,一幀圖像分爲多個slice進行編碼的話,那麼在使用ffmpeg解碼時調用slice級並行解碼就會獲得不錯的效果。而在實際應用中,大多數h264編碼的視頻都是一幀只有一個slice,對於這種視頻,就算採用了slice級並行,也只有一個線程在進行解碼做業。
若是一幀,即一個packet分爲幾個slice時,會先把這一幀前面的slice加入隊列,到最後一個slice時統一對這一幀的全部slice進行並行解碼[6]。其中涉及到的關鍵要素以下:
Slice Context List,slice的上下文是slice context(FFmpeg中的變量爲slice_ctx),若是一幀中有多個slice,那麼會把slice上下文組成一個列表。前面所說的入隊列操做會對該列表進行填充以供後續解碼使用。
M,主線程,如單線程同樣的流程,從用戶調用解碼API一直執行到咱們前面所說的入隊列,到最後一個slice時會調用一個入口函數啓動多線程解碼操做。在調用入口函數後,主線程參與的多線程解碼過程一共包含三個步驟[7]:
T,解碼線程,接收到主線程所發起的啓動消息後,解碼線程會到Slice Context List去提取其中一個slice context(原子操做),而後進行slice解碼[8]。
※在進行slice並行解碼時deblocking是沒法超越slice邊界的,若是視頻指定了超越邊界的deblocking,那麼deblocking須要要留到全部slice解碼完成後再作。與此同時,若是指定ffmpeg進行快速解碼,也會在解碼線程內進行deblocking,不過此時的deblocking就是對本來進行超越邊界的deblocking進行了非超越邊界的deblocking,會影響視頻圖像質量[9]。
ffmpeg只要在打開解碼器以前進行以下設置就能夠執行並行解碼。
avcodec_alloc_context3(NULL); avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[Stream]->codecpar); Codec = avcodec_find_decoder(pCodecCtx->codec_id); pCodecCtx->thread_count = 4; pCodecCtx->thread_type = FF_THREAD_FRAME; //pCodecCtx->thread_type = FF_THREAD_SLICE; avcodec_open2(pCodecCtx, pCodec, NULL);
兩行分別爲:
設置並行解碼數目,即解碼線程數。
設置並行解碼類型爲FF_THREAD_FRAME或者FF_THREAD_SLICE,分別對應Frame-level Parallelism以及Slice-level Parallelism。
Reference:
8. thread_worker, run_jobs, worker