Android 端處理 YUV 數據 - Libyuv 的編譯與使用

前言

在 Android 系統上, Camera 輸出的圖像通常爲 NV21(YUV420SP 系列) 格式, 當咱們想進行錄像處理時, 會面臨兩個問題html

問題 1

圖像的旋轉問題java

  • 後置鏡頭: 須要旋轉 90°
  • 前置鏡頭: 須要旋轉 270° 而後再進行鏡像處理

問題 2

處理好鏡頭的旋轉後, 當咱們嘗試使用 MediaCodec 進行 H.264 的硬編時, 便會發現偏色的問題android

這是由於 MediaCodec 的 COLOR_FormatYUV420SemiPlanar 格式爲 NV12, 並不是是 NV21, 雖然都是 YUV420SP 系列, 但他們的排列不一樣, 都是先存儲 Y 的數據, NV21 是 vu 交替存儲, NV12 是 uv 交替存儲git

- NV21: yyyy yyyy vu vu
- NV12: yyyy yyyy uv uv
複製代碼

爲了解決這個問題, 對於這個問題網上有不少的解決思路, 咱們能夠在 Java 層使用進行數據操做, 不過通過測試以後發現, 在 Samsung S7 Edge 上, 錄製 1080pgithub

  • 旋轉與鏡像: 20ms
  • NV21 轉 NV12: 16ms

消耗時長約爲 40ms, 這也僅僅是勉強可以進行 25 幀的錄製, 在使用 opencv 進行人臉識別或濾鏡處理時, 可以感受到明顯的卡頓感bash

libyuv 即是 google 爲了解決移動端 NV21 數據處理不便所提供的開源庫, 它提供了旋轉, 裁剪, 鏡像, 縮放等功能ide

接下來看看 libyuv 的編譯與使用工具

一. 環境

操做系統

MacOS Mojave version 10.14.5測試

Libyuv

chromium.googlesource.com/libyuv/liby…ui

git clone https://chromium.googlesource.com/libyuv/libyuv
複製代碼

libyuv 源碼

NDK 版本

NDK16

cmake 版本

➜  ~ cmake -version
cmake version 3.14.5
複製代碼

二. 編譯腳本

從 libyuv 的源碼中, 能夠看到 libyuv 已經提供了 CMakeLists.txt, 所以咱們能夠直接經過 cmake 生成 Makefile, 而後經過 make 對 Makefile 進行編譯

ARCH=arm
ANDROID_ARCH_ABI=armeabi-v7a
NDK_PATH=/Users/sharrychoo/Library/Android/ndk/android-ndk-r16b
PREFIX=`pwd`/android/${ARCH}/${CPU}

# cmake 傳參
cmake -G"Unix Makefiles" \
	-DANDROID_NDK=${NDK_PATH} \
    -DCMAKE_TOOLCHAIN_FILE=${NDK_PATH}/build/cmake/android.toolchain.cmake \
    -DANDROID_ABI=${ANDROID_ARCH_ABI} \
    -DANDROID_NATIVE_API_LEVE=16 \
    -DCMAKE_INSTALL_PREFIX=${PREFIX} \
	-DANDROID_ARM_NEON=TRUE \
    ..
    
# 生成動態庫
make 
make install
複製代碼

編譯結果

輸出的 so 庫

so 庫

三. 代碼編寫

咱們將 so 庫和頭文件拷貝到 AS 中, 即可以進行代碼的編寫了, 這裏編寫一個 Libyuv 的工具類, 方便後續使用

一) Java 代碼

這裏以 NV21 轉 I420 爲例

/**
 * 處理 YUV 的工具類
 *
 * @author Sharry <a href="sharrychoochn@gmail.com">Contact me.</a>
 * @version 1.0
 * @since 2019-07-23
 */
public class LibyuvUtil {

    static {
        System.loadLibrary("smedia-camera");
    }

    /**
     * 將 NV21 轉 I420
     */
    public static native void convertNV21ToI420(byte[] src, byte[] dst, int width, int height);
    
    ......
}
複製代碼

二) native 實現

這裏以將 NV21 轉 I420 爲例

namespace libyuv_util {

    void convertI420ToNV12(JNIEnv *env, jclass, jbyteArray i420_src, jbyteArray nv12_dst, int width,
                           int height) {
        jbyte *src = env->GetByteArrayElements(i420_src, NULL);
        jbyte *dst = env->GetByteArrayElements(nv12_dst, NULL);
        // 執行轉換 I420 -> NV12 的轉換
        LibyuvUtil::I420ToNV12(src, dst, width, height);
        // 釋放資源
        env->ReleaseByteArrayElements(i420_src, src, 0);
        env->ReleaseByteArrayElements(nv12_dst, dst, 0);
    }
    
}

void LibyuvUtil::NV21ToI420(jbyte *src, jbyte *dst, int width, int height) {
    // NV21 參數
    jint src_y_size = width * height;
    jbyte *src_y = src;
    jbyte *src_vu = src + src_y_size;
    // I420 參數
    jint dst_y_size = width * height;
    jint dst_u_size = dst_y_size >> 2;
    jbyte *dst_y = dst;
    jbyte *dst_u = dst + dst_y_size;
    jbyte *dst_v = dst + dst_y_size + dst_u_size;
    /**
    * <pre>
    * int NV21ToI420(const uint8_t* src_y,
    *          int src_stride_y,
    *          const uint8_t* src_vu,
    *          int src_stride_vu,
    *          uint8_t* dst_y,
    *          int dst_stride_y,
    *          uint8_t* dst_u,
    *          int dst_stride_u,
    *          uint8_t* dst_v,
    *          int dst_stride_v,
    *          int width,
    *          int height);
    * </pre>
    * <p>
    * stride 爲顏色份量的跨距: 它描述一行像素中, 該顏色份量所佔的 byte 數目, YUV 每一個通道均爲 1byte(8bit)
    * <p>
    * stride_y: Y 是最全的, 一行中有 width 個像素, 也就有 width 個 Y
    * stride_u: YUV420 的採樣爲 Y:U:V = 4:1:1, 從總體的存儲來看, 一個 Y 份量的數目爲 U/V 的四倍
    * 但從一行上來看, width 個 Y, 它會用到 width/2 個 U
    * stride_v: 同 stride_u 的分析方式
    */
    libyuv::NV21ToI420(
            (uint8_t *) src_y, width,
            (uint8_t *) src_vu, width,
            (uint8_t *) dst_y, width,
            (uint8_t *) dst_u, width >> 1,
            (uint8_t *) dst_v, width >> 1,
            width, height
    );
}
複製代碼

能夠看到方法的調用也很是的簡單, 只須要傳入相關參數便可, 其中有個很是重要的參數, stride 跨距, 它描述一行像素中, 該顏色份量所佔的 byte 數目

  • YUV420 系列
    • NV21
      • Y: 跨距爲 width
      • VU: 跨距爲 width
    • I420P(YU12):
      • Y: 跨距爲 width
      • U: 跨距爲 width/2
      • V: 跨距爲 width/2
  • ABGR: 跨距爲 4 *width

對經常使用的色彩空間不熟悉, 請點擊這裏查看

總結

經過 libyuv 進行旋轉鏡像轉碼等操做, 其時長以下

  • 旋轉鏡像: 5~8ms
  • NV21 轉 NV12: 0~3ms

能夠看到比起 java 代碼, 幾乎快了 3 倍, 這已經可以知足流暢錄製的需求了

筆者將經常使用的 YUV 操做整理成了demo 點擊查看, 若有須要能夠將代碼直接拷走使用

相關文章
相關標籤/搜索