C# 尝试将ffmpeg的二进制标准输出重定向到NeroAacEnc标准输出

C# 尝试将ffmpeg的二进制标准输出重定向到NeroAacEnc标准输出,c#,ffmpeg,stdout,stdin,redirectstandardoutput,C#,Ffmpeg,Stdout,Stdin,Redirectstandardoutput,我正试图用C#2010编写一个程序,通过ffmpeg.exe和NeroAACenc.exe将mp3文件转换成m4a格式的有声图书。 为此,我使用内置Diagnostics.Process类将ffmpeg的stdout重定向到应用程序中Nero编码器的stdin 一切似乎都按预期工作,但出于某种原因,StandardOutput.BaseStream 的ffmpeg在某个时间停止接收数据。进程不会退出,并且也不会引发ErrorDataReceived事件。 生成的输出m4a文件的长度始终为~2分钟

我正试图用C#2010编写一个程序,通过ffmpeg.exe和NeroAACenc.exe将mp3文件转换成m4a格式的有声图书。 为此,我使用内置Diagnostics.Process类将ffmpeg的stdout重定向到应用程序中Nero编码器的stdin

一切似乎都按预期工作,但出于某种原因,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");
    }

}

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


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

是否已解决此问题?我几周前(2014年11月)才注意到这个问题,我可能有一个解决方案。代码模式如下所示,用于对二进制stdin和二进制stdout数据进行操作

有很多事情要做,我必须考虑让它在我的程序中工作。

  • stdin、stdout可以表示大量数据。因此,标准输入和标准输出由外部来源提供

  • Win窗体在没有复杂代码开发的情况下不会处理异步事件。我用定时器简化了这个问题。(我认为.NET4.5是异步的,并等待着简化异步的复杂性,但我还没有使用它们。)

  • 退出的事件似乎不可靠,所以我使用了另一种方法来确定“已完成”。我定义了OnFinish()方法来广播“Finished”事件。确定“完成”需要两件事:1)Ps必须运行直到退出。使用WaitForExit()。2) 所有数据必须从Ps.StandardOutput读取。计时器检查Ps是否已退出,以及Ps.StandardOutput是否没有更多数据可读取。如果两个条件都为true,则调用OnFinish()。在其他论坛上有迹象表明,在继续处理StandardOutput数据之前,您必须同时读取WaitForExit()和所有StandardOutput。确保timer.Enabled为true。我还使用了它的默认计时器

  • StandardError(因为它不是二进制的,也可能不是那么大)是由它自己的事件处理程序处理的。指挥官提供了自己的StandardError,并将消息(如果有)输入到string ErrorMessage

  • (如明显观察到的)进程被装箱到指挥官对象中。所以我不直接使用流程。我使用Commander,并让Commander以更友好、更实用的方式使用流程

    公营班长{

    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());
        }
    }
    
    private bool DoneReadingStdOut{get;set;}
    私有bool DoneWaitingForPs{get;set;}
    公共字符串错误消息{get;set;}
    公共bool已完成{get;set;}
    私有进程Ps{get;set;}
    private ProcessPriorityClass m_PriorityClass=ProcessPriorityClass.Normal;
    公共进程PriorityClass PriorityClass{
    得到
    {
    返回m_PriorityClass;
    }
    设置
    {
    m_PriorityClass=值;
    }
    }
    私有内存流m_StdIn=null;
    公共内存流标准输入
    {
    获取{return m_StdIn;}
    设置
    {
    m_StdIn=数值;
    Ps.StartInfo.RedirectStandardInput=(值==null?false:true);
    }
    }
    私有内存流m_StdOut=null;
    公共内存流标准输出
    {
    获取{return m_StdOut;}
    设置
    {
    m_StdOut=值;
    Ps.StartInfo.RedirectStandardOutput=(值==null?false:true);
    }
    }
    private Timer m_Timer;//同步异步活动
    公共指挥官(字符串命令、字符串选项)
    {
    m_Timer=新定时器();
    m_定时器已启用;
    m_Timer.Tick+=定时器_Tick;
    ErrorMessage=null;
    IsFinished=false;
    Ps=新工艺();
    Ps.ErrorDataReceived+=Ps_ErrorDataReceived;
    Ps.StartInfo.Arguments=选项;
    Ps.StartInfo.CreateNoWindow=true;
    Ps.StartInfo.FileName=命令;
    Ps.StartInfo.RedirectStandardError=true;
    Ps.StartInfo.WorkingDirectory=Path.GetDirectoryName(命令);//可选
    }
    公共事件事件处理程序已完成;
    私有void OnFinish(事件参数e)
    {
    如果(完成)返回;
    IsFinished=true;
    如果(已完成!=null)
    {
    完成(本,e);
    }
    }
    公开募捐
    {
    Start();
    }
    公共无效运行(ref MemoryStream stdin,ref MemoryStream stdout)
    {
    标准输入=标准输入;
    标准输出=标准输出;
    Start();
    }
    私有void Start()
    {
    Ps.Start();
    Ps.PriorityClass=m_PriorityClass;
    Ps.BeginErrorReadLine();
    if(StandardInput!=null)
    {
    注入();
    }
    异步提取();
    Ps.WaitForExit();
    DoneWaitingForPs=真;
    }
    私有无效注入()
    {
    标准输入。位置=0;
    StandardInput.CopyTo(Ps.StandardInput.BaseStream);
    Ps.StandardInput.BaseStream.Close();
    }
    私有字节[]m_StreamData=null;
    private int m_StreamDataLength=8192;
    私有void AsyncExtract()
    {
    如果(m_StreamData==null)
    {
    m_StreamData=新字节[m_StreamDataLength];
    }
    Ps.StandardOutput.BaseStream.BeginRead(
    m_StreamData,0,m_StreamDataLength,
    新的AsyncCallBack(标准输出\u AsyncCallBack),
    无效的
    );
    }
    私有void StandardOutput\u AsyncCallBack(IAsyncResult asyncResult)
    {
    int stdouredLength=Ps.StandardOutput.BaseStream.EndRead(asyncResult);
    如果(stdoureadlength==0)
    {
    Ps.StandardOutput.BaseStream.Close();
    DoneReadingStdOut=true;
    }
    其他的
    
    ffmpeg -i tmp/jay001.mp3 -f wav pipe:1 | neroAacEnc -cbr 24000 -hev2 -ignorelength -if - -of tmp/jay001-24.m4a