前面在《YUV_420_888介紹及YUV420轉(zhuǎn)RGBA》一文中介紹了YUV420的轉(zhuǎn)換命贴,并提供了自己寫的轉(zhuǎn)換代碼暮的。但是實(shí)際項(xiàng)目中一般不會(huì)自己手寫代碼逐個(gè)像素去轉(zhuǎn)換奏黑,因?yàn)檫@樣轉(zhuǎn)換的速度比較慢。通常我們可以使用opencv或者libyuv來(lái)進(jìn)行轉(zhuǎn)換湾揽。本文就介紹使用libyuv進(jìn)行轉(zhuǎn)換茵瘾。
YUV420轉(zhuǎn)RGBA系列共三篇:
- YUV_420_888介紹及YUV420轉(zhuǎn)RGBA
- YUV420轉(zhuǎn)RGBA之使用opencv
- YUV420轉(zhuǎn)RGBA之使用libyuv
本文是其中的第三篇,作為最后一篇勺馆,本文對(duì)三種不同方式做了一個(gè)對(duì)比測(cè)試戏售。
1. 下載libyuv
網(wǎng)址:https://chromium.googlesource.com/libyuv/libyuv
git下載:git clone https://chromium.googlesource.com/libyuv/libyuv
下載libyuv源碼完后編譯,取得庫(kù)文件草穆。這里只是簡(jiǎn)單說(shuō)明libyuv的用法灌灾,不提供libyuv的編譯方法,編譯方法請(qǐng)參考官網(wǎng)或者google悲柱、百度锋喜。不會(huì)編譯libyuv也沒(méi)關(guān)系,可以拖到最后豌鸡,直接下載本文代碼嘿般,代碼中提供libyuv庫(kù)文件和頭文件,直接導(dǎo)入即可使用涯冠。
Update 2021-02-19
有幾位童鞋留言或私信提到文章沒(méi)有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官方說(shuō)明的編譯步驟就可以直接編譯通過(guò):
git clone https://chromium.googlesource.com/libyuv/libyuv
cd libyuv/
mkdir out
cd out
cmake ..
cmake --build .
方法二:
也可以參考這個(gè)項(xiàng)目:https://github.com/hzl123456/LibyuvDemo字旭。
把這個(gè)項(xiàng)目依賴包libyuv中的libyuv源碼更新到最新,使用Android Studio編譯之后在build目錄中可以找到最新版本的libyuv.so崖叫。然后我們就可以拷貝頭文件和庫(kù)文件去其它項(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)入庫(kù)文件
將libyuv的庫(kù)文件拷貝項(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)目,接下來(lái)我們只要在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"
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:
#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:
#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;
NV12ToABGR(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;
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"
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:
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:
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的效率對(duì)比
為了對(duì)比自寫c++代碼阔墩、opencv、libyuv的效率瓶珊,我用一臺(tái) 3+32G 8核(4x1.5Ghz, 4x2Ghz)的手機(jī)啸箫,處理分辨率為3264x2448的圖像,分別測(cè)試了:
-
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ù)長(zhǎng)度一樣忘苛,理論上轉(zhuǎn)換時(shí)間復(fù)雜度也一樣,我們選用了比較常用的I420進(jìn)行轉(zhuǎn)換唱较。同樣的扎唾,YUV420SP轉(zhuǎn)換成RGBA,NV12和NV21數(shù)據(jù)長(zhǎng)度一樣绊汹,理論上轉(zhuǎn)換時(shí)間復(fù)雜度也一樣稽屏,我們選用了比較常用的NV21進(jìn)行轉(zhuǎn)換扮宠。
另外西乖,由于opencv和libyuv都沒(méi)有直接可以用于旋轉(zhuǎn)YUV420SP圖像的接口函數(shù),未做旋轉(zhuǎn)YUV420SP的對(duì)比測(cè)試坛增。
每個(gè)函數(shù)測(cè)試5次并計(jì)算平均值获雕,測(cè)試結(jié)果如下表(單位:毫秒ms):
可以看到:
不同操作的對(duì)比,三種方式幾乎都是RGBA順時(shí)針旋轉(zhuǎn)90度的時(shí)間最長(zhǎng)收捣,所以做camera相關(guān)的開(kāi)發(fā)時(shí)届案,如果要旋轉(zhuǎn)camera的出圖,在條件允許的情況下一定要直接旋轉(zhuǎn)YUV420P罢艾,這樣效率才是最高的楣颠,旋轉(zhuǎn)后再做其它操作(例如:轉(zhuǎn)換成RGBA或者Bitmap)。
相同操作的對(duì)比咐蚯,三種方式童漩,自寫c++代碼(Native)幾乎在所有測(cè)試上都是耗時(shí)最久的,而opencv和libyuv在不同功能上各有優(yōu)勢(shì)春锋。由于opencv功能太多太齊全了矫膨,導(dǎo)致opencv的庫(kù)文件非常大,達(dá)到了20MB,而自寫c++代碼僅有97KB侧馅、libyuv僅有264.1KB危尿。在大家都拼命為APK或者ROM瘦身的今天,opencv肯定不會(huì)首先考慮馁痴,體積更小谊娇、性能也很優(yōu)秀的libyuv則會(huì)更加受到青睞。
綜合來(lái)看弥搞,處理YUV420時(shí)邮绿,libyuv是最佳解決方案。
如果你是android 系統(tǒng)開(kāi)發(fā)者或者說(shuō)ROM開(kāi)發(fā)者攀例,那使用libyuv就更加方便了船逮,因?yàn)閍ndroid系統(tǒng)源碼已經(jīng)集成了libyuv。具體路徑為:external/libyuv/粤铭。使用libyuv時(shí)挖胃,你甚至不需要額外導(dǎo)入libyuv的頭文件和庫(kù)文件,只需要為你的模塊添加依賴就可以了梆惯,并且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
最后,本文中的代碼已經(jīng)上傳到github:https://github.com/qiuxintai/YUV420Converter垛吗,如果本文代碼對(duì)你有幫助凹髓,煩請(qǐng)?jiān)趃ithub上給我一個(gè)小小的star。