简介
Google提供了一套Image的开源库libyuv(git clone https://chromium.googlesource.com/libyuv/libyuv),实现对各种yuv数据之间的转换,包括数据转换,裁剪,缩放,旋转。这里由于有墙,所以可以在github中clone这个https://github.com/bilibili/libyuv.git
一、配置Android NDK编译环境
1、配置NDK
首先下载NDK软件包,并解压:
1、从官网找到ndk的版本并下载:android-ndk-r17c-linux-x86_64.zip
2、解压:unzip android-ndk-r17c-linux-x86_64.zip
3、ls android-ndk-r17c
2、设置NDK的环境变量:
用root权限打开文件 :sudo vim /etc/profile,然后输入root用户密码。打开在最后一行中加入如下 其中“//home/wq/libyuv/ndk/android-ndk-r17c" 这是ndk解压出来的目录
NDKROOT=/home/wangqi/libyuv/ndk/android-ndk-r17c
JAVA_HOME=/usr/local/java/jdk1.6.0_45
PATH=$PATH:$HOME/bin:$JAVA_HOME/bin:$NDKROOT
export JAVA_HOME
export PATH
在编辑器界面按下Esc退出编辑模式然后输入 :qw 保存退出,输入命令:source /etc/profile
使配置生效,然后在任意目录下输入ndk-build 出现如下信息则说明配置正确。
Android NDK: Could not find application project directory !
Android NDK: Please define the NDK_PROJECT_PATH variable to point to it.
/home/wangqi/libyuv/ndk/android-ndk-r17c/build/core/build-local.mk:151: *** Android NDK: Aborting . Stop.
二、libYUV编译
1、新建一个libyuv文件夹,然后再新建一个jni文件夹,l把ibyuv代码下载到jni文件夹中,从github上面下载libyuv代码,
git clone https://github.com/bilibili/libyuv.git
2、在jni目录下面增加Application.mk文件
APP_ABI := armeabi-v7a arm64-v8a
APP_PLATFORM := android-14
3、在libyuv目录下,也就是与jni文件夹和ndk文件夹下面可以执行ndk-build clean,会清除编译生成的静态库,否则不会生成最新的库
ndk-build clean
4、编译
ndk-build
如果在没有添加Application.mk文件,就会生成所有能打出来的库,如果编写了Application.mk文件,就会打出指定架构的库文件。
5、静态库与动态库
# This is the Android makefile for libyuv for both platform and NDK.
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_CPP_EXTENSION := .cc
LOCAL_SRC_FILES := \
source/compare.cc \
source/compare_common.cc \
source/compare_neon64.cc \
source/compare_gcc.cc \
source/convert.cc \
source/convert_argb.cc \
source/convert_from.cc \
source/convert_from_argb.cc \
source/convert_to_argb.cc \
source/convert_to_i420.cc \
source/cpu_id.cc \
source/planar_functions.cc \
source/rotate.cc \
source/rotate_argb.cc \
source/rotate_mips.cc \
source/rotate_neon64.cc \
source/row_any.cc \
source/row_common.cc \
source/row_mips.cc \
source/row_neon64.cc \
source/row_gcc.cc \
source/scale.cc \
source/scale_any.cc \
source/scale_argb.cc \
source/scale_common.cc \
source/scale_mips.cc \
source/scale_neon64.cc \
source/scale_gcc.cc \
source/video_common.cc
# TODO(fbarchard): Enable mjpeg encoder.
# source/mjpeg_decoder.cc
# source/convert_jpeg.cc
# source/mjpeg_validate.cc
ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
LOCAL_CFLAGS += -DLIBYUV_NEON
LOCAL_SRC_FILES += \
source/compare_neon.cc.neon \
source/rotate_neon.cc.neon \
source/row_neon.cc.neon \
source/scale_neon.cc.neon
endif
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_C_INCLUDES += $(LOCAL_PATH)/include
# 生成的库名称
LOCAL_MODULE := libyuv
LOCAL_MODULE_TAGS := optional
# 生成静态库还是动态库
# BUILD_STATIC_LIBRSRY 静态库
# BUILD_SHARED_LIBRARY 动态库
include $(BUILD_STATIC_LIBRARY)
如果如上所操作你只会得到.a的静态库,而动态库需要在上述方法过程中修改一个文件Andorid.mk。这个文件在jni文件夹中,
三、libYUV的使用
1、JNI层代码使用
#include <jni.h>
#include <string>
#include "libyuv.h"
//分别用来存储1420,1420缩放,I420旋转和镜像的数据
static jbyte *Src_i420_data;
static jbyte *Src_i420_data_scale;
static jbyte *Src_i420_data_rotate;
JNIEXPORT void JNI_OnUnload(JavaVM *jvm, void *reserved) {
//进行释放
free(Src_i420_data);
free(Src_i420_data_scale);
free(Src_i420_data_rotate);
}
void scaleI420(jbyte *src_i420_data, jint width, jint height, jbyte *dst_i420_data, jint dst_width,
jint dst_height, jint mode) {
jint src_i420_y_size = width * height;
jint src_i420_u_size = (width >> 1) * (height >> 1);
jbyte *src_i420_y_data = src_i420_data;
jbyte *src_i420_u_data = src_i420_data + src_i420_y_size;
jbyte *src_i420_v_data = src_i420_data + src_i420_y_size + src_i420_u_size;
jint dst_i420_y_size = dst_width * dst_height;
jint dst_i420_u_size = (dst_width >> 1) * (dst_height >> 1);
jbyte *dst_i420_y_data = dst_i420_data;
jbyte *dst_i420_u_data = dst_i420_data + dst_i420_y_size;
jbyte *dst_i420_v_data = dst_i420_data + dst_i420_y_size + dst_i420_u_size;
libyuv::I420Scale((const uint8 *) src_i420_y_data, width,
(const uint8 *) src_i420_u_data, width >> 1,
(const uint8 *) src_i420_v_data, width >> 1,
width, height,
(uint8 *) dst_i420_y_data, dst_width,
(uint8 *) dst_i420_u_data, dst_width >> 1,
(uint8 *) dst_i420_v_data, dst_width >> 1,
dst_width, dst_height,
(libyuv::FilterMode) mode);
}
void rotateI420(jbyte *src_i420_data, jint width, jint height, jbyte *dst_i420_data, jint degree) {
jint src_i420_y_size = width * height;
jint src_i420_u_size = (width >> 1) * (height >> 1);
jbyte *src_i420_y_data = src_i420_data;
jbyte *src_i420_u_data = src_i420_data + src_i420_y_size;
jbyte *src_i420_v_data = src_i420_data + src_i420_y_size + src_i420_u_size;
jbyte *dst_i420_y_data = dst_i420_data;
jbyte *dst_i420_u_data = dst_i420_data + src_i420_y_size;
jbyte *dst_i420_v_data = dst_i420_data + src_i420_y_size + src_i420_u_size;
//要注意这里的width和height在旋转之后是相反的
if (degree == libyuv::kRotate90 || degree == libyuv::kRotate270) {
libyuv::I420Rotate((const uint8 *) src_i420_y_data, width,
(const uint8 *) src_i420_u_data, width >> 1,
(const uint8 *) src_i420_v_data, width >> 1,
(uint8 *) dst_i420_y_data, height,
(uint8 *) dst_i420_u_data, height >> 1,
(uint8 *) dst_i420_v_data, height >> 1,
width, height,
(libyuv::RotationMode) degree);
}
}
void mirrorI420(jbyte *src_i420_data, jint width, jint height, jbyte *dst_i420_data) {
jint src_i420_y_size = width * height;
jint src_i420_u_size = (width >> 1) * (height >> 1);
jbyte *src_i420_y_data = src_i420_data;
jbyte *src_i420_u_data = src_i420_data + src_i420_y_size;
jbyte *src_i420_v_data = src_i420_data + src_i420_y_size + src_i420_u_size;
jbyte *dst_i420_y_data = dst_i420_data;
jbyte *dst_i420_u_data = dst_i420_data + src_i420_y_size;
jbyte *dst_i420_v_data = dst_i420_data + src_i420_y_size + src_i420_u_size;
libyuv::I420Mirror((const uint8 *) src_i420_y_data, width,
(const uint8 *) src_i420_u_data, width >> 1,
(const uint8 *) src_i420_v_data, width >> 1,
(uint8 *) dst_i420_y_data, width,
(uint8 *) dst_i420_u_data, width >> 1,
(uint8 *) dst_i420_v_data, width >> 1,
width, height);
}
void nv21ToI420(jbyte *src_nv21_data, jint width, jint height, jbyte *src_i420_data) {
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 *src_i420_y_data = src_i420_data;
jbyte *src_i420_u_data = src_i420_data + src_y_size;
jbyte *src_i420_v_data = src_i420_data + src_y_size + src_u_size;
libyuv::NV21ToI420((const uint8 *) src_nv21_y_data, width,
(const uint8 *) src_nv21_vu_data, width,
(uint8 *) src_i420_y_data, width,
(uint8 *) src_i420_u_data, width >> 1,
(uint8 *) src_i420_v_data, width >> 1,
width, height);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_libyuv_util_YuvUtil_init(JNIEnv *env, jclass type, jint width, jint height, jint dst_width,
jint dst_height) {
Src_i420_data = (jbyte *) malloc(sizeof(jbyte) * width * height * 3 / 2);
Src_i420_data_scale = (jbyte *) malloc(sizeof(jbyte) * dst_width * dst_height * 3 / 2);
Src_i420_data_rotate = (jbyte *) malloc(sizeof(jbyte) * dst_width * dst_height * 3 / 2);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_libyuv_util_YuvUtil_compressYUV(JNIEnv *env, jclass type,
jbyteArray src_, jint width,
jint height, jbyteArray dst_,
jint dst_width, jint dst_height,
jint mode, jint degree,
jboolean isMirror) {
jbyte *Src_data = env->GetByteArrayElements(src_, NULL);
jbyte *Dst_data = env->GetByteArrayElements(dst_, NULL);
//nv21转化为i420
nv21ToI420(Src_data, width, height, Src_i420_data);
//进行缩放的操作
scaleI420(Src_i420_data, width, height, Src_i420_data_scale, dst_width, dst_height, mode);
if (isMirror) {
//进行旋转的操作
rotateI420(Src_i420_data_scale, dst_width, dst_height, Src_i420_data_rotate, degree);
//因为旋转的角度都是90和270,那后面的数据width和height是相反的
mirrorI420(Src_i420_data_rotate, dst_height, dst_width, Dst_data);
} else {
rotateI420(Src_i420_data_scale, dst_width, dst_height, Dst_data, degree);
}
env->ReleaseByteArrayElements(dst_, Dst_data, 0);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_libyuv_util_YuvUtil_cropYUV(JNIEnv *env, jclass type, jbyteArray src_, jint width,
jint height, jbyteArray dst_, jint dst_width, jint dst_height,
jint left, jint top) {
//裁剪的区域大小不对
if (left + dst_width > width || top + dst_height > height) {
return;
}
//left和top必须为偶数,否则显示会有问题
if (left % 2 != 0 || top % 2 != 0) {
return;
}
jint src_length = env->GetArrayLength(src_);
jbyte *src_i420_data = env->GetByteArrayElements(src_, NULL);
jbyte *dst_i420_data = env->GetByteArrayElements(dst_, NULL);
jint dst_i420_y_size = dst_width * dst_height;
jint dst_i420_u_size = (dst_width >> 1) * (dst_height >> 1);
jbyte *dst_i420_y_data = dst_i420_data;
jbyte *dst_i420_u_data = dst_i420_data + dst_i420_y_size;
jbyte *dst_i420_v_data = dst_i420_data + dst_i420_y_size + dst_i420_u_size;
libyuv::ConvertToI420((const uint8 *) src_i420_data, src_length,
(uint8 *) dst_i420_y_data, dst_width,
(uint8 *) dst_i420_u_data, dst_width >> 1,
(uint8 *) dst_i420_v_data, dst_width >> 1,
left, top,
width, height,
dst_width, dst_height,
libyuv::kRotate0, libyuv::FOURCC_I420);
env->ReleaseByteArrayElements(dst_, dst_i420_data, 0);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_libyuv_util_YuvUtil_yuvI420ToNV21(JNIEnv *env, jclass type, jbyteArray i420Src,
jbyteArray nv21Src,
jint width, jint height) {
jbyte *src_i420_data = env->GetByteArrayElements(i420Src, NULL);
jbyte *src_nv21_data = env->GetByteArrayElements(nv21Src, NULL);
jint src_y_size = width * height;
jint src_u_size = (width >> 1) * (height >> 1);
jbyte *src_i420_y_data = src_i420_data;
jbyte *src_i420_u_data = src_i420_data + src_y_size;
jbyte *src_i420_v_data = src_i420_data + src_y_size + src_u_size;
jbyte *src_nv21_y_data = src_nv21_data;
jbyte *src_nv21_vu_data = src_nv21_data + src_y_size;
libyuv::I420ToNV21(
(const uint8 *) src_i420_y_data, width,
(const uint8 *) src_i420_u_data, width >> 1,
(const uint8 *) src_i420_v_data, width >> 1,
(uint8 *) src_nv21_y_data, width,
(uint8 *) src_nv21_vu_data, width,
width, height);
}
2、java层的代码使用
public class YuvUtil {
static {
System.loadLibrary("yuvutil");
}
/**
* 初始化
*
* @param width 原始的宽
* @param height 原始的高
* @param dst_width 输出的宽
* @param dst_height 输出的高
**/
public static native void init(int width, int height, int dst_width, int dst_height);
/**
* YUV数据的基本的处理
*
* @param src 原始数据
* @param width 原始的宽
* @param height 原始的高
* @param dst 输出数据
* @param dst_width 输出的宽
* @param dst_height 输出的高
* @param mode 压缩模式。这里为0,1,2,3 速度由快到慢,质量由低到高,一般用0就好了,因为0的速度最快
* @param degree 旋转的角度,90,180和270三种
* @param isMirror 是否镜像,一般只有270的时候才需要镜像
**/
public static native void compressYUV(byte[] src, int width, int height, byte[] dst, int dst_width, int dst_height, int mode, int degree, boolean isMirror);
/**
* yuv数据的裁剪操作
*
* @param src 原始数据
* @param width 原始的宽
* @param height 原始的高
* @param dst 输出数据
* @param dst_width 输出的宽
* @param dst_height 输出的高
* @param left 裁剪的x的开始位置,必须为偶数,否则显示会有问题
* @param top 裁剪的y的开始位置,必须为偶数,否则显示会有问题
**/
public static native void cropYUV(byte[] src, int width, int height, byte[] dst, int dst_width, int dst_height, int left, int top);
/**
* 将I420转化为NV21
*
* @param i420Src 原始I420数据
* @param nv21Src 转化后的NV21数据
* @param width 输出的宽
* @param width 输出的高
**/
public static native void yuvI420ToNV21(byte[] i420Src, byte[] nv21Src, int width, int height);
}