您的代码只会返回正确的NV21如果根本没有填充,并且U and V平原重叠并且实际上代表交错VU价值观。这种情况在预览时经常发生,但在这种情况下,您需要分配额外的w*h/4
数组的字节(这可能不是问题)。也许对于捕获的图像,您需要更强大的实现,例如
private static byte[] YUV_420_888toNV21(Image image) {
int width = image.getWidth();
int height = image.getHeight();
int ySize = width*height;
int uvSize = width*height/4;
byte[] nv21 = new byte[ySize + uvSize*2];
ByteBuffer yBuffer = image.getPlanes()[0].getBuffer(); // Y
ByteBuffer uBuffer = image.getPlanes()[1].getBuffer(); // U
ByteBuffer vBuffer = image.getPlanes()[2].getBuffer(); // V
int rowStride = image.getPlanes()[0].getRowStride();
assert(image.getPlanes()[0].getPixelStride() == 1);
int pos = 0;
if (rowStride == width) { // likely
yBuffer.get(nv21, 0, ySize);
pos += ySize;
}
else {
long yBufferPos = -rowStride; // not an actual position
for (; pos<ySize; pos+=width) {
yBufferPos += rowStride;
yBuffer.position(yBufferPos);
yBuffer.get(nv21, pos, width);
}
}
rowStride = image.getPlanes()[2].getRowStride();
int pixelStride = image.getPlanes()[2].getPixelStride();
assert(rowStride == image.getPlanes()[1].getRowStride());
assert(pixelStride == image.getPlanes()[1].getPixelStride());
if (pixelStride == 2 && rowStride == width && uBuffer.get(0) == vBuffer.get(1)) {
// maybe V an U planes overlap as per NV21, which means vBuffer[1] is alias of uBuffer[0]
byte savePixel = vBuffer.get(1);
try {
vBuffer.put(1, (byte)~savePixel);
if (uBuffer.get(0) == (byte)~savePixel) {
vBuffer.put(1, savePixel);
vBuffer.position(0);
uBuffer.position(0);
vBuffer.get(nv21, ySize, 1);
uBuffer.get(nv21, ySize + 1, uBuffer.remaining());
return nv21; // shortcut
}
}
catch (ReadOnlyBufferException ex) {
// unfortunately, we cannot check if vBuffer and uBuffer overlap
}
// unfortunately, the check failed. We must save U and V pixel by pixel
vBuffer.put(1, savePixel);
}
// other optimizations could check if (pixelStride == 1) or (pixelStride == 2),
// but performance gain would be less significant
for (int row=0; row<height/2; row++) {
for (int col=0; col<width/2; col++) {
int vuPos = col*pixelStride + row*rowStride;
nv21[pos++] = vBuffer.get(vuPos);
nv21[pos++] = uBuffer.get(vuPos);
}
}
return nv21;
}
如果您无论如何打算将结果数组传递给 C++,您可以利用fact https://developer.android.com/reference/android/media/Image.Plane#getBuffer() that
返回的缓冲区将始终让 isDirect 返回 true,因此可以将底层数据映射为 JNI 中的指针,而无需使用 GetDirectBufferAddress 进行任何复制。
这意味着可以在 C++ 中以最小的开销完成相同的转换。在C++中,你甚至可能会发现实际的像素排列已经是NV21了!
PS实际上,这可以在 Java 中完成,开销可以忽略不计,请参见行if (pixelStride == 2 && …
多于。因此,我们可以将所有色度字节批量复制到结果字节数组中,这比运行循环快得多,但仍然比在 C++ 中实现这种情况要慢。有关完整实施,请参阅Image.toByteArray() https://github.com/alexcohn/camera-samples/blob/f3b797736153c6e0d03cf05e497677dfd168e765/CameraXTfLite/utils/src/main/java/com/example/android/camera/utils/YuvToRgbConverter.kt#L100.