libjpeg-turbo编译

一、简介

相关博客:
[YUV图片原理]https://yangandmore.github.io/2019/03/14/%E5%9B%BE%E7%89%87YUV%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F/
[RGB图片原理]https://yangandmore.github.io/2019/03/27/%E5%9B%BE%E7%89%87RGB%E6%95%B0%E6%8D%AE%E6%A0%BC%E5%BC%8F/
[libyuv for android库编译和简单使用]https://yangandmore.github.io/2019/03/07/libYUV%E7%BC%96%E8%AF%91/
[libturbojpeg for android库编译和简单使用]https://yangandmore.github.io/2019/03/08/libjpeg-turbo%E7%BC%96%E8%AF%91/
[yuv-tool for android库的项目]https://yangandmore.github.io/2020/07/13/yuv-tool%E5%B7%A5%E5%85%B7%E7%B1%BB/
[turbo-jpeg-tool for android库的项目]https://yangandmore.github.io/2019/03/08/libjpeg-turbo%E7%BC%96%E8%AF%91

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。
地址:
https://github.com/libjpeg-turbo/libjpeg-turbo.git
文档:
https://github.com/libjpeg-turbo/libjpeg-turbo/blob/master/BUILDING.md
接口文档:
https://cdn.rawgit.com/libjpeg-turbo/libjpeg-turbo/master/doc/html/group___turbo_j_p_e_g.html#ga04d1e839ff9a0860dd1475cff78d3364

我的环境:
centenOS7+ndkr16b+cmake
mac10.14+ndkr16b+cmake
这里两款系统都编译成功,windows则没有试过。

二、编译

1.clone

这个就不说了
网站在上面

2.整体结构

这里需要使用脚本进行编译,结构如下:

xxx
|
|-build.sh
|-build_all.sh
\libjpeg-turbo

  • build.sh:编译指定架构的脚本
  • build_all.sh:在编写build.sh后运行此脚本编译所有结构
  • libjpeg-turbo:clone下来的源库

3.环境

1.NDK

需要注意NDK的环境:为Android平台构建libjpeg-turbo需要v13b或更高版本的 Android NDK

2.m4,autoconf,automake,libtool,nasm

这里特别提示对于MAC系统nasm版本比较老,然而很多的第三方C开源库的编译要求的nasm版本会比较高。(官方文档说构建x86或x86_64时需要2.10版本)
首先查看nasm版本nasm -v
如果没有的话就需要安装nasm(在我的macOS下确实没有,因此爬坑爬了很久)

ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)" < /dev/null 2> /dev/null

上述命令执行后输入如下命令:

brew install nasm

一段时间后就可以检查nasm的版本了。

对于整个环境的安装,都是从左到右顺序逐个安装的(存在依赖关系)

4.编写build.sh

所有的注释都在下面已经写好了,需要注意的是在拼接路径的时候要确认路径是否正确,这个很重要。

#!/bin/bash

