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

C#调用FFmpeg将16位PCM转WebM时,进程写入首个缓冲区后立即退出(状态码0)的问题求助

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,根据需求选就行。

额外排查建议

  1. 收集完整FFmpeg日志:目前你只看到启动日志,没看到处理、编码和退出的完整日志,建议把所有stderr内容打出来,能帮你确认问题是否解决,以及后续有没有其他坑。
  2. 确认持续写入数据:确保调用第一次AppendAudioBuffer后,确实有持续调用它发送后续PCM数据,如果只发一次,FFmpeg处理完退出是正常的。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 11:40:27