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

網(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

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市浓利,隨后出現(xiàn)的幾起案子挤庇,更是在濱河造成了極大的恐慌,老刑警劉巖贷掖,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嫡秕,死亡現(xiàn)場離奇詭異,居然都是意外死亡苹威,警方通過查閱死者的電腦和手機昆咽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來牙甫,“玉大人掷酗,你說我怎么就攤上這事】卟福” “怎么了泻轰?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長且轨。 經(jīng)常有香客問我浮声,道長亩鬼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任阿蝶,我火速辦了婚禮雳锋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘羡洁。我一直安慰自己玷过,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布筑煮。 她就那樣靜靜地躺著辛蚊,像睡著了一般。 火紅的嫁衣襯著肌膚如雪真仲。 梳的紋絲不亂的頭發(fā)上袋马,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天,我揣著相機與錄音秸应,去河邊找鬼虑凛。 笑死,一個胖子當(dāng)著我的面吹牛软啼,可吹牛的內(nèi)容都是我干的桑谍。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼祸挪,長吁一口氣:“原來是場噩夢啊……” “哼锣披!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起贿条,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤雹仿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后整以,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胧辽,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年悄蕾,在試婚紗的時候發(fā)現(xiàn)自己被綠了票顾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡帆调,死狀恐怖奠骄,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情番刊,我是刑警寧澤含鳞,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站芹务,受9級特大地震影響蝉绷,放射性物質(zhì)發(fā)生泄漏鸭廷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一熔吗、第九天 我趴在偏房一處隱蔽的房頂上張望辆床。 院中可真熱鬧,春花似錦桅狠、人聲如沸讼载。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽咨堤。三九已至,卻和暖如春漩符,著一層夾襖步出監(jiān)牢的瞬間一喘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工嗜暴, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留凸克,地道東北人。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓灼伤,卻偏偏與公主長得像触徐,于是被迫代替她去往敵國和親咪鲜。 傳聞我的和親對象是個殘疾皇子狐赡,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內(nèi)容