libyuv 源碼: https://chromium.googlesource.com/libyuv/libyuv/
下載源碼(需翻墻)嚷掠,Android Studio 新建一個 NDK 項目,源碼拷貝到 cpp 目錄下荞驴。
include 下面是頭文件不皆, source 下面是源碼,其它文件基本用不到不用管熊楼。CMakeLists.txt 是 cmake 編譯腳本霹娄, 現(xiàn)在android ndk 默認(rèn)都是用 cmake 編譯。
// 格式轉(zhuǎn)換(NV21鲫骗、NV12犬耻、I420等格式互轉(zhuǎn))
libyuv\include\libyuv\convert.h
libyuv\include\libyuv\convert_from.h
// 圖像處理(鏡像、旋轉(zhuǎn)挎峦、縮放香追、裁剪)
libyuv\include\libyuv\planar_functions.h
libyuv\include\libyuv\rotate.h
libyuv\include\libyuv\scale.h
下面編寫我們自己的 CMakeLists , libyuv 作為一個子項目單獨(dú)編譯坦胶,按下面修改(我也是抄別人的)
cmake_minimum_required(VERSION 3.4.1)
# 外部頭文件路徑透典,因為我們要引用 libyuv.h
include_directories(libyuv/include)
# 添加子項目,libyuv 作為一個子項目自己編譯顿苇,有自己的 CMakeList.txt峭咒。
# 編譯結(jié)果存放在 build 目錄下,可以在里面找到生成的 .so 文件纪岁。
add_subdirectory(libyuv ./build)
# 生成動態(tài)鏈接庫 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數(shù)據(jù)
* @param width 原始寬
* @param height 原始高
* @param dst_i420_data 目前I420數(shù)據(jù)
*/
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);
相機(jī)預(yù)覽拿到 NV21 數(shù)據(jù)處理
camera.setPreviewCallback(new Camera.PreviewCallback() {
@Override
public void onPreviewFrame(byte[] bytes, Camera camera) {
// bytes 是 NV21 格式的 YUV 數(shù)據(jù)
Camera.Size previewSize = camera.getParameters().getPreviewSize();
int width = previewSize.width;
int height = previewSize.height;
try {
// NV21 轉(zhuǎn) 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;
// 旋轉(zhuǎn): 注意順時針旋轉(zhuǎn) 90 度后寬高對調(diào)了
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);
// 轉(zhuǎn)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 而都轉(zhuǎn)成 I420 來處理款青,這不麻煩嗎?霍狰?抡草?可能 I420 這種格式比較通用,很多算法都是針對 I420 來處理的蔗坯。
看網(wǎng)上有人問康震,你這么多步轉(zhuǎn)來轉(zhuǎn)去的可能還沒有 java 代碼效率高呢?宾濒?腿短?這是個好問題,雖然調(diào)用了好幾個 c++ 函數(shù)鼎兽,但一般來說肯定還是比 java 代碼效率高答姥, 否則還要什么 libyuv铣除。
目前這么多步都是單獨(dú)處理谚咬,其實可以封裝在 JNI 中,這里主要作為演示
直接 Build>Make 就會在 build 目錄下生成 .so 文件尚粘, 直接運(yùn)行 Gradle 會自動把編譯的 so 拷貝到 apk 中择卦。可通過 Build> Analyze APK 查看
網(wǎng)上這么多人編譯好的郎嫁,你為什么不直接用秉继??泽铛?首先別人編譯好的可能有 bug, 你不知道編譯的是不是有問題尚辑,可能別人編譯的不符合你的需求,只有自己會編譯修改才行盔腔。
特別涉及音視頻開發(fā)杠茬,NDK ,JNI 肯定是逃不掉的,ffmpeg, livyuv, ijkplayer 都需要自己能編譯和修改添加功能才行弛随。所以還是要自己搞一遍瓢喉。 LibYUV 我們寫不了,但原理要清楚舀透,要會比著葫蘆畫瓢栓票。
參考:
https://developer.android.google.cn/ndk/guides/cmake?hl=en
https://juejin.im/post/6844903949074432007
http://www.reibang.com/p/bd0feaf4c0f9