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);
}