参考这篇文章,我想写一个方法将Android YUV_420_888转换为nv21。虽然从camera2 API的图像是默认的NV21伪装,但需要一个更通用的实现。如下所示:
class NV21Image{
public byte[] y;
public byte[] uv;
}
public static void cvtYUV420ToNV21(Image image, NV21Image nv21) {
int width = image.getWidth();
int height = image.getHeight();
int ySize = width*height;
ByteBuffer yBuffer = image.getPlanes()[0].getBuffer(); // Y
ByteBuffer uBuffer = image.getPlanes()[1].getBuffer(); // U
ByteBuffer vBuffer = image.getPlanes()[2].getBuffer(); // V
int yRowStride = image.getPlanes()[0].getRowStride();
int vRowStride = image.getPlanes()[2].getRowStride();
int pixelStride = image.getPlanes()[2].getPixelStride();
assert(image.getPlanes()[0].getPixelStride() == 1);
assert(image.getPlanes()[2].getRowStride() == image.getPlanes()[1].getRowStride());
assert(image.getPlanes()[2].getPixelStride() == image.getPlanes()[1].getPixelStride());
int pos = 0;
int yBufferPos = -yRowStride; // not an actual position
for (; pos<ySize; pos+=width) {
yBufferPos += yRowStride;
yBuffer.position(yBufferPos);
yBuffer.get(nv21.y, pos, width);
}
pos = 0;
for (int row=0; row<height/2; row++) {
for (int col=0; col<vRowStride / pixelStride; col++) {
int vuPos = col*pixelStride + row * vRowStride;
nv21.uv[pos++] = vBuffer.get(vuPos);
nv21.uv[pos++] = uBuffer.get(vuPos);
}
}
}
以上代码工作如预期,而非常耗时的我的实时相机预览应用程序(约12ms每帧720p在Snapdragon 865 CPU),所以我试图加速它与JNI实现从字节访问和性能优势的利润:
JNIEXPORT void JNICALL
Java_com_example_Utils_nFillYUVArray(JNIEnv *env, jclass clazz, jbyteArray yArr, jbyteArray uvArr,
jobject yBuf, jobject uBuf, jobject vBuf,
jint yRowStride, jint vRowStride, jint vPixelStride, jint w, jint h) {
auto ySrcPtr = (jbyte const*)env->GetDirectBufferAddress(yBuf);
auto uSrcPtr = (jbyte const*)env->GetDirectBufferAddress(uBuf);
auto vSrcPtr = (jbyte const*)env->GetDirectBufferAddress(vBuf);
for(int row = 0; row < h; row++){
env->SetByteArrayRegion(yArr, row * w, w, ySrcPtr + row * yRowStride);
}
int pos = 0;
for (int row=0; row<h/2; row++) {
for (int col=0; col<w/2; col++) {
int vuPos = col * vPixelStride + row * vRowStride;
env->SetByteArrayRegion(uvArr, pos++, 1, vSrcPtr + vuPos);
env->SetByteArrayRegion(uvArr, pos++, 1, uSrcPtr + vuPos);
}
}
}
然而,它比我预期的要差(大约每帧107ms)。最耗时的部分是UV缓冲区的隔行内存复制
所以我的问题是是否有任何方法来加速和如何解决它?
更新
当U、V平面的pixelStride
都是1或2时,我成功地加速了它(检查我的answer),我相信这是大多数情况下发生的事情。
1条答案
按热度按时间x6h2sr281#
正如@snachmsm所说的libyuv可能会有帮助。我找到了一个可用的API
I420ToNV21
,但它不能接收pixelStride参数,因为YUV_420_888
不能保证U,V平面中相邻像素之间不存在间隙。当pixelStride为2(减少到每帧2.7ms)时,我成功地使用arm内部函数对其进行了加速:
没有对
pixelStride == 1
的情况进行充分测试,但我相信它会按预期工作。