當前位置: 妍妍網 > 碼農

c#螢幕錄制(經典)(含源碼和AForge.Video.FFMPEG.DLL)及填坑辦法

2024-02-12碼農

一直覺得.net在多媒體處理方面渣得不行。最近需要做一個網路攝影機的程式,為了方便,用了AForge這個開源計畫。AForge計畫中有AForge.Video和AForge.Video. DirectShow這兩個子計畫,可以方便的呼叫網路攝影機。但是這兩個計畫最終只能取得視訊幀,並不能保存為視訊檔。經高人指點,AForge還有一個子計畫AForge.Video.FFMPEG,它可以將圖片壓制成Avi視訊格式。不過這個AForge.Video.FFMPEG在實際使用的時候會遇到不少坑,下面我將我在這次使用中遇到的坑分享給大家。

AForge.NET是一個專門為開發者和研究者基於C#框架設計的,該庫是一個開源計畫,他包括電腦視覺與人工智慧,影像處理,神經網路,遺傳演算法,機器學習,模糊系統,機器人控制等領域,提供很多影像的處理,和視訊處理功能

這個框架由一系列的類別庫組成。主要包括有:

AForge.Imaging —— 一些日常的影像處理和過濾器
AForge.Vision —— 電腦視覺套用類別庫
AForge.Neuro —— 神經網路計算庫AForge.Genetic -前進演化演算法編程庫
AForge.MachineLearning —— 機器學習類別庫
AForge.Robotics —— 提供一些機器人的工具類別庫
AForge.Video —— 一系列的視訊處理類別庫
AForge.Fuzzy —— 模糊推理系統類別庫
AForge.Controls—— 影像,三維,圖表顯示控制項

官網:http://www.aforgenet.com/

Aforge.Net子計畫有個AForge.Video.VFW提供了對Avi檔的操作,AForge後面加入了子計畫 AForge.Video.FFMPEG 透過FFmpeg庫,提供了對大量視訊格式的支持,我們都知道,FFmpeg是一個非常強大的視訊處理類別庫,同樣也是開源的,不過 AForge.Video.FFMPEG 還處於實驗階段,目標是用 FFmpeg 取代 AForge.Video.VFW 提供一個更好的對視訊檔操作的庫,但是該庫值目前提供了對視訊數據的讀寫,不支持對音訊檔的讀寫,可能以後會支持

第一坑:參照

你要用AForge.Video.FFMPEG,當然第一步是參照啦。但這個AForge.Video.FFMPEG並不能像AForge其他計畫一樣可以用Visual Studio內建的NuGet去獲得,你會發現NuGet上根本找不到這個計畫。

