C#调用FFmpeg将16位PCM转WebM时,进程写入首个缓冲区后立即退出(状态码0)的问题求助
问题描述
我最近在开发一个实时音频编码功能,用C#调用FFmpeg把16位PCM(s16le)音频转换成WebM格式,通过管道做输入输出,最终推送到WebSocket。但遇到了一个奇怪的问题:FFmpeg进程在我写入第一个音频缓冲区后就立刻退出了,退出状态码是0(显示“成功完成”),可明明还有后续的PCM数据要处理啊!
我检查了FFmpeg的启动参数,也确认进程能正常启动,但就是找不到提前退出的原因。以下是我的代码和FFmpeg的部分日志,有没有大佬能帮忙分析下?
相关代码
public class SpeechService : ISpeechService { /// <summary> /// Defines the _audioInputStream /// </summary> private readonly MemoryStream _audioInputStream = new MemoryStream(); private Process _ffmpegProcess; private readonly string _ffmpegPath; private readonly ILogger _logger; private readonly WebSocket _ws; public async Task SendPcmAsWebmViaWebSocketAsync( MemoryStream pcmAudioStream, int sampleRate, int channels) { string inputFormat = "s16le"; var ffmpegProcessInfo = new ProcessStartInfo { FileName = _ffmpegPath, Arguments = $"-f {inputFormat} -ar {sampleRate} -ac {channels} -i pipe:0 " + $"-f webm pipe:1", RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = true, UseShellExecute = false, CreateNoWindow = true, }; _ffmpegProcess = new Process { StartInfo = ffmpegProcessInfo }; Console.WriteLine("Starting FFmpeg process..."); try { if (!await Task.Run(() => _ffmpegProcess.Start())) { Console.Error.WriteLine("Failed to start FFmpeg process."); return; } Console.WriteLine("FFmpeg process started."); } catch (Exception ex) { Console.Error.WriteLine($"Error starting FFmpeg process: {ex.Message}"); throw; } var encodeAndSendTask = Task.Run(async () => { try { using var ffmpegOutputStream = _ffmpegProcess.StandardOutput.BaseStream; byte[] buffer = new byte[8192]; // Temporary buffer to read data byte[] sendBuffer = new byte[8192]; // Buffer to accumulate data for sending int sendBufferIndex = 0; // Tracks the current size of sendBuffer int bytesRead; Console.WriteLine("Reading WebM output from FFmpeg and sending via WebSocket..."); while (true) { if ((bytesRead = await ffmpegOutputStream.ReadAsync(buffer, 0, buffer.Length)) > 0) { // Copy data to sendBuffer Array.Copy(buffer, 0, sendBuffer, sendBufferIndex, bytesRead); sendBufferIndex += bytesRead; // If sendBuffer is full, send it via WebSocket if (sendBufferIndex >= sendBuffer.Length) { var segment = new ArraySegment<byte>(sendBuffer, 0, sendBuffer.Length); _ws.SendMessage(segment); sendBufferIndex = 0; // Reset the index after sending } } } } catch (OperationCanceledException) { Console.WriteLine("Encode/Send operation cancelled."); } catch (IOException ex) when (ex.InnerException is ObjectDisposedException) { Console.WriteLine("Stream was closed, likely due to process exit or cancellation."); } catch (Exception ex) { Console.Error.WriteLine($"Error during encoding/sending: {ex}"); } }); var errorReadTask = Task.Run(async () => { Console.WriteLine("Starting to read FFmpeg stderr..."); using var errorReader = _ffmpegProcess.StandardError; try { string? line; while ((line = await errorReader.ReadLineAsync()) != null) { Console.WriteLine($"[FFmpeg stderr] {line}"); } } catch (OperationCanceledException) { Console.WriteLine("FFmpeg stderr reading cancelled."); } catch (TimeoutException) { Console.WriteLine("FFmpeg stderr reading timed out (due to cancellation)."); } catch (Exception ex) { Console.Error.WriteLine($"Error reading FFmpeg stderr: {ex.Message}"); } Console.WriteLine("Finished reading FFmpeg stderr."); }); } public async Task AppendAudioBuffer(AudioMediaBuffer audioBuffer) { try { // audio for a 1:1 call var bufferLength = audioBuffer.Length; if (bufferLength > 0) { var buffer = new byte[bufferLength]; Marshal.Copy(audioBuffer.Data, buffer, 0, (int)bufferLength); _logger.Info("_ffmpegProcess.HasExited:" + _ffmpegProcess.HasExited); using var ffmpegInputStream = _ffmpegProcess.StandardInput.BaseStream; await ffmpegInputStream.WriteAsync(buffer, 0, bufferLength); await ffmpegInputStream.FlushAsync(); // バッファをフラッシュ _logger.Info("Wrote buffer data."); } } catch (Exception e) { _logger.Error(e, "Exception happend writing to input stream"); } } }
FFmpeg部分日志
Starting FFmpeg process...
FFmpeg process started.
Starting to read FFmpeg stderr...
Reading WebM output from FFmpeg and sending via WebSocket...
[FFmpeg stderr] ffmpeg version 7.1.1-essentials_build-www.gyan.dev Copyright (c) 2000-2025 the FFmpeg developers
[FFmpeg stderr] built with gcc 14.2.0 (Rev1, Built by MSYS2 project)
[FFmpeg stderr] configuration: --enable-gpl --enable-version3 --enable-static --disable-w32threads --disable-autodetect --enable-fontconfig --enable-iconv --enable-gnutls --enable-libxml2 --enable-gmp --enable-bzlib --enable-lzma --enable-zlib --enable-libsrt --enable-libssh --enable-libzmq --enable-avisynth --enable-sdl2 --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxvid --enable-libaom --enable-libopenjpeg --enable-libvpx --enable-mediafoundation --enable-libass --enable-libfreetype --enable-libfribidi --enable-libharfbuzz --enable-li...
问题分析与解决方案
兄弟,我一眼就瞅到你代码里的致命问题了!就是AppendAudioBuffer方法里的这段代码:
using var ffmpegInputStream = _ffmpegProcess.StandardInput.BaseStream; await ffmpegInputStream.WriteAsync(buffer, 0, bufferLength); await ffmpegInputStream.FlushAsync();
你用using包裹了FFmpeg的标准输入流!using的作用是代码块结束时自动调用Dispose(),这就意味着你每次写完一个缓冲区,都会直接关闭FFmpeg的输入流!FFmpeg一检测到输入流被关闭,就会认为所有数据都传完了,处理完当前数据就正常退出(所以状态码是0),这完全符合它的逻辑!
解决方案1:修复输入流的错误使用
把using去掉,直接复用FFmpeg的StandardInput流就行,只有当所有PCM数据都发送完,你才需要手动关闭输入流。修改后的AppendAudioBuffer应该是这样:
public async Task AppendAudioBuffer(AudioMediaBuffer audioBuffer) { try { var bufferLength = audioBuffer.Length; // 先检查FFmpeg进程是否存活,避免无效操作 if (bufferLength > 0 && !_ffmpegProcess?.HasExited ?? false) { var buffer = new byte[bufferLength]; Marshal.Copy(audioBuffer.Data, buffer, 0, (int)bufferLength); _logger.Info("_ffmpegProcess.HasExited:" + _ffmpegProcess.HasExited); // 直接用进程的StandardInput流,别再用using包裹了! await _ffmpegProcess.StandardInput.BaseStream.WriteAsync(buffer, 0, bufferLength); await _ffmpegProcess.StandardInput.BaseStream.FlushAsync(); _logger.Info("Wrote buffer data."); } } catch (Exception e) { _logger.Error(e, "Exception happened writing to input stream"); } }
解决方案2:优化FFmpeg编码参数
另外,建议你明确指定WebM的音频编码器,WebM通常搭配Opus或Vorbis使用,不指定的话FFmpeg可能用默认编码器,偶尔会有兼容性问题。修改后的参数可以是:
Arguments = $"-f {inputFormat} -ar {sampleRate} -ac {channels} -i pipe:0 " + $"-c:a opus -f webm pipe:1"
如果偏好Vorbis就用-c:a libvorbis,根据需求选就行。
额外排查建议
- 收集完整FFmpeg日志:目前你只看到启动日志,没看到处理、编码和退出的完整日志,建议把所有stderr内容打出来,能帮你确认问题是否解决,以及后续有没有其他坑。
- 确认持续写入数据:确保调用第一次
AppendAudioBuffer后,确实有持续调用它发送后续PCM数据,如果只发一次,FFmpeg处理完退出是正常的。
内容来源于stack exchange

