如何在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




