Попытка перенаправить двоичный стандартный вывод ffmpeg в стандартный вывод NeroAacEnc
Я пытаюсь написать программу на C# 2010, которая преобразует mp3-файлы в аудиокнигу в формате m4a через ffmpeg.exe и NeroAACenc.exe. Для этого я перенаправляю стандартный вывод ffmpeg на стандартный кодировщик Nero в моем приложении, используя встроенный класс Diagnostics.Process.
Кажется, что все работает, как и ожидалось, но по какой-то причине 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");
}
}
3 ответа
Я столкнулся с аналогичной проблемой отправки двоичных данных в ffmpeg StandardInput
, Когда вы установите RedirectStandardError
в true
, вы должны справиться с этим в некотором роде. Кроме того, вы можете установить его на false
,
В противном случае процесс приостанавливается, ожидая, что вы прочитаете StandardError
/StandardOutput
что может вызвать проблемы.
Это было решено? Я только что заметил этот вопрос несколько недель назад (ноябрь 2014), и у меня может быть решение. Шаблон кода представлен ниже и предназначен для работы с данными двоичного стандартного ввода и двоичного стандартного вывода.
Происходит много событий, которые я должен был рассмотреть, чтобы заставить это работать в моей программе.
stdin, stdout может представлять большой объем данных. Поэтому StandardInput и StandardOutput предоставляются внешними источниками.
Win Forms не обрабатывает асинхронные события без сложной разработки кода. Я упростил проблему с помощью таймера. (Я считаю, что.NET 4.5 асинхронен и ожидаю упрощения асинхронных сложностей, но я пока не использую их.)
Событие Process.Exited не показалось надежным, поэтому я использовал альтернативный метод для определения "Завершено". Я определяю метод OnFinish() для трансляции события "Закончено". Для определения "Готово" необходимы две вещи: 1) Ps должны бежать до выхода. WaitForExit() используется. 2) Все данные должны быть прочитаны из Ps.StandardOutput. Таймер проверяет, что Ps вышел и у Ps.StandardOutput больше нет данных для чтения. Если оба условия выполняются, он вызывает OnFinish(). На других форумах есть указания на то, что вы должны как WaitForExit (), так и прочитать все StandardOutput, прежде чем сможете продолжить обработку данных StandardOutput. Убедитесь, что timer.Enabled - это правда. Я также использовал значение по умолчанию timer.Interval = 100.
Ошибка StandardError (поскольку она не двоичная и, вероятно, не такая большая) была обработана собственным обработчиком событий. И Commander предоставил свой собственный StandardError и передал сообщения (если они есть) в строку ErrorMessage.
(как очевидно, замечено) Процесс упакован в объект Commander. Поэтому я не использую 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()); } }
}
Может быть, эта оболочка поможет вам
ffmpeg -i tmp/jay001.mp3 -f wav pipe:1 | neroAacEnc -cbr 24000 -hev2 -ignorelength -if - -of tmp/jay001-24.m4a