android 相機(jī)預(yù)覽編譯 libyuv 處理 YUV 數(shù)據(jù)

libyuv 源碼: https://chromium.googlesource.com/libyuv/libyuv/

下載源碼(需翻墻)嚷掠,Android Studio 新建一個 NDK 項目,源碼拷貝到 cpp 目錄下荞驴。

native.png
source_code.png

include 下面是頭文件不皆, source 下面是源碼,其它文件基本用不到不用管熊楼。CMakeLists.txt 是 cmake 編譯腳本霹娄, 現(xiàn)在android ndk 默認(rèn)都是用 cmake 編譯。

// 格式轉(zhuǎn)換(NV21鲫骗、NV12犬耻、I420等格式互轉(zhuǎn))
libyuv\include\libyuv\convert.h
libyuv\include\libyuv\convert_from.h
// 圖像處理(鏡像、旋轉(zhuǎn)挎峦、縮放香追、裁剪)
libyuv\include\libyuv\planar_functions.h
libyuv\include\libyuv\rotate.h
libyuv\include\libyuv\scale.h

下面編寫我們自己的 CMakeLists , libyuv 作為一個子項目單獨(dú)編譯坦胶,按下面修改(我也是抄別人的)

cmake.png
cmake_minimum_required(VERSION 3.4.1)

# 外部頭文件路徑透典,因為我們要引用 libyuv.h
include_directories(libyuv/include)

# 添加子項目,libyuv 作為一個子項目自己編譯顿苇,有自己的 CMakeList.txt峭咒。
# 編譯結(jié)果存放在 build 目錄下,可以在里面找到生成的 .so 文件纪岁。
add_subdirectory(libyuv ./build)

# 生成動態(tài)鏈接庫 yuvutil,  YuvJni.cpp 是我們的源代碼凑队,可以指定多個源文件。
add_library(yuvutil SHARED YuvJni.cpp)

# 添加NDK里面 編譯好的  log 庫
find_library(log-lib log)

# 把 yuv (這個是 libyuv 子項目生成的 yuv.so) 和 log 庫鏈接到 yuvutil 中
target_link_libraries(yuvutil ${log-lib} yuv)

YuvJni.cpp 是JNI源碼,包裝一下 libyuv 的代碼漩氨。需要對各種 YUV 格式比較熟悉西壮,否則也包裝不出來。

#include <jni.h>
#include "libyuv.h"

/**
 * NV21 -> I420
 */
extern "C"
JNIEXPORT void JNICALL
Java_com_libyuv_util_YuvUtil_NV21ToI420(JNIEnv *env, jclass clazz, jbyteArray src_nv21_array,
                                        jint width, jint height, jbyteArray dst_i420_array) {
    jbyte *src_nv21_data = env->GetByteArrayElements(src_nv21_array, JNI_FALSE);
    jbyte *dst_i420_data = env->GetByteArrayElements(dst_i420_array, JNI_FALSE);

    jint src_y_size = width * height;
    jint src_u_size = (width >> 1) * (height >> 1);

    jbyte *src_nv21_y_data = src_nv21_data;
    jbyte *src_nv21_vu_data = src_nv21_data + src_y_size;

    jbyte *dst_i420_y_data = dst_i420_data;
    jbyte *dst_i420_u_data = dst_i420_data + src_y_size;
    jbyte *dst_i420_v_data = dst_i420_data + src_y_size + src_u_size;

    libyuv::NV21ToI420((const uint8_t *) src_nv21_y_data, width,
                       (const uint8_t *) src_nv21_vu_data, width,
                       (uint8_t *) dst_i420_y_data, width,
                       (uint8_t *) dst_i420_u_data, width >> 1,
                       (uint8_t *) dst_i420_v_data, width >> 1,
                       width, height);

    env->ReleaseByteArrayElements(src_nv21_array, src_nv21_data, 0);
    env->ReleaseByteArrayElements(dst_i420_array, dst_i420_data, 0);
}

YUVUtil.java 定義了 native 方法

public class YuvUtil {

    static {
        System.loadLibrary("yuvutil");
    }

    /**
     * NV21 -> I420
     *
     * @param src_nv21_data 原始NV21數(shù)據(jù)
     * @param width         原始寬
     * @param height        原始高
     * @param dst_i420_data 目前I420數(shù)據(jù)
     */
    public static native void NV21ToI420(byte[] src_nv21_data, int width, int height, byte[] dst_i420_data);

