/// <summary>
///
/// </summary>
/// <param name="voiceText"></param>
/// <returns></returns>
public static bool PlaySpeech(string voiceText)
{
#region 初始化语音
if (SpeechTxt == null)
{
try
{
SpeechTxt = new SpeechSynthesizer();
// SpeechTxt.SelectVoice("Microsoft Lili");//设置播音员(中文)
// SpeechTxt.SelectVoice("Microsoft Anna"); //英文
// SpeechTxt.Volume = 100; //音量
var cultureInfo = System.Windows.Forms.InputLanguage.CurrentInputLanguage.Culture ?? Thread.CurrentThread.CurrentCulture;
VoiceInfo voiceInfo = SpeechTxt.GetInstalledVoices(cultureInfo)?.FirstOrDefault()?.VoiceInfo;
SpeechTxt.SelectVoice(voiceInfo.Name ?? "Microsoft Lili");
}
catch (Exception ex)
{
logger.Error($"初始化语音出错,原因:{ex.InnerException}");
SpeechTxt = null;
}
}
#endregion
bool result = false;
if (string.IsNullOrWhiteSpace(voiceText))
{
return result;
}
try
{
//SpeechTxt.Speak(voiceText);
SpeechTxt.SpeakAsync(voiceText);
result = true;
}
catch (Exception ex)
{
logger.Error($"PlaySpeech,失败,原因:{ex.Message}");
result = false;
}
return result;
}
public static bool KillProcess(string prcessName, string command = "taskkill /IM ffmpeg.exe /F")
{
bool result = false;
System.Diagnostics.Process p = null;
try
{
if (!string.IsNullOrWhiteSpace(prcessName))
{
System.Diagnostics.Process[] processList = System.Diagnostics.Process.GetProcesses();
foreach (System.Diagnostics.Process process in processList)
{
if (process.ProcessName.ToLower() == prcessName.ToLower())
{
process.Kill(); //结束进程
}
}
result = true;
}
else
{
////ProcessRecord.StandardInput.WriteLine("taskkill /IM ffmpeg.exe /F");
p = new System.Diagnostics.Process();
p.StartInfo.FileName = "cmd.exe"; //设定程序名
p.StartInfo.Arguments = "/c " + command; //设定程式执行参数
p.StartInfo.UseShellExecute = false; //关闭Shell的使用
p.StartInfo.RedirectStandardInput = true; //重定向标准输入
p.StartInfo.RedirectStandardOutput = true; //重定向标准输出
p.StartInfo.RedirectStandardError = true; //重定向错误输出
p.StartInfo.CreateNoWindow = true; //设置不显示窗口
p.Start(); //启动
result = true;
}
}
catch (Exception ex)
{
logger.Error($"KillProcess,失败,原因:{ex.Message}");
//ProcessRecord.StandardInput.WriteLine("taskkill /IM ffmpeg.exe /F");
result = false;
}
finally
{
p.WaitForExit();
p.Close();
p.Dispose();
}
return result;
}
/// <summary>
/// 停止录像
/// </summary>
/// <returns></returns>
public static bool StopRecord()
{
bool result = false;
try
{
//string a = "q";
////ProcessRecord.StartInfo.RedirectStandardInput = true; // 重定向标准输入
//// 写入数据到标准输入
//using (StreamWriter sw = ProcessRecord.StandardInput)
//{
// if (sw.BaseStream.CanWrite)
// {
// sw.WriteLine(a);
// sw.WriteLine("exit");
// }
//}
if (ProcessRecord == null)
{
PlaySpeech("录音录像未开启,请先开启录音录像设备!");
return false;
}
ProcessRecord.StandardInput.WriteLine("q");
ProcessRecord.StandardInput.WriteLine("exit");
ProcessRecord.WaitForExit();
ProcessRecord.Close();
ProcessRecord.Dispose();
Untils.PlaySpeech("录音录像已关闭");
result = true;
}
catch (Exception ex)
{
logger.Error($"StopRecord,失败,原因:{ex.Message}");
ProcessRecord.WaitForExit();
ProcessRecord.Close();
ProcessRecord.Dispose();
//Untils.PlaySpeech("录音录像已关闭");
//ProcessRecord.StandardInput.WriteLine("taskkill /IM ffmpeg.exe /F");
result = false;
}
return result;
}
/// <summary>
/// 开始录像
/// </summary>
/// <returns></returns>
public static bool StartRecord(ref string recordvideoFilePath)
{
bool result = false;
try
{
//StopRecord();
KillProcess(null);
//string videoInput = GetSetting("RecordVideo");
//string audioInput = GetSetting("RecordAudio");
//logger.Info($"StartRecord,录像:{videoInput}");
//logger.Info($"StartRecord,录音:{audioInput}");
string videoInput = GetSetting("RecordVideoPID");
string audioInput = GetSetting("RecordAudioPID");
//string videoInput = GetSetting("FisheyeRecordVideoCameraPID");
videoInput = GetDeviceFromPID(videoInput, 2);
//logger.Info($"StartRecord,录音录像-录像设备:{videoInput}");
//string audioInput = GetSetting("FisheyeRecordAudioPID");
audioInput = GetDeviceFromPID(audioInput, 1);
////audioInput = GetSetting("RecordAudio");
//logger.Info($"StartRecord,录音录像-录音设备:{audioInput}");
string videoSaveFormat = GetSetting("RecordAudio");
if (string.IsNullOrWhiteSpace(videoInput))
{
//MessageBox.Show("未选择录像设备", "提示:");
return result;
}
if (string.IsNullOrWhiteSpace(audioInput))
{
//MessageBox.Show("未选择录音设备", "提示:");
return result;
}
if (string.IsNullOrWhiteSpace(videoSaveFormat))
{
//MessageBox.Show("未选择录音录像设备的视频录制格式", "提示:");
return result;
}
#region C# 代码用于通过 Process 调用 FFmpeg 进行音视频采集(使用 dshow 设备)并叠加时间水印,
//整体结构基本正确,但存在几个关键问题和优化建议,可能导致:
//FFmpeg 启动失败
//音视频设备无法识别
//中文路径 / 特殊字符问题
//进程无法正常结束或卡死
//string ffmpegPath = "ffmpeg.exe";//ffmpeg路径
////string videoFilePath = $"{DateTime.Now.ToString("yyyyMMddHHmmss")}.mp4";//视频地址
//string videoFilePath = "123.mp4";//视频地址
//ProcessRecord = new Process();
//ProcessStartInfo startInfo = new ProcessStartInfo();
//startInfo.FileName = ffmpegPath;
//string drawStr = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss");
//startInfo.Arguments = $@"-rtbufsize 200M -f dshow -i video=""{videoInput}"" -f dshow -i audio=""{audioInput}"" -pix_fmt yuv420p -tune zerolatency -ac 1 -ar 8000 -ab 44100 -vf drawtext=fontsize=56:x=100:y=100:fontcolor=red:text='{drawStr}' -y {videoFilePath}";
//startInfo.UseShellExecute = false;
//startInfo.CreateNoWindow = true;
//startInfo.RedirectStandardInput = true;
//startInfo.RedirectStandardOutput = true;
//startInfo.RedirectStandardError = true;
//startInfo.RedirectStandardInput = true;
//startInfo.WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory;
//ProcessRecord.StartInfo = startInfo;
//ProcessRecord.OutputDataReceived += Process_OutputDataReceived;
//ProcessRecord.ErrorDataReceived += Process_ErrorDataReceived;
////process.StartInfo.Arguments = a;
//ProcessRecord.Start();
//ProcessRecord.BeginErrorReadLine();
//ProcessRecord.BeginOutputReadLine();
//lblffmepg.ForeColor = Color.Green;
//lblffmepg.Text = "开始录音录像";
#endregion
//ffmpeg 指令执行格式:ffmpeg [输入选项] [滤镜选项] 输出文件路径
string ffmpegPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ffmpeg.exe");
if (!File.Exists(ffmpegPath))
{
logger.Error($"未找到 ffmpeg.exe--{ffmpegPath}");
return false;
}
#region .NET Framework ok
string drawStr = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss");
// 转义文本中的单引号(FFmpeg drawtext 要求)--- 将 text 中的 ' 替换为 '\''(FFmpeg 要求)
string safeText = drawStr.Replace("'", "'\\''");
string videoFilePath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), $"{DateTime.Now.ToString("yyyyMMddHHmmss")}.mp4");//视频地址
// 输出路径加引号防空格 输出文件名(带引号防止空格或特殊字符问题) 更推荐:对整个文本值 值使用双引号 + 转义(FFmpeg 支持)
string safeOutput = $"\"{videoFilePath ?? "123.mp4"}\"";
recordvideoFilePath = videoFilePath;
//string outputFileName = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), $"{DateTime.Now:yyyyMMddHHmmss}.mp4");//视频地址 DateTime.Now.ToString("yyyyMMddHHmmss") + ".mp4";
//string safeOutputPath = $"\"{outputFileName}\""; // 手动加双引号,输出路径用 \"...\" 包裹
//// 构建完整参数字符串(注意 drawtext 的 text 也要处理)text 中的 ' 要转义为 \'(FFmpeg 要求)
//string text = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss").Replace("'", "\\'");
////水印时间---没有双引号 //整个 -vf 参数值用 \"...\" 包裹(因为含 : 和 =)
////string vf = $"drawtext=fontsize=56:x=100:y=100:fontcolor=red:text='{text}'";
////设置时间水印居中
//string vf = $"drawtext=fontsize=56:fontcolor=red:text='{text}':x=(w-text_w)/2:y=(h-text_h)/2";
////w = 实际视频宽度(如 1280),h = 实际视频高度(如 720),text_w, text_h = 文字渲染后的像素尺寸, 无需知道具体分辨率,表达式会自动适配!
var startInfo = new ProcessStartInfo
{
FileName = ffmpegPath,
// 将 text 中的 ' 替换为 '\''(FFmpeg 要求)
//Arguments = $@"-rtbufsize 200M -f dshow -i video=""{videoInput}"" -f dshow -i audio=""{audioInput}"" -pix_fmt yuv420p -tune zerolatency -ac 1 -ar 8000 -ab 44100 -vf drawtext=fontsize=56:x=100:y=100:fontcolor=red:text='{safeText}' -y {safeOutput}",
//更推荐:对整个 text 值使用双引号 + 转义(FFmpeg 支持):string safeText = drawStr.Replace("\"", "\\\""); ... text =\"{safeText}\" ...
//Arguments = $@"-rtbufsize 200M -f dshow -i video=""{videoInput}"" -f dshow -i audio=""{audioInput}"" -pix_fmt yuv420p -tune zerolatency -ac 1 -ar 8000 -ab 44100 -vf drawtext=fontsize=56:x=100:y=100:fontcolor=red:text=\""{safeText}\"" -y {safeOutput}",
//设置时间水印居中
//Arguments = $@"-rtbufsize 200M -f dshow -i video=""{videoInput}"" -f dshow -i audio=""{audioInput}"" -pix_fmt yuv420p -tune zerolatency -ac 1 -ar 8000 -ab 44100 -vf drawtext=fontsize=56:fontcolor=red:text=\""{safeText}\"":x=(w-text_w)/2:y=(h-text_h)/2 -y {safeOutput}",
//强制指定分辨率,设置时间水印居中 ,在 -i 参数前使用 -video_size强制指定分辨率,但是指定分辨率支持的值必须是设备实际支持的分辨率,否则会报错。
//Arguments = $@"-rtbufsize 200M -f dshow -video_size 1280x720 -framerate 30 -i video=""{videoInput}"" -f dshow -i audio=""{audioInput}"" -pix_fmt yuv420p -tune zerolatency -ac 1 -ar 8000 -ab 44100 -vf drawtext=fontsize=56:fontcolor=red:text=\""{safeText}\"":x=(w-text_w)/2:y=(h-text_h)/2 -y {safeOutput}",
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true,
WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory
};
string issetwatermark = GetSetting("issetwatermark");
if (issetwatermark == "1")
{
// 3. 设置中文字体 (关键步骤,避免中文乱码)
// 黑体(SimHei):simhei.ttf 宋体:simsun.ttc 微软雅黑(Microsoft YaHei):msyh.ttc / msyhbd.ttc 仿宋(FangSong):simfang.ttf 楷体(KaiTi):simkai.ttf
string fontPath = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Fonts), "Arial.ttf") ?? System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Fonts), "arial.ttc");
// 使用黑体->微软雅黑
//string fontPath = @"C:\Windows\Fonts\simhei.ttf"; // 黑体--C:\Windows\Fonts\simhei.ttf 需要转换成-C\\:/Windows/Fonts/simhei.ttf 或者 C:/Windows/Fonts/simhei.ttf
fontPath = fontPath.Replace('\\', '/');
fontPath = fontPath.Replace("C:", "C\\:");
//设置时间水印 设置时间水印居中string vf = $"drawtext=fontsize=56:fontcolor=red:text='{text}':x=(w-text_w)/2:y=(h-text_h)/2";
//w = 实际视频宽度(如 1280),h = 实际视频高度(如 720),text_w, text_h = 文字渲染后的像素尺寸, 无需知道具体分辨率,表达式会自动适配!
//startInfo.Arguments = $@"-rtbufsize 200M -f dshow -i video=""{videoInput}"" -f dshow -i audio=""{audioInput}"" -pix_fmt yuv420p -tune zerolatency -ac 1 -ar 8000 -ab 44100 \""-vf drawtext=fontfile={fontPath}:fontsize=56:fontcolor=red:text='{safeText}':x=(w-text_w)/2:y=(h-text_h)/2\"" -y {safeOutput}";
//string text = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss").Replace("'", "\\'");
//水印时间---没有双引号 //整个 -vf 参数值用 \"...\" 包裹(因为含 : 和 =)
//string vf = $"drawtext=fontsize=56:x=100:y=100:fontcolor=red:text='{text}'";
//设置 时间水印居中 字体 ---有双引号
//string vf = $"drawtext=fontfile=\'{fontPath}\':fontsize=56:fontcolor=red:text=\\\"{safeText}\\\":x=(w-text_w)/2:y=(h-text_h)/2";
//ok--- vf=drawtext=fontfile='C\:/Windows/Fonts/Arial.ttf':fontsize=56:fontcolor=red:text=\"2026-03-05-09-52-51\":x=(w-text_w)/2:y=(h-text_h)/2
//设置 时间水印居中 字体 ---没有双引号
string vf = $"drawtext=fontfile=\'{fontPath}\':fontsize=56:fontcolor=red:text=\\\'{safeText}\\\':x=(w-text_w)/2:y=(h-text_h)/2";
startInfo.Arguments = $@"-rtbufsize 200M -f dshow -i video=""{videoInput}"" -f dshow -i audio=""{audioInput}"" -pix_fmt yuv420p -tune zerolatency -ac 1 -ar 8000 -ab 44100 -vf {vf} -y {safeOutput}";
//加字体下面这么写可以
//cmd ffmpeg -rtbufsize 200M -f dshow -i video="@device_pnp_\\?\usb#vid_1c76&pid_0676&mi_00#8&727dd20&4&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global" -f dshow -i audio="@device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\wave_{8DC48485-A62F-48B8-AAB1-033DCF66B586}" -pix_fmt yuv420p -tune zerolatency -ac 1 -ar 8000 -ab 44100 -vf drawtext=fontfile='C\:/Windows/Fonts/Arial.ttf':fontsize=56:fontcolor=red:text=\"2026-03-05-09-52-51\":x=(w-text_w)/2:y=(h-text_h)/2 -y "C:\Users\Administrator\Desktop\20260305095251.mp4"
//不加字体可以
//startInfo.Arguments = $@"-rtbufsize 200M -f dshow -i video=""{videoInput}"" -f dshow -i audio=""{audioInput}"" -pix_fmt yuv420p -tune zerolatency -ac 1 -ar 8000 -ab 44100 -vf drawtext=fontsize=56:fontcolor=red:text=\""{safeText}\"":x=(w-text_w)/2:y=(h-text_h)/2 -y {safeOutput}";
}
else
{
//不设置时间水印
startInfo.Arguments = $@"-rtbufsize 200M -f dshow -i video=""{videoInput}"" -f dshow -i audio=""{audioInput}"" -pix_fmt yuv420p -tune zerolatency -ac 1 -ar 8000 -ab 44100 -y {safeOutput}";
}
logger.Info($"startInfo.Arguments:{startInfo.Arguments}");
ProcessRecord = new Process { StartInfo = startInfo };
ProcessRecord.OutputDataReceived += Process_OutputDataReceived;
ProcessRecord.ErrorDataReceived += Process_ErrorDataReceived;
//process.StartInfo.Arguments = a;
ProcessRecord.Start();
ProcessRecord.BeginErrorReadLine();
ProcessRecord.BeginOutputReadLine();
//启动后不要立即 dispose!等 Exited 事件触发
//ProcessRecord.EnableRaisingEvents = true;
//ProcessRecord.Exited += (s, e) =>
//{
// Console.WriteLine($"FFmpeg 退出,退出码: {ProcessRecord.ExitCode}");
// ProcessRecord.Dispose();
//};
#endregion
#region 使用 ProcessStartInfo.ArgumentList(.NET Core 2.1+ / .NET 5+ 推荐)C# 中安全传递带引号的路径
//var startInfo = new ProcessStartInfo
//{
// FileName = "ffmpeg.exe",
// UseShellExecute = false,
// CreateNoWindow = true
//};
//// 直接添加参数,无需手动加引号!
//startInfo.ArgumentList.Add("-rtbufsize");
//startInfo.ArgumentList.Add("200M");
//startInfo.ArgumentList.Add("-f");
//startInfo.ArgumentList.Add("dshow");
//startInfo.ArgumentList.Add("-i");
//startInfo.ArgumentList.Add($"video={videoInput}"); // videoInput 已包含完整设备字符串
//startInfo.ArgumentList.Add("-f");
//startInfo.ArgumentList.Add("dshow");
//startInfo.ArgumentList.Add("-i");
//startInfo.ArgumentList.Add($"audio={audioInput}");
//startInfo.ArgumentList.Add("-pix_fmt");
//startInfo.ArgumentList.Add("yuv420p");
//startInfo.ArgumentList.Add("-tune");
//startInfo.ArgumentList.Add("zerolatency");
//startInfo.ArgumentList.Add("-ac");
//startInfo.ArgumentList.Add("1");
//startInfo.ArgumentList.Add("-ar");
//startInfo.ArgumentList.Add("8000");
//startInfo.ArgumentList.Add("-ab");
//startInfo.ArgumentList.Add("44100");
//startInfo.ArgumentList.Add("-vf");
//startInfo.ArgumentList.Add($"drawtext=fontsize=56:x=100:y=100:fontcolor=red:text='{DateTime.Now:yyyy-MM-dd-HH-mm-ss}'");
//startInfo.ArgumentList.Add("-y");
//startInfo.ArgumentList.Add(videoFilePath); // ← 直接传路径,自动处理引号!
//Process.Start(startInfo);
#endregion
#region 如果必须用 .Arguments(.NET Framework 或旧版)---ok
// var startInfo = new ProcessStartInfo
// {
// FileName = ffmpegPath,
// // 将 text 中的 ' 替换为 '\''(FFmpeg 要求)
// //Arguments = $@"-rtbufsize 200M -f dshow -i video=""{videoInput}"" -f dshow -i audio=""{audioInput}"" -pix_fmt yuv420p -tune zerolatency -ac 1 -ar 8000 -ab 44100 -vf drawtext=fontsize=56:x=100:y=100:fontcolor=red:text='{safeText}' -y {safeOutput}",
// //更推荐:对整个 text 值使用双引号 + 转义(FFmpeg 支持):string safeText = drawStr.Replace("\"", "\\\""); ... text =\"{safeText}\" ...
// //Arguments = $@"-rtbufsize 200M -f dshow -i video=""{videoInput}"" -f dshow -i audio=""{audioInput}"" -pix_fmt yuv420p -tune zerolatency -ac 1 -ar 8000 -ab 44100 -vf drawtext=fontsize=56:x=100:y=100:fontcolor=red:text=\""{safeText}\"" -y {safeOutput}",
// UseShellExecute = false,
// CreateNoWindow = true,
// RedirectStandardInput = true,
// RedirectStandardOutput = true,
// RedirectStandardError = true,
// WorkingDirectory = AppDomain.CurrentDomain.BaseDirectory
// };
// string outputFileName = System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), $"{DateTime.Now:yyyyMMddHHmmss}.mp4");//视频地址 DateTime.Now.ToString("yyyyMMddHHmmss") + ".mp4";
// string safeOutputPath = $"\"{outputFileName}\""; // 手动加双引号,输出路径用 \"...\" 包裹
// // 构建完整参数字符串(注意 drawtext 的 text 也要处理)text 中的 ' 要转义为 \'(FFmpeg 要求)
// string text = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss").Replace("'", "\\'");
// //水印时间---没有双引号 //整个 -vf 参数值用 \"...\" 包裹(因为含 : 和 =)
// //string vf = $"drawtext=fontsize=56:x=100:y=100:fontcolor=red:text='{text}'";
// //设置时间水印居中
// string vf = $"drawtext=fontsize=56:fontcolor=red:text='{text}':x=(w-text_w)/2:y=(h-text_h)/2";
// //w = 实际视频宽度(如 1280),h = 实际视频高度(如 720),text_w, text_h = 文字渲染后的像素尺寸, 无需知道具体分辨率,表达式会自动适配!
// #region MyRegion
// //FFmpeg 命令 水印 以及水印位置
// //水印时间---有双引号
// //string vf = $"drawtext=fontsize=56:x=100:y=100:fontcolor=red:text=\"{text}\"";//整个 -vf 参数值用 \"...\" 包裹(因为含 : 和 =)
// //要在 FFmpeg 的 drawtext 滤镜中将文字居中显示在视频正中心,不能使用固定的 x = 100:y = 100,而应使用 动态表达式,基于视频的宽度(w)和高度(h)以及文本自身的尺寸(text_w, text_h)来计算位置。
// //居中文本(水平 + 垂直居中)drawtext=fontsize=56:fontcolor=red:text='你的文字':x=(w-text_w)/2:y=(h-text_h)/2
// //参数说明:
// //| 表达式 | 含义 |
// //| --------| ------|
// //| `w` | 视频宽度(pixels) |
// //| `h` | 视频高度(pixels) |
// //| `text_w` | 当前文本渲染后的宽度 |
// //| `text_h` | 当前文本渲染后的高度 |
// //| `(w - text_w) / 2` | 水平居中:左边距 = (总宽 - 文字宽) ÷ 2 |
// //| `(h - text_h) / 2` | 垂直居中:上边距 = (总高 - 文字高) ÷ 2 |
// //其他常见居中需求
// //| 需求 | 表达式 |
// //| ------| --------|
// //| 水平居中,顶部对齐 | `x = (w - text_w) / 2:y = 0` |
// //| 水平居中,底部对齐 | `x = (w - text_w) / 2:y = h - text_h` |
// //| 垂直居中,左对齐 | `x = 0:y = (h - text_h) / 2` |
// //| 垂直居中,右对齐 | `x = w - text_w:y = (h - text_h) / 2` |
// //| 带边距的居中(如上下留 50px) | `x = (w - text_w) / 2:y = (h - text_h) / 2 - 50` |
// //⚠️注意事项
// //必须指定字体(可选但推荐)
// //默认字体可能不支持中文或样式不一致,建议指定:
// //bash
// //:fontfile =/ path / to / arial.ttf
// //Windows 示例:
// //bash
// //:fontfile = C\\:/ Windows / Fonts / arial.ttf
// //中文显示为方框?
// //确保字体支持中文(如 msyh.ttc 微软雅黑)
// //路径中的 \ 要转义为 \\ 或 /
// //bash
// //: fontfile = C\\:/ Windows / Fonts / msyh.ttc
// //实时流(dshow)中 text_w/ text_h 可能延迟生效
// //在录制开始的前几帧,文本尺寸可能未计算完成,导致短暂偏移(通常 1~2 帧后正常)。
// //✅ 最终命令示例(完整)
// //bash
// //ffmpeg -rtbufsize 200M -f dshow -i video="..." -f dshow -i audio="..." \
// // -pix_fmt yuv420p -tune zerolatency -ac 1 -ar 8000 -ab 44100 \
// // -vf "drawtext=fontsize=56:fontcolor=red:text='2026-03-04 14:30:00':x=(w-text_w)/2:y=(h-text_h)/2" \
// // -y output.mp4
// //这样文字就会完美居中在视频画面中央!
// //FFmpeg 命令录音录像时分辨率
// //FFmpeg 命令录音录像时候是没有固定“默认”分辨率!它完全取决于你的 DirectShow 视频采集设备(摄像头)的当前输出格式。
// //🔍 详细解释
// //- f dshow 表示使用 Windows 的 DirectShow 接口采集音视频。
// //FFmpeg 不会自动设置分辨率,而是直接使用设备当前激活的媒体类型(media type)中的分辨率。
// //常见情况:
// //表格
// //设备类型 可能的默认分辨率
// //笔记本内置摄像头 640x480(VGA)、1280x720(HD)
// //USB 摄像头 640x480、1280x720、1920x1080(取决于驱动和设备能力)
// //虚拟摄像头(OBS - VirtualCam 等) 用户自定义(如 1920x1080)
// //⚠️ 同一个摄像头,在不同软件中可能输出不同分辨率(因为每个软件会请求不同的格式)。
// #endregion
// startInfo.Arguments = "-rtbufsize 200M " + $"-f dshow -i \"video={videoInput}\" " + $"-f dshow -i \"audio={audioInput}\" " + "-pix_fmt yuv420p -tune zerolatency -ac 1 -ar 8000 -ab 44100 " + $"-vf \"{vf}\" " + "-y " + safeOutputPath;
// //强制指定分辨率,设置时间水印居中 ,在 -i 参数前使用 -video_size强制指定分辨率
// //startInfo.Arguments = "-rtbufsize 200M " + $"-f dshow -video_size 1280x720 -framerate 30 -i \"video={videoInput}\" " + $"-f dshow -i \"audio={audioInput}\" " + "-pix_fmt yuv420p -tune zerolatency -ac 1 -ar 8000 -ab 44100 " + $"-vf \"{vf}\" " + "-y " + safeOutputPath;
//// var sss =
//// ffmpeg - rtbufsize 200M \
////-f dshow - video_size 1280x720 - framerate 30 - i video = "..." \
////-f dshow - i audio = "..." \
////-pix_fmt yuv420p - tune zerolatency \
////-ac 1 - ar 8000 - ab 44100 \
////-vf "drawtext=fontsize=56:fontcolor=red:text='2026-03-04 14:30:00':x=(w-text_w)/2:y=(h-text_h)/2" \
////-y output.mp4
// //FFmepeg使用注意
// //1、text 中的 ' 要转义为 \'(FFmpeg 要求)
// //2、输出路径用 \"...\" 包裹
// //整个 -vf 参数值用 \"...\" 包裹(因为含 : 和 =)
// ProcessRecord = new Process { StartInfo = startInfo };
// ProcessRecord.OutputDataReceived += Process_OutputDataReceived;
// ProcessRecord.ErrorDataReceived += Process_ErrorDataReceived;
// //process.StartInfo.Arguments = a;
// ProcessRecord.Start();
// ProcessRecord.BeginErrorReadLine();
// ProcessRecord.BeginOutputReadLine();
#endregion
Untils.PlaySpeech("录音录像已开启");
result = true;
}
catch (Exception ex)
{
logger.Error($"StartRecord,失败,原因:{ex.Message}");
result = false;
}
return result;
}
public static void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
logger.Info($"StartRecord,录音录像 OutputDataReceived:{e.Data}");
//Log(e.Data, MessageType.info);
}
public static void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
logger.Error($"StartRecord,录音录像 ErrorDataReceived,原因:{e.Data}");
}