前面在《YUV_420_888介绍及YUV420转RGBA》一文中介绍了YUV420的转换,并提供了自己写的转换代码。但是实际项目中一般不会自己手写代码逐个像素去转换,因为这样转换的速度比较慢。通常我们可以使用opencv或者libyuv来进行转换。本文就介绍使用libyuv进行转换。
YUV420转RGBA系列共三篇:
- YUV_420_888介绍及YUV420转RGBA
- YUV420转RGBA之使用opencv
- YUV420转RGBA之使用libyuv
本文是其中的第三篇,作为最后一篇,本文对三种不同方式做了一个对比测试。
1. 下载libyuv
网址:https://chromium.googlesource.com/libyuv/libyuv
git下载:git clone https://chromium.googlesource.com/libyuv/libyuv
下载libyuv源码完后编译,取得库文件。这里只是简单说明libyuv的用法,不提供libyuv的编译方法,编译方法请参考官网或者google、百度。不会编译libyuv也没关系,可以拖到最后,直接下载本文代码,代码中提供libyuv库文件和头文件,直接导入即可使用。
2. 导入libyuv到Android Studio
2.1 导入头文件
在项目的app/src/main/cpp/下新建include文件夹。将libyuv的头文件拷贝到app/src/main/cpp/include下。
2.2 导入库文件
将libyuv的库文件拷贝项目的app/src/main/jniLibs下。
2.3 修改CMakeLists.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | include_directories(${CMAKE_SOURCE_DIR}/include) find_library(log-lib log) add_library(libyuv SHARED IMPORTED) set_target_properties( libyuv PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libyuv.so ) add_library( LibyuvUtils SHARED libyuv.cpp libyuv_utils.cpp ) target_link_libraries(LibyuvUtils libyuv ${log-lib}) |
修改完成后同步一下项目,接下来我们只要在libyuv.cpp和 libyuv_utils.cpp中编码调用 libyuv 的API就可以了。
3. 使用libyuv将YUV420转换成RGBA
libyuv.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | #include <jni.h> #include <string> #include "libyuv_utils.h" extern "C" JNIEXPORT void JNICALL Java_com_qxt_yuv420_LibyuvUtils_I420ToRGBA(JNIEnv *env, jclass clazz, jbyteArray src, jbyteArray dst, jint width, jint height) { jbyte *_src = env->GetByteArrayElements(src, nullptr); jbyte *_dst = env->GetByteArrayElements(dst, nullptr); libyuvI420ToRGBA(reinterpret_cast<unsigned char *>(_src), reinterpret_cast<unsigned char *>(_dst), width, height); env->ReleaseByteArrayElements(src, _src, JNI_ABORT); env->ReleaseByteArrayElements(dst, _dst, 0); } extern "C" JNIEXPORT void JNICALL Java_com_qxt_yuv420_LibyuvUtils_YV12ToRGBA(JNIEnv *env, jclass clazz, jbyteArray src, jbyteArray dst, jint width, jint height) { jbyte *_src = env->GetByteArrayElements(src, nullptr); jbyte *_dst = env->GetByteArrayElements(dst, nullptr); libyuvYV12ToRGBA(reinterpret_cast<unsigned char *>(_src), reinterpret_cast<unsigned char *>(_dst), width, height); env->ReleaseByteArrayElements(src, _src, JNI_ABORT); env->ReleaseByteArrayElements(dst, _dst, 0); } extern "C" JNIEXPORT void JNICALL Java_com_qxt_yuv420_LibyuvUtils_NV12ToRGBA(JNIEnv *env, jclass clazz, jbyteArray src, jbyteArray dst, jint width, jint height) { jbyte *_src = env->GetByteArrayElements(src, nullptr); jbyte *_dst = env->GetByteArrayElements(dst, nullptr); libyuvNV12ToRGBA(reinterpret_cast<unsigned char *>(_src), reinterpret_cast<unsigned char *>(_dst), width, height); env->ReleaseByteArrayElements(src, _src, JNI_ABORT); env->ReleaseByteArrayElements(dst, _dst, 0); } extern "C" JNIEXPORT void JNICALL Java_com_qxt_yuv420_LibyuvUtils_NV21ToRGBA(JNIEnv *env, jclass clazz, jbyteArray src, jbyteArray dst, jint width, jint height) { jbyte *_src = env->GetByteArrayElements(src, nullptr); jbyte *_dst = env->GetByteArrayElements(dst, nullptr); libyuvNV21ToRGBA(reinterpret_cast<unsigned char *>(_src), reinterpret_cast<unsigned char *>(_dst), width, height); env->ReleaseByteArrayElements(src, _src, JNI_ABORT); env->ReleaseByteArrayElements(dst, _dst, 0); } |
libyuv_utils.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | #ifndef LIBYUV_UTILS_H #define LIBYUV_UTILS_H #ifdef __cplusplus extern "C" { #endif void libyuvI420ToRGBA(unsigned char *src, unsigned char *dst, int width, int height); void libyuvYV12ToRGBA(unsigned char *src, unsigned char *dst, int width, int height); void libyuvNV12ToRGBA(unsigned char *src, unsigned char *dst, int width, int height); void libyuvNV21ToRGBA(unsigned char *src, unsigned char *dst, int width, int height); #ifdef __cplusplus } #endif #endif //LIBYUV_UTILS_H |
libyuv_utils.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | #include <stdint.h> #include <libyuv/convert.h> #include <libyuv/convert_argb.h> #include <libyuv/convert_from.h> #include <libyuv/rotate.h> #include <libyuv/rotate_argb.h> #include "logger.h" #include "libyuv_utils.h" using namespace std; using namespace libyuv; void libyuvI420ToRGBA(unsigned char *src, unsigned char *dst, int width, int height) { unsigned char *pY = src; unsigned char *pU = src + width * height; unsigned char *pV = src + width * height * 5 / 4; I420ToABGR(pY, width, pU, width >> 1, pV, width >> 1, dst, width * 4, width, height); } void libyuvYV12ToRGBA(unsigned char *src, unsigned char *dst, int width, int height) { unsigned char *pY = src; unsigned char *pU = src + width * height * 5 / 4; unsigned char *pV = src + width * height; I420ToABGR(pY, width, pU, width >> 1, pV, width >> 1, dst, width * 4, width, height); } void libyuvNV12ToRGBA(unsigned char *src, unsigned char *dst, int width, int height) { unsigned char *pY = src; unsigned char *pUV = src + width * height; NV21ToARGB(pY, width, pUV, width, dst, width * 4, width, height); } void libyuvNV21ToRGBA(unsigned char *src, unsigned char *dst, int width, int height) { unsigned char *pY = src; unsigned char *pUV = src + width * height; NV12ToARGB(pY, width, pUV, width, dst, width * 4, width, height); } |
这里值得注意的是,由于libyuv的ARGB和android bitmap的ARGB_8888的存储顺序是不一样的,因此如果想对应上android bitmap的ARGB_8888的存储顺序,将YUV420转换成ARGB_8888(也即本文所指的RGBA)时,需要按以下规律转换:
-
I420转RGBA使用libyuv的I420ToABGR函数
-
YV12转RGBA使用libyuv的I420ToABGR函数
-
NV12转RGBA使用libyuv的NV21ToARGB函数
-
NV21转RGBA使用libyuv的NV12ToARGB函数
4. 使用libyuv旋转RGBA和YUV420P
libyuv.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | extern "C" JNIEXPORT void JNICALL Java_com_qxt_yuv420_LibyuvUtils_rotateRGB(JNIEnv *env, jclass clazz, jbyteArray src, jbyteArray dst, jint width, jint height, jfloat degree) { jbyte *_src = env->GetByteArrayElements(src, nullptr); jbyte *_dst = env->GetByteArrayElements(dst, nullptr); libyuvRotateRGB(reinterpret_cast<unsigned char *>(_src), reinterpret_cast<unsigned char *>(_dst), width, height, degree); env->ReleaseByteArrayElements(src, _src, JNI_ABORT); env->ReleaseByteArrayElements(dst, _dst, 0); } extern "C" JNIEXPORT void JNICALL Java_com_qxt_yuv420_LibyuvUtils_rotateRGBA(JNIEnv *env, jclass clazz, jbyteArray src, jbyteArray dst, jint width, jint height, jfloat degree) { jbyte *_src = env->GetByteArrayElements(src, nullptr); jbyte *_dst = env->GetByteArrayElements(dst, nullptr); libyuvRotateRGBA(reinterpret_cast<unsigned char *>(_src), reinterpret_cast<unsigned char *>(_dst), width, height, degree); env->ReleaseByteArrayElements(src, _src, JNI_ABORT); env->ReleaseByteArrayElements(dst, _dst, 0); } extern "C" JNIEXPORT void JNICALL Java_com_qxt_yuv420_LibyuvUtils_rotateYUV420P(JNIEnv *env, jclass clazz, jbyteArray src, jbyteArray dst, jint width, jint height, jfloat degree) { jbyte *_src = env->GetByteArrayElements(src, nullptr); jbyte *_dst = env->GetByteArrayElements(dst, nullptr); libyuvRotateYUV420P(reinterpret_cast<unsigned char *>(_src), reinterpret_cast<unsigned char *>(_dst), width, height, degree); env->ReleaseByteArrayElements(src, _src, JNI_ABORT); env->ReleaseByteArrayElements(dst, _dst, 0); } |
libyuv_utils.h:
1 2 3 4 5 | void libyuvRotateRGB(unsigned char *src, unsigned char *dst, int width, int height, float degree); void libyuvRotateRGBA(unsigned char *src, unsigned char *dst, int width, int height, float degree); void libyuvRotateYUV420P(unsigned char *src, unsigned char *dst, int width, int height, float degree); |
libyuv_utils.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | void libyuvRotateRGB(unsigned char *src, unsigned char *dst, int width, int height, float degree) { if (degree == 90.0f) { ARGBRotate(src, width * 3, dst, height * 3, width, height, kRotate90); } else if (degree == 180.0f) { ARGBRotate(src, width * 3, dst, width * 3, width, height, kRotate180); } else if (degree == 270.0f) { ARGBRotate(src, width * 3, dst, height * 3, width, height, kRotate270); } else { return; } } void libyuvRotateRGBA(unsigned char *src, unsigned char *dst, int width, int height, float degree) { if (degree == 90.0f) { ARGBRotate(src, width * 4, dst, height * 4, width, height, kRotate90); } else if (degree == 180.0f) { ARGBRotate(src, width * 4, dst, width * 4, width, height, kRotate180); } else if (degree == 270.0f) { ARGBRotate(src, width * 4, dst, height * 4, width, height, kRotate270); } else { return; } } void libyuvRotateYUV420P(unsigned char *src, unsigned char *dst, int width, int height, float degree) { unsigned char *pSrcY = src; unsigned char *pSrcU = src + width * height; unsigned char *pSrcV = src + width * height * 5 / 4; unsigned char *pDstY = dst; unsigned char *pDstU = dst + width * height; unsigned char *pDstV = dst + width * height * 5 / 4; if (degree == 90.0f) { I420Rotate(pSrcY, width, pSrcU, width >> 1, pSrcV, width >> 1, pDstY, height, pDstU, height >> 1, pDstV, height >> 1, width, height, kRotate90); } else if (degree == 180.0f) { I420Rotate(pSrcY, width, pSrcU, width >> 1, pSrcV, width >> 1, pDstY, width, pDstU, width >> 1, pDstV, width >> 1, width, height, kRotate180); } else if (degree == 270.0f) { I420Rotate(pSrcY, width, pSrcU, width >> 1, pSrcV, width >> 1, pDstY, height, pDstU, height >> 1, pDstV, height >> 1, width, height, kRotate270); } else { return; } } |
5. 自写c++代码、opencv、libyuv的转换效率对比
为了对比自写c++代码、opencv、libyuv的转换效率,我用一台 3+32G 8核(41.5Ghz, 42Ghz)的手机,处理分辨率为3264x2448的图像,分别测试了:
-
YUV420P转换成RGBA(I420)
-
YUV420SP转换成RGBA(NV21)
-
RGBA顺时针旋转90度
-
YUV420P顺时针旋转90度
YUV420P转换成RGBA,I420和YV12数据长度一样,理论上转换时间复杂度也一样,我们选用了比较常用的I420进行转换。同样的,YUV420SP转换成RGBA,NV12和NV21数据长度一样,理论上转换时间复杂度也一样,我们选用了比较常用的NV21进行转换。
另外,由于opencv和libyuv都没有直接可以用于旋转YUV420SP图像的接口函数,未做旋转YUV420SP的对比测试。
每个函数测试5次并计算平均值,测试结果如下表(单位:毫秒ms):
处理时间对比.png
可以看到:
不同操作的对比,三种方式几乎都是RGBA顺时针旋转90度的时间最长,所以做camera相关的开发时,如果要旋转camera的出图,在条件允许的情况下一定要直接旋转YUV420P,这样效率才是最高的,旋转后再做其它操作(例如:转换成RGBA或者Bitmap)。
相同操作的对比,三种方式,自写c++代码(Native)几乎在所有测试上都是耗时最久的,而opencv和libyuv在不同功能上各有优势。由于opencv功能太多太齐全了,导致opencv的库文件非常大,达到了20MB,而自写c++代码仅有97KB、libyuv仅有264.1KB。在大家都拼命为APK或者ROM瘦身的今天,opencv肯定不会首先考虑,体积更小、性能也很优秀的libyuv则会更加受到青睐。
综合来看,处理YUV420时,libyuv是最佳解决方案。
本文中的代码已经上传到github:https://github.com/qiuxintai/YUV420Converter