1. C# 调用 FFmpeg 实现音视频录制与水印添加实战指南
在音视频处理领域,FFmpeg 堪称瑞士军刀般的存在。作为一名长期从事音视频开发的工程师,我经常需要在 C# 项目中集成 FFmpeg 来实现各种音视频处理功能。本文将分享如何通过 C# 调用 FFmpeg 命令实现音视频录制、添加动态时间水印以及设置分辨率等实用技巧。
1.1 核心功能概述
这个方案主要解决以下几个实际问题:
- 通过 C# 程序调用 FFmpeg 进行音视频同步录制
- 在视频画面中添加动态更新的时间水印
- 控制输出视频的分辨率和宽高比
- 处理音视频设备枚举和选择问题
提示:本文示例基于 Windows 平台的 DirectShow 设备采集,但核心原理同样适用于其他平台。
2. 环境准备与设备枚举
2.1 FFmpeg 环境配置
首先确保你的开发环境中已经正确安装了 FFmpeg。推荐将 ffmpeg.exe 放在项目目录下,或者添加到系统 PATH 环境变量中。
csharp复制string ffmpegPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "ffmpeg.exe");
if (!File.Exists(ffmpegPath))
{
logger.Error($"未找到 ffmpeg.exe--{ffmpegPath}");
return false;
}
2.2 音视频设备枚举
在开始录制前,我们需要先获取可用的音视频设备列表。以下代码展示了如何通过 C# 枚举系统中的音视频采集设备:
csharp复制public static string GetDeviceFromPID(string cameraVIDPID, int filterCategory = 2)
{
string monikerString = null;
FilterInfoCollection videoDevices = null;
try
{
switch (filterCategory)
{
case 1: // 音频设备
videoDevices = new FilterInfoCollection(FilterCategory.AudioInputDevice);
break;
case 2: // 视频设备
videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice);
break;
}
List<DeviceVideoAudio> videoDevicesList = new List<DeviceVideoAudio>();
for (int i = 0; i < videoDevices.Count; i++)
{
videoDevicesList.Add(new DeviceVideoAudio {
Name = videoDevices[i].Name,
MonikerString = videoDevices[i].MonikerString
});
}
// 根据设备PID/VID或名称匹配具体设备
// ...
}
catch (Exception ex)
{
logger.Error($"获取音视频设备出错: {ex.Message}");
}
return monikerString;
}
注意:设备枚举需要使用 DirectShow 相关库,可以通过 NuGet 安装 DirectShowLib 等包。
3. FFmpeg 录制命令详解
3.1 基础录制命令
最基本的音视频同步录制命令如下:
bash复制ffmpeg -f dshow -i video="摄像头名称" -f dshow -i audio="麦克风名称" -y output.mp4
在 C# 中,我们可以通过 Process 类来启动 FFmpeg 进程:
csharp复制ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = "ffmpeg.exe",
Arguments = $"-f dshow -i video=\"{videoInput}\" -f dshow -i audio=\"{audioInput}\" -y output.mp4",
UseShellExecute = false,
CreateNoWindow = true
};
Process.Start(startInfo);
3.2 添加时间水印
FFmpeg 的 drawtext 滤镜可以很方便地添加文字水印。以下是添加动态时间水印的命令:
bash复制-vf "drawtext=fontsize=56:fontcolor=red:text='%{localtime}':x=(w-text_w)/2:y=(h-text_h)/2"
在 C# 中的实现:
csharp复制string text = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss").Replace("'", "\\'");
string vf = $"drawtext=fontsize=56:fontcolor=red:text='{text}':x=(w-text_w)/2:y=(h-text_h)/2";
startInfo.Arguments = $"-f dshow -i video=\"{videoInput}\" " +
$"-f dshow -i audio=\"{audioInput}\" " +
$"-vf \"{vf}\" -y output.mp4";
3.3 设置分辨率与帧率
通过 -video_size 和 -framerate 参数可以指定采集的分辨率和帧率:
csharp复制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 output.mp4";
4. 完整实现与进程管理
4.1 完整的录制实现
以下是完整的音视频录制实现代码:
csharp复制public static bool StartRecord()
{
try
{
// 获取音视频设备
string videoInput = GetDeviceFromPID("摄像头PID", 2);
string audioInput = GetDeviceFromPID("麦克风PID", 1);
if (string.IsNullOrWhiteSpace(videoInput) || string.IsNullOrWhiteSpace(audioInput))
{
logger.Error("未找到音视频设备");
return false;
}
// 准备FFmpeg命令
string outputFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop),
$"{DateTime.Now:yyyyMMddHHmmss}.mp4");
string text = DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss").Replace("'", "\\'");
string vf = $"drawtext=fontsize=56:fontcolor=red:text='{text}':x=(w-text_w)/2:y=(h-text_h)/2";
var startInfo = new ProcessStartInfo
{
FileName = "ffmpeg.exe",
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 \"{outputFile}\"",
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardInput = true,
RedirectStandardOutput = true,
RedirectStandardError = true
};
ProcessRecord = new Process { StartInfo = startInfo };
ProcessRecord.OutputDataReceived += (s, e) => logger.Info($"FFmpeg输出: {e.Data}");
ProcessRecord.ErrorDataReceived += (s, e) => logger.Error($"FFmpeg错误: {e.Data}");
ProcessRecord.Start();
ProcessRecord.BeginErrorReadLine();
ProcessRecord.BeginOutputReadLine();
return true;
}
catch (Exception ex)
{
logger.Error($"录制失败: {ex.Message}");
return false;
}
}
4.2 进程管理与停止录制
要停止录制,我们需要向 FFmpeg 进程发送 'q' 命令:
csharp复制public static bool StopRecord()
{
try
{
if (ProcessRecord == null || ProcessRecord.HasExited)
return false;
ProcessRecord.StandardInput.WriteLine("q");
ProcessRecord.WaitForExit();
ProcessRecord.Close();
ProcessRecord.Dispose();
return true;
}
catch (Exception ex)
{
logger.Error($"停止录制失败: {ex.Message}");
return false;
}
}
5. 常见问题与解决方案
5.1 中文乱码问题
如果水印中的中文显示为方框,可能是因为字体不支持中文。解决方法是指定支持中文的字体:
csharp复制string vf = $"drawtext=fontfile='C\\:/Windows/Fonts/msyh.ttc':fontsize=56:fontcolor=red:text='中文测试':x=100:y=100";
5.2 分辨率设置无效
如果设置的分辨率无效,可能是因为设备不支持该分辨率。可以先查询设备支持的分辨率:
bash复制ffmpeg -list_options true -f dshow -i video="摄像头名称"
5.3 音视频不同步
可以尝试调整以下参数来改善音视频同步:
- 增加 -rtbufsize 缓冲区大小
- 使用 -async 1 参数
- 调整 -audio_preload 和 -audio_buffer_size 参数
5.4 设备占用问题
如果设备被其他程序占用,FFmpeg 会报错。可以尝试先关闭其他可能占用设备的程序,或者在代码中添加重试逻辑。
6. 高级技巧与优化建议
6.1 水印位置计算
FFmpeg 的 drawtext 滤镜支持动态计算水印位置:
| 位置需求 | 表达式 |
|---|---|
| 水平居中,顶部对齐 | x=(w-text_w)/2:y=0 |
| 水平居中,底部对齐 | x=(w-text_w)/2:y=h-text_h |
| 右上角 | x=w-text_w-10:y=10 |
| 左下角 | x=10:y=h-text_h-10 |
6.2 多水印叠加
可以叠加多个 drawtext 滤镜来实现复杂的水印效果:
csharp复制string vf = "drawtext=fontsize=24:text='公司名称':x=10:y=10," +
"drawtext=fontsize=20:text='机密':x=w-text_w-10:y=h-text_h-10," +
"drawtext=fontsize=56:text='时间':x=(w-text_w)/2:y=(h-text_h)/2";
6.3 硬件加速
对于高性能需求,可以使用硬件加速选项:
csharp复制startInfo.Arguments += " -c:v h264_nvenc"; // NVIDIA GPU加速
// 或
startInfo.Arguments += " -c:v h264_qsv"; // Intel QuickSync加速
6.4 日志与错误处理
完善的日志记录对于调试非常重要:
csharp复制ProcessRecord.ErrorDataReceived += (s, e) =>
{
if (!string.IsNullOrEmpty(e.Data))
{
if (e.Data.Contains("Error") || e.Data.Contains("failed"))
logger.Error($"FFmpeg错误: {e.Data}");
else
logger.Info($"FFmpeg输出: {e.Data}");
}
};
7. 性能优化与参数调优
7.1 缓冲区设置
适当增加缓冲区可以减少丢帧:
csharp复制startInfo.Arguments = "-rtbufsize 200M " + ...;
7.2 编码参数优化
以下参数可以提高编码效率和质量:
csharp复制startInfo.Arguments += " -pix_fmt yuv420p -tune zerolatency -preset fast -crf 23";
7.3 音频参数调整
根据实际需求调整音频参数:
csharp复制startInfo.Arguments += " -ac 2 -ar 44100 -ab 128k";
7.4 多线程处理
启用多线程编码可以提高性能:
csharp复制startInfo.Arguments += " -threads 4";
8. 实际应用中的经验分享
在实际项目开发中,我总结了以下几点经验:
-
设备兼容性:不同品牌和型号的摄像头支持的参数可能不同,建议在代码中添加设备检测和参数适配逻辑。
-
异常处理:FFmpeg 进程可能会因为各种原因崩溃,需要添加完善的异常处理和进程监控。
-
资源释放:确保在程序退出时正确释放所有资源,包括停止 FFmpeg 进程和释放设备。
-
用户反馈:长时间录制时,应该提供进度反馈,避免用户误以为程序没有响应。
-
格式兼容性:选择广泛支持的视频格式和编码参数,确保生成的视频能在各种设备上播放。
-
测试覆盖:针对不同的设备和环境进行充分测试,特别是要测试低配置设备上的表现。
-
日志记录:详细的日志记录对于后期排查问题非常重要,建议记录关键操作和 FFmpeg 的输出。
-
性能监控:在录制过程中监控 CPU、内存等资源使用情况,避免资源耗尽导致系统不稳定。
通过本文介绍的方法,你可以在 C# 项目中轻松实现音视频录制、水印添加和分辨率设置等功能。这些技术可以应用于视频监控、在线教育、视频会议等各种场景。