2017-07-21 102 views
2

我試圖用FFmpeg從我的程序生成的幀中編碼視頻文件,然後將FFmpeg的輸出重定向回我的程序以避免產生中間視頻文件。.NET進程 - 重定向stdin和stdout而不會導致死鎖

不過,我碰到什麼似乎是在重定向時輸出System.Diagnostic.Process,在文檔here,這是言論提到,它會導致死鎖如果同步運行一個相當普遍的問題。

在將我的頭髮撕掉一整天后,嘗試在網上找到幾個建議的解決方案之後,我仍然無法找到使其工作的方法。我得到了一些數據,但這個過程總是在結束之前凍結。


下面的代碼片段產生與所述問題:

static void Main(string[] args) 
    { 

     Process proc = new Process(); 

     proc.StartInfo.FileName = @"ffmpeg.exe"; 
     proc.StartInfo.Arguments = String.Format("-f rawvideo -vcodec rawvideo -s {0}x{1} -pix_fmt rgb24 -r {2} -i - -an -codec:v libx264 -preset veryfast -f mp4 -movflags frag_keyframe+empty_moov -", 
      16, 9, 30); 
     proc.StartInfo.UseShellExecute = false; 
     proc.StartInfo.RedirectStandardInput = true; 
     proc.StartInfo.RedirectStandardOutput = true; 

     FileStream fs = new FileStream(@"out.mp4", FileMode.Create, FileAccess.Write); 

     //read output asynchronously 
     using (AutoResetEvent outputWaitHandle = new AutoResetEvent(false)) 
     { 
      proc.OutputDataReceived += (sender, e) => 
      { 
       if (e.Data == null) 
       { 
        outputWaitHandle.Set(); 
       } 
       else 
       { 
        string str = e.Data; 
        byte[] bytes = new byte[str.Length * sizeof(char)]; 
        System.Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length); 
        fs.Write(bytes, 0, bytes.Length); 
       } 
      }; 
     } 


     proc.Start(); 
     proc.BeginOutputReadLine(); 

     //Generate frames and write to stdin 
     for (int i = 0; i < 30*60*60; i++) 
     { 
      byte[] myArray = Enumerable.Repeat((byte)Math.Min(i,255), 9*16*3).ToArray(); 
      proc.StandardInput.BaseStream.Write(myArray, 0, myArray.Length); 
     } 

     proc.WaitForExit(); 
     fs.Close(); 
     Console.WriteLine("Done!"); 
     Console.ReadKey(); 

    } 

我目前正試圖這麼寫輸出到文件進行調試,但這不是數據如何將最終使用。

如果有人知道解決方案,將非常感激。

回答

1

你的進程掛起,因爲你永遠不會告訴ffmpeg你正在寫入StandardInput流。所以它仍然坐在那裏等着你發送更多的數據。

個人而言,我認爲這將是更可靠,更易於代碼使用普通文件作爲ffmpeg的輸入和輸出選項(即INFILEOUTFILE在命令行參數)。那麼你不需要重定向任何流。

但是,如果你想或者需要自己重定向—例如,你實際上產生輸入數據獨立的一些文件,而你正在消耗比文件其他某種方式輸出數據—你可以得到它通過正確使用Process類及其屬性來工作。

特別是,這意味着兩件事情:

  1. 你不能把輸出數據作爲字符時該程序用於流產生原始字節數據。
  2. 必須完成後關閉輸入流,以便程序知道輸入流的末尾已達到。

這裏是一個版本的程序,實際工作:

static void Main(string[] args) 
{ 
    Process proc = new Process(); 

    proc.StartInfo.FileName = @"ffmpeg.exe"; 
    proc.StartInfo.Arguments = String.Format("-f rawvideo -vcodec rawvideo -s {0}x{1} -pix_fmt rgb24 -r {2} -i - -an -codec:v libx264 -preset veryfast -f mp4 -movflags frag_keyframe+empty_moov -", 
     16, 9, 30); 
    proc.StartInfo.UseShellExecute = false; 
    proc.StartInfo.RedirectStandardInput = true; 
    proc.StartInfo.RedirectStandardOutput = true; 

    FileStream fs = new FileStream(@"out.mp4", FileMode.Create, FileAccess.Write); 

    proc.Start(); 
    var readTask = _ConsumeOutputAsync(fs, proc.StandardOutput.BaseStream); 

    //Generate frames and write to stdin 
    for (int i = 0; i < 30 * 60 * 60; i++) 
    { 
     byte[] myArray = Enumerable.Repeat((byte)Math.Min(i, 255), 9 * 16 * 3).ToArray(); 
     proc.StandardInput.BaseStream.Write(myArray, 0, myArray.Length); 
    } 

    proc.StandardInput.BaseStream.Close(); 

    readTask.Wait(); 
    fs.Close(); 
    Console.WriteLine("Done!"); 
    Console.ReadKey(); 
} 

private static async Task _ConsumeOutputAsync(FileStream fs, Stream baseStream) 
{ 
    int cb; 
    byte[] rgb = new byte[4096]; 

    while ((cb = await baseStream.ReadAsync(rgb, 0, rgb.Length)) > 0) 
    { 
     fs.Write(rgb, 0, cb); 
    } 
} 

需要注意的是,即使你是動態生成的數據,你的例子顯示,你寫輸出到文件中。如果你真的想在一個文件中輸出,那麼我會絕對是使用outfile命令行參數來指定輸出文件,而不是自己重定向StandardOutput。爲什麼在處理數據時注入自己的代碼,當你正在運行的外部進程將爲你處理所有這些工作?

我還是不知道你用AutoResetEvent這個對象試圖做什麼,因爲你永遠不會等待這個對象,並且你的實現被破壞了,因爲在你使用它之前你已經放棄了對象。因此上述修改過的示例都沒有嘗試複製該行爲。沒有它,它工作得很好。

+0

這確實解決了我的問題,所以謝謝你。我只想澄清一下,我確實在我的問題中指定了我只是爲了調試目的而寫入文件,除非需要,否則我不會重定向sdtout。然而,你對'AutoResetEvent'說得很對,建議使用'OutputDataReceived'的解決方案存在殘留。我已經嘗試了使用'async Task'的解決方案,因爲它沒有提及關閉輸入流。當然,關閉輸入流看起來相當明顯,它並沒有跨越我的想法,因爲當不重定向標準輸出時我不必這樣做。 – user1150856

相關問題