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

前面在《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系列共三篇:

本文是其中的第三篇,作為最后一篇勺馆,本文對(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):

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

可以看到:
不同操作的對(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。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末怯屉,一起剝皮案震驚了整個(gè)濱河市蔚舀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌锨络,老刑警劉巖赌躺,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異羡儿,居然都是意外死亡礼患,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門掠归,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)缅叠,“玉大人,你說(shuō)我怎么就攤上這事虏冻》袅唬” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵兄旬,是天一觀的道長(zhǎng)狼犯。 經(jīng)常有香客問(wèn)我余寥,道長(zhǎng),這世上最難降的妖魔是什么悯森? 我笑而不...
    開(kāi)封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任宋舷,我火速辦了婚禮,結(jié)果婚禮上瓢姻,老公的妹妹穿的比我還像新娘祝蝠。我一直安慰自己,他們只是感情好幻碱,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開(kāi)白布绎狭。 她就那樣靜靜地躺著,像睡著了一般褥傍。 火紅的嫁衣襯著肌膚如雪儡嘶。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天恍风,我揣著相機(jī)與錄音蹦狂,去河邊找鬼。 笑死朋贬,一個(gè)胖子當(dāng)著我的面吹牛凯楔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播锦募,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼摆屯,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了糠亩?” 一聲冷哼從身側(cè)響起虐骑,我...
    開(kāi)封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎削解,沒(méi)想到半個(gè)月后富弦,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體沟娱,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡氛驮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了济似。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片矫废。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖砰蠢,靈堂內(nèi)的尸體忽然破棺而出蓖扑,到底是詐尸還是另有隱情,我是刑警寧澤台舱,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布律杠,位于F島的核電站潭流,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏柜去。R本人自食惡果不足惜灰嫉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嗓奢。 院中可真熱鬧讼撒,春花似錦、人聲如沸股耽。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)物蝙。三九已至炎滞,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間诬乞,已是汗流浹背厂榛。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留丽惭,地道東北人击奶。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像责掏,于是被迫代替她去往敵國(guó)和親柜砾。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355