You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在C# WPF应用中实现FFMPEG转换进度到进度条的展示

实时捕获FFmpeg输出并同步到WPF进度条的实现方案

搞定FFmpeg进度同步这个需求其实不难,核心就是抓准FFmpeg的输出规律,再配合WPF的UI线程更新机制就行。我给你一套完整的实现方案,包含代码和关键细节说明:

核心思路

要实现实时进度同步,我们需要做三件事:

  • 异步启动FFmpeg进程,避免阻塞UI线程
  • 捕获FFmpeg的结构化进度输出(用-progress pipe:1参数让输出更易解析)
  • 解析进度数据后,通过Dispatcher跨线程更新文本框和进度条

完整代码实现

按钮点击事件处理

private async void convertbutton_Click(object sender, RoutedEventArgs e)
{
    // 基础路径配置
    string resDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "res");
    string ffmpegPath = Path.Combine(resDir, "ffmpeg.exe");
    string inputFile = "你的输入文件路径"; // 替换为实际输入文件路径
    string outputFile = "你的输出文件路径"; // 替换为实际输出文件路径

    // 提前用FFprobe获取输入文件总时长(秒),示例值为300秒(5分钟)
    // 实际项目中可以通过执行ffprobe命令获取:ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 "输入文件路径"
    double totalDuration = 300;

    // 构建FFmpeg命令,加入-progress pipe:1输出结构化进度信息
    string arguments = $"-i \"{inputFile}\" -c:v libx264 -crf 23 -progress pipe:1 \"{outputFile}\"";

    var processStartInfo = new ProcessStartInfo
    {
        FileName = ffmpegPath,
        Arguments = arguments,
        RedirectStandardOutput = true, // 因为-progress输出到stdout
        RedirectStandardError = true, // 可以同时捕获错误日志
        UseShellExecute = false,
        CreateNoWindow = true,
        StandardOutputEncoding = Encoding.UTF8,
        StandardErrorEncoding = Encoding.UTF8
    };

    using (var process = new Process { StartInfo = processStartInfo })
    {
        // 订阅标准输出(进度信息)
        process.OutputDataReceived += (s, args) =>
        {
            if (!string.IsNullOrEmpty(args.Data))
            {
                // 更新文本框(必须回到UI线程)
                Dispatcher.Invoke(() =>
                {
                    outputTextBox.AppendText(args.Data + Environment.NewLine);
                    outputTextBox.ScrollToEnd(); // 自动滚动到最新内容
                });

                // 解析进度并更新进度条
                double progress = ParseFFmpegProgress(args.Data, totalDuration);
                if (progress >= 0)
                {
                    Dispatcher.Invoke(() =>
                    {
                        progressBar.Value = progress;
                    });
                }
            }
        };

        // 订阅标准错误(捕获日志/错误信息)
        process.ErrorDataReceived += (s, args) =>
        {
            if (!string.IsNullOrEmpty(args.Data))
            {
                Dispatcher.Invoke(() =>
                {
                    outputTextBox.AppendText($"[ERROR] {args.Data}" + Environment.NewLine);
                    outputTextBox.ScrollToEnd();
                });
            }
        };

        // 启动进程并开始异步读取输出
        process.Start();
        process.BeginOutputReadLine();
        process.BeginErrorReadLine();

        // 异步等待进程完成,不阻塞UI
        await Task.Run(() => process.WaitForExit());

        // 进程结束后收尾
        Dispatcher.Invoke(() =>
        {
            progressBar.Value = 100;
            outputTextBox.AppendText("转换完成!" + Environment.NewLine);
        });
    }
}

进度解析方法

private double ParseFFmpegProgress(string outputLine, double totalDuration)
{
    // 处理转换结束的标记
    if (outputLine.Trim() == "progress=end")
    {
        return 100;
    }

    // 解析当前已处理的时间
    var timeMatch = Regex.Match(outputLine, @"time=(\d+):(\d+):(\d+\.\d+)");
    if (timeMatch.Success && totalDuration > 0)
    {
        int hours = int.Parse(timeMatch.Groups[1].Value);
        int minutes = int.Parse(timeMatch.Groups[2].Value);
        double seconds = double.Parse(timeMatch.Groups[3].Value);
        double currentTime = hours * 3600 + minutes * 60 + seconds;

        // 计算进度百分比,确保不超过100%
        return Math.Min(100, Math.Round((currentTime / totalDuration) * 100, 2));
    }

    // 无法解析时返回-1,不更新进度条
    return -1;
}

关键细节说明

  • 结构化输出的重要性:使用-progress pipe:1参数让FFmpeg输出键值对格式的进度数据,比直接解析原始日志稳定得多,避免因FFmpeg版本差异导致正则匹配失败。
  • UI线程安全:Process的输出事件是在后台线程触发的,必须用Dispatcher.Invoke将UI更新操作切回主线程,否则会抛出跨线程访问异常。
  • 总时长获取:要准确计算进度,必须提前获取输入文件的总时长。用FFprobe是最可靠的方式,你可以把FFprobe也打包到res目录里,在转换前异步执行命令获取时长。
  • 异常处理:实际项目中建议给代码加上try-catch块,处理文件不存在、FFmpeg启动失败、权限不足等异常情况,提升程序稳定性。

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

火山引擎 最新活动