# 获取指定架构名称
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下的文件夹名称前缀
TOOLCHAIN_LEFT=arm-linux-androideabi
# android版本下的编译器
ANDROID_PLATFORMS=arm
# CMAKE_SYSTEM_PROCESSOR下的配置名称
PROCESSOR_PLATFORMS=arm
# prebuilt文件夹下的编译器名称
TOOLCHAIN_PLEBUILT=arm-linux-androideabi
# include下全名
INCLUDE_PLATFORMS=arm-linux-androideabi
# CFLAGS是否有配置项
CFLAGS_MARCH="-march=armv7-a -mfloat-abi=softfp -fprefetch-loop-arrays"
# 文件夹名称
BUILD_NAME=armeabi
;;
armv7a|armeabi-v7a)
# 配置参数
# toolchain下的文件夹名称前缀
TOOLCHAIN_LEFT=arm-linux-androideabi
# android版本下的编译器
ANDROID_PLATFORMS=arm
# CMAKE_SYSTEM_PROCESSOR下的配置名称
PROCESSOR_PLATFORMS=arm
# prebuilt文件夹下的编译器名称
TOOLCHAIN_PLEBUILT=arm-linux-androideabi
# include下全名
INCLUDE_PLATFORMS=arm-linux-androideabi
# CFLAGS是否有配置项
CFLAGS_MARCH="-march=armv7-a -mfloat-abi=softfp -fprefetch-loop-arrays"
BUILD_NAME=armeabi-v7a
;;
armv8|armv8a|aarch64|arm64|arm64-v8a)
# 配置参数
# toolchain下的文件夹名称前缀
TOOLCHAIN_LEFT=aarch64-linux-android
# android版本下的编译器
ANDROID_PLATFORMS=arm64
# CMAKE_SYSTEM_PROCESSOR下的配置名称
PROCESSOR_PLATFORMS=aarch64
# prebuilt文件夹下的编译器名称
TOOLCHAIN_PLEBUILT=aarch64-linux-android
# include下全名
INCLUDE_PLATFORMS=aarch64-linux-android
# CFLAGS是否有配置项
CFLAGS_MARCH=""
BUILD_NAME=arm64-v8a
;;
x86)
# 配置参数
# toolchain下的文件夹名称前缀
TOOLCHAIN_LEFT=x86
# android版本下的编译器
ANDROID_PLATFORMS=x86
# CMAKE_SYSTEM_PROCESSOR下的配置名称
PROCESSOR_PLATFORMS=x86
# prebuilt文件夹下的编译器名称
TOOLCHAIN_PLEBUILT=i686-linux-android
# include下全名
INCLUDE_PLATFORMS=i686-linux-android
# CFLAGS是否有配置项
CFLAGS_MARCH=""
BUILD_NAME=x86
;;
x86_64|x64)
# 配置参数
# toolchain下的文件夹名称前缀
TOOLCHAIN_LEFT=x86_64
# android版本下的编译器
ANDROID_PLATFORMS=x86_64
# CMAKE_SYSTEM_PROCESSOR下的配置名称
PROCESSOR_PLATFORMS=x86_64
# prebuilt文件夹下的编译器名称
TOOLCHAIN_PLEBUILT=x86_64-linux-android
# include下全名
INCLUDE_PLATFORMS=x86_64-linux-android
# CFLAGS是否有配置项
CFLAGS_MARCH=""
BUILD_NAME=x86_64
;;
esac
# 基本配置
#ndk所在目录
NDK_PATH=/Users/.../android-ndk-r16b
#编译环境这里是 macOS
BUILD_PLATFORM=darwin-x86_64
#编译工具链版本
TOOLCHAIN_VERSION=4.9
#最低兼容
ANDROID_VERSION=21

# 编译配置
SYSROOT=${NDK_PATH}/platforms/android-${ANDROID_VERSION}/arch-${ANDROID_PLATFORMS}
export CFLAGS="${CFLAGS_MARCH} -D__ANDROID_API__=${ANDROID_VERSION} --sysroot=${SYSROOT} \
-isystem ${NDK_PATH}/sysroot/usr/include \
-isystem ${NDK_PATH}/sysroot/usr/include/${INCLUDE_PLATFORMS}"
export LDFLAGS=-pie
TOOLCHAIN=${NDK_PATH}/toolchains/${TOOLCHAIN_LEFT}-${TOOLCHAIN_VERSION}/prebuilt/${BUILD_PLATFORM}

# 创建该架构下的对应文件夹
mkdir -p ${BUILD_NAME}

cd ${BUILD_NAME}
cat <<EOF >toolchain.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR ${ANDROID_PLATFORMS})
set(CMAKE_C_COMPILER ${TOOLCHAIN}/bin/${TOOLCHAIN_PLEBUILT}-gcc)
set(CMAKE_FIND_ROOT_PATH ${TOOLCHAIN}/${TOOLCHAIN_PLEBUILT})
EOF

cmake -G"Unix Makefiles" -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake \
-DCMAKE_POSITION_INDEPENDENT_CODE=1 \
../libjpeg-turbo # 这里是源库的地址

make clean
make
make install

完成上述后就可以打出我们想要的包,只需键入sudo ./build.sh 架构名称后等待即可。

5.编写build_all.sh

上面只能打印指定包,而在这里编写后可以打出完整的所有依赖包:

for arch in armeabi armeabi-v7a arm64-v8a x86 x86_64
do
bash build.sh $arch
done

这里使用循环分别编译出:armeabi、armeabi-v7a、arm64-v8a、x86和x86_64的包。

6.使用

