android 相机预览编译 libyuv 处理 YUV 数据

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

下载源码(需翻墙),Android Studio 新建一个 NDK 项目,源码拷贝到 cpp 目录下。

native.png
source_code.png

include 下面是头文件, source 下面是源码,其它文件基本用不到不用管。CMakeLists.txt 是 cmake 编译脚本, 现在android ndk 默认都是用 cmake 编译。

// 格式转换(NV21、NV12、I420等格式互转)
libyuv\include\libyuv\convert.h
libyuv\include\libyuv\convert_from.h
// 图像处理(镜像、旋转、缩放、裁剪)
libyuv\include\libyuv\planar_functions.h
libyuv\include\libyuv\rotate.h
libyuv\include\libyuv\scale.h

下面编写我们自己的 CMakeLists , libyuv 作为一个子项目单独编译,按下面修改(我也是抄别人的)

cmake.png
cmake_minimum_required(VERSION 3.4.1)

# 外部头文件路径,因为我们要引用 libyuv.h
include_directories(libyuv/include)

# 添加子项目,libyuv 作为一个子项目自己编译,有自己的 CMakeList.txt。
# 编译结果存放在 build 目录下,可以在里面找到生成的 .so 文件。
add_subdirectory(libyuv ./build)

# 生成动态链接库 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数据
     * @param width         原始宽
     * @param height        原始高
     * @param dst_i420_data 目前I420数据
     */
    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);

相机预览拿到 NV21 数据处理

camera.setPreviewCallback(new Camera.PreviewCallback() {
    @Override
    public void onPreviewFrame(byte[] bytes, Camera camera) {
        // bytes 是  NV21 格式的 YUV 数据
        Camera.Size previewSize = camera.getParameters().getPreviewSize();
        int width = previewSize.width;
        int height = previewSize.height;
        try {
            // NV21  转 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;

            // 旋转: 注意顺时针旋转 90 度后宽高对调了
            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);

            // 转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 而都转成 I420 来处理,这不麻烦吗???可能 I420 这种格式比较通用,很多算法都是针对 I420 来处理的。
看网上有人问,你这么多步转来转去的可能还没有 java 代码效率高呢???这是个好问题,虽然调用了好几个 c++ 函数,但一般来说肯定还是比 java 代码效率高, 否则还要什么 libyuv。
目前这么多步都是单独处理,其实可以封装在 JNI 中,这里主要作为演示

直接 Build>Make 就会在 build 目录下生成 .so 文件, 直接运行 Gradle 会自动把编译的 so 拷贝到 apk 中。可通过 Build> Analyze APK 查看

apk.png
yuv.jpeg
网上这么多人编译好的,你为什么不直接用???首先别人编译好的可能有 bug, 你不知道编译的是不是有问题,可能别人编译的不符合你的需求,只有自己会编译修改才行。
特别涉及音视频开发,NDK ,JNI 肯定是逃不掉的,ffmpeg, livyuv, ijkplayer 都需要自己能编译和修改添加功能才行。所以还是要自己搞一遍。 LibYUV 我们写不了,但原理要清楚,要会比着葫芦画瓢。

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容