libjpeg - turbo
是一个JPEG图像编解码器
,它使用SIMD指令(MMX,SSE2,AVX2,NEON,AltiVec)来加速x86,x86-64,ARM和PowerPC系统上的基线JPEG压缩和解压缩,以及渐进式JPEG压缩x86和x86-64系统。在这样的系统上, libjpeg - turbo
的速度通常是libjpeg
的2-6倍
,在其他类型的系统上,凭借其高度优化的霍夫曼编码例程, libjpeg - turbo
仍然可以大大超过libjpeg
操作系统:ubuntu 16.05
ndk版本:android-ndk-r16b
克隆最新的libjpeg - turbo
源代码
git clone git@github.com:libjpeg-turbo/libjpeg-turbo.git
编辑libjpeg-turbo/CMakeLists.txt
,注释掉红框中的内容
编辑libjpeg-turbo/sharedlib/CMakeLists.txt
,注释掉红框中的内容
第一步:编写配置脚本config.sh
#架构
if [ "$#" -lt 1 ]; then
THE_ARCH=armv7
else
THE_ARCH=$(tr [A-Z] [a-z] <<< "$1")
fi
#根据不同架构配置环境变量
case "$THE_ARCH" in
arm|armv5|armv6|armv7|armeabi)
TOOLCHAIN_BASE="arm-linux-androideabi"
HOST="arm-linux-androideabi"
AOSP_ABI="armeabi"
AOSP_ARCH="arch-arm"
AOSP_FLAGS="-march=armv7-a -mfloat-abi=softfp -fprefetch-loop-arrays"
PROCESSOR="arm"
;;
armv7a|armeabi-v7a)
TOOLCHAIN_BASE="arm-linux-androideabi"
HOST="arm-linux-androideabi"
AOSP_ABI="armeabi-v7a"
AOSP_ARCH="arch-arm"
AOSP_FLAGS="-march=armv7-a -mfloat-abi=softfp -fprefetch-loop-arrays"
PROCESSOR="arm"
;;
armv8|armv8a|aarch64|arm64|arm64-v8a)
TOOLCHAIN_BASE="aarch64-linux-android"
HOST="aarch64-linux-android"
AOSP_ABI="arm64-v8a"
AOSP_ARCH="arch-arm64"
AOSP_FLAGS=""
PROCESSOR="aarch64"
;;
x86)
TOOLCHAIN_BASE="x86"
HOST="i686-linux-android"
AOSP_ABI="x86"
AOSP_ARCH="arch-x86"
AOSP_FLAGS=""
PROCESSOR="i386"
;;
x86_64|x64)
TOOLCHAIN_BASE="x86_64"
HOST="x86_64-linux-android"
AOSP_ABI="x86_64"
AOSP_ARCH="arch-x86_64"
AOSP_FLAGS=""
PROCESSOR="x86_64"
;;
*)
echo "ERROR: Unknown architecture $1"
[ "$0" = "$BASH_SOURCE" ] && exit 1 || return 1
;;
esac
echo "TOOLCHAIN_BASE="$TOOLCHAIN_BASE
echo "TOOLNAME_BASE="$TOOLNAME_BASE
echo "AOSP_ABI="$AOSP_ABI
echo "AOSP_ARCH="$AOSP_ARCH
echo "AOSP_FLAGS="$AOSP_FLAGS
echo "HOST="$HOST
# Set these variables to suit your needs
NDK_PATH=/media/byhook/backup/android/android-ndk-r10e
BUILD_PLATFORM=linux-x86_64
TOOLCHAIN_VERSION=4.9
ANDROID_VERSION=19
# It should not be necessary to modify the rest
HOST=arm-linux-androideabi
SYSROOT=${NDK_PATH}/platforms/android-${ANDROID_VERSION}/arch-arm
export CFLAGS="-march=armv7-a -mfloat-abi=softfp -fprefetch-loop-arrays \ -D__ANDROID_API__=${ANDROID_VERSION} --sysroot=${SYSROOT} \ -isystem ${NDK_PATH}/sysroot/usr/include \ -isystem ${NDK_PATH}/sysroot/usr/include/${HOST}"
export LDFLAGS=-pie
TOOLCHAIN=${NDK_PATH}/toolchains/${HOST}-${TOOLCHAIN_VERSION}/prebuilt/${BUILD_PLATFORM}
cat <<EOF >toolchain.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER ${TOOLCHAIN}/bin/${HOST}-gcc)
set(CMAKE_FIND_ROOT_PATH ${TOOLCHAIN}/${HOST})
EOF
cmake -G"Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake \
-DCMAKE_POSITION_INDEPENDENT_CODE=1 \
[additional CMake flags] libjpeg-turbo
make
第二步:编写构建脚本build_jpeg.sh
,注意下面的ANDROID_NDK_ROOT
路径需要配置成自己的路径
#!/bin/sh
#初始化环境变量
source config.sh
# 获取当前路径
NOW_DIR=$(cd `dirname $0`; pwd)
# 待编译的库目录名称
MY_LIBS_NAME=libjpeg-turbo
# 源代码路径
MY_SOURCE_DIR=$NOW_DIR/libjpeg-turbo
#编译的过程中产生的中间件的存放目录
BINARY_DIR=binary
#NDK路径
ANDROID_NDK_ROOT=/media/byhook/backup/android/android-ndk-r16b
BUILD_PLATFORM=linux-x86_64
AOSP_TOOLCHAIN_SUFFIX=4.9
AOSP_API=21
LIBS_DIR=$NOW_DIR/libs
echo "LIBS_DIR="$LIBS_DIR
# 构建中间文件
BUILD_DIR=./${BINARY_DIR}/${AOSP_ABI}
# 最终编译的安装目录
PREFIX=${LIBS_DIR}/${AOSP_ABI}/
SYSROOT=${ANDROID_NDK_ROOT}/platforms/android-${AOSP_API}/${AOSP_ARCH}
export CFLAGS="$AOSP_FLAGS -D__ANDROID_API__=${AOSP_API} --sysroot=${SYSROOT} \ -isystem ${ANDROID_NDK_ROOT}/sysroot/usr/include \ -isystem ${ANDROID_NDK_ROOT}/sysroot/usr/include/${HOST} "
export LDFLAGS=-pie
TOOLCHAIN=${ANDROID_NDK_ROOT}/toolchains/$TOOLCHAIN_BASE-$AOSP_TOOLCHAIN_SUFFIX/prebuilt/${BUILD_PLATFORM}
#创建当前编译目录
mkdir -p ${BUILD_DIR}
mkdir -p ${PREFIX}
cd ${BUILD_DIR}
cat <<EOF >toolchain.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR ${PROCESSOR})
set(CMAKE_C_COMPILER ${TOOLCHAIN}/bin/${HOST}-gcc)
set(CMAKE_FIND_ROOT_PATH ${TOOLCHAIN}/${HOST})
EOF
cmake -G"Unix Makefiles" \
-DCMAKE_TOOLCHAIN_FILE=toolchain.cmake \
-DCMAKE_POSITION_INDEPENDENT_CODE=1 \
-DCMAKE_INSTALL_PREFIX=${PREFIX} \
-DWITH_JPEG8=1 \
${MY_SOURCE_DIR}
make clean
make
make install
文件目录结构:
第三步:开始执行编译脚本
bash build_jpeg.sh armeabi-v7a
一次编译全平台版本的脚本:build_jpeg_all.sh
for arch in armeabi armeabi-v7a arm64-v8a x86 x86_64
do
bash build_jpeg.sh $arch
done
新建native-jpeg-turbo工程
定义java层的接口
:
package com.onzhou.libjpeg.turbo.loader;
import android.view.Surface;
public class NativeImageLoader {
static {
System.loadLibrary("native-image");
}
public native void loadJPEGImage(String imagePath, Surface surface);
}
新建native_image.cpp文件
,开始编写native层的实现
:
#include <jni.h>
#include <stdio.h>
#include <time.h>
#include <android/bitmap.h>
#include <android/native_window.h>
#include <android/native_window_jni.h>
#include <EGL/egl.h>
#include <GLES3/gl3.h>
#include <jpeglib.h>
#include "native_image.h"
#ifdef ANDROID
#include <android/log.h>
#include <malloc.h>
#include <string.h>
#define LOG_TAG "NativeImage"
#define LOGE(format, ...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, format, ##__VA_ARGS__)
#define LOGI(format, ...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, format, ##__VA_ARGS__)
#else
#define LOGE(format, ...) printf(LOG_TAG format "\n", ##__VA_ARGS__)
#define LOGI(format, ...) printf(LOG_TAG format "\n", ##__VA_ARGS__)
#endif
/** * 动态注册 */
JNINativeMethod methods[] = {
{"loadJPEGImage", "(Ljava/lang/String;Landroid/view/Surface;)V", (void *) loadJPEGImage}
};
/** * 动态注册 * @param env * @return */
jint registerNativeMethod(JNIEnv *env) {
jclass cl = env->FindClass("com/onzhou/libjpeg/turbo/loader/NativeImageLoader");
if ((env->RegisterNatives(cl, methods, sizeof(methods) / sizeof(methods[0]))) < 0) {
return -1;
}
return 0;
}
/** * 加载默认回调 * @param vm * @param reserved * @return */
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env = NULL;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
return -1;
}
//注册方法
if (registerNativeMethod(env) != JNI_OK) {
return -1;
}
return JNI_VERSION_1_6;
}
void ThrowException(JNIEnv *env, const char *exception, const char *message) {
jclass clazz = env->FindClass(exception);
if (NULL != clazz) {
env->ThrowNew(clazz, message);
}
}
int drawJPEG(const char *input_filename, ANativeWindow_Buffer &nwBuffer) {
jpeg_decompress_struct jpegInfo;
jpeg_error_mgr jpegError;
FILE *input_file;
JSAMPARRAY buffer;
int row_width;
unsigned char *pixel;
jpegInfo.err = jpeg_std_error(&jpegError);
if ((input_file = fopen(input_filename, "rb")) == NULL) {
fprintf(stderr, "can't open %s\n", input_filename);
LOGE("open file error");
return -1;
}
//初始化对象信息
jpeg_create_decompress(&jpegInfo);
//指定图片
jpeg_stdio_src(&jpegInfo, input_file);
//读取文件头信息,设置默认的解压参数
jpeg_read_header(&jpegInfo, TRUE);
//开始解压
jpeg_start_decompress(&jpegInfo);
row_width = jpegInfo.output_width * jpegInfo.output_components;
buffer = (*jpegInfo.mem->alloc_sarray)((j_common_ptr) &jpegInfo, JPOOL_IMAGE,
row_width, 1);
//一行
pixel = (unsigned char *) malloc(row_width);
memset(pixel, 0, row_width);
uint32_t *line = (uint32_t *) nwBuffer.bits;
for (int i = 0; i < jpegInfo.output_height; i++) {
//读取一行数据
jpeg_read_scanlines(&jpegInfo, buffer, 1);
pixel = *buffer;
//根据缩放选取行
for (int j = 0; j < jpegInfo.output_width; j++) {
//存储顺序为BGR,BGR,BGR......
line[j] = ((uint32_t) pixel[3 * j + 2]) << 16
| ((uint32_t) pixel[3 * j + 1] << 8)
| ((uint32_t) (pixel[3 * j + 0]));
}
line = line + nwBuffer.stride;
}
free(pixel);
//完成解压
jpeg_finish_decompress(&jpegInfo);
//销毁解压相关信息
jpeg_destroy_decompress(&jpegInfo);
//关闭文件句柄
fclose(input_file);
return 0;
}
void loadJPEGImage(JNIEnv *env, jobject obj, jstring jpegPath, jobject surface) {
const char *path = env->GetStringUTFChars(jpegPath, 0);
//获取目标surface
ANativeWindow *window = ANativeWindow_fromSurface(env, surface);
if (NULL == window) {
ThrowException(env, "java/lang/RuntimeException", "unable to get native window");
return;
}
//默认的是RGB_565
int32_t result = ANativeWindow_setBuffersGeometry(window, 0, 0, WINDOW_FORMAT_RGBA_8888);
if (result < 0) {
ThrowException(env, "java/lang/RuntimeException", "unable to set buffers geometry");
//释放窗口
ANativeWindow_release(window);
window = NULL;
return;
}
ANativeWindow_acquire(window);
ANativeWindow_Buffer buffer;
//锁定窗口的绘图表面
if (ANativeWindow_lock(window, &buffer, NULL) < 0) {
ThrowException(env, "java/lang/RuntimeException", "unable to lock native window");
//释放窗口
ANativeWindow_release(window);
window = NULL;
return;
}
//绘制JPEG图片
drawJPEG(path, buffer);
//解锁窗口的绘图表面
if (ANativeWindow_unlockAndPost(window) < 0) {
ThrowException(env, "java/lang/RuntimeException",
"unable to unlock and post to native window");
}
env->ReleaseStringUTFChars(jpegPath, path);
//释放
ANativeWindow_release(window);
}
注意:上面的操作步骤实际就是:
ANativeWindow_fromSurface
拿到对应的窗口libjpeg-turbo
库读取对应的JPEG图片,然后解码成对应的RGB数据RGB数据
写到窗口的buffer中去,完成绘制编写cmake的配置文件CMakeLists.txt
cmake_minimum_required(VERSION 3.4.1)
##官方标准配置
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fno-rtti -fno-exceptions -Wall")
add_library(native-image
SHARED
src/main/cpp/native_image.cpp)
add_library(jpeg
SHARED
IMPORTED)
set_target_properties(jpeg
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libjpeg.so
)
add_library(jpeg-turbo
SHARED
IMPORTED)
set_target_properties(jpeg-turbo
PROPERTIES IMPORTED_LOCATION
${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/libturbojpeg.so
)
#头文件
include_directories(${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}/include)
target_link_libraries(native-image
jpeg
jpeg-turbo
android
jnigraphics
log)
在启动的目标Activity
中加载我们指定的JPEG图片
mSurfaceView.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(SurfaceHolder holder) {
NativeImageLoader nativeImageLoader = new NativeImageLoader();
File file = new File(getExternalFilesDir(null), "input.jpeg");
nativeImageLoader.loadJPEGImage(file.getAbsolutePath(), holder.getSurface());
}
@Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
}
});
可以看到如下输出:
项目地址:native-jpeg-turbo
https://github.com/byhook/graphic4android
参考:
https://github.com/libjpeg-turbo/libjpeg-turbo/blob/master/BUILDING.md