本次嘗試在視頻A中的任意位置插入視頻B.shell
在上一篇中,咱們經過調整PTS能夠實現視頻的加減速。這只是對同一個視頻的調轉,本次咱們嘗試對多個視頻進行合併處理。ide
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的幀。
從上面兩個圖來看,問題好像不是很難解決。 只要達到截斷
的條件,就去處理另一個視頻,等待視頻處理完畢以後。再返回來處理被截斷
的視頻。
但在實現的道路上有以下三個問題須要解決:
下面就須要逐個問題解決了。
由於咱們是須要在視頻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 關閉全部的輸入流 關閉輸出流
這是最後一個待解決的問題了,當視頻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
裏面的代碼。