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;
}
}
示例代码下载