使用FFmpeg管道处理视频流时输出流为空的问题求助
解决FFmpeg管道流输出为空的问题
问题场景
无需落地存储,直接通过输入流降低视频码率,输出流上传至DigitalOcean Spaces,但尝试多种方法后输出流始终为空,生成的文件也为空。
第一种实现代码
public async Task VideoOperationAsync(Stream inputStream) { try { // Ensure the input stream is at the beginning inputStream.Position = 0; // Create a memory stream to hold the output data using (var outputStream = new MemoryStream()) { var arguments = $"-i pipe:0 -f mp4 pipe:1"; await Cli.Wrap("ffmpeg") .WithArguments(arguments) .WithStandardInputPipe(PipeSource.FromStream(inputStream)) .WithStandardOutputPipe(PipeTarget.ToStream(outputStream)) .WithValidation(CommandResultValidation.None) .ExecuteAsync(); // Ensure the output stream is at the beginning before reading outputStream.Position = 0; using (var fileStream = new FileStream(@"D:\Gremlin-data\VideoResized\output_cropped.mp4", FileMode.Create, FileAccess.Write)) { await outputStream.CopyToAsync(fileStream); } } } catch (Exception ex) { Console.WriteLine($"An error occurred: {ex.Message}"); throw; // Re-throw the exception if needed } }
第二种实现代码(未完成)
public async Task VideoOperationAsync(Stream inputStream) { try { // Ensure the input stream is at the beginning inputStream.Position = 0; var ffmpegProcess = new Process { StartInfo = new ProcessStartInfo { FileName = "ffmpeg", // Ensure ffmpeg is in your PATH or provide the full path Arguments = $"-i pipe:0 -b:v 2000k -f mp4 pipe:1", // Correct bitrate format RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = true, // Capture standard error UseShellExecute = false, CreateNoWindow = true } }; ffmpegProcess.Start(); // Write input stream to ffmpeg's standard input asynchronously Task writingTask = Task.Run(async () => { await inputStream.CopyToAsync(ffmpegProcess.StandardInput.BaseStream); ffmpegProcess.StandardInput.BaseStream.Close(); }); // Read ffmpeg's standard output to a memory stream asynchronously using (MemoryStream ms = new MemoryStream()) { Task readingTask = Task.Run(async () => { await ffmpegProcess.StandardOutput.BaseStream.CopyToAsync(ms); });
问题根源分析
- FFmpeg参数缺失关键配置:MP4格式默认需要生成完整文件头后才会输出数据,管道模式下必须添加
-movflags frag_keyframe+empty_moov参数,让FFmpeg生成可流式传输的碎片化MP4,否则输出流会一直为空直到输入完全结束。 - 未指定编码器:未明确指定视频编码器时,FFmpeg可能选择不支持管道输出的编码器,导致无输出。
- 流处理同步逻辑缺失:第二种实现中未等待读写任务完成和进程退出,导致FFmpeg还未处理完数据就提前终止。
- 不可查找流处理不当:如果输入流是网络流等不可查找类型,
inputStream.Position = 0会直接抛出异常,中断流程。 - 错误信息未捕获:未收集FFmpeg的标准错误输出,无法排查编码失败的具体原因(如输入格式不支持、编码器缺失等)。
修正后的实现代码
使用CliWrap的完整版本
public async Task VideoOperationAsync(Stream inputStream) { try { // 仅在流支持查找时重置位置 if (inputStream.CanSeek) { inputStream.Position = 0; } using (var outputStream = new MemoryStream()) using (var errorStream = new MemoryStream()) { // 配置FFmpeg参数:指定编码器、码率、流式MP4输出 var arguments = new[] { "-i", "pipe:0", "-b:v", "2000k", "-c:v", "libx264", "-movflags", "frag_keyframe+empty_moov", "-f", "mp4", "pipe:1" }; var result = await Cli.Wrap("ffmpeg") .WithArguments(arguments) .WithStandardInputPipe(PipeSource.FromStream(inputStream)) .WithStandardOutputPipe(PipeTarget.ToStream(outputStream)) .WithStandardErrorPipe(PipeTarget.ToStream(errorStream)) .WithValidation(CommandResultValidation.None) .ExecuteAsync(); // 检查FFmpeg执行状态,读取错误输出排查问题 if (result.ExitCode != 0) { errorStream.Position = 0; using var reader = new StreamReader(errorStream); var errorMsg = await reader.ReadToEndAsync(); throw new InvalidOperationException($"FFmpeg执行失败:{errorMsg}"); } outputStream.Position = 0; // 替换为上传至DigitalOcean Spaces的逻辑 using (var fileStream = new FileStream(@"D:\Gremlin-data\VideoResized\output_cropped.mp4", FileMode.Create, FileAccess.Write)) { await outputStream.CopyToAsync(fileStream); } } } catch (Exception ex) { Console.WriteLine($"发生错误:{ex.Message}"); throw; } }
使用Process类的完整版本
public async Task VideoOperationAsync(Stream inputStream) { try { if (inputStream.CanSeek) { inputStream.Position = 0; } var ffmpegProcess = new Process { StartInfo = new ProcessStartInfo { FileName = "ffmpeg", Arguments = "-i pipe:0 -b:v 2000k -c:v libx264 -movflags frag_keyframe+empty_moov -f mp4 pipe:1", RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true } }; // 捕获FFmpeg错误输出 string errorOutput = string.Empty; ffmpegProcess.ErrorDataReceived += (sender, e) => { if (!string.IsNullOrEmpty(e.Data)) { errorOutput += e.Data + Environment.NewLine; } }; ffmpegProcess.Start(); ffmpegProcess.BeginErrorReadLine(); // 异步写入输入流 var writeTask = Task.Run(async () => { try { await inputStream.CopyToAsync(ffmpegProcess.StandardInput.BaseStream); } finally { // 关闭输入流,告知FFmpeg输入结束 ffmpegProcess.StandardInput.BaseStream.Close(); } }); // 异步读取输出流 using var outputStream = new MemoryStream(); var readTask = Task.Run(async () => { await ffmpegProcess.StandardOutput.BaseStream.CopyToAsync(outputStream); }); // 等待读写任务完成,再等待进程退出 await Task.WhenAll(writeTask, readTask); ffmpegProcess.WaitForExit(); // 检查进程退出状态 if (ffmpegProcess.ExitCode != 0) { throw new InvalidOperationException($"FFmpeg执行失败:{errorOutput}"); } outputStream.Position = 0; // 替换为上传至DigitalOcean Spaces的逻辑 using (var fileStream = new FileStream(@"D:\Gremlin-data\VideoResized\output_cropped.mp4", FileMode.Create, FileAccess.Write)) { await outputStream.CopyToAsync(fileStream); } } catch (Exception ex) { Console.WriteLine($"发生错误:{ex.Message}"); throw; } }
关键注意事项
- 强制添加
-movflags frag_keyframe+empty_moov:这是管道输出MP4的核心配置,让FFmpeg边处理边输出碎片化数据,避免等待输入完全结束才生成文件。 - 明确指定编码器:使用
-c:v libx264确保FFmpeg使用支持流式输出的H.264编码器。 - 兼容不可查找流:先判断
inputStream.CanSeek再重置位置,避免异常。 - 捕获错误输出:FFmpeg的错误信息是排查编码失败的关键,必须收集并分析。
- 同步任务与进程:等待读写任务完成后再让进程退出,确保所有数据都被处理和读取。
内容的提问来源于stack exchange,提问作者Aryan Kumar
相关产品推荐
相关产品推荐

