一、啰嗦幾句
好幾年不寫博客了,一是工作計算機都加密了沒法編輯提交;二是各種語言混用,什么都會就是什么都不會,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%),非常穩定,無異常崩潰退出,視頻無馬賽克、前后視頻銜接很棒。
七、總結
堅持實踐就是硬道理,無論什么職位、角色都不能眼高手低。
抄別人代碼一千遍不如自己動手調一遍。