c#屏幕录制(经典)(含源码和AForge.Video.FFMPEG.DLL)及填坑办法

语言: CN / TW / HK

一直觉得.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

喜欢就来个三连,让更多人因你而受益