.NET进程 - 重定向stdin和stdout,而不会导致死锁 [英] .NET Process - Redirect stdin and stdout without causing deadlock

查看:294
本文介绍了.NET进程 - 重定向stdin和stdout,而不会导致死锁的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用FFmpeg从我的程序生成的帧编码视频文件,然后将FFmpeg的输出重定向到我的程序,以避免有一个中间的视频文件。



但是,在 System.Diagnostic.Process 中重定向输出时,我遇到了一个相当常见的问题,文档这里,这是它导致如果同步运行,则会导致死锁。



在这一天撕裂了我的头发,并尝试了几个在线提出的解决方案,我仍然找不到一种方法来使其工作。我得到一些数据,但这个过程总是在完成之前冻结。






这是一个代码段,产生所述问题

$ b $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ 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);

//异步读取输出
使用(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();

//生成框架并写入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();

}

目前我正在尝试将输出写入文件无论如何,为了调试目的,但这不是最终将如何使用数据。



如果有人知道解决方案,将非常感激。

解决方案

您的进程挂起,因为您不会告诉ffmpeg您已经写入 StandardInput 。所以它仍然坐在那里等待你发送更多的数据。



个人而言,我认为使用普通文件作为输入是更可靠和更容易的代码,输出选项ffmpeg(即 infile outfile 在命令行参数)。那么你不需要重定向任何流。



但是,如果你想要或需要重定向自己—例如,您实际上是生成独立于某个文件的输入数据,而您以某种方式使用输出数据而不是文件—您可以通过使用进程类及其属性正确地使其正常工作。



具体来说,这意味着几件事:


  1. 当程序生成流的原始字节数据时,不能将输出数据视为字符。 li>
  2. 您完成后,您必须关闭输入流,以便程序知道输入流的结束已达到。

这是一个实际工作的程序版本:

  static void Main(string [] args)
{
进程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);

//生成框架并写入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();
}

私有静态异步任务_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 对象,因为你从来没有等待对象,并且你的实现被打破了,因为你在处理对象之前,你曾经使用它。所以没有关于上述修改的示例尝试复制该行为。没有它就可以正常工作。


I'm trying to encode a video file with FFmpeg from frames generated by my program and then redirect the output of FFmpeg back to my program to avoid having an intermediate video file.

However, I've run into what seems to be a rather common problem when redirecting outputs in System.Diagnostic.Process, mentioned in the remarks of the documentation here, which is that it causes a deadlock if run synchronously.

After tearing my hair out over this for a day, and trying several proposed solutions found online, I still cannot find a way to make it work. I get some data out, but the process always freezes before it finishes.


Here is a code snippet that produces said problem:

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();

    }

Currently i'm trying to write the output to a file anyway for debugging purposes, but this is not how the data will eventually be used.

If anyone knows a solution it would be very much appreciated.

解决方案

Your process hangs because you never tell ffmpeg that you're done writing to the StandardInput stream. So it's still sitting there waiting for you to send it more data.

Personally, I think it would be more reliable and easier to code to use regular files as the input and output options for ffmpeg (i.e. infile and outfile in the command line arguments). Then you don't need to redirect any stream.

But, if you do want or need to redirect yourself — for example, you are in fact generating the input data independent of some file, and you are consuming the output data in some way other than a file — you can get it to work by using the Process class and its properties correctly.

In particular, this means a couple of things:

  1. You can't treat the output data as characters when the program is producing raw byte data for the stream.
  2. You must close the input stream when you are done, so that the program knows that the end of the input stream has been reached.

Here is a version of your program that actually works:

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);
    }
}

Note that even if you are generating the data dynamically, your example shows you writing the output to a file. If you really want the output in a file, then I would definitely use the outfile command line parameter to specify the output file and not redirect StandardOutput yourself. Why inject your own code in the handling of the data when the external process you're running will handle all that work for you?

I still have no idea what you were trying to do with the AutoResetEvent object, since you never waited on the object, and your implementation of it was broken, because you disposed the object before you ever got around to using it. So nothing about the above revised example attempts to replicate that behavior. It works fine without it.

这篇关于.NET进程 - 重定向stdin和stdout,而不会导致死锁的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