close

ffmpeg 中使用到的多線程的概念:

共享變量的互斥

互斥鎖(mutex-lock)是一種信號量,用來防止兩個線程在同一時刻訪問相同的共享資源,它有鎖定狀態和非鎖定狀態。

在任意時刻,一個線程要想存取共享數據,線程必須首先獲得mutex-lock,當此線程釋放此共享數據的時候必須對mutex-lock解鎖,在一個任意的時間內,只有一個線程能鎖定互斥鎖,通過函數pthread_mutex_lock上鎖,通過函數pthread_mutex_unlock解鎖。

同步條件變量

條件變量用來提供另一種線程同步的方法,其基於實際的變量值來實現線程的同步操作,設置了條件變量的情況下,線程就不需要通過不停的輪詢來查詢條件是否滿足,也不需要不停的忙等,從而能夠節省很多系統資源。

一個條件變量總是和一個mutex-lock對應,系統通過pthread_cond_await函數來阻塞調用的線程,一直到條件變量得到滿足。

當這個線程阻塞的時候對應的mutex-lock會自動解鎖,但當該線程運行的時候,其對應的mutex-lock會被加鎖。

使用函數pthread_cond_signal來喚醒等待在條件變量的另一個線程,當用來喚醒多個處於阻塞狀態線程時通過pthread_cond_broadcast函數來完成。

ffmpeg實現多線程方案:

Thread List,線程列表,線程列表中的每一項都映射一個解碼線程。

主線程會從線程列表中按照序號由小到大(循環)提取解碼線程,並把解碼任務提交到該解碼線程。

同時主線程在提交完解碼任務後也會從線程列表中按照序號由小到大(循環)提取解碼線程,並嘗試從該解碼線程獲取解碼完成的幀。

M,主線程,主要目的有兩個:

向解碼線程提交解碼任務。FFmpeg中是以packet為單位進行解碼任務的提交的,按照前一小節的描述,FFmpeg就是以frame為單位進行解碼任務的提交的。

從解碼線程獲取解碼所得的幀並進行返回。

不過在第一輪進行任務提交的時候是不會去獲取幀,在第一輪任務提交完成後,此時所有解碼線程都已經開始進行了解碼作業,那麼主線程就可以開始等待第一個線程解碼完成,然後嘗試去獲得解碼完成的幀。

這裡的「嘗試」,是因為就像單線程解碼時那樣,並不一定是每次調用解碼API都會返回一幀的。由於h264編碼的視頻中常常包含B幀,這會使得碼流的解碼順序並非幀的播放順序,但是解碼API必須按照幀的播放順序進行返回,因此在進行幀的返回時會進行相應的調整。

接下來每次向一個線程提交一個解碼任務後,都需要等待下一個線程空閒並嘗試返回幀。

T,解碼線程,接收解碼任務並進行解碼。解碼線程是以frame為單位進行處理的。解碼線程解碼主線程所提交的packet,執行與單線程時一樣的解碼作業。

ffmpeg中的多線程解碼主要分為片 Slice級別的多線程解碼 和 幀Frame級別的多線程解碼:

Slice級的解碼效率比Frame級的解碼效率要低,故,本文先考慮Frame級的多線程解碼。

Frame級的多線程解碼

通過查看ffmpeg源碼,了解到Frame級的多線程解碼流程大致如下:

其中右側的frame_worker_thread為解碼線程,在open解碼器時就已經創建,隨後阻塞在pthread_cond_wait(&p->input_cond, &p->mutex)函數。等待被主線程喚醒。

當主線程運行到ff_thread_decode_frame函數時,會調用submit_packet函數,這個函數的目的就是將packet包交給解碼線程。

submit_packet函數會調用pthread_cond_signal(&p->input_cond)函數,這個函數就是為喚醒剛才阻塞的解碼線程。

當主線程喚醒解碼線程後,其pthread_cond_wait(&p->output_cond, &p->progress_mutex)函數會進入阻塞狀態,等待解碼線程喚醒。

如果Codec未實現update_thread_context()和線程安全的get_buffer(),則必須在解碼完成後才能將狀態轉換為STATUS_SETUP_FINISHED,意味着下一個線程只能在當前線程解碼完成後才能開始解碼。當解碼線程解碼完成後,會用pthread_cond_signal(&p->output_cond)將主線程喚醒,其中會通過回調函數將解碼線程解碼出來的frame獲取,從而輸出。

2. 如果Codec實現update_thread_context()和線程安全的get_buffer(),線程狀態可以在解碼開始之前轉換為STATUS_SETUP_FINISHED,這樣,主線程就能夠被喚醒,就能夠去讀包進行下一個包的解碼,因此下一個線程就可能與當前線程並行。

Slice級的多線程解碼:

ffmpeg的slice級並行只能在幀內並行。

因此,如果在某個視頻在編碼時,一幀圖像分為多個slice進行編碼的話,那麼在使用ffmpeg解碼時調用slice級並行解碼就會得到不錯的效果。

而在實際應用中,大多數h264編碼的視頻都是一幀只有一個slice,對於這種視頻,就算採用了slice級並行,也只有一個線程在進行解碼作業。

如果一幀,即一個packet分為幾個slice時,會先把這一幀前面的slice加入隊列,到最後一個slice時統一對這一幀的所有slice進行並行解碼。其中涉及到的關鍵要素如下:

Slice Context List,slice的上下文是slice context(FFmpeg中的變量為slice_ctx),如果一幀中有多個slice,那麼會把slice上下文組成一個列表。前面所說的入隊列操作會對該列表進行填充以供後續解碼使用。

M,主線程,如單線程一樣的流程,從用戶調用解碼API一直執行到我們前面所說的入隊列,到最後一個slice時會調用一個入口函數啟動多線程解碼操作。在調用入口函數後,主線程參與的多線程解碼過程一共包含三個步驟:

通過發送啟動消息激活其它正在等待的解碼線程。
在啟動多線程解碼後,主線程也會一同作為其中一個線程進行slice的解碼。
最後等待所有線程完成任務後返回。

T,解碼線程,接收到主線程所發起的啟動消息後,解碼線程會到Slice Context List去提取其中一個slice context(原子操作),然後進行slice解碼。

作者:frgfnjrgn來源:https://blog.csdn.net/yihuanyihuan/article/details/104019536

一個音視頻領域專業問答的小圈子!

推薦閱讀:

音視頻開發工作經驗分享 || 視頻版

OpenGL ES 學習資源分享

開通專輯 | 細數那些年寫過的技術文章專輯

Android NDK 免費視頻在線學習!!!

你想要的音視頻開發資料庫來了

推薦幾個堪稱教科書級別的 Android 音視頻入門項目

覺得不錯,點個在看唄~

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 鑽石舞台 的頭像
    鑽石舞台

    鑽石舞台

    鑽石舞台 發表在 痞客邦 留言(0) 人氣()