網(wǎng)址:https://chromium.googlesource.com/libyuv/libyuv
git下載:git clonehttps://chromium.googlesource.com/libyuv/libyuv
下載libyuv源碼完后編譯,取得庫文件义桂。這里只是簡單說明libyuv的用法登失,不提供libyuv的編譯方法肴掷,編譯方法請參考官網(wǎng)或者google、百度其屏。不會編譯libyuv也沒關(guān)系,可以拖到最后,直接下載本文代碼尸昧,代碼中提供libyuv庫文件和頭文件,直接導(dǎo)入即可使用旷偿。
Update 2021-02-19
有幾位童鞋留言或私信提到文章沒有l(wèi)ibyuv的編譯步驟烹俗,這里補充一下。
libyuv有多種構(gòu)建方式萍程,我們可以選擇ninja幢妄、cmake、make任意一種茫负。并且Google已經(jīng)提供了相應(yīng)的構(gòu)建文件蕉鸳。從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. 導(dǎo)入libyuv到Android Studio
2.1 導(dǎo)入頭文件
在項目的app/src/main/cpp/下新建include文件夾度宦。將libyuv的頭文件拷貝到app/src/main/cpp/include下。
2.2 導(dǎo)入庫文件
將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中編碼調(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的存儲順序是不一樣的划鸽。ARGB_8888的存儲順序?qū)嶋H上是RGBA(這也是為什么我寫的函數(shù)名都是xxxToRGBA的原因),對應(yīng)的是libyuv的ABGR戚哎。因此如果想對應(yīng)上android bitmap的ARGB_8888的存儲順序裸诽,需要按以下規(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的效率對比
為了對比自寫c++代碼丈冬、opencv、libyuv的效率甘畅,我用一臺 3+32G 8核(4x1.5Ghz, 4x2Ghz)的手機埂蕊,處理分辨率為3264x2448的圖像,分別測試了:
YUV420P轉(zhuǎn)換成RGBA(I420)
YUV420SP轉(zhuǎn)換成RGBA(NV21)
RGBA順時針旋轉(zhuǎn)90度
YUV420P順時針旋轉(zhuǎn)90度
YUV420P轉(zhuǎn)換成RGBA疏唾,I420和YV12數(shù)據(jù)長度一樣蓄氧,理論上轉(zhuǎn)換時間復(fù)雜度也一樣,我們選用了比較常用的I420進行轉(zhuǎn)換槐脏。同樣的喉童,YUV420SP轉(zhuǎn)換成RGBA,NV12和NV21數(shù)據(jù)長度一樣准给,理論上轉(zhuǎn)換時間復(fù)雜度也一樣泄朴,我們選用了比較常用的NV21進行轉(zhuǎn)換。
另外露氮,由于opencv和libyuv都沒有直接可以用于旋轉(zhuǎn)YUV420SP圖像的接口函數(shù)祖灰,未做旋轉(zhuǎn)YUV420SP的對比測試。
每個函數(shù)測試5次并計算平均值畔规,測試結(jié)果如下表(單位:毫秒ms):
處理時間對比.png
可以看到:
不同操作的對比局扶,三種方式幾乎都是RGBA順時針旋轉(zhuǎn)90度的時間最長,所以做camera相關(guān)的開發(fā)時叁扫,如果要旋轉(zhuǎn)camera的出圖三妈,在條件允許的情況下一定要直接旋轉(zhuǎn)YUV420P,這樣效率才是最高的莫绣,旋轉(zhuǎn)后再做其它操作(例如:轉(zhuǎn)換成RGBA或者Bitmap)畴蒲。
相同操作的對比,三種方式对室,自寫c++代碼(Native)幾乎在所有測試上都是耗時最久的模燥,而opencv和libyuv在不同功能上各有優(yōu)勢咖祭。由于opencv功能太多太齊全了,導(dǎo)致opencv的庫文件非常大蔫骂,達到了20MB么翰,而自寫c++代碼僅有97KB、libyuv僅有264.1KB辽旋。在大家都拼命為APK或者ROM瘦身的今天浩嫌,opencv肯定不會首先考慮,體積更小补胚、性能也很優(yōu)秀的libyuv則會更加受到青睞码耐。
綜合來看,處理YUV420時糖儡,libyuv是最佳解決方案伐坏。
如果你是android 系統(tǒng)開發(fā)者或者說ROM開發(fā)者怔匣,那使用libyuv就更加方便了握联,因為android系統(tǒng)源碼已經(jīng)集成了libyuv。具體路徑為:external/libyuv/每瞒。使用libyuv時金闽,你甚至不需要額外導(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