使用 MediaCodec 将视频帧 RGB 编码为 YUV_420_888 | Android 相机 2 API

Encoding video frames RGB to YUV_420_888 with MediaCodec | Android Camera2 API

我正在使用 MediaCodec 将图像编码为视频。我可以在网上找到大量关于将 YUV_420_888 格式图像转换为 RGB 的文档,以便使用 ImageReader 从 Camera2 API 接收和处理帧。但是,我发现很难找到任何关于如何将 RGB 图像转换为 YUV_420_888 用于编码目的的文档 - 问题是这种格式很灵活并且可以表示编码器未专门提供的任何多种格式.

我已经能够通过将位图转换为 YUV420SP 图像并将字节发送到编码器来对帧进行编码。但是,在某些设备上,输出视频会变色或失真,因为编码器需要不同的格式。如果没有为特定设备或编码器编写 hacky work-arounds 就没有办法做到这一点,我会感到震惊。

以下是我的视频编解码器的设置方式:

Bitmap frameBitmap; //this is initiated later in my app
final int VIDEO_BIT_RATE = 4000000; //min supported by Android for 1280x720
final int VIDEO_FRAME_INTERVAL = 1;
final int VIDEO_WIDTH = 1280;
final int VIDEO_HEIGHT = 720;
final int VIDEO_FRAME_RATE = 30;
//...
videoCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_MPEG4);
MediaFormat videoFormat = MediaFormat.createVideoFormat(videoMimeType, mVideoSize.getWidth(), mVideoSize.getHeight());
videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, selectColorFormat(videoCodecInfo, videoMimeType));
videoFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormatSelected);
videoFormat.setInteger(MediaFormat.KEY_BIT_RATE, VIDEO_BIT_RATE);
videoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, VIDEO_FRAME_RATE);
videoFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, VIDEO_FRAME_INTERVAL);
videoFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 0);
//...
private static int selectColorFormat(MediaCodecInfo codecInfo,
                                     String mimeType)
{
    MediaCodecInfo.CodecCapabilities capabilities = codecInfo
            .getCapabilitiesForType(mimeType);
    int selectedColorFormat = 0;
    for (int i = 0; i < capabilities.colorFormats.length; i++)
    {
        int colorFormat = capabilities.colorFormats[i];

        if (isRecognizedFormat(colorFormat))
        {
            selectedColorFormat = colorFormat;
        }
    }
    return selectedColorFormat;
}
private static boolean isRecognizedFormat(int colorFormat)
{
    switch (colorFormat)
    {
        //I use YUV420Flexible - other values are deprecated.
        case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible:
            return true;
        default:
            return false;
    }
}

这是我目前写入编码器的两种方法——我知道它们都不完整,仅供参考:

int inputBufIndex = videoCodec.dequeueInputBuffer(timeout);
if (inputBufIndex >= 0) {

    //APPROACH A - using getInputImage
    currentVideoInputImage = videoCodec.getInputImage(inputBufIndex);
    assert currentVideoInputImage != null;
    ByteBuffer yBuffer = currentVideoInputImage.getPlanes()[0].getBuffer();
    ByteBuffer uBuffer = currentVideoInputImage.getPlanes()[1].getBuffer();
    ByteBuffer vBuffer = currentVideoInputImage.getPlanes()[2].getBuffer();
    yBuffer.put(yBytes);
    uBuffer.put(uBytes);
    vBuffer.put(vBytes);

    //or APPROACH B - writing the bytes directly
    currentVideoInputBuffer = videoCodec.getInputBuffer(inputBufIndex);
    assert currentVideoInputBuffer != null;
    int remaining = currentVideoInputBuffer.remaining(); 
    currentVideoInputBuffer.put(data); //data is in YUV420SP format currently
    videoCodec.queueInputBuffer(inputBufIndex, 0, remaining, presentationTimestamp, 0);
    totalFramesQueued++;

}

我考虑过可能从相机获取一帧,检测其行和像素跨度,并使用它来确定编码器可能期望的内容。但即便如此,我仍然不知道如何 assemble 字节,而且,我不确定编码器是否会期望相同的格式。

任何帮助或指出正确的方向将不胜感激。

最后我完全放弃了这种方法,并使用 OpenGL 实现了一个解决方案。很有魅力,但学习起来非常困难,需要很长时间才能实施。我对任何想学习 OpenGL 的人的建议是从开源 Grafika 项目开始。 Link: https://github.com/google/grafika

最好的学习方法是 运行 Grafika 应用程序,尝试其不同的活动,结合一些概念等。