Unity:使用 FFmpeg 将 Texture2D 转换为 YUV420P

Unity: Converting Texture2D to YUV420P using FFmpeg

我正在尝试在 Unity 中创建一个游戏,其中每一帧都被渲染成一个纹理,然后使用 FFmpeg 将它们组合成一个视频。 FFmpeg 创建的输出最终应该通过网络发送到客户端 UI。但是,我主要在捕获帧的部分苦苦挣扎,并将其作为字节数组传递给不安全的方法,FFmpeg 应该在其中进一步处理它。我使用的包装器是 FFmpeg.AutoGen.

渲染到纹理的方法:

private IEnumerator CaptureFrame()
{
    yield return new WaitForEndOfFrame();

    RenderTexture.active = rt;
    frame.ReadPixels(rect, 0, 0);
    frame.Apply();

    bytes = frame.GetRawTextureData();

    EncodeAndWrite(bytes, bytes.Length);
}

目前不安全的编码方式:

private unsafe void EncodeAndWrite(byte[] bytes, int size)
{
    GCHandle pinned = GCHandle.Alloc(bytes, GCHandleType.Pinned);
    IntPtr address = pinned.AddrOfPinnedObject();

    sbyte** inData = (sbyte**)address;
    fixed(int* lineSize = new int[1])
    {
        lineSize[0] = 4 * textureWidth;
        // Convert RGBA to YUV420P
        ffmpeg.sws_scale(sws, inData, lineSize, 0, codecContext->width, inputFrame->extended_data, inputFrame->linesize);
    }

    inputFrame->pts = frameCounter++;

    if(ffmpeg.avcodec_send_frame(codecContext, inputFrame) < 0)
        throw new ApplicationException("Error sending a frame for encoding!");

    pkt = new AVPacket();
    fixed(AVPacket* packet = &pkt)
        ffmpeg.av_init_packet(packet);
    pkt.data = null;
    pkt.size = 0;

    pinned.Free();
    ...
}

sws_scalesbyte** 作为第二个参数,因此我试图通过首先用 GCHandle 固定输入字节数组并将其转换为 sbyte** 和之后进行显式类型转换。不过,我不知道这是否是正确的方法。

此外,条件if(ffmpeg.avcodec_send_frame(codecContext, inputFrame) < 0)总是抛出一个ApplicationException,我也真的不知道为什么会这样。 codecContextinputFrame分别是我的AVCodecContext和AVFrame对象,字段定义如下:

codecContext

codecContext = ffmpeg.avcodec_alloc_context3(codec);
codecContext->bit_rate = 400000;
codecContext->width = textureWidth;
codecContext->height = textureHeight;

AVRational timeBase = new AVRational();
timeBase.num = 1;
timeBase.den = (int)fps;
codecContext->time_base = timeBase;
videoAVStream->time_base = timeBase;

AVRational frameRate = new AVRational();
frameRate.num = (int)fps;
frameRate.den = 1;
codecContext->framerate = frameRate;

codecContext->gop_size = 10;
codecContext->max_b_frames = 1;
codecContext->pix_fmt = AVPixelFormat.AV_PIX_FMT_YUV420P;

inputFrame

inputFrame = ffmpeg.av_frame_alloc();
inputFrame->format = (int)codecContext->pix_fmt;
inputFrame->width = textureWidth;
inputFrame->height = textureHeight;
inputFrame->linesize[0] = inputFrame->width;

如果能帮助解决问题,我们将不胜感激:)

在此处查看示例:https://github.com/FFmpeg/FFmpeg/tree/master/doc/examples

特别是scaling_video.c。在 FFmpeg 中缩放和像素格式转换是相同的操作(仅像素格式转换保持大小参数相同)。

这些例子很容易理解。试一试。

我认为你的转换不正确sbyte** inData = (sbyte**)address; 因为地址是 IntPtr 对象,所以正确的转换可能应该是 sbyte* pinData = (sbyte *)address.ToPointer(); sbyte** ppInData = &pinData;