1. 程式人生 > >android全平臺編譯libjpeg-turbo並基於ANativeWindow載入JPEG圖片

android全平臺編譯libjpeg-turbo並基於ANativeWindow載入JPEG圖片

圖形影象實踐

概述

libjpeg - turbo是一個JPEG影象編解碼器,它使用SIMD指令(MMX,SSE2,AVX2,NEON,AltiVec)來加速x86,x86-64,ARM和PowerPC系統上的基線JPEG壓縮和解壓縮,以及漸進式JPEG壓縮x86和x86-64系統。在這樣的系統上, libjpeg - turbo的速度通常是libjpeg2-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

載入JPEG圖片

新建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