android使用libyuv

libyuv可以帮我们将rgb转换成yuv,也可以用来旋转yuv,转换成其他yuv格式
libyuv git地址是https://chromium.googlesource.com/libyuv/libyuv,由于众所周知的原因,无法直接访问,我们可以用gitlab来中转一下
首先创建一个project,选择Import project,点击Repo by URL,在url里填入这个网址,勾选Mirror repository,然后创建工程,等待同步完成就可以下载代码了

这个是我创建的一个镜像 https://gitlab.com/cc_linco/libyuv1.git

接下来创建AS工程,勾选C++支持,一路next就可以了
将libyuv源码解压到src/main/cpp目录下,修改一下libyuv/CMakeLists.txt,去掉一些编译生成的文件,也可以不改,按自己需要编辑,下面是我注释掉一些编译目标后的文件

# CMakeLists for libyuv
# Originally created for "roxlu build system" to compile libyuv on windows
# Run with -DTEST=ON to build unit tests

PROJECT ( YUV C CXX )   # "C" is required even for C++ projects
CMAKE_MINIMUM_REQUIRED( VERSION 2.8 )
OPTION( TEST "Built unit tests" OFF )

SET ( ly_base_dir   ${PROJECT_SOURCE_DIR} )
SET ( ly_src_dir    ${ly_base_dir}/source )
SET ( ly_inc_dir    ${ly_base_dir}/include )
SET ( ly_tst_dir    ${ly_base_dir}/unit_test )
SET ( ly_lib_name   yuv )
SET ( ly_lib_static ${ly_lib_name} )
SET ( ly_lib_shared ${ly_lib_name}_shared )

