關於使用ffmpeg的一些牢騷
一、啰嗦幾句
好幾年不寫部落格了,一是工作電腦都加密了沒法編輯提交;二是各種語言混用,什麼都會就是什麼都不會,delphi、c#、vb、python、c++要說我精通啥,啥也不精,所以不敢亂寫。
最近做一個關於影片處理的項目,用到ffmpeg,實在是憋不住,在此記錄一下摸索的過程。可以毫不誇張的說,網上關於ffmpeg的使用,大部分用命令行方式,調用api方式的很少,而且盲目抄襲甚盛,斗膽妄言,罪過罪過。
二、感謝
我是通過學習雷神的部落格逐漸掌握了ffmpeg的一些東西,好歹把項目做完了,效果很好,在此向雷神由衷的表示感謝。雷神由淺入深的介紹了ffmpeg的使用方法,有理論有實踐,可以說網上的很多文章很難與雷神媲美,而且中國這方面的文章太少了,這麼多做影片方面的,怎麼就沒有這方面的優質文章,在此是個疑點。可惜的是雷神花費了大量時間開放自己的學習探索的心得,當逐步的到達核心地帶時,戛然而止,雷神去世了,天妒英才吶。在此沉痛緬懷並致以崇高的敬意!
在本文中,沒有直接可運行的程式碼,一是加密,無法拷貝;二是提倡動手實踐,先把雷神的實例程式碼挨個學習調試,自會有極大的提高;
三、項目背景
核心一句話:接收高清影片流(H264+mp3 TS流),每30分鐘存儲一個mp4文件,相鄰兩個文件的播放要順暢不能丟幀。為啥說是高清呢,30分鐘文件就有5個G。
程式語言c++
四、踩過的坑
4.1進程方式
網上很多文章都是用命令行的,這種方式只能說測試還行,真正項目應用差點意思了,因為你要管理這個進程,他是個什麼狀態,你不知,但你又不能不管,關鍵做不到前後兩個影片無縫銜接,咋整,雞肋啊,做個測試、驗證等可以,做項目不行。用api吧,資料太少,項目組意見不一,最後舉個例子達成一致了,前面有個碉堡,我們明知道用手榴彈不行,還堅持讓大家扔手榴彈,這是瞎耽誤工夫;拿zha yao肯定行,但是有人得犧牲(扔手榴彈站在遠處扔就行,zha yao包得有人扛到跟前),要想徹底解決就得用徹底的辦法。所以很多時候我們缺少的就是沉下心的耐力和扛zha yao包的勇氣,潰癰雖痛勝於養毒,把雷神用api的例子全部從頭調試一遍,總結出流程,都需要哪些要素,時間基、取樣率、音影片流是啥用來做啥,搞明白就完事了。
4.2 ffmpeg rtp
ffmpeg可以直接接收RTP,也有提供轉換MP4的方法,要注意的是接收和處理放在一個執行緒中有問題,容易丟幀,因為UDP通訊必須設置快取大小,但是一旦處理堵住了,數據絕對會丟失。程式在現場長時間不間斷運行,很難保證不出現丟幀的情況,經簡單測試,直接拋棄該方式。
五、我的實現方法
1、使用UDP方式接收組播影片流,並寫入文件中,文件按時間命名。
2、當檢測到夠30分鐘時,停止寫入當前文件,開始寫入另一個文件。
3、通知影片轉換執行緒,處理當前寫完的文件。
4、影片轉換執行緒,讀取文件,
打開輸入文件流(avformat_open_input),
創建輸出上下文(avformat_alloc_output_context2),我們要根據文件轉成mp4。
查找影片資訊(avformat_find_stream_info),查找輸入的碼流:影片流、音頻流、字幕流。
根據輸入碼流創建輸出碼流,流的參數拷貝就行(avcodec_parameters_copy),特別要注意的是輸入輸出流的時間基(time_base)。
打開輸出流,寫入文件頭,設定一個文件結尾的閾值,當輸入流剩餘位元組數小於該值時並且找到最後一個關鍵幀,則寫入到輸出流後,將剩餘輸入文件的結尾置換到下一個文件的開頭中,這樣前後兩個文件無縫銜接,第一個文件最後一個關鍵幀是第二個文件開頭的第一幀,所以無縫銜接了。
循環讀取輸入流(av_read_frame)根據流索引確定是音頻還是影片流,如果是影片流寫入文件的第一幀必須是關鍵幀。寫入時特別注意音頻和影片的pkt的時間(pts、dts、duration)需要根據自己的時間基重新換算(av_rescale_q_rnd),並記錄第一幀的時間戳pts,換算後的pts和dts要減去第一幀的pts,這樣每個文件播放就是從頭開始了。寫入輸出流用av_interleaved_write_frame。
讀取文件轉換成mp4,在現場機器(高速快取設備)上總共需要不到15秒鐘。
六、最終效果
項目部署5個多月,記憶體(103M左右,峰值180M)、cpu(3%–8%),非常穩定,無異常崩潰退出,影片無馬賽克、前後影片銜接很棒。
七、總結
堅持實踐就是硬道理,無論什麼職位、角色都不能眼高手低。
抄別人程式碼一千遍不如自己動手調一遍。