美文网首页音视频积累
android 相机预览编译 libyuv 处理 YUV 数据

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

作者: lesliefang | 来源:发表于2020-09-12 19:07 被阅读0次

    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
    https://www.jianshu.com/p/bd0feaf4c0f9

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

    相关文章

      网友评论

        本文标题:android 相机预览编译 libyuv 处理 YUV 数据

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