需求
我们的 YUV 超级夜景算法的输出是 YUV 数据,客户由于要在结果图上绘制水印,需要我们给他们输出 ARGB_8888 的数据。
分析
YUV 数据在 Java 中可以直接通过公式计算转换为 ARGB 数据,但是由于用了多个循环,效率会比较低,在客户的平台 Qualcomm MSM6125 上 12M 的图大概需要用 400ms。400ms 对于 2.5s 的算法耗时来说算得上庞然大物了。所以得想一下其他思路来解决这个问题。
我们内部有很多 HPC 团队写的图形格式转换的高性能代码,速度很快,所以自然想到能不能在这个方向找一下解决方案。最简单的方案当然是像 YUV 数据一样,在 SDK 中利用高性能代码将 YUV 数据转换为 ARGB 格式,然后直接将 ARGB 格式的数据写入到客户传进来的 buffer 中。
实现
当输出数据格式为 YUV 时,流程是这样的:
客户在 Java 代码中分配符合 YUV 大小的 byte 数组给我们写入输出图数据。
12int dataSize = width * height * 3 / 2;byte[] outData = new byte[dataSize];在 JNI 代码中将 byte 数组转换为 char 数组,由于 Java 中的 byte 和 C++ 中的 char 都是 1 字节,所以我们这里转换后数据是一一对应的。
12unsigned char *image_data = reinterpret_cast<unsigned char *>(env->GetByteArrayElements(outYUVData, 0));SDK 中直接将输出图的 YUV 数据写入 char 数组中。
- 客户在 Java 代码中处理格式为 byte[] 的 YUV 数据,比如直接用来创建 YuvImage。
当输出图为 ARGB 格式时,首先想到的是分配如下大小的 byte 数组来存储 ARGB 格式的输出图数据。
|
|
但是 Bitmap 需要 int[] 的数据来创建,客户现有接口也是直接操作 int[]。
虽然有了 byte[width * height * 4]
的输出图数据后,可以把 4(ARGB) 个 8 位的 byte 放在 1 个 32 位的 int 里,把他们转换为 int[width * height]
。但是这样又多了一些操作和一部分 Java 内存的使用。
那么能不能直接在 C++ 中就把我们的 ARGB 数据放在 Java 的 int 数组里呢?
答案是可以的。
来看一下当输出数据格式为 ARGB 时的流程:
客户在 Java 代码中分配 int 数组给我们写入输出图。由于 int 是 32 位的,所以这里数组的大小只需要为
width * height
,内存大小等价于new byte[width * height * 4]
;12int dataSize = width * height;int[] outData = new int[dataSize];在 JNI 代码中将 int 数组转换为 char 数组,由于在 Java 中分配的数组内存地址是连续的,所以我们这里直接将 int 数组转换为 char 数组来使用,即 char 数组中每 4 个值组成了 int 数组中的 1 个值。这样就做到了直接将每一个 ARGB 数据都放在 int 中。
12jint *image_data_int = env->GetIntArrayElements(outARGBData, 0);unsigned char *image_data = (unsigned char *) image_data_int;SDK 中将输出图的 YUV 数据利用 Neon 代码转换为 BGRA 数据,并写入到 char 数组中。
客户在 Java 代码中处理格式为 int[] 的 ARGB_8888 数据,比如直接用来创建 Bitmap。
12Bitmap bitmap = Bitmap.createBitmap(argbData, 0, width, width, height,Bitmap.Config.ARGB_8888);
整个过程和前面直接写入到保存 YUV 的 byte[] 中差不太多,关键点是:
- 根据数据在地址中的表现来将 Java int[] 和 C++ char * 进行转换。
- 利用速度更快的 Neon 代码来做到更快的速度。
上面的过程有一点需要单独讲一讲,Java 中需要的是 ARGB 格式的数据,但我们在 SDK 中是将 YUV 转换为 BGRA 格式并写入到 buffer 中,而不是转换为 ARGB,这是为什么呢?
这一点同样和数据在地址中的表现有关,这里我们只看输出图的前 4 个字节(即一个 ARGB 像素),分别在 C++ 和 Java 中将前 4 个字节打印出来。
|
|
|
|
打印的结果为:
|
|
可以看到前四个字节的数据在 C++ 中和 Java 中的十六进制表示反过来了, 7c8080ff 变成了 ff80807c,这是为什么呢?这是因为在客户的平台上(或者说在 Android 中,未考证) Java 中 int 型的字节顺序为小端法。
所以这也就是为什么 Java 中需要的是 ARGB,但我们在 C++ 中往内存中写入的是 BGRA 数据,因为到了 Java 中数据自然就变成了 ARGB。
性能节省
通过使用 Neon 代码将 YUV 转换为 ARGB,在客户平台上耗时仅为 20ms~30ms,相比在 Java 上用 for 循环来做节省了 400ms,满足客户需求的同时对性能的影响几乎可以忽略。