在Unity(C#)中使用FFMPEG录制屏幕

参考文献:
https://bbs.csdn.net/topics/391895974
https://blog.csdn.net/cuoban/article/details/50556044

在Unity中通过C#代码调用FFMPEG简单地实现了录制Windows屏幕的功能,顺便解决了每次录制后如果不完全退出程序就不能再次正常结束录制的问题。

FFMPEG参数说明

这里只是简单地实现了录制整个屏幕或者录制指定窗口,没有实现录制指定区域地功能,所以只简单描述一下用到的几个参数:

  • -f :格式

    • gdigrab :ffmpeg内置的用于抓取Windows桌面的方法,支持抓取指定名称的窗口
    • dshow :依赖于第三方软件Screen Capture Recorder(后面简称SCR,下载地址在下面)
    • 补充 :-f也可以用于指定输出视频地格式,例如-f flv;gdigrab和dshow可以混用,分别捕获视频和音频
  • -i :输入源
    • title :要录制的窗口的名称,仅用于gdigrab方式
    • video :视频播放硬件名称或者"screen-capture-recorder",后者依赖SCR
    • audio :音频播放硬件名称或者"virtual-audio-capturer",后者依赖SCR
  • -preset ultrafast :以最快的速度进行编码,实时录制过程中如果编码速度慢会丢帧
  • -c:v :视频编码方式
  • -c:a :音频编码方式
  • -b:v :视频比特率
  • -r :视频帧率
  • -s :视频分辨率
  • -y :输出文件覆盖已有文件不提示

示例

ffmpeg -f dshow -i video="screen-capture-recorder" -f dshow -i audio="virtual-audio-capturer" -y -preset ultrafast -c:v libx264 -b:v 1500k -r 24 -s 1280x720 -y D:Records/output.mp4

FFMPEG完整官方文档:http://ffmpeg.org/ffmpeg-all.html
Screen Capture Recorder主页:https://github.com/rdp/screen-capture-recorder-to-video-windows-free

关键代码

  • 启动录制进程 该过程主要就是创建一个Process对象,然后向其传递可执行文件的路径、启动参数后将其启动。
Process ffp = new Process();
ffp.StartInfo.FileName = _ffpath;           // 进程可执行文件位置
ffp.StartInfo.Arguments = _ffargs;          // 传给可执行文件的命令行参数
ffp.StartInfo.CreateNoWindow = false;       // 不显示控制台窗口,需要和下一个参数一起才能完全隐藏窗口
ffp.StartInfo.UseShellExecute = false;      // 不使用操作系统Shell程序启动进程
ffp.Start();                                // 开始进程
_pid = ffp.Id;                             // 后面停止录制会用到
  • 停止录制 停止录制的过程比较复杂,需要用到 kernel32.dll 中的接口。为了能向控制台发送指令,首先要将当前的Unity进程附加到前面开启的ffmpeg进程的控制台中;然后要设置Unity进程响应控制台指令的句柄,这里设置为空,即收到控制台指令后不执行任何操作;接下来向控制台发送 Ctrl C 结束指令,ffmpeg进程在收到该指令后将会结束录制,如果上一步没有把Unity进程的响应方式设为空,那么Unity进程也会跟着结束;ffmpeg收到结束指令后,还要进行一些操作才能正确停止录制,大概要1秒,所以要利用协程等待几秒;最后,将前面设置的句柄卸载,然后释放已附加的控制台。卸载句柄和释放控制台一定不能忘,否则就会出现ffmpeg无法正常停止录制的情况。
// 将当前进程附加到pid进程的控制台
AttachConsole(_pid);
// 将控制台事件的处理句柄设为Zero,即当前进程不响应控制台事件
// 避免在向控制台发送【Ctrl C】指令时连带当前进程一起结束
SetConsoleCtrlHandler(IntPtr.Zero, true);
// 向控制台发送 【Ctrl C】结束指令
// ffmpeg会收到该指令停止录制
GenerateConsoleCtrlEvent(0, 0);// ffmpeg不能立即停止,等待一会,否则视频无法播放
yield return new WaitForSeconds(3.0f);// 卸载控制台事件的处理句柄,不然之后的ffmpeg调用无法正常停止
SetConsoleCtrlHandler(IntPtr.Zero, false);
// 剥离已附加的控制台
FreeConsole();

完整源代码

using System;
using System.Collections;
using System.Diagnostics;
using System.Runtime.InteropServices;
using UnityEngine;[ExecuteInEditMode]
public class FFRecorder : MonoBehaviour
{#region 模拟控制台信号需要使用的DLL[DllImport("kernel32.dll")]static extern bool GenerateConsoleCtrlEvent(int dwCtrlEvent, int dwProcessGroupId);[DllImport("kernel32.dll")]static extern bool SetConsoleCtrlHandler(IntPtr handlerRoutine, bool add);[DllImport("kernel32.dll")]static extern bool AttachConsole(int dwProcessId);[DllImport("kernel32.dll")]static extern bool FreeConsole();#endregion#region 设置菜单public enum RecordType{GDIGRAB,DSHOW}public enum Bitrate{_1000k,_1500k,_2000k,_2500k,_3000k,_3500k,_4000k,_5000k,_8000k}public enum Framerate{_14,_24,_30,_45,_60}public enum Resolution{_1280x720,_1920x1080,_Auto}public enum OutputPath{Desktop,StreamingAsset,DataPath,Custom}#endregion#region 成员[Tooltip("启用Debug则显示CMD窗口,否则不显示。")][SerializeField]private bool _debug = false;[Tooltip("DSHOW - 录制全屏 \nGUIGRAB - 录制游戏窗口(仅用于发布版)")]public RecordType recordType = RecordType.DSHOW;public Resolution resolution = Resolution._1280x720;public Framerate framerate = Framerate._24;public Bitrate bitrate = Bitrate._1500k;public OutputPath outputPath = OutputPath.Desktop;public string customOutputPath = @"D:/Records";public bool IsRecording { get { return _isRecording; } }/** ffmpeg参数说明* -f :格式*     gdigrab :ffmpeg内置的用于抓取Windows桌面的方法,支持抓取指定名称的窗口*     dshow :依赖于第三方软件Screen Capture Recorder(后面简称SCR)* -i :输入源*     title :要录制的窗口的名称,仅用于GDIGRAB方式*     video :视频播放硬件名称或者"screen-capture-recorder",后者依赖SCR*     audio :音频播放硬件名称或者"virtual-audio-capturer",后者依赖SCR* -preset ultrafast :以最快的速度进行编码,生成的视频文件大* -c:v :视频编码方式* -c:a :音频编码方式* -b:v :视频比特率* -r :视频帧率* -s :视频分辨率* -y :输出文件覆盖已有文件不提示* * FFMPEG官方文档:http://ffmpeg.org/ffmpeg-all.html* Screen Capture Recorder主页:https://github.com/rdp/screen-capture-recorder-to-video-windows-free*/// 参数:窗口名称 -b比特率 -r帧率 -s分辨率 文件路径 文件名private const string FFARGS_GDIGRAB = "-f gdigrab -i title={0} -f dshow -i audio=\"virtual-audio-capturer\" -y -preset ultrafast -rtbufsize 3500k -b:v {1} -r {2} -s {3} {4}/{5}.mp4";// 参数:-b比特率 -r帧率 -s分辨率 文件路径 文件名private const string FFARGS_DSHOW = "-f dshow -i video=\"screen-capture-recorder\" -f dshow -i audio=\"virtual-audio-capturer\" -y -preset ultrafast -rtbufsize 3500k -b:v {0} -r {1} -s {2} {3}/{4}.mp4";private string _ffpath;private string _ffargs;private int _pid;private bool _isRecording = false;#endregion#if !UNITY_EDITOR && !DEVELOPMENT_BUILDprivate void Start(){_debug = false;}
#endif#if UNITY_EDITOR || DEVELOPMENT_BUILDprivate void OnGUI(){if (GUILayout.Button("Start")) StartRecording();if (GUILayout.Button("Stop")) StopRecording(() => { UnityEngine.Debug.Log("结束录制。"); });}
#endif#if UNITY_EDITORprivate void OnValidate(){if (_debug) UnityEngine.Debug.Log("FFRecorder - CMD窗口已启用。");else UnityEngine.Debug.Log("FFRecorder - CMD窗口已禁用。");if (recordType == RecordType.GDIGRAB){UnityEngine.Debug.Log("FFRecorder - 使用【GDIGRAB】模式录制当前窗口。");UnityEngine.Debug.LogError("FFRecorder - 【GDIGRAB】模式在编辑器中不可用。");}else if (recordType == RecordType.DSHOW){UnityEngine.Debug.Log("FFRecorder - 使用【DSHOW】模式录制全屏。");}}
#endifpublic void StartRecording(){if (_isRecording){UnityEngine.Debug.LogError("FFRecorder::StartRecording - 当前已有录制进程。");return;}// 杀死已有的ffmpeg进程,不要加.exe后缀Process[] goDie = Process.GetProcessesByName("ffmpeg");foreach (Process p in goDie) p.Kill();// 解析设置,如果设置正确,则开始录制bool validSettings = ParseSettings();if (validSettings){UnityEngine.Debug.Log("FFRecorder::StartRecording - 执行命令:" + _ffpath + " " + _ffargs);StartCoroutine(IERecording());}else{UnityEngine.Debug.LogError("FFRecorder::StartRecording - 设置不当,录制取消,请检查控制台输出。");}}public void StopRecording(Action _OnStopRecording){if (!_isRecording){UnityEngine.Debug.LogError("FFRecorder::StopRecording - 当前没有录制进程,已取消操作。");return;}StartCoroutine(IEExitCmd(_OnStopRecording));}private bool ParseSettings(){_ffpath = Application.streamingAssetsPath + @"/ffmpeg/ffmpeg.exe";string name = Application.productName + "_" + DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss");// 分辨率string s;if (resolution == Resolution._1280x720){int w = 1280;int h = 720;if (Screen.width < w){w = Screen.width;UnityEngine.Debug.LogWarning(string.Format("录制水平分辨率大于屏幕水平分辨率,已自动缩小为{0}。", w));}if (Screen.height < h){h = Screen.height;UnityEngine.Debug.LogWarning(string.Format("录制垂直分辨率大于屏幕垂直分辨率,已自动缩小为{0}。", h));}s = w.ToString() + "x" + h.ToString();}else if (resolution == Resolution._1920x1080){int w = 1920;int h = 1080;if (Screen.width < w){w = Screen.width;UnityEngine.Debug.LogWarning(string.Format("录制水平分辨率大于屏幕水平分辨率,已自动缩小为{0}。", w));}if (Screen.height < h){h = Screen.height;UnityEngine.Debug.LogWarning(string.Format("录制垂直分辨率大于屏幕垂直分辨率,已自动缩小为{0}。", h));}s = w.ToString() + "x" + h.ToString();}else  /*(resolution == Resolution._Auto)*/{s = Screen.width.ToString() + "x" + Screen.height.ToString();}// 帧率string r = framerate.ToString().Remove(0, 1);// 比特率string b = bitrate.ToString().Remove(0, 1);// 输出位置string output;if (outputPath == OutputPath.Desktop) output = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + "/" + Application.productName + "_Records";else if (outputPath == OutputPath.DataPath) output = Application.dataPath + "/" + Application.productName + "_Records";else if (outputPath == OutputPath.StreamingAsset) output = Application.streamingAssetsPath + "/" + Application.productName + "_Records";else /*(outputPath == OutputPath.Custom)*/ output = customOutputPath;// 命令行参数if (recordType == RecordType.GDIGRAB){_ffargs = string.Format(FFARGS_GDIGRAB, Application.productName, b, r, s, output, name);}else /*(recordType == RecordType.DSHOW)*/{_ffargs = string.Format(FFARGS_DSHOW, b, r, s, output, name);}// 创建输出文件夹if (!System.IO.Directory.Exists(output)){try{System.IO.Directory.CreateDirectory(output);}catch (Exception e){UnityEngine.Debug.LogError("FFRecorder::ParseSettings - " + e.Message);return false;}}return true;}// 不一定要用协程private IEnumerator IERecording(){yield return null;Process ffp = new Process();ffp.StartInfo.FileName = _ffpath;                   // 进程可执行文件位置ffp.StartInfo.Arguments = _ffargs;                  // 传给可执行文件的命令行参数ffp.StartInfo.CreateNoWindow = !_debug;             // 是否显示控制台窗口ffp.StartInfo.UseShellExecute = _debug;             // 是否使用操作系统Shell程序启动进程ffp.Start();                                        // 开始进程_pid = ffp.Id;_isRecording = true;}private IEnumerator IEExitCmd(Action _OnStopRecording){// 将当前进程附加到pid进程的控制台AttachConsole(_pid);// 将控制台事件的处理句柄设为Zero,即当前进程不响应控制台事件// 避免在向控制台发送【Ctrl C】指令时连带当前进程一起结束SetConsoleCtrlHandler(IntPtr.Zero, true);// 向控制台发送 【Ctrl C】结束指令// ffmpeg会收到该指令停止录制GenerateConsoleCtrlEvent(0, 0);// ffmpeg不能立即停止,等待一会,否则视频无法播放yield return new WaitForSeconds(3.0f);// 卸载控制台事件的处理句柄,不然之后的ffmpeg调用无法正常停止SetConsoleCtrlHandler(IntPtr.Zero, false);// 剥离已附加的控制台FreeConsole();_isRecording = false;if (_OnStopRecording != null){_OnStopRecording();}}// 程序结束时要杀掉后台的录制进程,但这样会导致输出文件无法播放private void OnDestroy(){if (_isRecording){try{UnityEngine.Debug.LogError("FFRecorder::OnDestroy - 录制进程非正常结束,输出文件可能无法播放。");Process.GetProcessById(_pid).Kill();}catch (Exception e){UnityEngine.Debug.LogError("FFRecorder::OnDestroy - " + e.Message);}}}
}

在Unity(C#)中使用FFMPEG录制屏幕相关推荐

  1. ffmpeg 录制屏幕

    文章目录 1.ffmpeg 下载 2.screen capture recorder 下载 3.ffmpeg 录屏命令 4.ffmpeg 其他命令 后续 <寻路篇> 立理想, 坐豪车,住豪 ...

  2. ffmpeg录制屏幕和截屏

    本篇是在ffmpeg已经下载安装的前提下,是接着上一篇ffmpeg下载安装教程及介绍_水w的博客-CSDN博客 目录 1.录制无声音视频 2.两段视频先后播放拼接:把两个视频文件前后拼接为一个视频文件 ...

  3. FFmpeg录制屏幕与传屏

    使用FFmpeg,可以快捷的录制屏幕,输出录制文件,也可通过UDP传输到远端播放器,实时播放. 屏幕录象到文件 ffmpeg -f gdigrab -i desktop out.mpg 局部录像到文件 ...

  4. Java通过FFmpeg录制屏幕

    FFmpeg是一套可以用来记录.转换数字音频.视频,并能将其转化为流的开源计算机程序.采用LGPL或GPL许可证.它提供了录制.转换以及流化音视频的完整解决方案.它包含了非常先进的音频/视频编解码库l ...

  5. ffmpeg android屏幕录制视频,FFmpeg录制屏幕

    首先通过下面的命令查看一下 mac 上都有哪些设备. ffmpeg -f avfoundation -list_devices true -i "" 没有外界显示器的电脑中: 没有 ...

  6. ffmpeg录制屏幕并推流ffmpeg+nginx-http-flv-module+flv.js

    https://blog.csdn.net/u012848709/article/details/106064127 https://blog.csdn.net/qq_33456552/article ...

  7. ffmpeg mac录制屏幕和声音

    需要安装sunflower 安装和配置参考:在macOS下使用ffmpeg录屏 - 简书 我按照帖子中测试不行,声音是混乱的 我的最终录制屏幕脚本是: ffmpeg -f avfoundation - ...

  8. ios录制屏幕_如何使您的ios应用免受屏幕截图和录制的影响

    ios录制屏幕 防止在iOS App中捕获和录制屏幕(Preventing Screen Capturing and Recording in iOS App) Thanks to the mobil ...

  9. 【转载】iphone 录制屏幕以及音效的混合实现源码分享

    原文摘自:http://www.cocoachina.com/iphonedev/toolthain/2011/1010/3342.html 源码由论坛会员tanhaogg分享,实现了录制屏幕以及音效 ...

最新文章

  1. php imagemagick 漏洞,ImageMagick漏洞EXP简易生成脚本
  2. js中获取时间new date()的用法
  3. Java Eclipse进行断点调试
  4. SegNet网络简析及caffe工程应用
  5. 服务器批量修改代码,利用Redis实现多服务器批量操作
  6. 非插件实现回复可见效果
  7. Android与Asp.Net Web服务器的文件上传下载BUG汇总[更新]
  8. Nodejs nmp 常用命令
  9. 基于JAVA+SpringMVC+Mybatis+MYSQL的高校学习智能管理论坛系统
  10. C语言和设计模式(享元模式)
  11. 培训协议与服务器有没有要求,上面说的是提供专业培训且规定了服务器和培训费以及违约金的问题。试用期辞职单位有没有权利追究违约金呢?- 法律快车法律咨询...
  12. Spring Boot + thymeleaf 后台与页面(二)
  13. dedecms php用不了,织梦DEDECMS安装360漏洞补丁之后不能够运行PHP代码的问题
  14. Unity3D Editor Undo回退效果实现3 Odin相关
  15. 蒙版操作—快速蒙版扣图
  16. python安装 文件或目录损坏_文件或目录损坏且无法读取怎么办?
  17. 有符号数的二进制表示方式
  18. 纯CSS+CSS3右上角倾斜四十五度封页角效果
  19. 翻译翻译什么叫HTML5(四)jQuery——给我的网页换个小皮肤
  20. HIVE如何进行随机抽样

热门文章

  1. 码仔漫画:“你是什么垃圾?”(下)
  2. 【论文笔记】DP-SLAM:一种动态环境下基于移动概率的SALM系统A visual SLAM with moving probability towards dynamic environments
  3. 淘宝怎么设置微博一键直达 - 淘宝店铺装修入门教程①
  4. [POI2007]洪水pow 并查集
  5. 放蛋糕,字符串转换成整数
  6. 综合实践活动信息技术小学版第三册电子课本_福建首个电子竞技专业为你开启电竞之路,圆你电竞梦想!——三明医学科技职业学院电子竞技运动与管理专业介绍...
  7. 新解决方案销售之二:创造新机会
  8. MOS管防反接防过压电路
  9. 上海交通卡因可疑记录无法充值和退钱
  10. Ajax:ajax发送Post请求、ajax案例