在编译成功后,选择自己需要的架构下的libjpegturbo.a文件,放置在项目下的libsjniLibs文件夹下,前者需要在gradle中添加引导。
然后在cpp/include文件夹下添加turbojpeg.h头文件(libjpeg-turbo源库下)。就可以自己随意使用了。

三、使用

初始化与释放

初始化

  • tjInitCompress:创建TurboJPEG压缩器实例
  • tjInitDecompress:创建TurboJPEG解压器
  • tjInitTransform:创建TurboJPEG转换器实例

以上三个参数均会返回一个tjhandle的实例,用于添加至对应的函数中使用。

释放

  • tjDestory(tjhandle):销毁TurboJPEG压缩器、解压器或转换器实例

压缩器-Compress

I420 转 JPEG

函数如下:

DLLEXPORT int DLLCALL tjCompressFromYUV(tjhandle handle, unsigned char *srcBuf,
int width, int pad, int height, int subsamp, unsigned char **jpegBuf,
unsigned long *jpegSize, int jpegQual, int flags);

  • tjhandle:就是上述初始化的tjInitCompress压缩器
  • strBuf:也就是我们的I420原数据。需要注意该yuv图片大小必须与tjBufSizeYUV2()函数的返回值匹配。
    ** tjBufSizeYUV2(int width, int pad, int height, int subsamp):用于读取相应的大小,后续会用到这个参数padsubsamp

    width & height:为图片的宽高
    pad:值必须为2的幂,图像每个平面中每条线的宽度填充到这个字节数的最接近的倍数
    subsamp:图像的采样类型
    github

  • width & pad & height & subsamp:4个参数都是在上述tjBufSizeYUV2()函数中使用到的参数,这里继续使用即可
  • jpegBuf:可以看到参数为指针的指针,TurboJPEG能够重新分配JPEG缓冲区以适应JPEG图像的大小。

1.使用tjAlloc()函数来预先分配任意大小的JPEG缓冲区,并让TurboJPEG根据需要增加缓冲区 2.设置*jpegBuf为NULL以告诉TurboJPEG为您分配缓冲区
* 3.将缓冲区预分配到通过调用tjBufSize()确定的最大值。他会确保永远不必重新分配缓冲区,但是会需要去除后余空间

/**
* 使用给定参数保存JPEG图像所需的缓冲区的最大大小(以字节为单位)。
* width & height 为图片的宽高
* jpegSubsamp为原数据的采样类型
*/
unsigned long tjBufSize(int width, int height, jpegSubsamp)

需要注意在使用以上任何一个方式时,除非该函数的最后一个参数flag设置TJFLAG_NOREALLOC,否则都应检查\
jpegBuf的值,因为他可能已更改

  • jpegSize:指向保存JPEG缓冲区大小的无符号长变量指针。
  • jpegQual:生成的JPEG图像质量(差=1;好=100)
  • flags:一个或多个标识位,如下图:
    github
  • 函数返回0表示成功;-1表示错误。

注意:TurboJPEG能够重新分配JPEG缓冲区以适应JPEG图像的大小。因此在使用时需要注意与GetPrimitiveArrayCritical&ReleasePrimitiveArrayCritical的组合使用,因为在释放数据时不允许指针发生改变。

当然,还有很多其他的Compress压缩器的函数

  • 用于将RGB,灰度或CMYK图像压缩为JPEG图像

    DLLEXPORT int DLLCALL tjCompress2(tjhandle handle, unsigned char *srcBuf,
    int width, int pitch, int height, int pixelFormat, unsigned char **jpegBuf,
    unsigned long *jpegSize, int jpegSubsamp, int jpegQual, int flags);
  • 用于将一组Y,U(Cb)和V(Cr)图像平面压缩为JPEG图像。

    DLLEXPORT int DLLCALL tjCompressFromYUVPlanes(tjhandle handle,
    unsigned char **srcPlanes, int width, int *strides, int height, int subsamp,
    unsigned char **jpegBuf, unsigned long *jpegSize, int jpegQual, int flags);
  • 将RGB或灰度图像编码为YUV平面图像。
    此函数使用底层编解码器中的加速颜色转换例程,但不执行JPEG压缩过程中的任何其他步骤。

    DLLEXPORT int DLLCALL tjEncodeYUV3(tjhandle handle,
    unsigned char *srcBuf, int width, int pitch, int height, int pixelFormat,
    unsigned char *dstBuf, int pad, int subsamp, int flags);

