C# 调用Ffmpeg录制视频流实践

调用Ffmpeg命令行工具录制视频流(以flv >> mp4为例):

  .\ffmpeg.exe -i "https://···:···/remote.flv" .\···\local.mp4

针对两种需求:

  1. 用户指定录制时间
  2. 用户手动终止录制

针对需求1,可以通过参数实现

https://ffmpeg.org/ffmpeg.html#Main-options
-t duration
在 -i 之前使用,可以限制从数据源读取数据的持续时间

例如,希望录制30s的视频可以:

  .\ffmpeg.exe -i "https://···:···/remote.flv" -t 00:00:30 .\···\local.mp4

其他的time duaring格式可以参照https://ffmpeg.org/ffmpeg-utils.html#date-syntax

而对于需求2,则需要手动结束转码程序,以在windows terminal中运行转码命令为例,经过测试:

  • 直接关闭窗口
  • 输入:q
  • 输入:Ctrl+C
    录制的视频可以正常播放。
  • 直接结束进程
    视频无法打开。

创建ffmpeg进程,将pid存入字典

// uid >> ffmpeg process id
private static ConcurrentDictionary<string, int> ffmpegs = new ConcurrentDictionary<string,int>();

[HttpPost]
public string StartRecordJob(args ...)
{
    const string ffmpegPath = "c:/···/ffmpeg.exe";
    var output = @"c:/···/local.mp4";

    var cmdStr = $@"-i ""{url}"" {output} -y";

    var startInfo = new ProcessStartInfo
    {
        FileName = ffmpegPath,
        Arguments = cmdStr,
        // 不调用Shell执行
        UseShellExecute = false,
        CreateNoWindow = true,
    };
        
    var rp = Process.Start(startInfo);
    var uid = Regex.Replace(Convert.ToBase64String(Guid.NewGuid().ToByteArray()), "[/+=]", "");
    var ok =  rp != null && ffmpegs.TryAdd(uid, rp.Id);

    return $"{uid}";
}

在另一个请求中,根据ffmpeg任务的uid,获取进程id,对其进行操作

我的做法是,向进程发送退出(Ctrl+C)命令,以下做法依赖windows环境

https://blog.codetitans.pl/post/sending-ctrl-c-signal-to-another-application-on-windows/


[DllImport("kernel32.dll", SetLastError = true)]
static extern bool AttachConsole(uint dwProcessId);

[DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
static extern bool FreeConsole();

[DllImport("kernel32.dll")]
static extern bool SetConsoleCtrlHandler(ConsoleCtrlDelegate handler, bool add);

// Delegate type to be used as the Handler Routine for SCCH
delegate Boolean ConsoleCtrlDelegate(CtrlTypes type);

// Enumerated type for the control messages sent to the handler routine
enum CtrlTypes : uint
{
    CTRL_C_EVENT = 0,
    CTRL_BREAK_EVENT,
    CTRL_CLOSE_EVENT,
    CTRL_LOGOFF_EVENT = 5,
    CTRL_SHUTDOWN_EVENT
}

[DllImport("kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GenerateConsoleCtrlEvent(CtrlTypes dwCtrlEvent, uint dwProcessGroupId);

public static void StopProgram(uint pid)
{
    // It's impossible to be attached to 2 consoles at the same time,
    // so release the current one.
    FreeConsole();

    // This does not require the console window to be visible.
    if (AttachConsole(pid))
    {
        // Disable Ctrl-C handling for our program
        SetConsoleCtrlHandler(null, true);
        GenerateConsoleCtrlEvent(CtrlTypes.CTRL_C_EVENT, 0);

        // Must wait here. If we don't and re-enable Ctrl-C
        // handling below too fast, we might terminate ourselves.
        Thread.Sleep(2000);

        FreeConsole();

        // Re-enable Ctrl-C handling or any subsequently started
        // programs will inherit the disabled state.
        SetConsoleCtrlHandler(null, false);
    }
}


[HttpDelete]
public string StopRecordJob(string uid)
{
    try
    {
        threads.TryGetValue(uid, out var p);
        StopProgram((uint)p);
        return "true";
    }
    catch (Exception e)
    {
        ···
        return "not found:"+ p;
    }
}

还有一种做法是将startInfo中的 UseShellExecute设为 trueWindowStyle设为 ProcessWindowStyle.Minimized
这样可以通过:

   Process.CloseMainWindow()

来结束进程,这种方式的问题是会弹出一个窗口。

posted @ 2021-09-15 11:03  陈昱行  阅读(1928)  评论(0编辑  收藏  举报