YUV420转RGBA之使用libyuv

网址:https://chromium.googlesource.com/libyuv/libyuv

git下载:git clonehttps://chromium.googlesource.com/libyuv/libyuv

下载libyuv源码完后编译,取得库文件。这里只是简单说明libyuv的用法,不提供libyuv的编译方法,编译方法请参考官网或者google、百度。不会编译libyuv也没关系,可以拖到最后,直接下载本文代码,代码中提供libyuv库文件和头文件,直接导入即可使用。

Update 2021-02-19

有几位童鞋留言或私信提到文章没有libyuv的编译步骤,这里补充一下。

libyuv有多种构建方式,我们可以选择ninja、cmake、make任意一种。并且Google已经提供了相应的构建文件。从libyuv源码中我们也可以看到确实包含:android.bp、android.mk、linux.mk、CMakeLists.txt。

方法一:

一般情况下,按照Google官方说明的编译步骤就可以直接编译通过:

git clone https://chromium.googlesource.com/libyuv/libyuvcd libyuv/mkdir outcd outcmake ..cmake --build .

方法二:

也可以参考这个项目:https://github.com/hzl123456/LibyuvDemo

把这个项目依赖包libyuv中的libyuv源码更新到最新,使用Android Studio编译之后在build目录中可以找到最新版本的libyuv.so。然后我们就可以拷贝头文件和库文件去其它项目上使用了。

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

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:

#include<jni.h>#include<string>#include"libyuv_utils.h"extern"C"JNIEXPORTvoidJNICALLJava_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<unsignedchar*>(_src),reinterpret_cast<unsignedchar*>(_dst),width,height);env->ReleaseByteArrayElements(src,_src,JNI_ABORT);env->ReleaseByteArrayElements(dst,_dst,0);}extern"C"JNIEXPORTvoidJNICALLJava_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<unsignedchar*>(_src),reinterpret_cast<unsignedchar*>(_dst),width,height);env->ReleaseByteArrayElements(src,_src,JNI_ABORT);env->ReleaseByteArrayElements(dst,_dst,0);}extern"C"JNIEXPORTvoidJNICALLJava_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<unsignedchar*>(_src),reinterpret_cast<unsignedchar*>(_dst),width,height);env->ReleaseByteArrayElements(src,_src,JNI_ABORT);env->ReleaseByteArrayElements(dst,_dst,0);}extern"C"JNIEXPORTvoidJNICALLJava_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<unsignedchar*>(_src),reinterpret_cast<unsignedchar*>(_dst),width,height);env->ReleaseByteArrayElements(src,_src,JNI_ABORT);env->ReleaseByteArrayElements(dst,_dst,0);}

libyuv_utils.h:

#ifndefLIBYUV_UTILS_H#defineLIBYUV_UTILS_H#ifdef__cplusplusextern"C"{#endifvoidlibyuvI420ToRGBA(unsignedchar*src,unsignedchar*dst,intwidth,intheight);voidlibyuvYV12ToRGBA(unsignedchar*src,unsignedchar*dst,intwidth,intheight);voidlibyuvNV12ToRGBA(unsignedchar*src,unsignedchar*dst,intwidth,intheight);voidlibyuvNV21ToRGBA(unsignedchar*src,unsignedchar*dst,intwidth,intheight);#ifdef__cplusplus}#endif#endif//LIBYUV_UTILS_H

libyuv_utils.cpp:

#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"usingnamespacestd;usingnamespacelibyuv;voidlibyuvI420ToRGBA(unsignedchar*src,unsignedchar*dst,intwidth,intheight){unsignedchar*pY=src;unsignedchar*pU=src+width*height;unsignedchar*pV=src+width*height*5/4;I420ToABGR(pY,width,pU,width>>1,pV,width>>1,dst,width*4,width,height);}voidlibyuvYV12ToRGBA(unsignedchar*src,unsignedchar*dst,intwidth,intheight){unsignedchar*pY=src;unsignedchar*pU=src+width*height*5/4;unsignedchar*pV=src+width*height;I420ToABGR(pY,width,pU,width>>1,pV,width>>1,dst,width*4,width,height);}voidlibyuvNV12ToRGBA(unsignedchar*src,unsignedchar*dst,intwidth,intheight){unsignedchar*pY=src;unsignedchar*pUV=src+width*height;NV12ToABGR(pY,width,pUV,width,dst,width*4,width,height);}voidlibyuvNV21ToRGBA(unsignedchar*src,unsignedchar*dst,intwidth,intheight){unsignedchar*pY=src;unsignedchar*pUV=src+width*height;NV21ToABGR(pY,width,pUV,width,dst,width*4,width,height);}

