将各个帧作为 BGRA 字节数组传递,并通过管道将时间戳设置为 FFmpeg

问题描述 投票:0回答:1

我有一组图像(如 BGRA

byte[]
),其各自的时间戳以毫秒为单位,我想将其传递给 FFmpeg 来构建动画。

我现在使用 FFmpeg v6,在本例中我期望输出 GIF,但稍后我将导出为多种格式。

var arguments = "-vsync passthrough  
-f rawvideo 
-pix_fmt bgra 
-video_size {width}x{height} 
-i -  
-loop 0 
-lavfi palettegen=stats_mode=diff[pal],[0:v][pal]paletteuse=new=1:dither=sierra2_4a:diff_mode=rectangle 
-f gif 
-y \"C:\Users\User\Desktop\test.gif\"";

var process = new Process
{
    StartInfo = new ProcessStartInfo
    {
        FileName = "./ffmpeg.exe",
        Arguments = arguments.Replace("{width}", width.ToString()).Replace("{height}", height.ToString()),
        RedirectStandardInput = true,
        RedirectStandardOutput = true,
        UseShellExecute = false,
        CreateNoWindow = true
    }
};

_process.Start();

然后在渲染循环中,我尝试一一发送帧及其时间戳。

public void EncodeFrame(IntPtr bufferAddress, int bufferStride, int width, int height, int index, long timestamp, int delay)
{
    var frameSize = height * bufferStride;
    var frameBytes = new byte[frameSize];
    System.Runtime.InteropServices.Marshal.Copy(bufferAddress, frameBytes, 0, frameSize);

    _process.StandardInput.BaseStream.Write(frameBytes, 0, frameSize);
    _process.StandardInput.BaseStream.Write(_delimiter, 0, _delimiter.Length);
    _process.StandardInput.BaseStream.Write(BitConverter.GetBytes(timestamp), 0, sizeof(long));
}

问题是我收到 IOException (管道已结束),所以可能我没有正确发送帧(不发送分隔符和时间戳没有帮助)。

这可能吗?

c# ffmpeg pipe
1个回答
0
投票

我也在另一个问题中回答了同样的问题,所以下面的文字是该答案的副本。

解决这个问题的方法是使用命名管道。 现在我唯一的问题是知道如何格式化数据(图像字节),以便 FFmpeg 不会抱怨无效数据。

\\.\pipe\pipe_to_ffmpeg: Invalid data found when processing input


这是创建命名管道并将其设置为 FFmpeg 命令输入的正确方法。

我们首先声明一些变量:

private Process _process;
private NamedPipeServerStream _pipeServer;
private StreamWriter _pipeStreamWriter;
private const string _pipeName = @"pipe_to_ffmpeg";
private readonly string _pipeStructured = @"\\.\pipe\" + _pipeName;

然后让我们创建一个进程并传递必要的参数:

public void Start(string ffmpegPath, int width, int height)
{
    //Example argument, used to export GIFs
    var arguments = "-vsync passthrough " +
        "-f rawvideo -pix_fmt bgra -video_size {width}x{height} " +
        $"-i {_pipeStructured} " +
        "-loop 0 " +
        "-lavfi palettegen=stats_mode=diff[pal],[0:v][pal]paletteuse=new=1:dither=sierra2_4a:diff_mode=rectangle " +
        "-f gif " +
        "-y \"C:\Users\User\Desktop\ffmpeg.gif\"";

    _process = new Process
    {
        StartInfo = new ProcessStartInfo
        {
            FileName = ffmpegPath,
            Arguments = arguments.Replace("{width}", width.ToString()).Replace("{height}", height.ToString()),
            UseShellExecute = false,
            CreateNoWindow = true
        }
    };

    _pipeServer = new NamedPipeServerStream(_pipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
    _pipeStreamWriter = new StreamWriter(_pipeServer);

    var pipeServerThread = new Thread(() =>
    {
        //Needs to be in a separate thread or you could use other async methods.
        _pipeServer.WaitForConnection();

        _pipeStreamWriter.AutoFlush = true;
    });
    pipeServerThread.Start();

    _process.Start();
}

然后在渲染迭代器上,您可以通过

StreamWriter
:

传递数据
public void Encode(byte[] buffer, CancellationTokenSource tokenSource)
{
    while (!_pipeServer.IsConnected) //TODO: Limit wait period.
        Thread.Sleep(100);

    //Here you may want to spend some time understanding how to properly pass the data to FFmpeg.
    _pipeStreamWriter.BaseStream.Write(buffer, 0, buffer.Length);
}

不要忘记稍后清洁并处理所有东西:

public void Finish()
{
    _pipeServer.WaitForPipeDrain();
    _pipeStreamWriter.Dispose();
    _pipeServer.Dispose();

    _process.WaitForExit();
}
© www.soinside.com 2019 - 2024. All rights reserved.