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


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

在上一篇中,我們通過調整PTS可以實現視頻的加減速。這只是對同一個視頻的調轉,本次我們嘗試對多個視頻進行合並處理。

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流程圖可以看到一些細節:

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的邏輯來看看如何實現變序合並。

變序合並

為了方便說明問題,我們來看一下順序和變序不同點到底在哪里。

  • 問題分析

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

順序合並

        +---------------------------------------------------------------------------------------------------------------+
        |       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里面的代碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM