






















### C# 使用FFmpeg 命令 水印以及水印位置 录音录像时设置分辨率-分辨率一般是宽高比是 4:3 和16:9 少数是 5:4 ``` /// <summary> /// 找到所有的采集设备,按照cameraVIDPID找到人脸验证摄像头 /// </summary> /// <param name="cameraVIDPID">摄像头的pid和vid</param> /// <param name="filterCategory">设备类型,1 音频设备 2 录像设备</param> /// <returns></returns> public static string GetDeviceFromPID(string cameraVIDPID, int filterCategory = 2) { if (string.IsNullOrEmpty(cameraVIDPID)) return null; string monikerString = null;//摄像头设备名称 FilterInfoCollection videoDevices = null; try { //找到所有的视频采集设备 switch (filterCategory) { case 1://FilterCategory.AudioInputDevice videoDevices = new FilterInfoCollection(FilterCategory.AudioInputDevice); logger.Info($"音频设备:"); break; case 2: //FilterCategory.VideoInputDevice videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice); logger.Info($"录像设备:"); break; } List<DeviceVideoAudio> videoDevicesList = new List<DeviceVideoAudio>(); for (int i = 0; i < videoDevices.Count; i++) { logger.Info($"录像设备/音频设备:Name={videoDevices[i].Name},MonikerString={videoDevices[i].MonikerString}"); videoDevicesList.Add(new DeviceVideoAudio { Name = videoDevices[i].Name, MonikerString = videoDevices[i].MonikerString }); } switch (filterCategory) { case 1://FilterCategory.AudioInputDevice monikerString = videoDevicesList.Where(a => a.Name.Contains(cameraVIDPID)).ToList().FirstOrDefault(b => b.Name.Contains(cameraVIDPID)).MonikerString.Replace(":", "_"); logger.Info($"匹配到 音频设备:Name={cameraVIDPID},MonikerString={monikerString}"); break; case 2: //FilterCategory.VideoInputDevice if (cameraVIDPID.ToLower().Contains("pid") || cameraVIDPID.ToLower().Contains("vid")) { monikerString = videoDevicesList.Where(a => a.MonikerString.Contains("pid") || a.MonikerString.Contains("vid")).ToList().FirstOrDefault(b => b.MonikerString.Contains(cameraVIDPID.ToLower())).MonikerString.Replace(":", "_"); logger.Info($"匹配 到 录像设备:vid——pid={cameraVIDPID},MonikerString={monikerString}"); } else { //videoDevicesList = videoDevicesList.Where(a => a.Name.Contains(cameraVIDPID)).ToList(); monikerString = videoDevicesList.FirstOrDefault(b => b.Name.Contains(cameraVIDPID)).MonikerString.Replace(":", "_"); logger.Info($"匹配 到 录像设备:Name={cameraVIDPID},MonikerString={monikerString}"); } break; } } catch (Exception ex) { logger.Error($"GetDeviceFromPID获取音视频设备出错,原因:{ex.InnerException}"); monikerString = null; } return monikerString; } /// <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() { 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}"); //string drawStr = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss"); //// 转义文本中的单引号(FFmpeg drawtext 要求)--- 将 text 中的 ' 替换为 '\''(FFmpeg 要求) //string safeText = drawStr.Replace("'", "'\\''"); //string videoFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), $"{DateTime.Now.ToString("yyyyMMddHHmmss")}.mp4");//视频地址 //// 输出路径加引号防空格 输出文件名(带引号防止空格或特殊字符问题) 更推荐:对整个文本值 值使用双引号 + 转义(FFmpeg 支持) //string safeOutput = $"\"{videoFilePath ?? "123.mp4"}\""; //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 //}; //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(); //}; #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 或旧版) 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}"); } ```
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。