YUV420轉(zhuǎn)RGBA之使用libyuv

網(wǎng)址:https://chromium.googlesource.com/libyuv/libyuv

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

下載libyuv源碼完后編譯,取得庫文件。這里只是簡單說明libyuv的用法,不提供libyuv的編譯方法,編譯方法請(qǐng)參考官網(wǎng)或者google、百度。不會(huì)編譯libyuv也沒關(guān)系,可以拖到最后,直接下載本文代碼,代碼中提供libyuv庫文件和頭文件,直接導(dǎo)入即可使用。

Update 2021-02-19

有幾位童鞋留言或私信提到文章沒有l(wèi)ibyuv的編譯步驟,這里補(bǔ)充一下。

libyuv有多種構(gòu)建方式,我們可以選擇ninja、cmake、make任意一種。并且Google已經(jīng)提供了相應(yīng)的構(gòu)建文件。從libyuv源碼中我們也可以看到確實(shí)包含:android.bp、android.mk、linux.mk、CMakeLists.txt。

方法一:

一般情況下,按照Google官方說明的編譯步驟就可以直接編譯通過:

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

方法二:

也可以參考這個(gè)項(xiàng)目:https://github.com/hzl123456/LibyuvDemo。

把這個(gè)項(xiàng)目依賴包libyuv中的libyuv源碼更新到最新,使用Android Studio編譯之后在build目錄中可以找到最新版本的libyuv.so。然后我們就可以拷貝頭文件和庫文件去其它項(xiàng)目上使用了。

2. 導(dǎo)入libyuv到Android Studio

2.1 導(dǎo)入頭文件

在項(xiàng)目的app/src/main/cpp/下新建include文件夾。將libyuv的頭文件拷貝到app/src/main/cpp/include下。

2.2 導(dǎo)入庫文件

將libyuv的庫文件拷貝項(xiàng)目的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})

修改完成后同步一下項(xiàng)目,接下來我們只要在libyuv.cpp和 libyuv_utils.cpp中編碼調(diào)用 libyuv 的API就可以了。

3. 使用libyuv將YUV420轉(zhuǎn)換成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的存儲(chǔ)順序是不一樣的。ARGB_8888的存儲(chǔ)順序?qū)嶋H上是RGBA(這也是為什么我寫的函數(shù)名都是xxxToRGBA的原因),對(duì)應(yīng)的是libyuv的ABGR。因此如果想對(duì)應(yīng)上android bitmap的ARGB_8888的存儲(chǔ)順序,需要按以下規(guī)律轉(zhuǎn)換:

I420轉(zhuǎn)RGBA使用libyuv的I420ToABGR函數(shù)

YV12轉(zhuǎn)RGBA使用libyuv的I420ToABGR函數(shù)

NV12轉(zhuǎn)RGBA使用libyuv的NV12ToABGR函數(shù)

NV21轉(zhuǎn)RGBA使用libyuv的NV21ToABGR函數(shù)

4. 使用libyuv旋轉(zhuǎn)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的效率對(duì)比

為了對(duì)比自寫c++代碼、opencv、libyuv的效率,我用一臺(tái) 3+32G 8核(4x1.5Ghz, 4x2Ghz)的手機(jī),處理分辨率為3264x2448的圖像,分別測試了:

YUV420P轉(zhuǎn)換成RGBA(I420)

YUV420SP轉(zhuǎn)換成RGBA(NV21)

RGBA順時(shí)針旋轉(zhuǎn)90度

YUV420P順時(shí)針旋轉(zhuǎn)90度

YUV420P轉(zhuǎn)換成RGBA,I420和YV12數(shù)據(jù)長度一樣,理論上轉(zhuǎn)換時(shí)間復(fù)雜度也一樣,我們選用了比較常用的I420進(jìn)行轉(zhuǎn)換。同樣的,YUV420SP轉(zhuǎn)換成RGBA,NV12和NV21數(shù)據(jù)長度一樣,理論上轉(zhuǎn)換時(shí)間復(fù)雜度也一樣,我們選用了比較常用的NV21進(jìn)行轉(zhuǎn)換。

另外,由于opencv和libyuv都沒有直接可以用于旋轉(zhuǎn)YUV420SP圖像的接口函數(shù),未做旋轉(zhuǎn)YUV420SP的對(duì)比測試。

每個(gè)函數(shù)測試5次并計(jì)算平均值,測試結(jié)果如下表(單位:毫秒ms):

處理時(shí)間對(duì)比.png

可以看到:

不同操作的對(duì)比,三種方式幾乎都是RGBA順時(shí)針旋轉(zhuǎn)90度的時(shí)間最長,所以做camera相關(guān)的開發(fā)時(shí),如果要旋轉(zhuǎn)camera的出圖,在條件允許的情況下一定要直接旋轉(zhuǎn)YUV420P,這樣效率才是最高的,旋轉(zhuǎn)后再做其它操作(例如:轉(zhuǎn)換成RGBA或者Bitmap)。

相同操作的對(duì)比,三種方式,自寫c++代碼(Native)幾乎在所有測試上都是耗時(shí)最久的,而opencv和libyuv在不同功能上各有優(yōu)勢(shì)。由于opencv功能太多太齊全了,導(dǎo)致opencv的庫文件非常大,達(dá)到了20MB,而自寫c++代碼僅有97KB、libyuv僅有264.1KB。在大家都拼命為APK或者ROM瘦身的今天,opencv肯定不會(huì)首先考慮,體積更小、性能也很優(yōu)秀的libyuv則會(huì)更加受到青睞。

綜合來看,處理YUV420時(shí),libyuv是最佳解決方案。

如果你是android 系統(tǒng)開發(fā)者或者說ROM開發(fā)者,那使用libyuv就更加方便了,因?yàn)閍ndroid系統(tǒng)源碼已經(jīng)集成了libyuv。具體路徑為:external/libyuv/。使用libyuv時(shí),你甚至不需要額外導(dǎo)入libyuv的頭文件和庫文件,只需要為你的模塊添加依賴就可以了,并且system和vendor分區(qū)都可以使用。

在模塊的Android.mk文件中添加依賴:

system分區(qū)的模塊:

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

LOCAL_SHARED_LIBRARIES += libyuv

vendor分區(qū)的模塊:

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

LOCAL_SHARED_LIBRARIES += libyuv.vendor

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容