+-
通过camera2 API,我们收到一个格式为YUV_420_888的 Image对象.我们正在使用以下函数转换为NV21:
private static byte[] YUV_420_888toNV21(Image image) {
byte[] nv21;
ByteBuffer yBuffer = image.getPlanes()[0].getBuffer();
ByteBuffer uBuffer = image.getPlanes()[1].getBuffer();
ByteBuffer vBuffer = image.getPlanes()[2].getBuffer();
int ySize = yBuffer.remaining();
int uSize = uBuffer.remaining();
int vSize = vBuffer.remaining();
nv21 = new byte[ySize + uSize + vSize];
//U and V are swapped
yBuffer.get(nv21, 0, ySize);
vBuffer.get(nv21, ySize, vSize);
uBuffer.get(nv21, ySize + vSize, uSize);
return nv21;
}
虽然这个函数适用于cameraCaptureSessions.setRepeatingRequest,但在调用cameraCaptureSessions.capture时,我们在进一步处理(在JNI端)时会出现分段错误.两者都通过ImageReader请求YUV_420_888格式.
为什么两个函数调用的结果不同,而请求的类型是相同的?
更新:正如评论中所提到的,由于图像大小不同(捕获请求的维度大得多),我得到了这种行为.但是我们在JNI方面的进一步处理操作对于两个请求都是相同的,并且不依赖于图像尺寸(仅在纵横比上,在两种情况下都是相同的).
最佳答案
如果根本没有填充,则代码将仅返回正确的NV21,并且U和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 = width - rowStride; // not an actual position
for (; pos<ySize; pos+=width) {
yBufferPos += rowStride - width;
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);
vBuffer.put(1, (byte)0);
if (uBuffer.get(0) == 0) {
vBuffer.put(1, (byte)255);
if (uBuffer.get(0) == 255) {
vBuffer.put(1, savePixel);
vBuffer.get(nv21, ySize, uvSize);
return nv21; // shortcut
}
}
// 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那个
the buffer returned will always have isDirect return true, so the underlying data could be mapped as a pointer in JNI without doing any copies with GetDirectBufferAddress.
这意味着可以在C中以最小的开销完成相同的转换.在C中,你甚至可能发现实际的像素排列已经是NV21了!
PS实际上,这可以用Java完成,开销可以忽略不计,请参阅if行(pixelStride == 2&& … above.
点击查看更多相关文章
转载注明原文:android – camera2捕获图片 – 从YUV_420_888转换为NV21 - 乐贴网