【问题标题】:Trying to redirect binary stdout of ffmpeg to NeroAacEnc stdin试图将 ffmpeg 的二进制标准输出重定向到 NeroAacEnc 标准输入
【发布时间】:2026-01-30 01:05:03
【问题描述】:

我正在尝试用 C# 2010 编写一个程序,通过 ffmpeg.exe 和 NeroAACenc.exe 将 mp3 文件转换为 m4a 格式的有声读物。 为此,我使用 Diagnostics.Process 类中的构建将 ffmpeg 的标准输出重定向到我的应用程序中 Nero 编码器的标准输入。

一切似乎都按预期工作,但出于某种原因 StandardOutput.BaseStream ffmpeg 有时会停止接收数据。该过程不会退出,并且 ErrorDataReceived 事件也不会引发。 生成的输出 m4a 文件的长度始终约为 2 分钟。如果我只是将 mp3 文件编码为临时 wav 文件而不提供 Nero,这同样适用。

我通过命令行尝试了同样的方法,这没有任何问题。

ffmpeg -i test.mp3 -f wav - | neroAacEnc -ignorelength -if - -of test.m4a 

谁能告诉我我在这里做错了什么? 提前致谢。

class Encoder
{
    private byte[] ReadBuffer = new byte[4096];
    private Process ffMpegDecoder = new Process();
    private Process NeroEncoder = new Process();
    private BinaryWriter NeroInput;

    //Create WAV temp file for testing
    private Stream s = new FileStream("D:\\test\\test.wav", FileMode.Create);
    private BinaryWriter outfile;

    public void Encode()
    {
        ProcessStartInfo ffMpegPSI = new ProcessStartInfo("ffmpeg.exe", "-i D:\\test\\test.mp3 -f wav -");
        ffMpegPSI.UseShellExecute = false;
        ffMpegPSI.CreateNoWindow = true;
        ffMpegPSI.RedirectStandardOutput = true;
        ffMpegPSI.RedirectStandardError = true;
        ffMpegDecoder.StartInfo = ffMpegPSI;

        ProcessStartInfo NeroPSI = new ProcessStartInfo("neroAacEnc.exe", "-if - -ignorelength -of D:\\test\\test.m4a");
        NeroPSI.UseShellExecute = false;
        NeroPSI.CreateNoWindow = true;
        NeroPSI.RedirectStandardInput = true;
        NeroPSI.RedirectStandardError = true;
        NeroEncoder.StartInfo = NeroPSI;

        ffMpegDecoder.Exited += new EventHandler(ffMpegDecoder_Exited);
        ffMpegDecoder.ErrorDataReceived += new DataReceivedEventHandler(ffMpegDecoder_ErrorDataReceived);
        ffMpegDecoder.Start();

        NeroEncoder.Start();
        NeroInput = new BinaryWriter(NeroEncoder.StandardInput.BaseStream);

        outfile = new BinaryWriter(s);

        ffMpegDecoder.StandardOutput.BaseStream.BeginRead(ReadBuffer, 0, ReadBuffer.Length, new AsyncCallback(ReadCallBack), null);

    }

    private void ReadCallBack(IAsyncResult asyncResult)
    {
        int read = ffMpegDecoder.StandardOutput.BaseStream.EndRead(asyncResult);
        if (read > 0)
        {

            NeroInput.Write(ReadBuffer);
            NeroInput.Flush();

            outfile.Write(ReadBuffer);
            outfile.Flush();

            ffMpegDecoder.StandardOutput.BaseStream.Flush();

            ffMpegDecoder.StandardOutput.BaseStream.BeginRead(ReadBuffer, 0, ReadBuffer.Length, new AsyncCallback(ReadCallBack), null);
        }
        else
        {
            ffMpegDecoder.StandardOutput.BaseStream.Close();
            outfile.Close();
        }

    }

    private void ffMpegDecoder_Exited(object sender, System.EventArgs e)
    {
        Console.WriteLine("Exit");
    }

    private void ffMpegDecoder_ErrorDataReceived(object sender, DataReceivedEventArgs errLine)
    {
        Console.WriteLine("Error");
    }

}