FILE ( GLOB_RECURSE ly_source_files ${ly_src_dir}/*.cc )
LIST ( SORT         ly_source_files )

FILE ( GLOB_RECURSE ly_unittest_sources ${ly_tst_dir}/*.cc )
LIST ( SORT         ly_unittest_sources )

INCLUDE_DIRECTORIES( BEFORE ${ly_inc_dir} )

# this creates the static library (.a)
ADD_LIBRARY             ( ${ly_lib_static} STATIC ${ly_source_files} )

# this creates the shared library (.so)
ADD_LIBRARY             ( ${ly_lib_shared} SHARED ${ly_source_files} )
SET_TARGET_PROPERTIES   ( ${ly_lib_shared} PROPERTIES OUTPUT_NAME "${ly_lib_name}" )
SET_TARGET_PROPERTIES   ( ${ly_lib_shared} PROPERTIES PREFIX "lib" )

# this creates the conversion tool
#ADD_EXECUTABLE         ( yuvconvert ${ly_base_dir}/util/yuvconvert.cc )
#TARGET_LINK_LIBRARIES  ( yuvconvert ${ly_lib_static} )


INCLUDE ( FindJPEG )
if (JPEG_FOUND)
  include_directories( ${JPEG_INCLUDE_DIR} )
  target_link_libraries( yuvconvert ${JPEG_LIBRARY} )
  add_definitions( -DHAVE_JPEG )
endif()

if(TEST)
  find_library(GTEST_LIBRARY gtest)
  if(GTEST_LIBRARY STREQUAL "GTEST_LIBRARY-NOTFOUND")
    set(GTEST_SRC_DIR /usr/src/gtest CACHE STRING "Location of gtest sources")
    if(EXISTS ${GTEST_SRC_DIR}/src/gtest-all.cc)
      message(STATUS "building gtest from sources in ${GTEST_SRC_DIR}")
      set(gtest_sources ${GTEST_SRC_DIR}/src/gtest-all.cc)
      add_library(gtest STATIC ${gtest_sources})
      include_directories(${GTEST_SRC_DIR})
      include_directories(${GTEST_SRC_DIR}/include)
      set(GTEST_LIBRARY gtest)
    else()
      message(FATAL_ERROR "TEST is set but unable to find gtest library")
    endif()
  endif()

  add_executable(libyuv_unittest ${ly_unittest_sources})
  target_link_libraries(libyuv_unittest ${ly_lib_name} ${GTEST_LIBRARY})
  find_library(PTHREAD_LIBRARY pthread)
  if(NOT PTHREAD_LIBRARY STREQUAL "PTHREAD_LIBRARY-NOTFOUND")
    target_link_libraries(libyuv_unittest pthread)
  endif()
  if (JPEG_FOUND)
    target_link_libraries(libyuv_unittest ${JPEG_LIBRARY})
  endif()

  if(NACL AND NACL_LIBC STREQUAL "newlib")
    target_link_libraries(libyuv_unittest glibc-compat)
  endif()

  find_library(GFLAGS_LIBRARY gflags)
  if(NOT GFLAGS_LIBRARY STREQUAL "GFLAGS_LIBRARY-NOTFOUND")
    target_link_libraries(libyuv_unittest gflags)
    add_definitions(-DLIBYUV_USE_GFLAGS)
  endif()
endif()


# install the conversion tool, .so, .a, and all the header files
#INSTALL ( PROGRAMS ${CMAKE_BINARY_DIR}/yuvconvert         DESTINATION bin )
#INSTALL ( TARGETS ${ly_lib_static}                        DESTINATION lib )
#INSTALL ( TARGETS ${ly_lib_shared} LIBRARY                DESTINATION lib RUNTIME DESTINATION bin )
INSTALL ( DIRECTORY ${PROJECT_SOURCE_DIR}/include/     DESTINATION include )

# create the .deb and .rpm packages using cpack
#INCLUDE ( CM_linux_packages.cmake )

然后编辑工程根目录下的CMakeLists.txt,主要是引入libyuv

cmake_minimum_required(VERSION 3.4.1)

include_directories(src/main/cpp/libyuv/include)

add_subdirectory(src/main/cpp/libyuv)

add_library( native-lib
             SHARED
             src/main/cpp/native-lib.cpp)


find_library(log-lib
              log )

target_link_libraries( native-lib
                       ${log-lib}
                       yuv)

点击Make Project,不出意外编译成功,我用的是NDK16

创建一个YuvUtils,有三个常用的方法,第一个就是NV21转I420,然后旋转I420,最后一个是NV21转换I420并顺时针旋转90度,可以替换前两个方法

package com.example.yuv;

public class YuvUtils {
    static {
        System.loadLibrary("native-lib");
    }
    public static native void NV21ToI420(byte[] input, byte[] output, int width, int height);
    public static native void RotateI420(byte[] input, byte[] output, int width, int height, int rotation);
    public static native void NV21ToI420andRotate90Clockwise(byte[] input, byte[] output, int width, int height);
}

在native-lib.cpp添加实现的代码

#include 
//#include "libyuv.h"
#include "libyuv/include/libyuv.h"
#include 
#include 

#define LOG_TAG "libyuv"
#define LOGV(...)  __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...)  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...)  __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

using namespace libyuv;
extern "C"
JNIEXPORT void JNICALL
Java_com_example_yuv_YuvUtils_NV21ToI420(JNIEnv *env, jobject instance, jbyteArray input_,
                                                   jbyteArray output_, jint in_width, jint in_height) {
    jbyte *srcData = env->GetByteArrayElements(input_, NULL);
    jbyte *dstData = env->GetByteArrayElements(output_, NULL);

    NV21ToI420((const uint8_t *)srcData, in_width,
               (uint8_t *)srcData + (in_width * in_height), in_width,
               (uint8_t *)dstData, in_width,
               (uint8_t *)dstData + (in_width * in_height), in_width / 2,
               (uint8_t *)dstData + (in_width * in_height * 5 / 4), in_width / 2,
               in_width, in_height);
    env->ReleaseByteArrayElements(input_, srcData, 0);
    env->ReleaseByteArrayElements(output_, dstData, 0);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_yuv_YuvUtils_RotateI420(JNIEnv *env, jobject type, jbyteArray input_,
                                                   jbyteArray output_, jint in_width, jint in_height,
                                                   jint rotation) {
    jbyte *srcData = env->GetByteArrayElements(input_, NULL);
    jbyte *dstData = env->GetByteArrayElements(output_, NULL);


    RotationMode rotationMode = kRotate0;
    switch (rotation) {
        case 90:
            rotationMode = kRotate90;
            break;
        case 180:
            rotationMode = kRotate180;
            break;
        case 270:
            rotationMode = kRotate270;
            break;
    }
    I420Rotate((const uint8_t *)srcData, in_width,
               (uint8_t *)srcData + (in_width * in_height), in_width / 2,
               (uint8_t *)srcData + (in_width * in_height * 5 / 4), in_width / 2,
               (uint8_t *)dstData, in_height,
               (uint8_t *)dstData + (in_width * in_height), in_height / 2,
               (uint8_t *)dstData + (in_width * in_height * 5 / 4), in_height / 2,
               in_width, in_height,
               rotationMode);

    env->ReleaseByteArrayElements(input_, srcData, 0);
    env->ReleaseByteArrayElements(output_, dstData, 0);
}

extern "C"
JNIEXPORT void JNICALL
Java_com_example_yuv_YuvUtils_NV21ToI420andRotate90Clockwise(JNIEnv *env, jobject type,
                                                                       jbyteArray input_,
                                                                       jbyteArray output_,
                                                                       jint in_width, jint in_height) {
    jbyte *srcData = env->GetByteArrayElements(input_, NULL);
    jbyte *dstData = env->GetByteArrayElements(output_, NULL);
    jsize size = env->GetArrayLength(input_);

    NV21ToI420((const uint8_t *) srcData, in_width,
               (uint8_t *)srcData + (in_width * in_height), in_width,
               (uint8_t *)dstData, in_width,
               (uint8_t *)dstData + (in_width * in_height), in_width / 2,
               (uint8_t *)dstData + (in_width * in_height * 5 / 4), in_width / 2,
               in_width, in_height);

    I420Rotate((const uint8_t *)dstData, in_width,
               (uint8_t *)dstData + (in_width * in_height), in_width / 2,
               (uint8_t *)dstData + (in_width * in_height * 5 / 4), in_width / 2,
               (uint8_t *)srcData, in_height,
               (uint8_t *)srcData + (in_width * in_height), in_height / 2,
               (uint8_t *)srcData + (in_width * in_height * 5 / 4), in_height / 2,
               in_width, in_height,
               kRotate90);
    memcpy(dstData, srcData, size);

//    fixme can't work
//    ConvertToI420((const uint8_t *) srcData, size,
//                  (uint8_t *)dstData, in_width,
//                  (uint8_t *)dstData + (in_width * in_height), in_width / 2,
//                  (uint8_t *)dstData + (in_width * in_height * 5 / 4), in_width / 2,
//                  0, 0,
//                  in_width, in_height,
//                  in_width, in_height,
//                  kRotate90,
//                  FOURCC_NV21);
//
//   fixme can't work
//    NV12ToI420Rotate((const uint8_t *) srcData, in_width,
//                     (uint8_t *) srcData + (in_width * in_height), in_width,
//                     (uint8_t *)dstData, in_width,
//                     (uint8_t *)dstData + (in_width * in_height * 5 / 4), in_width / 2,
//                     (uint8_t *)dstData + (in_width * in_height), in_width / 2,
//                     in_width, in_height,
//                     kRotate90);

    env->ReleaseByteArrayElements(input_, srcData, 0);
    env->ReleaseByteArrayElements(output_, dstData, 0);
}

其中ConvertToI420和NV12ToI420Rotate无法将NV21转换成I420后并旋转,会画面异常,不知道为什么,只能简单组合调用NV21ToI420和I420Rotate了

在附赠一个YUVConverter,使用了YuvUtils,可以将camera的预览数据转换成MediaCodec H264编码器需要的yuv420sp/yuv420p格式,这里使用了一些java方法转换数据,因为还没怎么研究libyuv转换其他数据

import android.graphics.ImageFormat;
import android.media.MediaCodecInfo;

import static android.graphics.ImageFormat.NV21;

public enum YUVConverter {
    NV21_YUV420p(ImageFormat.NV21, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar),
    NV21_YUV420sp(ImageFormat.NV21, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar),
    YV12_YUV420p(ImageFormat.YV12, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar),
    YV12_YUV420sp(ImageFormat.YV12, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar),
    ;

    private int cameraPreviewFormat;
    private int mediaCodecColorFormat;
    private boolean rotate90 = false;
    private boolean useLibyuv = false;
    private byte[] buf;

    YUVConverter(int cameraPreviewFormat, int mediaCodecColorFormat) {
        this.cameraPreviewFormat = cameraPreviewFormat;
        this.mediaCodecColorFormat = mediaCodecColorFormat;
    }

    public int getCameraPreviewFormat() {
        return cameraPreviewFormat;
    }

    public int getMediaCodecColorFormat() {
        return mediaCodecColorFormat;
    }

    public boolean isRotate90() {
        return rotate90;
    }

    public void setRotate90(boolean rotate90) {
        this.rotate90 = rotate90;
    }

    public boolean isUseLibyuv() {
        return useLibyuv;
    }

    public void setUseLibyuv(boolean useLibyuv) {
        this.useLibyuv = useLibyuv;
    }

    public void convert(byte[] input, byte[] output, int width, int height) {
        if (buf == null || buf.length != output.length) {
            buf = new byte[output.length];
        }
        switch (cameraPreviewFormat) {
            case ImageFormat.NV21:
                switch (mediaCodecColorFormat) {
                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
                        if (useLibyuv) {
                            if (rotate90) {
                                YuvUtils.NV21ToI420andRotate90Clockwise(input, output, width, height);
                            } else {
                                YuvUtils.NV21ToI420(input, output, width, height);
                            }
                        } else {
                            if (rotate90) {
                                swapNV21toI420(input, buf, width, height);
                                YuvUtils.RotateI420(buf, output, width, height, 90);
                            } else {
                                swapNV21toI420(input, output, width, height);
                            }
                        }
                        break;
                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
                        if (rotate90) {
                            swapNV21toNV12(input, buf, width, height);
                            YUV420spRotate90Clockwise(buf, output, width, height);
                        } else {
                            swapNV21toNV12(input, output, width, height);
                        }
                        break;
                    default:
                        throw new RuntimeException("unsupport color format for NV21");
                }
                break;
            case ImageFormat.YV12:
                switch (mediaCodecColorFormat) {
                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
                        if (rotate90) {
                            swapYV12toI420(input, buf, width, height);
                            YuvUtils.RotateI420(buf, output, width, height, 90);
                        } else {
                            swapYV12toI420(input, output, width, height);
                        }
                        break;
                    case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
                        if (rotate90) {
                            swapYV12toNV12(input, buf, width, height);
                            YUV420spRotate90Clockwise(buf, output, width, height);
                        } else {
                            swapYV12toNV12(input, output, width, height);
                        }
                        break;
                    default:
                        throw new RuntimeException("unsupport color format for YV12");
                }
                break;
        }
    }

    //    I420: YYYYYYYY UUVV ->YUV420P
    //    YV12: YYYYYYYY VVUU ->YUV420P
    //    NV12: YYYYYYYY UVUV ->YUV420SP
    //    NV21: YYYYYYYY VUVU ->YUV420SP

    //VVUU => UVUV
    private void swapYV12toNV12(byte[] yv12bytes, byte[] nv12bytes, int width, int height) {
        System.arraycopy(yv12bytes, 0, nv12bytes, 0, width * height);
        int size = width * height;

        for (int i = 0; i < size / 4; i++) {
            nv12bytes[size + 2 * i + 1] = yv12bytes[size + i];
            nv12bytes[size + 2 * i] = yv12bytes[size + size / 4 + i];
        }
    }

    //VVUU => UUVV
    private void swapYV12toI420(byte[] yv12bytes, byte[] i420bytes, int width, int height) {
        System.arraycopy(yv12bytes, 0, i420bytes, 0, width * height);
        System.arraycopy(yv12bytes, width * height + width * height / 4, i420bytes, width * height, width * height / 4);
        System.arraycopy(yv12bytes, width * height, i420bytes, width * height + width * height / 4, width * height / 4);
    }

    //VUVU => UVUV
    private void swapNV21toNV12(byte[] nv21bytes, byte[] i420bytes, int width, int height) {
        System.arraycopy(nv21bytes, 0, i420bytes, 0, width * height);
        int size = width * height;
        for (int i = size; i < size + size / 2; i += 2) {
            i420bytes[i] = nv21bytes[i + 1];
            i420bytes[i + 1] = nv21bytes[i];
        }
    }

    //VUVU => UUVV
    private void swapNV21toI420(byte[] nv21bytes, byte[] i420bytes, int width, int height) {
        System.arraycopy(nv21bytes, 0, i420bytes, 0, width * height);
        int size = width * height;
        for (int i = 0; i < size / 4; i += 1) {
            i420bytes[size + i] = nv21bytes[size + 2 * i + 1];
            i420bytes[size + size / 4 + i] = nv21bytes[size + 2 * i];
        }
    }

    //顺时针旋转90
    private void YUV420spRotate90Clockwise(byte[] src, byte[] dst, int width, int height) {
        int wh = width * height;
        int k = 0;
        for (int i = 0; i < width; i++) {
            for (int j = height - 1; j >= 0; j--) {
                dst[k] = src[width * j + i];
                k++;
            }
        }
        for (int i = 0; i < width; i += 2) {
            for (int j = height / 2 - 1; j >= 0; j--) {
                dst[k] = src[wh + width * j + i];
                dst[k + 1] = src[wh + width * j + i + 1];
                k += 2;
            }
        }
    }

    public int getPreviewBufferSize(int width, int height) {
        int size = 0;
        switch (cameraPreviewFormat) {
            case ImageFormat.YV12: {
                int yStride = (int) Math.ceil(width / 16.0) * 16;
                int uvStride = (int) Math.ceil((yStride / 2) / 16.0) * 16;
                int ySize = yStride * height;
                int uvSize = uvStride * height / 2;
                size = ySize + uvSize * 2;
            }
            break;

            case ImageFormat.NV21: {
                float bytesPerPix = (float) ImageFormat.getBitsPerPixel(cameraPreviewFormat) / 8;
                size = (int) (width * height * bytesPerPix);
            }
            break;
        }

        return size;
    }
}

示例代码下载

你可能感兴趣的:(android)