找不到麽,那我就去官網找好了,咱們可以去AForge計畫官網下載AForge計畫的源碼和已編譯檔。不過這裏有倆問題:

  1. AForge計畫官網開啟速度非常非常非常慢,你可以點連結開啟官網,然後開啟遊戲玩一會兒。(這裏我就給各位放個AForge下載頁直鏈:http://www.aforgenet.com/framework/downloads.html)

  2. AForge計畫的源碼和生成檔最終都是放在GoogleCode上的,國內你懂得。不過這邊我們就可以用的小花招就是用迅雷之類的下載器下載,他們的離線下載是可以翻墻的。

我是選擇了「Download Installer」,右鍵選擇「復制連結地址」,然後放進迅雷下載。

下載下來之後是一個壓縮包,AForge.Video.FFMPEG.dll就放在壓縮包的Release資料夾中。

第二坑:呼叫

剛剛我們從官網下載下來了AForge.Video.FFMPEG.dll,接下來呼叫就好了對吧。

然而並不是,你只是一腳踏進了一個深坑罷了,為什麽說是深坑呢?因為這個dll呼叫非常非常的惡心。

我們來看一下有多惡心,首先我們假設我們已經在計畫中已經添加了AForge.Video和AForge.Video.FFMPEG這二個類別庫。

然後修改Main函式:

using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace ConsoleRecoderTest{ class Program { static void Main(string[] args) { ScreenRecorderTemplate tmp = new ScreenRecorderTemplate() { IsStartUp = false, StartDateTime = DateTime.Now, StopDateTime = DateTime.Now.AddMinutes(2) }; new ScreenRecorderTool(tmp).StartRecording(); Console.WriteLine("complete"); Console.Read(); } }}

按F5偵錯,瞬間爆炸:

發生這個問題的原因比較簡單,因為這個AForge.Video.FFMPEG使用VC++寫的,編譯的時候已經被編譯成本地程式碼,而我們現在C#一般目標平台都是「Any CPU」,所以會發生這個問題。

解決方案就是不再選擇使用「Any CPU」作為目標平台,改成「x86」或者「x64」。因為x86可以跑在x64上,而x64不能在x86上跑,所以我選擇了x86。

現在再按F5啟動偵錯,再一次瞬間爆炸【冷漠臉】。

怎麽說呢,起碼出錯提示換了對吧【冷漠臉】。

那麽這次又是出什麽問題了呢。

咱們現在用的是AForge.Video.FFMPEG對吧。我們都知道FFMPEG是一個著名的開源多媒體處理計畫對吧,這個AForge.Video.FFMPEG其實是在內部呼叫FFMPEG來工作的。所以這個FileNotFoundException其實是AForge.Video.FFMPEG找不到FFMPEG的檔所以丟擲來的。AForge.Video.FFMPEG依賴的FFMPEG元件其實已經放在了剛剛下載下來的壓縮包的\Externals\ffmpeg\bin目錄下:

我們把這個8個檔復制到程式目錄下,註意我們剛剛改過目標平台了,現在程式編譯輸出的目錄已經是\bin\x86\Debug,不要復制錯了。

復制好之後我們繼續按F5偵錯程式。

嗯,爆炸了,我已經習慣了【冷漠臉】

這次問題的原因是什麽呢……

其實是因為我的計畫目標框架是.net Framework 4.0,而AForge官方在編譯AForge.Video.FFMPEG.dll的時候,目標框架選的是.net Framework 2.0……

在.net Framework 4.0以前,由於程式執行環境本質還是.net Framework 2.0,並且.net Framework 2.0相容.net Framework 1.0和1.1,但在升級到.net Framework 4.0時,.NET的內核作了重大調整,以前在.net Framework 2.0或.net3.5中生成的程式集,如果要在.net Framework 4.0下執行,需要在配置檔中指定此應用程式支持的公共語言執行時版本和啟用.net Framework 2.0執行時啟用策略。

解決方案有三種:

  1. 降低自己計畫的目標.net Framework版本;

  2. 修改Config檔;

  3. 重新編譯Video.FFMPEG。

這裏我就講一下方法二,

在Visual Studio中按Ctrl+Shift+A,開啟「添加新項」視窗,選擇「應用程式配置檔」,再點選「添加」(vs2017建立的時候已經內建了App.config無需再次添加)

開啟新建的App.Config檔,在<configuration>和</configuration>標簽中加入以下內容:

<startup useLegacyV2RuntimeActivationPolicy="true"> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1"/> </startup>

添加完成後,按F5啟動偵錯。

終於一切正常。

錄制的視訊在檔執行目錄下:

計畫的參照項:

使用的開源的視訊處理元件AForge,當然它所包含的功能遠不止於此,想了解更多到官網上去看吧。一下程式碼主要是錄制桌面螢幕,每20秒存入一個視訊檔,可以為有類似需要的通知提供一點幫助。

ScreenRecorderTool.cs

  • using System;using System.Collections.Generic;using System.Diagnostics;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;//using Accord.Video;//using Accord.Video.FFMPEG;using AForge.Video;using AForge.Video.FFMPEG;namespace ConsoleRecoderTest{ /// <summary> /// 位元率 /// </summary> public enum BitRate : int { _50kbit = 5000, _100kbit = 10000, _500kbit = 50000, _1000kbit = 1000000, _2000kbit = 2000000, _3000kbit = 3000000 } /// <summary> /// 螢幕錄制樣版 /// </summary> public class ScreenRecorderTemplate { /// <summary> /// 樣版名稱 /// </summary> public string TmpName { get; set; } /// <summary> /// 錄屏開始時間 /// </summary> public DateTime? StartDateTime { get; set; } /// <summary> /// 錄屏結束時間 /// </summary> public DateTime? StopDateTime { get; set; } /// <summary> /// 是否為開機啟動 /// </summary> public bool IsStartUp { get; set; } } /// <summary> /// 螢幕錄制工具類 /// </summary> public class ScreenRecorderTool { #region Fields private int screenWidth; private int screenHight; private int bitRate = (int)BitRate._500kbit; private int frameRate = 5;//預設幀率為5 private bool isRecording; private string saveFolderPath; private string fileName; private Stopwatch stopWatch; private Rectangle screenArea; private VideoFileWriter videoWriter; private ScreenCaptureStream videoStreamer; private VideoCodec videoCodec = VideoCodec.MSMPEG4v2; private ScreenRecorderTemplate recorderTmp; private static object key = new object(); #endregion /// <summary> /// 是否正在錄制 /// </summary> private bool IsRecording { get { lock (key) { return isRecording; } } set { lock (key) { isRecording = value; } } } public ScreenRecorderTool(ScreenRecorderTemplate recorderTmp) { this.recorderTmp = recorderTmp; this.screenWidth = SystemInformation.VirtualScreen.Width; this.screenHight = SystemInformation.VirtualScreen.Height; this.IsRecording = false; this.SaveFolderPath = AppDomain.CurrentDomain.BaseDirectory; this.stopWatch = new Stopwatch(); this.screenArea = Rectangle.Empty; SetScreenArea(); } /// <summary> /// 視訊保存位置 /// </summary> private string SaveFolderPath { get { return this.saveFolderPath; } set { if (string.IsNullOrEmpty(value)) { throw new ArgumentNullException("saveFolderpath", "保存路徑不能為空"); } this.saveFolderPath = value; } } /// <summary> /// 視訊檔名稱 /// </summary> private string FileName { get { return this.fileName; } set { if (string.IsNullOrEmpty(value)) { throw new ArgumentNullException("fileName", "File name can not be empty or null"); } this.fileName = value; } } /// <summary> /// 完成一幀錄制的事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void video_NewFrame(object sender, NewFrameEventArgs e) { if (this.IsRecording) { if (videoWriter != null) { this.videoWriter.WriteVideoFrame(e.Frame); } if (this.stopWatch.Elapsed.Seconds >= 20) { Console.WriteLine("超過指定時間,寫入檔"); StopRecording(); } } else { videoStreamer.SignalToStop(); videoWriter.Close(); videoWriter.Dispose(); //GC.Collect(); Console.WriteLine("停止錄制"); if (recorderTmp.IsStartUp)//開機錄制 { StartRecording(); } else { if (DateTime.Now <= recorderTmp.StopDateTime.Value) { Console.WriteLine("記錄重新開機錄制"); StartRecording(); } else { Console.WriteLine("時間到不再錄制"); } } } } /// <summary> /// 設定必要參數開啟視訊寫入工具 /// </summary> private void InitializeRecordingParameters() { if (!this.IsRecording) { this.IsRecording = true; CreateCatalog(); this.FileName = saveFolderPath + string.Format (@"{0}-{1}.avi", "MSR", DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")); this.videoWriter.Open(this.FileName, this.screenWidth, this.screenHight, this.frameRate, this.videoCodec, this.bitRate); } } /// <summary> /// 建立目錄 /// </summary> private void CreateCatalog() { if (saveFolderPath == AppDomain.CurrentDomain.BaseDirectory) { var catalog = SaveFolderPath + DateTime.Now.ToString("yyyy-MM-dd") + "\\"; if (!System.IO.Directory.Exists(catalog)) { System.IO.Directory.CreateDirectory(catalog); } SaveFolderPath = catalog; } } /// <summary> /// 設定螢幕錄制區域為全螢幕 /// </summary> private void SetScreenArea() { foreach (Screen screen in Screen.AllScreens) { this.screenArea = Rectangle.Union(this.screenArea, screen.Bounds); } if (this.screenArea == Rectangle.Empty) { //logger.Error("沒有獲取到螢幕資訊"); throw new InvalidOperationException("Screan area can not be set"); } } /// <summary> /// 舊檔清理(避免檔大小超標) /// </summary> private void ClearOldVideo() { } #region public method /// <summary> /// 開啟視訊流開始錄制 /// </summary> public void StartRecording() { if (recorderTmp == null) { Console.WriteLine("樣版不能為空"); return; } if (!recorderTmp.IsStartUp) { if (!recorderTmp.StartDateTime.HasValue || !recorderTmp.StopDateTime.HasValue || recorderTmp.StartDateTime.Value > recorderTmp.StopDateTime.Value) { Console.WriteLine("樣版不正確"); return; } } this.videoWriter = new VideoFileWriter(); InitializeRecordingParameters(); this.videoStreamer = new ScreenCaptureStream(this.screenArea); this.videoStreamer.NewFrame += new NewFrameEventHandler(video_NewFrame); this.videoStreamer.Start(); this.stopWatch.Start(); this.IsRecording = true; Console.WriteLine("開始錄制..."); } /// <summary> /// 停止錄制 /// </summary> public void StopRecording() { this.stopWatch.Reset(); this.IsRecording = false; } #endregion }}

    範例呼叫:

    using System;using System.Collections.Generic;using System.Linq;using System.Text;namespace ConsoleRecoderTest{ class Program { static void Main(string[] args) { ScreenRecorderTemplate tmp = new ScreenRecorderTemplate() { IsStartUp = false, StartDateTime = DateTime.Now, StopDateTime = DateTime.Now.AddMinutes(2) }; new ScreenRecorderTool(tmp).StartRecording(); Console.WriteLine("complete"); Console.Read(); } }}

    補充:

    直接執行的話有問題,frameRate = 5;//預設幀率為5,實際上寫在視訊裏是30秒!問題出在幀率與螢幕擷取間隔不對。` //this.videoStreamer = new ScreenCaptureStream(this.screenArea); //要加錄制間隔時間 this.videoStreamer = new ScreenCaptureStream(this.screenArea, 1000 / frameRate);`另外 this.stopWatch.Elapsed.Seconds >= 20要改成21,因為大於等於20的話就停止的話實際上就只錄了19秒,所有要改為21` if (this.stopWatch.Elapsed.Seconds >= 21)

    源碼、播放器、AForge.NET Framework-2.2.5.exe下載地址:

    連結:https://pan.baidu.com/s/11O8z8Fj4JyMqgQ3ybxZ3ZQ

    提取碼:5fxo

    參考連結:

    1. http://www.diqisoft.com/technical/20087.htm

    2. https://www.cnblogs.com/yyq745201/p/5334294.html

    3. 技術群: 需要進技術群學習交流的請添加小編微信,切記備註:加群,對以上內容有什麽疑問也可以直接和小編直接溝通交流!

      小編微信:mm1552923

      公眾號:dotNet編程大全