You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用C#结合ffmpeg.exe为视频添加图片叠加层(附代码)

使用C#结合FFmpeg实现视频图片叠加层的完整方案

1. 指定图片叠加层的位置与尺寸

位置控制

FFmpeg的overlay滤镜通过x和y参数定义图片位置:

  • 固定像素定位:overlay=25:25 表示距离视频左上角25x25像素
  • 相对位置(基于视频分辨率):
    • 右对齐:overlay=W-w:25(W为视频宽度,w为图片宽度)
    • 底部居中:overlay=(W-w)/2:H-h(H为视频高度,h为图片高度)
    • 百分比定位:overlay=W*0.1:H*0.9(距离左侧10%、底部10%的位置)

尺寸控制

需调整图片尺寸时,在图片输入流后添加scale滤镜:

-filter_complex "[1:v]scale=150:100[scaled_img];[0:v][scaled_img]overlay=25:25"
  • scale=150:100 强制设置图片为150x100像素
  • scale=min(W/4,H/4):-1 按比例缩小图片至视频宽/高的1/4(-1自动保持宽高比)

2. 确保图片全程可见(适配分辨率/宽高比变化)

要实现全程可见需注意两点:

  1. 移除时间限制:删除overlay滤镜中的enable='between(t,0,20)'参数,该参数仅让图片显示0-20秒;全程显示无需添加enable,或设置为enable='gte(t,0)'。
  2. 使用相对位置/自适应尺寸:
    • 基于视频分辨率的相对值定位,避免固定像素在高分辨率视频中偏居一角、低分辨率视频中超出画面
    • 用比例缩放图片,确保不会因视频尺寸过小被裁切

示例FFmpeg参数(全程显示+底部右对齐+自适应尺寸):

-i input.mp4 -i overlay.png -filter_complex "[1:v]scale=min(W/5,H/5):-1[img];[0:v][img]overlay=W-w-10:H-h-10" -c:a copy output.mp4

3. C#中执行FFmpeg并捕获错误/状态

核心要点

  • 异步读取StandardOutput和StandardError,避免进程阻塞
  • 监听进程事件实时获取执行状态
  • 检查退出码(ExitCode == 0表示执行成功)
  • 用临时输出文件避免原文件被占用

修正后的C#代码

private async void button6_Click(object sender, EventArgs e)
{
    var testVideoPath = "temp_input.mp4";
    var outputVideoPath = "temp_output.mp4";
    var overlayImagePath = "temp_overlay.jpg";

    // 生成测试视频(实际项目替换为真实视频路径)
    await RunFfmpegCommand($"-f lavfi -i nullsrc -t 10 -s {pictureBox2.Width}x{pictureBox2.Height} -r 30 -c:v libx264 {testVideoPath}");

    render = true;
    await Task.Run(async () =>
    {
        for (int i = 0; i < 10; i++)
        {
            // 从PictureBox获取最新图片
            Bitmap bmp = null;
            pictureBox1.Invoke((MethodInvoker)(() =>
            {
                bmp = (Bitmap)pictureBox1.Image.Clone();
            }));
            
            // 等待图片更新
            while (true)
            {
                Bitmap currentBmp = null;
                pictureBox1.Invoke((MethodInvoker)(() =>
                {
                    currentBmp = (Bitmap)pictureBox1.Image.Clone();
                }));
                if (!bmp.Equals(currentBmp))
                {
                    bmp.Dispose();
                    bmp = currentBmp;
                    break;
                }
                await Task.Delay(10);
            }

            // 保存图片并释放资源
            bmp.Save(overlayImagePath);
            bmp.Dispose();

            // 执行叠加命令
            var ffmpegArgs = $"-i {testVideoPath} -i {overlayImagePath} -filter_complex \"[1:v]scale=min(W/5,H/5):-1[img];[0:v][img]overlay=W-w-10:H-h-10\" -c:a copy {outputVideoPath}";
            var (exitCode, errorOutput) = await RunFfmpegCommand(ffmpegArgs);

            // 处理执行错误
            if (exitCode != 0)
            {
                MessageBox.Show($"FFmpeg执行出错:{errorOutput}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
                break;
            }

            // 更新输入文件为当前输出结果
            File.Delete(testVideoPath);
            File.Move(outputVideoPath, testVideoPath);
        }
    });

    // 清理临时文件
    if (File.Exists(overlayImagePath)) File.Delete(overlayImagePath);
    if (File.Exists(testVideoPath)) File.Delete(testVideoPath);
    if (File.Exists(outputVideoPath)) File.Delete(outputVideoPath);
}

/// <summary>
/// 封装FFmpeg命令执行逻辑,返回退出码和错误输出
/// </summary>
private async Task<(int ExitCode, string ErrorOutput)> RunFfmpegCommand(string arguments)
{
    var startInfo = new ProcessStartInfo
    {
        FileName = "ffmpeg.exe",
        Arguments = arguments,
        UseShellExecute = false,
        RedirectStandardOutput = true,
        RedirectStandardError = true,
        CreateNoWindow = true,
        StandardOutputEncoding = Encoding.UTF8,
        StandardErrorEncoding = Encoding.UTF8
    };

    using var process = new Process { StartInfo = startInfo };
    var errorOutputBuilder = new StringBuilder();

    // 实时捕获错误输出
    process.ErrorDataReceived += (s, e) =>
    {
        if (!string.IsNullOrEmpty(e.Data))
        {
            errorOutputBuilder.AppendLine(e.Data);
            // 可在此处更新UI状态,比如显示处理进度
            // Invoke(() => { lblStatus.Text = e.Data; });
        }
    };

    process.Start();
    process.BeginErrorReadLine();
    await process.StandardOutput.ReadToEndAsync();
    await process.WaitForExitAsync();

    return (process.ExitCode, errorOutputBuilder.ToString());
}

代码说明

  • RunFfmpegCommand封装了FFmpeg执行逻辑,异步捕获错误输出,避免UI线程阻塞
  • 使用临时文件循环处理,解决原文件被占用的问题
  • 优化图片更新等待逻辑,用Task.Delay替代Thread.Sleep,避免线程阻塞
  • 实时捕获FFmpeg输出,便于调试和错误提示

内容的提问来源于stack exchange,提问作者Frost Dream

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 21:10:58