这里值得注意的是,由于libyuv的ARGB和android bitmap的ARGB_8888的存储顺序是不一样的。ARGB_8888的存储顺序实际上是RGBA(这也是为什么我写的函数名都是xxxToRGBA的原因),对应的是libyuv的ABGR。因此如果想对应上android bitmap的ARGB_8888的存储顺序,需要按以下规律转换:

I420转RGBA使用libyuv的I420ToABGR函数

YV12转RGBA使用libyuv的I420ToABGR函数

NV12转RGBA使用libyuv的NV12ToABGR函数

NV21转RGBA使用libyuv的NV21ToABGR函数

4. 使用libyuv旋转RGBA和YUV420P

libyuv.cpp:

extern"C"JNIEXPORTvoidJNICALLJava_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<unsignedchar*>(_src),reinterpret_cast<unsignedchar*>(_dst),width,height,degree);env->ReleaseByteArrayElements(src,_src,JNI_ABORT);env->ReleaseByteArrayElements(dst,_dst,0);}extern"C"JNIEXPORTvoidJNICALLJava_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<unsignedchar*>(_src),reinterpret_cast<unsignedchar*>(_dst),width,height,degree);env->ReleaseByteArrayElements(src,_src,JNI_ABORT);env->ReleaseByteArrayElements(dst,_dst,0);}extern"C"JNIEXPORTvoidJNICALLJava_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<unsignedchar*>(_src),reinterpret_cast<unsignedchar*>(_dst),width,height,degree);env->ReleaseByteArrayElements(src,_src,JNI_ABORT);env->ReleaseByteArrayElements(dst,_dst,0);}

libyuv_utils.h:

voidlibyuvRotateRGB(unsignedchar*src,unsignedchar*dst,intwidth,intheight,floatdegree);voidlibyuvRotateRGBA(unsignedchar*src,unsignedchar*dst,intwidth,intheight,floatdegree);voidlibyuvRotateYUV420P(unsignedchar*src,unsignedchar*dst,intwidth,intheight,floatdegree);

libyuv_utils.cpp:

voidlibyuvRotateRGB(unsignedchar*src,unsignedchar*dst,intwidth,intheight,floatdegree){if(degree==90.0f){ARGBRotate(src,width*3,dst,height*3,width,height,kRotate90);}elseif(degree==180.0f){ARGBRotate(src,width*3,dst,width*3,width,height,kRotate180);}elseif(degree==270.0f){ARGBRotate(src,width*3,dst,height*3,width,height,kRotate270);}else{return;}}voidlibyuvRotateRGBA(unsignedchar*src,unsignedchar*dst,intwidth,intheight,floatdegree){if(degree==90.0f){ARGBRotate(src,width*4,dst,height*4,width,height,kRotate90);}elseif(degree==180.0f){ARGBRotate(src,width*4,dst,width*4,width,height,kRotate180);}elseif(degree==270.0f){ARGBRotate(src,width*4,dst,height*4,width,height,kRotate270);}else{return;}}voidlibyuvRotateYUV420P(unsignedchar*src,unsignedchar*dst,intwidth,intheight,floatdegree){unsignedchar*pSrcY=src;unsignedchar*pSrcU=src+width*height;unsignedchar*pSrcV=src+width*height*5/4;unsignedchar*pDstY=dst;unsignedchar*pDstU=dst+width*height;unsignedchar*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);}elseif(degree==180.0f){I420Rotate(pSrcY,width,pSrcU,width>>1,pSrcV,width>>1,pDstY,width,pDstU,width>>1,pDstV,width>>1,width,height,kRotate180);}elseif(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核(4x1.5Ghz, 4x2Ghz)的手机,处理分辨率为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是最佳解决方案。

如果你是android 系统开发者或者说ROM开发者,那使用libyuv就更加方便了,因为android系统源码已经集成了libyuv。具体路径为:external/libyuv/。使用libyuv时,你甚至不需要额外导入libyuv的头文件和库文件,只需要为你的模块添加依赖就可以了,并且system和vendor分区都可以使用。

在模块的Android.mk文件中添加依赖:

system分区的模块:

LOCAL_C_INCLUDES += $(TOP)/external/libyuv/files/include/

LOCAL_SHARED_LIBRARIES += libyuv

vendor分区的模块:

LOCAL_C_INCLUDES += $(TOP)/external/libyuv/files/include/

LOCAL_SHARED_LIBRARIES += libyuv.vendor

github:https://github.com/qiuxintai/YUV420Converter

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容