I am trying to write a program in C# 2010 that converts mp3 files to an audio book in m4a format via ffmpeg.exe and NeroAACenc.exe. For doing so I redirect stdout of ffmpeg to stdin of the Nero encoder within my application using the build in Diagnostics.Process class.
Everything seems to work as expected but for some reason StandardOutput.BaseStream of ffmpeg stops receiving data at some time. The process does not exit and the ErrorDataReceived event is also not getting raised. The produced output m4a file has always a length of ~2 minutes. The same applies if I just encode the mp3 file to a temp wav file without feeding Nero.
I tried the same via the command line and this works without any problem.
ffmpeg -i test.mp3 -f wav - | neroAacEnc -ignorelength -if - -of test.m4a
Can anyone please tell me what I am doing wrong here? Thanks in advance.
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 += 开发者_如何学Cnew 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");
}
}
I ran into a similar problem sending binary data to ffmpeg's StandardInput
. When you set RedirectStandardError
to true
, you must handle it in some way. Alternatively, you can set it to false
.
Otherwise, the process suspends waiting for you to read the StandardError
/StandardOutput
which can cause problems.
Has this been resolved? I just noticed this question a few weeks ago (Nov 2014) and I may have a solution. Code pattern is presented below and is meant to operate on binary stdin and binary stdout data.
There is a lot going on that I had to consider to get this to work in my program.
stdin, stdout could represent a large amount of data. Therefore StandardInput and StandardOutput are provided by external sources.
Win Forms does not handle Asynchronous events without complex code development. I simplified the problem by using a timer. (I believe .NET 4.5 as async and await to simplify asynchronous complexities, but I'm not using them yet.)
Process.Exited event didn't seem reliable so I used an alternate method to determine "Finished". I define method OnFinish() to broadcast the "Finished" event. Two things are necessary to determined "Finished": 1) The Ps must run until exit. WaitForExit() is used. 2) All data must be read from Ps.StandardOutput. The timer checks that the Ps has exited and that Ps.StandardOutput has no more data to read. If both conditions are true, it calls OnFinish(). There is indication on other forums that you must both WaitForExit() and read all of StandardOutput before you can continue processing StandardOutput data. Make sure timer.Enabled is true. I also used its default timer.Interval = 100.
The StandardError (as it is not binary, and probably not so large) was handled by its own event handler. And the Commander provided its own StandardError and fed the messages (if any) to string ErrorMessage.
(as obviously observed) Process is boxed into a Commander object. So I don't use Process directly. I use Commander and let Commander use Process in a more friendly usable way.
public class 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()); } }
}
Maybe this shell will help you
ffmpeg -i tmp/jay001.mp3 -f wav pipe:1 | neroAacEnc -cbr 24000 -hev2 -ignorelength -if - -of tmp/jay001-24.m4a
精彩评论