YUV format NV21 has good support in Android platform. The default camera output byte is NV21, and we can directly compress the byte to JPEG with helper of the class YuvImage.
However NV12 is very common also, e.g output format of some H.264 video stream.
The bit pattern between NV12 and NV21 are almost the same except their UV vector are swapped.
NV12 has the following format
YYYYYYYY UVUV
while NV21 has the following format
YYYYYYYY VUVU
So by swapping the U and V byte, we can easily convert one of these two format to another.
The old and conventional way is straight and simple
byte[] yuv; // fill the yuv data array final int length = yuv.length; for (int i1 = 0; i1 < length; i1 += 2) { if (i1 >= width * height) { byte tmp = yuv[i1]; yuv[i1] = yuv[i1+1]; yuv[i1+1] = tmp; } }
In order to make it looks like faster, we can do the swapping in renderscript. We treat the yuv byte array as a one dimension vector. In renderscript, skipping the Y values. In a width*height image, the size of Y is width*height bytes and UV size is width*height/2 bytes.
So we can write the rs script like below.
// byteswapper.rs #pragma version(1) #pragma rs java_package_name(com.example.android) rs_allocation gIn; uint32_t gWidth; uint32_t gHeight; static uchar previous; uchar __attribute__((kernel)) swap(uchar in, uint32_t x, uint32_t y) { if (x < (gWidth * gHeight)) { return in; } uchar out; if (x % 2 == 0) { previous = in; out = rsGetElementAt_uchar(gIn, x + 1, y); } else { out = previous; } return out; } void init() { rsDebug("uvswap init", rsUptimeMillis()); }
Then we can use the rs script above in Java code like following
byte[] yuv; // fill yuv byte array Type.Builder typeBuilder = new Type.Builder(renderScript, Element.U8(renderScript)) .setX(yuv.length).setY(1); Allocation inAllocation = Allocation.createTyped(renderScript, typeBuilder.create(), Allocation.USAGE_SCRIPT | Allocation.USAGE_SHARED); Allocation outAllocation = Allocation.createTyped(renderScript, typeBuilder.create(), Allocation.USAGE_SCRIPT | Allocation.USAGE_SHARED); inAllocation.copyFrom(yuv); byteswapper.set_gIn(inAllocation); byteswapper.set_gWidth(width); byteswapper.set_gHeight(height); byteswapper.forEach_swap(inAllocation, outAllocation); outAllocation.copyTo(yuv);
After the conversion have done, we can use YuvImage to compress to JPEG format.
YuvImage yuvImage = new YuvImage(yuv, ImageFormat.NV21, width, height, null); Rect rect = new Rect(0, 0, width, height); try { yuvImage.compressToJpeg(rect, 100, new FileOutputStream( new File(Environment.getExternalStorageDirectory() + File.separator + "test.jpg"))); } catch (FileNotFoundException e) { e.printStackTrace(); }
About the performance of these two ways, in Nexus 5 device, the old way seems take less time than renderscript. This is very confused.
Why the renderscript need to take more time to do the swapping? Is renderscript really designed for fast computing? Or the optimization in Java code is very good nowadays?