如何解决从 C# 写入两个标准输入管道
我使用 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],videoBuffer[i].Length);
}
_ffMpegProcess.StandardInput.BaseStream.Close();
我试图解决的挑战之一是写入两个输入管道,类似于我可以通过引用 pipe:4
或 pipe:5
在 Node.js 中执行此操作的方式。 似乎我只能直接写入标准输入,而不能将其拆分为“通道”。
在 C# 中执行此操作的方法是什么?
解决方法
根据所写的内容 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原子然后转到管道的开头)。参见here例如
-
我在流式传输结束时收到错误消息。该文件似乎是正确的。我不知道为什么。其他一些人发出了信号,但我没有看到任何解释
\.\pipe\55afc0c8e95f4a4c9cec5ae492bc518a:参数无效 \.\pipe\92205c79c26a410aa46b9b35eb3bbff6:参数无效
-
我通常不使用
Task
和Async
,所以我不能 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(); }
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。