在unity中使用FFmpeg

文章目录[x]
  1. 1:一、FFmpeg简介
  2. 2:二、下载FFmpeg
  3. 3:三、FFmpeg常用参数及命令
  4. 4:四、FFmpeg在Unity 3D中的使用
  5. 5:五、补充说明 2022.11.03

一、FFmpeg简介

官网上是这样介绍的:

FFmpeg能够实现对视频音频编码、解码、转码、流传输等等一系列功能。它包含有libavcodec, libavutil, libavformat, libavfilter, libavdevice, libswscale,libswresample 库。其中:

libavcodec      是一个包含用于音频/视频编解码器的解码器和编码器的库。
libavutil       是一个包含简化编程功能的库,包括随机数生成器,数据结构,数学例程,核心多媒体实用程序等等。
libavformat     是一个包含多媒体容器格式的解复用器和复用器的库。
libavdevice     是一个包含输入和输出设备的库,用于从许多常见的多媒体输入/输出软件框架中获取和呈现,包括Video4Linux,Video4Linux2,VfW和ALSA。
libavfilter     是一个包含媒体过滤器的库。
libswscale      是一个执行高度优化的图像缩放和色彩空间/像素格式转换操作的库。
libswresample   是一个执行高度优化的音频重采样,重新矩阵化和样本格式转换操作的库。

二、下载FFmpeg

下载地址:http://www.ffmpeg.org/download.html

我们找到Windows对应的下载链接,官网提供了三种文件:1.下载编译成可执行的程序;2.下载编译好的库;3.开发版的库和头文件。

①Static里面只有3个应用程序:ffmpeg.exe,ffplay.exe,ffprobe.exe,每个exe的体积都很大,相关的Dll已经被编译到exe里面去了。

②Shared里面除了3个应用程序之外,还有一些Dll,比如说avcodec-54.dll之类的。Shared里面的exe体积很小,他们在运行的时候,到相应的Dll中调用功能。

③Dev版本是用于开发的,里面包含了库文件xxx.lib以及头文件xxx.h,这个版本不包含exe文件。

我们在unity中使用FFmpeg时一般是使用ffmpeg.exe,把该exe放到固定位置然后再使用相应的参数命令控制ffmpeg执行对应功能。

三、FFmpeg常用参数及命令

1、命令的格式:

ffmpeg [global_options] {[input_file_options] -i input_url} ... {[output_file_optionsoutput_url} ...

示例:
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

 

2、常用参数:

-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 :输出文件覆盖已有文件不提示

3、常用命令

(1)将视频的帧速率改为24

ffmpeg -i input.avi -r 24 output.avi

(2)视频格式转换,将avi转成mp4

ffmpeg -i input.avi output.mp4

(3)从视频中提取音频

ffmpeg -i test.mp4 -acodec libmp3lame output.mp3

(4)视频剪切

ffmpeg -ss 00:00:15 -t 00:00:05 -i input.mp4 -vcodec copy -acodec copy output.mp4

(5)将100张图片合成视频,并为其添加背景音频(注意:图片要放在同一个文件夹下,并将图片按001--100的格式命名)

ffmpeg -i 001.mp3 -i %3d.jpg -s 1024x768 -author fy -vcodec mpeg4 darkdoor.avi

(6)官方文档:http://www.ffmpeg.org/ffmpeg.html

四、FFmpeg在Unity 3D中的使用

1、如果需要使用dshow的方式录屏需要提前在电脑上安装Screen Capture Recorder

https://github.com/rdp/screen-capture-recorder-to-video-windows-free

2、命令行调用FFmpeg的核心代码如下:

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_BUILD
    private void Start()
    {
        _debug = false;
    }
#endif

#if UNITY_EDITOR || DEVELOPMENT_BUILD
    private void OnGUI()
    {
        if (GUILayout.Button("Start")) StartRecording();
        if (GUILayout.Button("Stop")) StopRecording(() => { UnityEngine.Debug.Log("结束录制。"); });
    }
#endif

#if UNITY_EDITOR
    private 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】模式录制全屏。");
        }
    }
#endif

    public 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);
            }
        }
    }
}

3、推流服务

需要安装配置了rtmp模块的nginx。

参考资料:
https://zhuanlan.zhihu.com/p/93525011
https://blog.csdn.net/mazaiting/article/details/79709753
https://blog.csdn.net/qq_21397217/article/details/80537263

五、补充说明 2022.11.03

1.解决Windows中录屏不能使用默认播放器播放,ffmpeg编码视频时采用了 yuv444,而windows 对yuv444 应该支持不够,需要增加-pix_fmt yuv420p。

./ffmpeg.exe -f gdigrab -i desktop  -f dshow -i audio="virtual-audio-capturer" -vcodec libx264 -pix_fmt yuv420p -acodec aac test.mp4

2.解决需要额外安装Screen Capture Recorder。

ffmpeg只使用了SCR的部分功能,不需要全部安装,只需要把其中两个dll注册即可。

注册dll一般是在dll目录下打开有管理员权限的命令行,直接输入regsvr32+xxx.dll注册,注意一定要是管理员权限,dll可以放在任何文件夹。
1.注册录屏dll regsvr32 screen-capture-recorder-x64.dll
2.注册录音dll regsvr32 audio_sniffer-x64.dll

