一、简介
相关博客:
[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下确实没有,因此爬坑爬了很久)
|
上述命令执行后输入如下命令: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
文件,放置在项目下的libs
或jniLibs
文件夹下,前者需要在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):用于读取相应的大小,后续会用到这个参数pad
和subsamp
。width & height:为图片的宽高
pad:值必须为2的幂,图像每个平面中每条线的宽度填充到这个字节数的最接近的倍数
subsamp:图像的采样类型 - 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:一个或多个标识位,如下图:
- 函数返回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表示错误。
这里只需要注意传入的tjhandler
为tjDecompress的解码器
。
当然,还有很多其他的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;
}
}