CSharp FFmpeg 音視頻開發總結

‍爲什麼選擇 FFmpeg?

1、延遲低,參數可控,相關函數方便查詢,是選擇 FFmpeg 作爲編解碼器最主要原因,如果是處理實時流,要求低延遲,最好選擇是 FFmpeg。

2、如果需要用 Opencv 或者 C# 的 Emgucv 這種庫來處理視頻流,也多是用 FFmpeg 做編解碼然後再轉換圖像數據給 Opencv 去處理。用 Opencv 編解碼延遲很高。

3、其他的庫多是基於 FFmpeg 封裝,如果做一個視頻播放器,像 vlc 這種庫是非常方便的,缺點是臃腫,需要手動剔除一些文件,當然也有一些是基於 FFmpeg 封裝好的視頻播放器庫,也能快速實現一個播放器。

4、如果是加載單 Usb 接口中的多 Usb 攝像頭,FFmpeg 這時就無能爲力了,經過測試使用 DirectShow 能夠實現。AForge 一個很好的學習樣例,它將 DirectShow 封裝的很好,能輕鬆實現加載單 Usb 接口中的多 Usb 攝像頭 (不過它很久沒更新了,目前無法設置攝像頭參數,也沒有 Usb 攝像頭直接錄製,所以我把它重寫了),當然使用其他 DirectShow 的庫也是可以的。

5、寫此文章時才發現 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 官網 [1] (官網文檔,編解碼參數很全),當然製作視頻濾鏡和一些其他功能,也是參考官網的參數。對於部分基礎函數(有些函數會把幀用掉就釋放,要注意)查看 FFmpeg 的源碼,理解原理。

對於一些概念性的東西,我是翻閱碩博論文 (一般都有總結這些)。

C# 使用 FFmpeg 需要注意什麼?

1、FFmpeg.autogen 是有一個缺點的,它是全靜態的,不支持多線程 (這個我問作者了),所以用多進程,而用多進程渲染到同一畫面,可以參考我上一篇 MAF 的文章。

2、尤其要注意幀釋放,編解碼的幀如果沒有釋放是一定會產生內存泄漏的,而且速度很快。

3、其次是 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。

4、對於 FFmpeg 很多函數都是會返回錯誤信息,一定要將錯誤信息記錄到日誌,方便查找和查看 (基本每個函數要加錯誤信息判斷)。

5、軟編碼會佔用大量的 CPU 資源,所以最好採用硬編碼。FFmpeg 有一個查找編解碼器的函數,它並不能查看硬件編碼器。如果要使用硬件加速查找編解碼器最好是用其他方式獲取系統設備或者直接一個一個打開 NVDIA 和 QSV 等加速,都失敗了再啓用軟編解碼。

6、QSV 硬編碼要求輸入的像素格式必須爲 AVPixelFormat.AV_PIX_FMT_NV12,如果是硬解碼出的數據,可以直接編碼,否則需要添加格式轉換。FFmepg.autogen 的官方樣例中有格式轉換函數,但由於它沒有指定轉換後的格式會出問題 (踩坑)。

7、儘量少的格式轉換,或者幀複製。這兩種方式會提高 cpu 和內存使用率同時也會有更高的延遲。

8、在製作 FFmpeg 的帶有文本的 Filter 時,將需要使用的字體複製到項目目錄然後指定字體位置而不是調用系統的字體 (不知道是版本原因還是什麼問題,一用系統字體就會產生內存泄漏)。

9、注意編解碼數據的格式。一些老的格式,雖然解碼沒有什麼問題 (ffmpeg 會有提示) 但是編碼是不支持的,出現這種問題,程序會直接死掉(踩坑)。

10、解碼時可以通過解碼數據自動搜尋硬件解碼器,而硬件編碼需要手動指定編碼器 (可以通過,查找並自動選擇 GPU 來實現自動選擇)。

11、多線程實現播放同時錄製時,最好採用幀複製 ffmpeg.av_frame_clone(hwframe) 不用對同一個幀進行操作。當然也可以不用多線程,同一個幀在播放完成後進行,錄製。

暫時只想到這些,有其他的想法再更新,如果有任何錯誤歡迎批評指正。

相關鏈接

FFmpeg 官網: https://ffmpeg.org/documentation.html

轉自:莫如風

鏈接:cnblogs.com/mrf2233/p/17442871.html

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/48q7Smy0VvNRTQEx0q9Fzw