【问题标题】:Writing to two standard input pipes from C#从 C# 写入两个标准输入管道
【发布时间】:2021-04-09 02:27:38
【问题描述】:

我正在使用我的 C# 应用程序中的 FFMPEG 从原始未编码帧构建视频流。对于一个输入流,这相当简单:

var argumentBuilder = new List<string>();
argumentBuilder.Add("-loglevel panic");
argumentBuilder.Add("-f h264");
argumentBuilder.Add("-i pipe:");
argumentBuilder.Add("-c:v libx264");
argumentBuilder.Add("-bf 0");
argumentBuilder.Add("-pix_fmt yuv420p");
argumentBuilder.Add("-an");
argumentBuilder.Add(filename);

startInfo.Arguments = string.Join(" ", argumentBuilder.ToArray());

var _ffMpegProcess = new Process();
_ffMpegProcess.EnableRaisingEvents = true;
_ffMpegProcess.OutputDataReceived += (s, e) => { Debug.WriteLine(e.Data); };
_ffMpegProcess.ErrorDataReceived += (s, e) => { Debug.WriteLine(e.Data); };

_ffMpegProcess.StartInfo = startInfo;

Console.WriteLine($"[log] Starting write to {filename}...");

_ffMpegProcess.Start();
_ffMpegProcess.BeginOutputReadLine();
_ffMpegProcess.BeginErrorReadLine();

for (int i = 0; i < videoBuffer.Count; i++)
{
    _ffMpegProcess.StandardInput.BaseStream.Write(videoBuffer[i], 0, videoBuffer[i].Length);
}

_ffMpegProcess.StandardInput.BaseStream.Close();

我试图解决的挑战之一是写入两个输入管道,类似于我可以通过引用 pipe:4pipe:5 从 Node.js 做到这一点。 似乎我只能直接写入标准输入,但不能将其拆分为“通道”。

在 C# 中执行此操作的方法是什么?

【问题讨论】:

  • 目的是什么?你想让 ffmpeg 在输出文件中创建两个视频流吗?
  • 你必须使用NamedPipeServerStream...没有写多个NamedPipeServerStream的例子...Here建议异步写...
  • @CaiusJard 不,我想合并视频和音频流,ffmpeg 通过分离管道来支持。但是,似乎没有像在其他平台上那样简单的方法。
  • 谢谢@xanatos - 这似乎是正确的方法。我将探索该选项并报告结果。随意张贴这个问题的答案,这样我就可以在我完成实验后将其标记出来(你得到分数)。

标签: c# ffmpeg process console-application stdin


【解决方案1】:

根据here 所写的内容和一夜好眠(我梦想可以使用Stream.CopyAsync),这是解决方案的骨架:

string pathToFFmpeg = @"C:\ffmpeg\bin\ffmpeg.exe";

string[] inputs = new[] { "video.m4v", "audio.mp3" };

string output = "output2.mp4";

var npsss = new NamedPipeServerStream[inputs.Length];
var fss = new FileStream[inputs.Length];

try
{
    for (int i = 0; i < fss.Length; i++)
    {
        fss[i] = File.OpenRead(inputs[i]);
    }

    // We use Guid for pipeNames
    var pipeNames = Array.ConvertAll(inputs, x => Guid.NewGuid().ToString("N"));

    for (int i = 0; i < npsss.Length; i++)
    {
        npsss[i] = new NamedPipeServerStream(pipeNames[i], PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
    }

    string pipeNamesFFmpeg = string.Join(" ", pipeNames.Select(x => $@"-i \\.\pipe\{x}"));

    using (var proc = new Process
    {
        StartInfo = new ProcessStartInfo
        {
            FileName = pathToFFmpeg,
            Arguments = $@"-loglevel debug -y {pipeNamesFFmpeg} -c:v copy -c:a copy ""{output}""",
            UseShellExecute = false,
        }
    })
    {
        Console.WriteLine($"FFMpeg path: {pathToFFmpeg}");
        Console.WriteLine($"Arguments: {proc.StartInfo.Arguments}");

        proc.EnableRaisingEvents = false;
        proc.Start();

        var tasks = new Task[npsss.Length];

        for (int i = 0; i < npsss.Length; i++)
        {
            var pipe = npsss[i];
            var fs = fss[i];

            pipe.WaitForConnection();

            tasks[i] = fs.CopyToAsync(pipe)
                // .ContinueWith(_ => pipe.FlushAsync()) // Flush does nothing on Pipes
                .ContinueWith(x => {
                    pipe.WaitForPipeDrain();
                    pipe.Disconnect();
                });
        }

        Task.WaitAll(tasks);

        proc.WaitForExit();
    }
}
finally
{
    foreach (var fs in fss)
    {
        fs?.Dispose();
    }

    foreach (var npss in npsss)
    {
        npss?.Dispose();
    }
}

有各种注意点:

  • 并非所有格式都与管道兼容。例如,许多 .mp4 不是,因为它们的 moov 原子位于文件末尾,但 ffmpeg 立即需要它,并且管道不可搜索(ffmpeg 无法到达管道末尾,请阅读 moov atom 然后转到管道的开头)。以here 为例

  • 我在流式传输结束时收到错误消息。该文件似乎是正确的。我不知道为什么。其他人发出了信号,但我没有看到任何解释

    \.\pipe\55afc0c8e95f4a4c9cec5ae492bc518a:参数无效 \.\pipe\92205c79c26a410aa46b9b35eb3bbff6:参数无效

  • 我通常不使用TaskAsync,所以我不能100% 确定我写的内容是否正确。例如,此代码不起作用:

    tasks[i] = pipe.WaitForConnectionAsync().ContinueWith(x => fs.CopyToAsync(pipe, 4096)).ContinueWith(...);
    

    嗯,也许最后一个可以解决:

    tasks[i] = ConnectAndCopyToPipe(fs, pipe);
    

    然后

    public static async Task ConnectAndCopyToPipe(FileStream fs, NamedPipeServerStream pipe)
    {
        await pipe.WaitForConnectionAsync();
        await fs.CopyToAsync(pipe);
        // await fs.FlushAsync(); // Does nothing
        pipe.WaitForPipeDrain();
        pipe.Disconnect();
    }
    

【讨论】:

  • 我将此标记为答案,因为它确实朝着正确的方向发展。似乎ffmpeg 不能很好地处理双流(至少是 MP4 视频帧和 AAC 音频),每次我尝试使用它时,它都会死锁或不使用流。我最终首先对视频进行了编码,然后在另一个 ffmpeg 运行的帮助下覆盖了音频。感谢@xanatos 提供有关命名管道的指针 - 这对我来说是一个“灵光乍现”的时刻。
  • @DenDelimarsky 正如我所写,mp4 流存在问题,因为它们的“标题”接近尾声
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-23
  • 1970-01-01
  • 2011-06-10
  • 1970-01-01
  • 1970-01-01
  • 2019-09-11
相关资源
最近更新 更多