注册dll 需要用到regsvr32命令,其用法为:
"regsvr32 [/s] [/n] [/u] [/i[:cmdline]] dllname”。其中dllname为dll文件名
参数有如下意义:
/u——反注册控件
/s——不管注册成功与否,均不显示提示框
/c——控制台输出
/i——跳过控件的选项进行安装(与注册不同)
/n——不注册控件,此选项必须与/i选项一起使用

3.使用代码进行dll注册。

private bool RegisterDll()
{
    bool result = true;
    try
    {
        string dllPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "XXX.dll");//获得要注册的dll的物理路径
        if (!File.Exists(dllPath))
        {
            Console.Write(string.Format("“{0}”目录下无“XXX.dll”文件!", AppDomain.CurrentDomain.BaseDirectory));
            return false;
        }
        //拼接命令参数
        string startArgs = string.Format("/s \"{0}\"", dllPath);
 
        Process p = new Process();//创建一个新进程,以执行注册动作
        p.StartInfo.FileName = "regsvr32";
        p.StartInfo.Arguments = startArgs;
 
        //以管理员权限注册dll文件
        WindowsIdentity winIdentity = WindowsIdentity.GetCurrent(); //引用命名空间 System.Security.Principal
        WindowsPrincipal winPrincipal = new WindowsPrincipal(winIdentity);
        if (!winPrincipal.IsInRole(WindowsBuiltInRole.Administrator))
        {
            p.StartInfo.Verb = "runas";//管理员权限运行
        }
        p.Start();
        p.WaitForExit();
        p.Close();
        p.Dispose();
    }
    catch (Exception ex)
    {
        result = false;          //记录日志,抛出异常
    }
 
    return result;
}

 

点赞
  1. 狸子Neazle说道:

    你好,我想问问我按照这个教程做了,我放好了ffmpeg.exe,用GDIGRAB的方式录制视频,在录制完成后他会创建导出视频的路径,但不会生成视频文件,也没有任何报错,这可能是什么原因呢?

    1. Alan Alan说道:

      有可能是参数错误,你可以先用命令行(cmd)的方式调用FFmpeg.exe,这样在命令行窗口会打印出错误信息。

      1. 狸子Neazle说道:

        我试着这么做了,我用win+R然后输入cmd来启动了cmd,然后我把目录切换到ffmpeg所在的地方,然后我输入了start ffmpeg.exe并按了回车,然后标题似乎有一帧变成了ffmpeg,然后又变回了原样,除此之外什么都没有发生,也没有任何Log,我怀疑这会不会是我下载的ffmpeg.exe有问题?如果是的话我想问问可以请您给我邮箱发一份正确的文件吗?对不起又来打扰您还提出了这么麻烦的请求,如果您愿意帮忙的话真的感激不尽!

      2. 狸子Neazle说道:

        我的邮箱是1678555697@qq.com,顺便一提我看到你的网站上写收到回复后会在邮箱收到邮件提醒,但昨天收到你的回复之后我并没有收到新邮件,不知道是不是有什么问题)

      3. 狸子Neazle说道:

        啊对了顺便送您一个我的软件的Steam激活码,因为没有您的联系方式,我就直接发在这里了,希望不会被其他人兑换走
        R5LEI-6NA0E-HV6X0
        不过感觉我的exe文件或许其实没问题?我是直接下载的BtbN的Github上打的最新包(只有3个exe文件的那个),然后log中似乎也成功发出指令了,运行时的Log是:
        FFRecorder::StartRecording - 执行命令:D:/问问/活字引擎_Data/StreamingAssets/ffmpeg/ffmpeg.exe -f gdigrab -i title=活字引擎 -f dshow -i audio="virtual-audio-capturer" -y -preset ultrafast -rtbufsize 3500k -b:v 1500k -r 24 -s 1632x999 D:\/12.mp4/活字引擎_2022-11-03-09-26-05.mp4

        1. Alan Alan说道:

          1.你的输出路径是不是没有写对, D:\/12.mp4不是一个路径格式。
          2.有人天天在我评论区刷广告,我后台没有配置邮箱,所以不能给你发送邮件通知。

          1. 狸子Neazle说道:

            我换成了D://12.mp4,但依旧没用)

          2. 狸子Neazle说道:

            然后我也试了一下D:\12.mp4,但这个也不行

      4. 狸子Neazle说道:

        顺便一提我还试过选择在StreamingAssest导出而不是我的自定义位置,但也没用)

        1. Alan Alan说道:

          我复制了你的命令,发现是你重复使用了-f ,你写了-f gdigrab和 -f dshow。

          1. 狸子Neazle说道:

            似乎还是不行,我现在的指令是:
            FFRecorder::StartRecording - 执行命令:D:/超级大问问/活字引擎_Data/StreamingAssets/ffmpeg/ffmpeg.exe -f gdigrab -i title=活字引擎 dshow -i audio="virtual-audio-capturer" -y -preset ultrafast -rtbufsize 3500k -b:v 1500k -r 30 -s 1632x999 D:/12.mp4/活字引擎_2022-11-03-10-21-37.mp4
            (顺便一提您提供的代码里就是带有两个-f的)

        2. Alan Alan说道:

          我发现问题所在了,我写的是录屏方式是gdigrab,但是录音还是需要dshow的。使用了dshow的话,需要在电脑上安装Screen Capture Recorder。还有问题的话你可以扫描右下角的二维码加我微信。

发表回复

昵称和uid可以选填一个,填邮箱必填(留言回复后将会发邮件给你)
tips:输入uid可以快速获得你的昵称和头像