【问题讨论】:

    标签: c# ffmpeg stdout stdin redirectstandardoutput


    【解决方案1】:

    也许这个shell会帮助你

    ffmpeg -i tmp/jay001.mp3 -f wav pipe:1 | neroAacEnc -cbr 24000 -hev2 -ignorelength -if - -of tmp/jay001-24.m4a
    

    【讨论】:

      【解决方案2】:

      这个问题解决了吗?几周前(2014 年 11 月)我刚刚注意到这个问题,我可能有一个解决方案。代码模式如下所示,旨在对二进制标准输入和二进制标准输出数据进行操作。

      为了让它在我的程序中发挥作用,我必须考虑很多事情。

      1. stdin、stdout 可能代表大量数据。因此 StandardInput 和 StandardOutput 由外部来源提供。

      2. Win Forms 不处理没有复杂代码开发的异步事件。我通过使用计时器简化了问题。 (我相信 .NET 4.5 作为 async 和 await 可以简化异步复杂性,但我还没有使用它们。)

      3. Process.Exited 事件似乎并不可靠,因此我使用了另一种方法来确定“已完成”。我定义方法 OnFinish() 来广播“Finished”事件。确定“完成”需要两件事: 1) Ps 必须运行直到退出。使用 WaitForExit()。 2) 必须从 Ps.StandardOutput 读取所有数据。计时器检查 Ps 是否已退出并且 Ps.StandardOutput 没有更多数据要读取。如果两个条件都为真,则调用 OnFinish()。其他论坛上有迹象表明,您必须先 WaitForExit() 并阅读所有 StandardOutput,然后才能继续处理 StandardOutput 数据。确保 timer.Enabled 为真。我还使用了它的默认计时器。Interval = 100。

      4. StandardError(因为它不是二进制的,可能不是那么大)由它自己的事件处理程序处理。指挥官提供了自己的 StandardError 并将消息(如果有)提供给字符串 ErrorMessage。

      5. (显然可以观察到)进程被装箱到一个指挥官对象中。所以我不直接使用 Process 。我使用Commander,让Commander以更友好的使用方式使用Process。

        公共类指挥官{

        private bool DoneReadingStdOut { get; set; }
        
        private bool DoneWaitingForPs { get; set; }
        
        public string ErrorMessage { get; set; }
        
        public bool IsFinished { get; set; }
        
        private Process Ps { get; set; }
        
        private ProcessPriorityClass m_PriorityClass = ProcessPriorityClass.Normal;
        public ProcessPriorityClass PriorityClass {
            get
            {
                return m_PriorityClass;
            }
            set
            {
                m_PriorityClass = value;
            }
        }
        
        private MemoryStream m_StdIn = null;
        public MemoryStream StandardInput
        {
            get { return m_StdIn; }
            set
            {
                m_StdIn = value;
                Ps.StartInfo.RedirectStandardInput = (value == null ? false : true);
            }
        }
        
        private MemoryStream m_StdOut = null;
        public MemoryStream StandardOutput
        {
            get { return m_StdOut; }
            set
            {
                m_StdOut = value;
                Ps.StartInfo.RedirectStandardOutput = (value == null ? false : true);
            }
        }
        
        private Timer m_Timer;  // To synchronize asynchronous activity
        
        public Commander(string command, string options)
        {
            m_Timer = new Timer();
            m_Timer.Enabled;
            m_Timer.Tick += timer_Tick;
            ErrorMessage = null;
            IsFinished = false;
            Ps = new Process();
            Ps.ErrorDataReceived += Ps_ErrorDataReceived;
            Ps.StartInfo.Arguments = options;
            Ps.StartInfo.CreateNoWindow = true;
            Ps.StartInfo.FileName = command;
            Ps.StartInfo.RedirectStandardError = true;
            Ps.StartInfo.WorkingDirectory = Path.GetDirectoryName(command); // optional
        }
        
        public event EventHandler<EventArgs> Finished;
        private void OnFinish(EventArgs e)
        {
            if (IsFinished) return;
            IsFinished = true;
            if (Finished != null)
            {
                Finished(this, e);
            }
        }
        
        public void Run()
        {
            Start();
        }
        
        public void Run(ref MemoryStream stdin, ref MemoryStream stdout)
        {
            StandardInput = stdin;
            StandardOutput = stdout;
            Start();
        }
        
        private void Start()
        {
            Ps.Start();
            Ps.PriorityClass = m_PriorityClass;
            Ps.BeginErrorReadLine();
            if (StandardInput != null)
            {
                Inject();
            }
            AsyncExtract();
            Ps.WaitForExit();
            DoneWaitingForPs = true;
        }
        
        private void Inject()
        {
            StandardInput.Position = 0;
            StandardInput.CopyTo(Ps.StandardInput.BaseStream);
            Ps.StandardInput.BaseStream.Close();
        }
        
        private byte[] m_StreamData = null;
        private int m_StreamDataLength = 8192;
        private void AsyncExtract()
        {
            if (m_StreamData == null)
            {
                m_StreamData = new byte[m_StreamDataLength];
            }
            Ps.StandardOutput.BaseStream.BeginRead(
                    m_StreamData, 0, m_StreamDataLength,
                    new AsyncCallBack(StandardOutput_AsyncCallBack),
                    null
                    );
        }
        
        private void StandardOutput_AsyncCallBack(IAsyncResult asyncResult)
        {
            int stdoutreadlength = Ps.StandardOutput.BaseStream.EndRead(asyncResult);
            if (stdoutreadlength == 0)
            {
                Ps.StandardOutput.BaseStream.Close();
                DoneReadingStdOut = true;
            }
            else
            {
                StandardOutput.Write(m_StreamData, 0, stdoutreadlength);
                AsyncExtract();
            }
        }
        
        private void Ps_ErrorDataReceived(object sender, DataReceivedEventArgs e)
        {
            if (e.Data == null) return;
            ErrorMessage += e.Data;
        }
        
        private timer_Tick(object sender, EventArgs e)
        {
            if (DoneWaitingForPs && DoneReadingStdOut)
            {
                m_Timer.Enabled = false;
                OnFinish(new EventArgs());
            }
        }
        

        }

      【讨论】:

        【解决方案3】:

        我在向 ffmpeg 的StandardInput 发送二进制数据时遇到了类似的问题。当您将RedirectStandardError 设置为true 时,您必须以某种方式对其进行处理。或者,您可以将其设置为false

        否则,进程暂停等待您读取StandardError/StandardOutput,这可能会导致问题。

        【讨论】: