从 C# 写入两个标准输入管道
Writing to two standard input pipes from 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], 0, videoBuffer[i].Length);
}
_ffMpegProcess.StandardInput.BaseStream.Close();
我试图解决的挑战之一是写入两个输入管道,类似于我可以通过引用 pipe:4
或 [= 从 Node.js 做到这一点12=]。 似乎我只能直接写入标准输入而不能将其拆分为“通道”。
在 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 atom 然后转到管道的开头)。例如参见 [=18=]
我在流媒体结束时收到错误消息。该文件似乎是正确的。我不知道为什么。其他人发信号了,但我没看到任何解释
\.\pipeafc0c8e95f4a4c9cec5ae492bc518a: 参数无效
\.\pipe205c79c26a410aa46b9b35eb3bbff6: 无效参数
我一般不使用Task
和Async
,所以我不能100%确定我写的是否正确。此代码不起作用,例如:
tasks[i] = pipe.WaitForConnectionAsync().ContinueWith(x => fs.CopyToAsync(pipe, 4096)).ContinueWith(...);
emmmh也许最后可以解决:
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();
}
我正在使用我的 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:4
或 [= 从 Node.js 做到这一点12=]。 似乎我只能直接写入标准输入而不能将其拆分为“通道”。
在 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 atom 然后转到管道的开头)。例如参见 [=18=]
我在流媒体结束时收到错误消息。该文件似乎是正确的。我不知道为什么。其他人发信号了,但我没看到任何解释
\.\pipeafc0c8e95f4a4c9cec5ae492bc518a: 参数无效 \.\pipe205c79c26a410aa46b9b35eb3bbff6: 无效参数
我一般不使用
Task
和Async
,所以我不能100%确定我写的是否正确。此代码不起作用,例如:tasks[i] = pipe.WaitForConnectionAsync().ContinueWith(x => fs.CopyToAsync(pipe, 4096)).ContinueWith(...);
emmmh也许最后可以解决:
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(); }