簡介
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);
}