新手學習FFmpeg - 調用API完成兩個視頻的任意合併

本次嘗試在視頻A中的任意位置插入視頻B.shell

在上一篇中,咱們經過調整PTS能夠實現視頻的加減速。這只是對同一個視頻的調轉,本次咱們嘗試對多個視頻進行合併處理。ide

Concat如何運行

ffmpeg提供了一個concat濾鏡來合併多個視頻,例如:要合併視頻Video A和Video B,經過調用指針

ffmpeg -i va.mp4 -i vb.mp4 -filter_complex "[0][1]concat[out]" -map '[out]' -y output.mp4

concat支持多個Input Source,上面的命令只合並了兩個視頻,經過生成concat流程圖能夠看到一些細節:code

echo "movie=va.mp4[0];movie=vb.mp4[1];[0][1]concat,nullsink" | graph2dot -o graph.tmp
dot -Tpng graph.tmp -o graph.png

這是concat典型用法,循環讀取輸入源,而後經過修改pts完成合並。視頻

concat是順序修改,若是須要在video A中某個時間點插入video B,那麼concat就沒法完成了。 順序合併是經過修改PTS實現,那麼變序合併也能夠經過修改PTS來實現,下面藉助concat的邏輯來看看如何實現變序合併。blog

變序合併

爲了方便說明問題,咱們來看一下順序和變序不一樣點到底在哪裏。get

  • 問題分析

咱們仍然假設須要合併的兩個視頻分別是Video A和Video B, 須要將Video B插入在Video A中。AF表示Video A的幀, BF表示Video B的幀。class

順序合併ffmpeg

+---------------------------------------------------------------------------------------------------------------+
        |       AF1    AF2    AF3     AF4     AF5    AF6     AF7    BF1    BF2     BF3    BF4     BF5    BF6            |
        |       |--------------|--------------|--------------|--------------|--------------|--------------|--->         |
        |Time   0              10             20             30            40              50            60             |
        |PTS    0      100     200     250    300     350    400    500    600     650    700    750     800            |
        +---------------------------------------------------------------------------------------------------------------+

順序合併就是讀取Video B的幀,而後將pts以Video A結束時的PTS爲基準進行修改。循環

變序合併

+---------------------------------------------------------------------------------------------------------------+
        |       AF1    AF2    AF3     AF4     BF1    BF2     BF3    BF4     BF5    BF6    AF5    AF6     AF7            |
        |       |--------------|--------------|--------------|--------------|--------------|--------------|--->         |
        |Time   0              10             20             30            40              50            60             |
        |PTS    0      100     200     250    300     350    400    500    600     650    700    750     800            |
        +---------------------------------------------------------------------------------------------------------------+

變序合併時先讀取Video A的幀,當達到規定的PTS時,開始讀取Video B的幀,而後以A截斷時的PTS爲基準從新計算PTS。當Video B全部的幀都處理完畢以後,在從截斷處開始從新處理Video A的幀。

從上面兩個圖來看,問題好像不是很難解決。 只要達到截斷的條件,就去處理另一個視頻,等待視頻處理完畢以後。再返回來處理被截斷的視頻。

但在實現的道路上有以下三個問題須要解決:

  1. 如何判斷到達插入時間點
  2. 如何判斷視頻處理完畢
  3. 如何從斷點處從新讀取Frame

下面就須要逐個問題解決了。

  • 如何判斷到達插入時間點

由於咱們是須要在視頻A中插入視頻B,因此須要首先找到插入點。 而根據時間來判斷插入點無疑是最簡單的一種形式,計算時間就能夠依靠前幾篇中介紹的PTS知識了。

當從視頻源中讀取到每幀後,咱們經過幀的PTS和Time-Base根據pts * av_q2d(time_base)轉換成播放時間。 這樣第一個問題就順利解決。

當找到插入點後,咱們須要暫存當前的位置,等待插入結束後,須要從斷點處從新加載幀。

  • 如何判斷視頻處理完畢

執行插入本質就是讀取視頻B的數據幀,而後修改PTS值。但咱們須要得知視頻B已經處理完畢,這樣才能返回到視頻A的斷點處繼續處理。 因此如何獲取到視頻處理完畢就是第二個問題。

若是拋開ffmpeg來講,處理視頻本質也是一個IO流(從視頻文件中讀取的IO流),當判斷到IO流結束時(經過seek來判斷EOF)時就是視頻處理完畢的時候。 但ffmpeg將這一層屏蔽掉了,也就是在filter中是沒法直接獲取到IO流狀態的。

ffmpeg在屏蔽的同時,也提供了一種判斷方式。filter在處理完每一幀以後,須要確認下一幀的狀態(有下一幀/無下一幀),因此若是ffmpeg在讀取到下一幀時返回了無下一幀,那就表示當前視頻處理完畢。

經過ff_inlink_acknowledge_status(AVFilterLink *link, int *rstatus, int64_t *rpts)來獲取下一幀的狀態,當返回的ret>0表示沒有下一幀,這個時候就能夠經過判斷當前處理狀態來決定是否關閉輸出流。

if 當前處理視頻B
                切換到視頻A的斷點
        else 當前處理視頻A
                關閉全部的輸入流
                關閉輸出流
  • 如何從斷點處從新讀取Frame

這是最後一個待解決的問題了,當視頻B的數據都處理完以後,就須要從視頻A的斷點處從新讀取數據幀。上面說到對視頻流的讀取,本質就是對一個文件的IO流處理,而在IO時都會有一個指針來表示當前位置。

ff_inlink_acknowledge_status有兩個做用,一方面獲取下一幀,另外一方面是確認當前幀處理結束。 換言之,當調用ff_inlink_acknowledge_status以後,ffmpeg會將IO流的指針向後移動到下一幀的起始位置,若是移動失敗,則表示沒有下一幀了。 若是移動成功,那麼下次ff_inlink_consume_frame讀取幀時,就從這個位置開始讀取。

所以如何從斷點處從新讀取Frame其實不是問題,只要斷點處的幀被確認處理結束了,ffmpeg會自動的移到下一幀位置。當咱們將輸入源切換到視頻A時,就自動從斷點處開始讀取幀了。

  • 僞代碼實現

經過下面的僞代碼簡要描述上述的過程:

經過ff_outlink_get_status判斷輸出流狀態
        if 輸出流已關閉
                退出

        for {
                經過ff_inlink_consume_frame 獲取下一幀

                經過frame->pts * av_q2d(time_base)計算時間

                if 時間達到插入點
                        修改當前狀態, 進入暫存狀態。

                經過push_frame處理每一幀
        }

        經過ff_inlink_acknowledge_status確認幀狀態

        if 當前是暫存狀態
                切換到視頻B

        if 沒有下一幀
                if 當前是視頻B && 當前是暫存狀態
                        關閉視頻B
                        切換回視頻A

                if 當前是視頻A && 當前是暫存狀態
                        關閉視頻A
                        關閉輸出流

大體就是這個處理流程, 完整代碼能夠參考iconcat裏面的代碼。

相關文章
相關標籤/搜索