C# 调用Ffmpeg录制视频流实践
调用Ffmpeg命令行工具录制视频流(以flv >> mp4为例):
.\ffmpeg.exe -i "https://···:···/remote.flv" .\···\local.mp4
针对两种需求:
- 用户指定录制时间
- 用户手动终止录制
针对需求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
设为 true
,WindowStyle
设为 ProcessWindowStyle.Minimized
。
这样可以通过:
Process.CloseMainWindow()
来结束进程,这种方式的问题是会弹出一个窗口。
站外博客地址:https://blog.yuhang.ch