实例

用于I420转JPEG的工具类

TurboUtils::TurboUtils() {
handlerCompress = tjInitCompress();
handlerDecompress = tjInitDecompress();
}

TurboUtils::~TurboUtils() {
if (handlerCompress != NULL) tjDestroy(handlerCompress);
if (handlerDecompress != NULL) tjDestroy(handlerDecompress);
handlerCompress = NULL;
handlerDecompress = NULL;
}

uint8** TurboUtils::tI420ToJPEG(uint8 *i420, long size, int width, int height, int quality) {

int tap = 1;
// 找到对应pad
while (1) {
if (size == tjBufSizeYUV2(width, tap, height, TJSAMP_420)) {
break;
} else {
tap <= 1;
}
}

// 自定义jpeg指针
uint8** alloc = static_cast<uint8 **>(malloc(0));

// jpegsize
u_long* jpegsize = static_cast<u_long *>(malloc(sizeof(u_long)));

int ret = tjCompressFromYUV(handlerCompress, i420, width, 1, height, TJSAMP_420, alloc, jpegsize, quality, TJFLAG_ACCURATEDCT);

jpegSize = *jpegsize;

free(jpegsize);
if (ret == 0) {

return alloc;
} else {
free(alloc);

return NULL;
}
}

解压器-Decompress & 转换器 Transform

JPEG 转 I420

函数如下:

DLLEXPORT int DLLCALL tjDecompressToYUV2(tjhandle handle,
unsigned char *jpegBuf, unsigned long jpegSize, unsigned char *dstBuf,
int width, int pad, int height, int flags);

  • tjhandle:这里是上述初始化的tjInitDecompress解码器或者tjInitTransform转换器
  • jpegBuf & jpegSize:原数据jpeg的指针和长度
  • dstBuf:转码后的数据指针。需要注意该指针的长度需要用tjBufSizeYUV2函数来获取Yuv图片的长度。(函数已在压缩器中提起)
  • width & height:图片宽高
  • pad:值必须为2的幂,图像每个平面中每条线的宽度填充到这个字节数的最接近的倍数,该值在tjBufSizeYUV2函数中也需要
  • flag:一个或多个标识(标识集合在压缩器中提起)
  • 函数返回0表示成功;-1表示错误。

这里只需要注意传入的tjhandlertjDecompress的解码器

当然,还有很多其他的Compress压缩器的函数

  • 将YUV平面图像解码为RGB或灰度图像。
    此函数使用底层编解码器中的加速颜色转换例程,但不执行JPEG解压缩过程中的任何其他步骤。

    DLLEXPORT int DLLCALL tjDecodeYUV(tjhandle handle, unsigned char *srcBuf,
    int pad, int subsamp, unsigned char *dstBuf, int width, int pitch,
    int height, int pixelFormat, int flags);
  • 将一组Y,U(Cb)和V(Cr)图像平面解码为RGB或灰度图像。

此函数使用底层编解码器中的加速颜色转换例程,但不执行JPEG解压缩过程中的任何其他步骤。

DLLEXPORT int DLLCALL tjDecompressToYUVPlanes(tjhandle handle,
unsigned char *jpegBuf, unsigned long jpegSize, unsigned char **dstPlanes,
int width, int *strides, int height, int flags);

  • 将JPEG图像解压缩为RGB,灰度或CMYK图像。
    DLLEXPORT int DLLCALL tjDecompress2(tjhandle handle,
    unsigned char *jpegBuf, unsigned long jpegSize, unsigned char *dstBuf,
    int width, int pitch, int height, int pixelFormat, int flags);

实例

这是一个将Jpeg转I420的功能

uint8 * TurboUtils::tJPEGToI420(uint8* jpeg, long size, int width, int height) {

long I420_size = tjBufSizeYUV2(width, 2, height, TJSAMP_420);

uint8 * I420 = static_cast<uint8 *>(malloc(I420_size));

int ret = tjDecompressToYUV2(handlerDecompress, jpeg, size, I420, width, 2, height, TJFLAG_ACCURATEDCT);

yuvSize = I420_size;
if (ret == 0) {
return I420;
} else {
free(I420);
return NULL;
}
}