如何正确使用YUV_420_888和MediaCodec的ImageReader将视频编码为h264格式?

use*_*481 5 camera yuv android-camera android-camera2

我正在Android设备上实现相机应用程序。当前,我使用Camera2 API和ImageReader来获取YUV_420_888格式的图像数据,但是我不知道如何将这些数据准确地写入MediaCodec。

这是我的问题:

  1. 什么YUV_420_888啊

该格式YUV_420_888是不明确的,因为它可以属于任何形式YUV420的家庭,如YUV420P,YUV420PP,YUV420SP和YUV420PSP,对不对?

通过访问图像的三个平面(#0,#1,#2),我可以获得该图像的Y(#0),U(#1),V(#2)值。但是这些值的排列在不同设备上可能不相同。例如,如果YUV_420_888确实表示YUV420P,则Plane#1和Plane#2的大小均为Plane#0的四分之一。如果YUV_420_888确实表示YUV420SP,则Plane#1和Plane#2的大小都是Plane#0大小的一半(Plane#1和Plane#2的每个包含U,V值)。

如果要将这些数据从图像的三个平面写入MediaCodec,我需要转换为哪种格式?YUV420,NV21,NV12 ...

  1. 什么COLOR_FormatYUV420Flexible啊

该格式COLOR_FormatYUV420Flexible也有歧义,因为它可以是属于该YUV420家族的任何格式,对吗?如果将KEY_COLOR_FORMATMediaCodec对象的选项设置为COLOR_FormatYUV420Flexible,我应该输入哪种格式的数据(YUV420P,YUV420SP ...?)?

  1. 怎么样使用COLOR_FormatSurface?

我知道MediaCodec具有自己的表面,如果我将KEY_COLOR_FORMATMediaCodec对象的选项设置为,则可以使用该表面COLOR_FormatSurface。使用Camera2 API,我不需要自己将任何数据写入MediaCodec对象。我可以耗尽输出缓冲区。

但是,我需要从相机更改图像。例如,绘制其他图片,在上面写一些文字,或将另一个视频插入为POP(图片的图片)。

我可以使用ImageReader从Camera读取图像,然后重新绘制图像,然后将新数据写入MediaCodec的表面,然后将其排干吗?怎么做?

编辑1

我通过使用COLOR_FormatSurface和RenderScript 实现了该功能。这是我的代码:

onImageAvailable 方法:

public void onImageAvailable(ImageReader imageReader) {
    try {
        try (Image image = imageReader.acquireLatestImage()) {
            if (image == null) {
                return;
            }
            Image.Plane[] planes = image.getPlanes();
            if (planes.length >= 3) {
                ByteBuffer bufferY = planes[0].getBuffer();
                ByteBuffer bufferU = planes[1].getBuffer();
                ByteBuffer bufferV = planes[2].getBuffer();
                int lengthY = bufferY.remaining();
                int lengthU = bufferU.remaining();
                int lengthV = bufferV.remaining();
                byte[] dataYUV = new byte[lengthY + lengthU + lengthV];
                bufferY.get(dataYUV, 0, lengthY);
                bufferU.get(dataYUV, lengthY, lengthU);
                bufferV.get(dataYUV, lengthY + lengthU, lengthV);
                imageYUV = dataYUV;
            }
        }
    } catch (final Exception ex) {

    }
}
Run Code Online (Sandbox Code Playgroud)

将YUV_420_888转换为RGB:

public static Bitmap YUV_420_888_toRGBIntrinsics(Context context, int width, int height, byte[] yuv) {
    RenderScript rs = RenderScript.create(context);
    ScriptIntrinsicYuvToRGB yuvToRgbIntrinsic = ScriptIntrinsicYuvToRGB.create(rs, Element.U8_4(rs));

    Type.Builder yuvType = new Type.Builder(rs, Element.U8(rs)).setX(yuv.length);
    Allocation in = Allocation.createTyped(rs, yuvType.create(), Allocation.USAGE_SCRIPT);

    Type.Builder rgbaType = new Type.Builder(rs, Element.RGBA_8888(rs)).setX(width).setY(height);
    Allocation out = Allocation.createTyped(rs, rgbaType.create(), Allocation.USAGE_SCRIPT);


    Bitmap bmpOut = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);

    in.copyFromUnchecked(yuv);

    yuvToRgbIntrinsic.setInput(in);
    yuvToRgbIntrinsic.forEach(out);
    out.copyTo(bmpOut);
    return bmpOut;
}
Run Code Online (Sandbox Code Playgroud)

MediaCodec:

mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
...
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
...
surface = mediaCodec.createInputSurface(); // This surface is not used in Camera APIv2. Camera APIv2 uses ImageReader's surface.
Run Code Online (Sandbox Code Playgroud)

并在另一个线程中:

while (!stop) {
    final byte[] image = imageYUV;

    // Do some yuv computation

    Bitmap bitmap = YUV_420_888_toRGBIntrinsics(getApplicationContext(), width, height, image);
    Canvas canvas = surface.lockHardwareCanvas();
    canvas.drawBitmap(bitmap, matrix, paint);
    surface.unlockCanvasAndPost(canvas);
}
Run Code Online (Sandbox Code Playgroud)

这种方法有效,但是性能不好。它无法输出30fps的视频文件(仅〜12fps)。也许我不应该使用COLOR_FormatSurface和表面的画布进行编码。计算得出的YUV数据应直接写入mediaCodec,而无需任何表面进行任何转换。但是我仍然不知道该怎么做。

Ale*_*ohn 5

你是对的,YUV_420_888是一种可以包装不同YUV 420格式的格式。规范仔细解释了,U、V平面的排列方式没有规定,但是有一定的限制;例如,如果 U 平面的像素步长为 2,则同样适用于 V(然后底层字节缓冲区可以是 NV21)。

COLOR_FormatYUV420Flexible是YUV_420_888的同义词,但它们分别属于不同的类:MediaCodec 和 ImageFormat。

规范解释:

自LOLLIPOP_MR1以来,所有视频编解码器都支持灵活的 YUV 4:2:0 缓冲区。

COLOR_FormatSurface 是一种不透明格式,可以为 MediaCodec 提供最佳性能,但这是有代价的:您无法直接读取或操作其内容。如果您需要操作发送至 MediaCodec 的数据,则可以选择使用 ImageReader;它是否会比 ByteBuffer 更高效,取决于你做什么以及如何做。请注意,对于API 24+,您可以在 C++ 中使用camera2 和 MediaCodec。

有关 MediaCodec 详细信息的宝贵资源是http://www.bigflake.com/mediacodec。它引用了264 编码的完整示例。