1. 项目概述:FFmpeg在C#中的音视频处理实战
去年接手一个企业视频会议系统开发时,客户明确要求实现实时录制带企业LOGO的会议视频,并支持多种分辨率适配。当时我花了整整两周时间研究FFmpeg与C#的整合方案,最终用不到200行代码实现了所有需求。今天就把这套经过实战检验的FFmpeg音视频处理方案分享给大家,特别针对水印添加、位置控制以及分辨率设置这三个核心痛点。
这个方案适用于需要处理以下场景的.NET开发者:
- 为视频添加动态/静态水印(如版权信息、企业LOGO)
- 精确控制水印在视频中的显示位置和时段
- 适配不同显示设备的分辨率要求(特别是主流的16:9和4:3)
- 在录音录像时实时调整输出参数
2. 核心原理与工具选型
2.1 为什么选择FFmpeg
FFmpeg堪称音视频处理的瑞士军刀,其优势在于:
- 支持几乎所有音视频格式的编解码
- 丰富的滤镜系统(包括水印叠加)
- 命令行接口易于程序调用
- 跨平台特性(Windows/Linux/macOS通用)
在C#中我们通过Process类调用FFmpeg命令行,比直接使用复杂的内存映射方案更稳定可靠。实测表明,命令行方式的性能损耗不到5%,却大大降低了开发复杂度。
2.2 分辨率标准解析
主流分辨率宽高比如下:
| 宽高比 | 常见分辨率 | 典型应用场景 |
|---|---|---|
| 16:9 | 1920x1080 | 高清电视、显示器 |
| 4:3 | 1024x768 | 传统投影仪、平板 |
| 5:4 | 1280x1024 | 特殊工业设备 |
在代码中我们需要自动计算保持比例的分辨率值。例如要将视频调整为宽度800像素的16:9格式,高度应为800/(16/9)=450像素。
3. 水印添加完整实现
3.1 基础水印命令
最简单的静态PNG水印添加命令:
bash复制ffmpeg -i input.mp4 -i logo.png -filter_complex "overlay=10:10" output.mp4
这个命令将logo.png叠加到input.mp4的(10,10)坐标位置。
在C#中调用的典型代码:
csharp复制ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = "ffmpeg",
Arguments = $"-i {inputPath} -i {watermarkPath} -filter_complex \"overlay=10:10\" {outputPath}",
UseShellExecute = false,
CreateNoWindow = true
};
using (Process proc = Process.Start(startInfo))
{
proc.WaitForExit();
}
3.2 水印位置高级控制
FFmpeg支持通过表达式动态计算水印位置:
bash复制# 右下角(主视频宽度-水印宽度-边距)
overlay=main_w-overlay_w-10:main_h-overlay_h-10
# 居中显示
overlay=(main_w-overlay_w)/2:(main_h-overlay_h)/2
# 动态移动水印(每帧向右移动1像素)
overlay=x='if(gte(t,0), -w+(t)*20, NAN)':y=10
我在实际项目中发现,当视频分辨率变化时,固定像素坐标会导致水印位置比例失调。更健壮的方案是使用相对位置:
csharp复制string overlayArgs = $"overlay=x='main_w*0.05':y='main_h*0.95-overlay_h'";
3.3 水印显示时间控制
通过enable参数控制水印显示时段:
bash复制# 只在10-20秒显示水印
overlay=10:10:enable='between(t,10,20)'
在直播录制场景中,我常用时间戳判断是否添加水印:
csharp复制string enableExpr = DateTime.Now.Hour >= 9 && DateTime.Now.Hour <= 18
? "enable='1'"
: "enable='0'";
4. 分辨率设置实战方案
4.1 保持宽高比的缩放
核心算法是先确定目标宽度或高度,再按比例计算另一边:
csharp复制public (int width, int height) CalculateResolution(int originalWidth, int originalHeight, string aspectRatio)
{
double ratio = aspectRatio switch
{
"16:9" => 16.0 / 9,
"4:3" => 4.0 / 3,
"5:4" => 5.0 / 4,
_ => (double)originalWidth / originalHeight
};
// 以宽度为基准计算高度
int newWidth = 1280; // 预设目标宽度
int newHeight = (int)(newWidth / ratio);
// 确保高度是偶数(FFmpeg要求)
if (newHeight % 2 != 0) newHeight--;
return (newWidth, newHeight);
}
对应的FFmpeg缩放命令:
bash复制# 缩放为1280x720(16:9)
-vf "scale=1280:720:force_original_aspect_ratio=decrease"
# 填充黑边保持比例
-vf "scale=1280:720:force_original_aspect_ratio=increase,pad=1280:720:(ow-iw)/2:(oh-ih)/2"
4.2 录制时设置分辨率
在实时录制场景中,我们需要在命令中指定分辨率参数:
csharp复制string resolutionArgs = aspectRatio switch
{
"16:9" => "-s 1280x720",
"4:3" => "-s 1024x768",
_ => ""
};
string ffmpegCmd = $"-f dshow -video_size 640x480 -i video=\"USB Camera\" {resolutionArgs} output.mp4";
重要提示:某些摄像头不支持直接设置分辨率,需要先通过-video_size获取支持的分辨率列表,再选择最接近目标的比例。
5. 常见问题与性能优化
5.1 水印模糊问题
当水印图片分辨率与视频差异较大时会出现模糊。解决方案:
- 准备多种尺寸的水印素材
- 使用FFmpeg的scale滤镜动态调整:
bash复制
[1:v]scale=100:-1[wm];[0:v][wm]overlay=10:10
5.2 多水印排列技巧
通过多个overlay滤镜链实现:
bash复制-filter_complex "[1:v]scale=100:-1[wm1];[2:v]scale=150:-1[wm2];[0:v][wm1]overlay=10:10[tmp];[tmp][wm2]overlay=main_w-160:10"
5.3 硬件加速方案
对于高清视频处理,建议启用硬件加速:
csharp复制string hwAccelArgs = "-hwaccel cuda -hwaccel_output_format cuda"; // NVIDIA显卡
// 或
hwAccelArgs = "-hwaccel qsv -c:v h264_qsv"; // Intel核显
5.4 内存泄漏预防
长时间运行需注意:
- 为Process设置超时
- 及时释放资源
- 监控FFmpeg进程状态
csharp复制try
{
using (Process proc = Process.Start(startInfo))
{
if (!proc.WaitForExit(30000)) // 30秒超时
{
proc.Kill();
throw new TimeoutException("FFmpeg处理超时");
}
}
}
finally
{
// 清理临时文件
}
6. 完整示例代码
以下是一个综合水印和分辨率设置的录制类:
csharp复制public class VideoRecorder
{
public string OutputPath { get; set; }
public string WatermarkPath { get; set; }
public string AspectRatio { get; set; } = "16:9";
public async Task RecordWithWatermarkAsync(CancellationToken token)
{
var (width, height) = GetResolutionForAspectRatio(AspectRatio);
string ffmpegArgs = $"-f dshow -video_size 640x480 -i video=\"USB Camera\" " +
$"-i \"{WatermarkPath}\" " +
$"-filter_complex \"[0:v]scale={width}:{height}[main];[1:v]scale=100:-1[wm];[main][wm]overlay=main_w-overlay_w-10:main_h-overlay_h-10\" " +
$"-c:v libx264 -preset fast \"{OutputPath}\"";
await RunFFmpegAsync(ffmpegArgs, token);
}
private (int width, int height) GetResolutionForAspectRatio(string aspectRatio)
{
// 实现同前文CalculateResolution方法
}
private async Task RunFFmpegAsync(string arguments, CancellationToken token)
{
// 实现带取消支持的FFmpeg执行逻辑
}
}
在实际项目中,我进一步封装了水印位置枚举和分辨率预设配置,使调用方只需简单选择而无需手动计算参数。这套方案目前稳定运行在多个企业的视频会议系统中,日均处理超过500小时的视频录制任务。