為什麼選擇FFmpeg? 延遲低,參數可控,相關函數方便查詢,是選擇FFmpeg作為編解碼器最主要原因,如果是處理實時流,要求低延遲,最好選擇是FFmpeg。 如果需要用Opencv或者C#的Emgucv這種庫來處理視頻流,也多是用FFmpeg做編解碼然後再轉換圖像數據給Opencv去處理。用Ope ...
為什麼選擇FFmpeg?
- 延遲低,參數可控,相關函數方便查詢,是選擇FFmpeg作為編解碼器最主要原因,如果是處理實時流,要求低延遲,最好選擇是FFmpeg。
- 如果需要用Opencv或者C#的Emgucv這種庫來處理視頻流,也多是用FFmpeg做編解碼然後再轉換圖像數據給Opencv去處理。用Opencv編解碼延遲很高。
- 其他的庫多是基於FFmpeg封裝,如果做一個視頻播放器,像vlc這種庫是非常方便的,缺點是臃腫,需要手動剔除一些文件,當然也有一些是基於FFmpeg封裝好的視頻播放器庫,也能快速實現一個播放器。
- 如果是載入單Usb介面中的多Usb攝像頭,FFmpeg這時就無能為力了,經過測試使用DirectShow能夠實現。AForge一個很好的學習樣例,它將DirectShow封裝的很好,能輕鬆實現載入單Usb介面中的多Usb攝像頭(不過它很久沒更新了,目前無法設置攝像頭參數,也沒有Usb攝像頭直接錄製,所以我把它重寫了),當然使用其他DirectShow的庫也是可以的。
- 寫此文章時才發現CaptureManager這個2023年4月發佈的非常簡便好用的基於D3D封裝的音視頻庫,它的官方樣例非常豐富,能實現很多功能。我嘗試了運行了他的官方樣例,打開相同規格的Usb攝像頭,發覺cpu占用是FFmpeg的兩倍。
如何學習FFmpeg?
記錄一下我是如何學習FFmpeg。首先是C#使用FFmpeg基本上用的是FFmpeg.autogen這個庫。也可以使用FFmpeg.exe,先不談論FFmpeg.exe的大小,我嘗試過從exe中取數據到C#前端顯示,相同參數情況下,延遲比使用FFmpeg.autogen高,主要是不能邊播放邊錄製(可以用其它的庫來錄製,但是效率比不上只使用一個庫)。
當然如果只需要部分功能也可以自己封裝FFmpeg(太花時間了,我放棄了。如果是專門從事這一行的可以試試)。
學習FFmpeg.autogen可以先去Github上下載它的樣例(其實樣例有個小問題,後面說),學習基礎的編解碼。
後面有人把官網的C++的樣例用FFmpeg.autogen寫了一遍,我把樣例壓縮好放誇克網盤了:https://pan.quark.cn/s/c579aad1d8e0。
然後是查看一些博客和Github上一些項目,瞭解編解碼整體架構,因為FFmpeg很多參考代碼都是c++的所以我基本是參考C++寫C#,寫出整體的編解碼代碼。
無論是編解碼還是開發Fliter都會涉及到很多參數設置。要查找這些參數,我先是去翻博客,最後還是去FFmpeg官網(官網文檔,編解碼參數很全),當然製作視頻濾鏡和一些其他功能,也是參考官網的參數。
對於部分基礎函數(有些函數會把幀用掉就釋放,要註意)查看FFmpeg的源碼,理解原理。
對於一些概念性的東西,我是翻閱碩博論文(一般都有總結這些)。
C#使用FFmpeg需要註意什麼?
- FFmpeg.autogen是有一個缺點的,它是全靜態的,不支持多線程(這個我問作者了),所以用多進程,而用多進程渲染到同一畫面,可以參考我上一篇MAF的文章。
- 尤其要註意幀釋放,編解碼的幀如果沒有釋放是一定會產生記憶體泄漏的,而且速度很快。
- 其次是c# 要將圖像數據渲染到界面顯示,最最好使用WriteableBitmap,將WriteableBitmap和綁定到一個Image然後更新WriteableBitmap。我記得在一篇博客中提到高性能渲染,使用MoveMemory來填充WriteableBitmap的BackBuffer,核心代碼如下。
[DllImport("kernel32.dll", EntryPoint = "RtlMoveMemory")] private static extern void MoveMemory(IntPtr dest, IntPtr src, uint count);
writeableBitmap.Lock(); unsafe { fixed (byte* ptr = intPtr) { MoveMemory(writeableBitmap.BackBuffer, new IntPtr(ptr), (uint)intPtr.Length); } } writeableBitmap.AddDirtyRect(new Int32Rect(0, 0, width, height)); writeableBitmap.Unlock();
這樣處理有個致命的缺點。WriteableBitamp的寬高必須為2的整數倍,即使是修正過大小,當傳入數據為特殊尺寸使用此方法時還是會出現顯示異常的情況。所以還是老實使用WriteableBitmap的WritePixels。 - 對於FFmpeg很多函數都是會返回錯誤信息,一定要將錯誤信息記錄到日誌,方便查找和查看(基本每個函數要加錯誤信息判斷)。
- 軟編碼會占用大量的CPU資源,所以最好採用硬編碼。FFmpeg有一個查找編解碼器的函數,它並不能查看硬體編碼器。如果要使用硬體加速查找編解碼器最好是用其他方式獲取系統設備或者直接一個一個打開NVDIA和QSV等加速,都失敗了再啟用軟編解碼。
- QSV硬編碼要求輸入的像素格式必須為AVPixelFormat.AV_PIX_FMT_NV12,如果是硬解碼出的數據,可以直接編碼,否則需要添加格式轉換。FFmepg.autogen的官方樣例中有格式轉換函數,但由於它沒有指定轉換後的格式會出問題(踩坑)。
- 儘量少的格式轉換,或者幀複製。這兩種方式會提高cpu和記憶體使用率同時也會有更高的延遲。
- 在製作FFmpeg的帶有文本的Filter時,將需要使用的字體複製到項目目錄然後指定字體位置而不是調用系統的字體(不知道是版本原因還是什麼問題,一用系統字體就會產生記憶體泄漏)。
- 註意編解碼數據的格式。一些老的格式,雖然解碼沒有什麼問題(ffmpeg 會有提示)但是編碼是不支持的,出現這種問題,程式會直接死掉(踩坑)。
- 解碼時可以通過解碼數據自動搜尋硬體解碼器,而硬體編碼需要手動指定編碼器(可以通過,查找並自動選擇GPU來實現自動選擇)。
- 多線程實現播放同時錄製時,最好採用幀複製ffmpeg.av_frame_clone(hwframe)不用對同一個幀進行操作。當然也可以不用多線程,同一個幀在播放完成後進行,錄製。
暫時只想到這些,有其他的想法再更新
如果有任何錯誤歡迎批評指正。