美文网首页图像RGB、YUV专题
YUV420转RGBA之使用libyuv

YUV420转RGBA之使用libyuv

作者: qiuxintai | 来源:发表于2020-06-12 12:06 被阅读0次

    前面在《YUV_420_888介绍及YUV420转RGBA》一文中介绍了YUV420的转换,并提供了自己写的转换代码。但是实际项目中一般不会自己手写代码逐个像素去转换,因为这样转换的速度比较慢。通常我们可以使用opencv或者libyuv来进行转换。本文就介绍使用libyuv进行转换。

    YUV420转RGBA系列共三篇:

    本文是其中的第三篇,作为最后一篇,本文对三种不同方式做了一个对比测试。

    1. 下载libyuv

    网址:https://chromium.googlesource.com/libyuv/libyuv
    git下载:git clone https://chromium.googlesource.com/libyuv/libyuv
    下载libyuv源码完后编译,取得库文件。这里只是简单说明libyuv的用法,不提供libyuv的编译方法,编译方法请参考官网或者google、百度。不会编译libyuv也没关系,可以拖到最后,直接下载本文代码,代码中提供libyuv库文件和头文件,直接导入即可使用。

    Update 2021-02-19
    有几位童鞋留言或私信提到文章没有libyuv的编译步骤,这里补充一下。
    libyuv有多种构建方式,我们可以选择ninja、cmake、make任意一种。并且Google已经提供了相应的构建文件。从libyuv源码中我们也可以看到确实包含:android.bp、android.mk、linux.mk、CMakeLists.txt。
    方法一:
    一般情况下,按照Google官方说明的编译步骤就可以直接编译通过:

    git clone https://chromium.googlesource.com/libyuv/libyuv
    cd libyuv/
    mkdir out
    cd out
    cmake ..
    cmake --build .
    

    方法二:
    也可以参考这个项目:https://github.com/hzl123456/LibyuvDemo
    把这个项目依赖包libyuv中的libyuv源码更新到最新,使用Android Studio编译之后在build目录中可以找到最新版本的libyuv.so。然后我们就可以拷贝头文件和库文件去其它项目上使用了。

    2. 导入libyuv到Android Studio

    2.1 导入头文件

    在项目的app/src/main/cpp/下新建include文件夹。将libyuv的头文件拷贝到app/src/main/cpp/include下。

    2.2 导入库文件

    将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中编码调用 libyuv 的API就可以了。

    3. 使用libyuv将YUV420转换成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的存储顺序是不一样的。ARGB_8888的存储顺序实际上是RGBA(这也是为什么我写的函数名都是xxxToRGBA的原因),对应的是libyuv的ABGR。因此如果想对应上android bitmap的ARGB_8888的存储顺序,需要按以下规律转换:

    • I420转RGBA使用libyuv的I420ToABGR函数
    • YV12转RGBA使用libyuv的I420ToABGR函数
    • NV12转RGBA使用libyuv的NV12ToABGR函数
    • NV21转RGBA使用libyuv的NV21ToABGR函数

    4. 使用libyuv旋转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的效率对比

    为了对比自写c++代码、opencv、libyuv的效率,我用一台 3+32G 8核(4x1.5Ghz, 4x2Ghz)的手机,处理分辨率为3264x2448的图像,分别测试了:

    • YUV420P转换成RGBA(I420)
    • YUV420SP转换成RGBA(NV21)
    • RGBA顺时针旋转90度
    • YUV420P顺时针旋转90度

    YUV420P转换成RGBA,I420和YV12数据长度一样,理论上转换时间复杂度也一样,我们选用了比较常用的I420进行转换。同样的,YUV420SP转换成RGBA,NV12和NV21数据长度一样,理论上转换时间复杂度也一样,我们选用了比较常用的NV21进行转换。
    另外,由于opencv和libyuv都没有直接可以用于旋转YUV420SP图像的接口函数,未做旋转YUV420SP的对比测试。
    每个函数测试5次并计算平均值,测试结果如下表(单位:毫秒ms):

    处理时间对比.png

    可以看到:
    不同操作的对比,三种方式几乎都是RGBA顺时针旋转90度的时间最长,所以做camera相关的开发时,如果要旋转camera的出图,在条件允许的情况下一定要直接旋转YUV420P,这样效率才是最高的,旋转后再做其它操作(例如:转换成RGBA或者Bitmap)。

    相同操作的对比,三种方式,自写c++代码(Native)几乎在所有测试上都是耗时最久的,而opencv和libyuv在不同功能上各有优势。由于opencv功能太多太齐全了,导致opencv的库文件非常大,达到了20MB,而自写c++代码仅有97KB、libyuv仅有264.1KB。在大家都拼命为APK或者ROM瘦身的今天,opencv肯定不会首先考虑,体积更小、性能也很优秀的libyuv则会更加受到青睐。

    综合来看,处理YUV420时,libyuv是最佳解决方案。

    如果你是android 系统开发者或者说ROM开发者,那使用libyuv就更加方便了,因为android系统源码已经集成了libyuv。具体路径为:external/libyuv/。使用libyuv时,你甚至不需要额外导入libyuv的头文件和库文件,只需要为你的模块添加依赖就可以了,并且system和vendor分区都可以使用。
    在模块的Android.mk文件中添加依赖:

    • system分区的模块:
      LOCAL_C_INCLUDES += $(TOP)/external/libyuv/files/include/
      LOCAL_SHARED_LIBRARIES += libyuv
    • vendor分区的模块:
      LOCAL_C_INCLUDES += $(TOP)/external/libyuv/files/include/
      LOCAL_SHARED_LIBRARIES += libyuv.vendor

    最后,本文中的代码已经上传到github:https://github.com/qiuxintai/YUV420Converter,如果本文代码对你有帮助,烦请在github上给我一个小小的star。

    相关文章

      网友评论

        本文标题:YUV420转RGBA之使用libyuv

        本文链接:https://www.haomeiwen.com/subject/wrdgtktx.html