    /**
     * I420 -> NV21
     *
     * @param src_i420_data
     * @param width
     * @param height
     * @param dst_nv21_data
     */
    public static native void I420ToNV21(byte[] src_i420_data, int width, int height, byte[] dst_nv21_data);

相機(jī)預(yù)覽拿到 NV21 數(shù)據(jù)處理

camera.setPreviewCallback(new Camera.PreviewCallback() {
    @Override
    public void onPreviewFrame(byte[] bytes, Camera camera) {
        // bytes 是  NV21 格式的 YUV 數(shù)據(jù)
        Camera.Size previewSize = camera.getParameters().getPreviewSize();
        int width = previewSize.width;
        int height = previewSize.height;
        try {
            // NV21  轉(zhuǎn) I420
            byte[] i420Data = new byte[width * height * 3 / 2];
            YuvUtil.NV21ToI420(bytes, width, height, i420Data);

            // 鏡像
            byte[] i420MirrorData = new byte[width * height * 3 / 2];
            YuvUtil.I420Mirror(i420Data, width, height, i420MirrorData);

            // 縮放叫惊,注意縮放后寬高會改變
            byte[] i420ScaleData = new byte[dstWith * dstHeight * 3 / 2];
            YuvUtil.I420Scale(i420MirrorData, width, height, i420ScaleData, dstWith, dstHeight, 0);
            width = dstWith;
            height = dstHeight;

            // 旋轉(zhuǎn): 注意順時針旋轉(zhuǎn) 90 度后寬高對調(diào)了
            byte[] i420RotateData = new byte[width * height * 3 / 2];
            YuvUtil.I420Rotate(i420ScaleData, width, height, i420RotateData, 90);
            int temp = width;
            width = height;
            height = temp;

            // I420 -> NV21
            byte[] newNV21Data = new byte[width * height * 3 / 2];
            YuvUtil.I420ToNV21(i420RotateData, width, height, newNV21Data);

            // 轉(zhuǎn)Bitmap
            YuvImage yuvImage = new YuvImage(newNV21Data, ImageFormat.NV21, width, height, null);
            ByteArrayOutputStream stream = new ByteArrayOutputStream();
            yuvImage.compressToJpeg(new Rect(0, 0, width, height), 80, stream);
            Bitmap bitmap = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());
            stream.close();

            if (bitmap != null) {
                bitmapSurfaceView.drawBitmap(bitmap);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
});
為什么不直接處理 NV21 而都轉(zhuǎn)成 I420 來處理款青,這不麻煩嗎?霍狰?抡草?可能 I420 這種格式比較通用,很多算法都是針對 I420 來處理的蔗坯。
看網(wǎng)上有人問康震,你這么多步轉(zhuǎn)來轉(zhuǎn)去的可能還沒有 java 代碼效率高呢?宾濒?腿短?這是個好問題,雖然調(diào)用了好幾個 c++ 函數(shù)鼎兽,但一般來說肯定還是比 java 代碼效率高答姥, 否則還要什么 libyuv铣除。
目前這么多步都是單獨(dú)處理谚咬,其實可以封裝在 JNI 中,這里主要作為演示

直接 Build>Make 就會在 build 目錄下生成 .so 文件尚粘, 直接運(yùn)行 Gradle 會自動把編譯的 so 拷貝到 apk 中择卦。可通過 Build> Analyze APK 查看

apk.png
yuv.jpeg
網(wǎng)上這么多人編譯好的郎嫁,你為什么不直接用秉继??泽铛?首先別人編譯好的可能有 bug, 你不知道編譯的是不是有問題尚辑,可能別人編譯的不符合你的需求,只有自己會編譯修改才行盔腔。
特別涉及音視頻開發(fā)杠茬,NDK ,JNI 肯定是逃不掉的,ffmpeg, livyuv, ijkplayer 都需要自己能編譯和修改添加功能才行弛随。所以還是要自己搞一遍瓢喉。 LibYUV 我們寫不了,但原理要清楚舀透,要會比著葫蘆畫瓢栓票。

參考:
https://developer.android.google.cn/ndk/guides/cmake?hl=en
https://juejin.im/post/6844903949074432007
http://www.reibang.com/p/bd0feaf4c0f9

DEMO: https://github.com/lesliebeijing/LibyuvDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市愕够,隨后出現(xiàn)的幾起案子走贪,更是在濱河造成了極大的恐慌佛猛,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坠狡,死亡現(xiàn)場離奇詭異挚躯,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)擦秽,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門码荔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人感挥,你說我怎么就攤上這事缩搅。” “怎么了触幼?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵硼瓣,是天一觀的道長。 經(jīng)常有香客問我置谦,道長堂鲤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任媒峡,我火速辦了婚禮瘟栖,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘谅阿。我一直安慰自己半哟,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布签餐。 她就那樣靜靜地躺著寓涨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪氯檐。 梳的紋絲不亂的頭發(fā)上戒良,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天,我揣著相機(jī)與錄音冠摄,去河邊找鬼糯崎。 笑死,一個胖子當(dāng)著我的面吹牛耗拓,可吹牛的內(nèi)容都是我干的拇颅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼乔询,長吁一口氣:“原來是場噩夢啊……” “哼樟插!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤黄锤,失蹤者是張志新(化名)和其女友劉穎搪缨,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鸵熟,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡副编,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了流强。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片痹届。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖打月,靈堂內(nèi)的尸體忽然破棺而出队腐,到底是詐尸還是另有隱情,我是刑警寧澤奏篙,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布柴淘,位于F島的核電站,受9級特大地震影響秘通,放射性物質(zhì)發(fā)生泄漏为严。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一肺稀、第九天 我趴在偏房一處隱蔽的房頂上張望第股。 院中可真熱鬧,春花似錦盹靴、人聲如沸炸茧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至辕狰,卻和暖如春改备,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蔓倍。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工悬钳, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人偶翅。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓默勾,卻偏偏與公主長得像,于是被迫代替她去往敵國和親聚谁。 傳聞我的和親對象是個殘疾皇子母剥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

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