Camera2 YUV_420_888转NV21

Camera2 Image转NV21


项目需要对Android相机预览帧进行图像处理。而Camera2从ImageReader(设置通用的ImageFormat.YUV_420_888格式)中获取的是YUV格式数据。其中YUV本身只存储颜色信息,不包含图像宽高信息,回调后的YUV数据分别存储在Image Planes的3个数组中。

设置相机预览大小1920 * 1440后打印3个planes数组信息如下:

			int width = image.getWidth();
            int height = image.getHeight();
            ByteBuffer buffer = planes[i].getBuffer();
            int rowStride = planes[i].getRowStride();
            int pixelStride = planes[i].getPixelStride();
            Log.v(TAG, "pixelStride " + pixelStride);
            Log.v(TAG, "rowStride " + rowStride);
            Log.v(TAG, "width " + width);
            Log.v(TAG, "height " + height);
            Log.v(TAG, "buffer size " + buffer.remaining());

在这里插入图片描述
从上至下依次对应Y、U、V三个分量的buffer数据。可见Y的buffer大小就是width * height。U和V的buffer大小是width * height的1/2。

  • 为什么是NV21格式?

NV21格式每四个Y分量共用一组U分量和V分量,Y连续排序,U与V交叉排序。即如下排列:

Y Y   Y Y   Y Y   Y Y
Y Y   Y Y   Y Y   Y Y
Y Y   Y Y   Y Y   Y Y
Y Y   Y Y   Y Y   Y Y
V U   V U   V U   V U
V U   V U   V U   V U

对于一组NV21数据,Y的size为width * height,U和V的size都是width * height * 1 / 4,总size为width * height * 3 / 2

根据第3个通道planes[2].getPixelStride() = 2可知V分量每两个连续的V之间隔了一个像素值,实际隔出的那个值就是一个U值。而plane[0]就是所有的Y值。

仔细观察U和V的buffer size发现比Y的buffer size的一半少了一个像素值。举例来说,planes[1] 代表的是U分量,PixelStride为2,取U分量索引为0, 2, 4, 6 … 1382398,省略了最后一个V值,size就是1382399。

  • 如何转化成NV21?

综上,NV21数据可先由planes[0]+planes[2]组合而成,最后再从planes[1]中取出最后的值追加到末尾。
Java代码如下:

    private static byte[] convertPlanes2NV21(int width, int height, ByteBuffer yPlane, ByteBuffer uPlane, ByteBuffer vPlane) {
        int totalSize = width * height * 3 / 2;
        byte[] nv21Buffer = new byte[totalSize];
        int len = yPlane.capacity();
        yPlane.get(nv21Buffer, 0, len);
        vPlane.get(nv21Buffer, len, vPlane.capacity());
        byte lastValue = uPlane.get(uPlane.capacity() - 1);
        nv21Buffer[totalSize - 1] = lastValue;
        return nv21Buffer;
    }
  • 优化速度

项目需要对Camera2每一帧图像数据的处理速度进行优化,故相机Image转NV21的操作放到JNI层去处理,同时省略了追加最后一值的操作(不影响结果)。
JNI代码如下:

Java_com_libyuv_util_YuvUtils_convertPlanes2NV21(JNIEnv *env, jclass type,
                                                 jint width, jint height,
                                                 jobject yPlane, jobject vPlane,
                                                 jbyteArray bufferArray) {
    jbyte *y_buffer = (jbyte *) env->GetDirectBufferAddress(yPlane);
    jbyte *v_buffer = (jbyte *) env->GetDirectBufferAddress(vPlane);
    jbyte *Dst_data = env->GetByteArrayElements(bufferArray, NULL);
    int len = width * height;
    memcpy(Dst_data, y_buffer, static_cast<size_t>(len));
    jlong vBufferCapacity = env->GetDirectBufferCapacity(vPlane);
    memcpy(Dst_data + len, v_buffer, static_cast<size_t>(vBufferCapacity));
    env->ReleaseByteArrayElements(bufferArray, Dst_data, 0);
}

猜你喜欢

转载自blog.csdn.net/qq_23069607/article/details/122698315