1. libjpeg介绍

 libJPEG库是一款功能强大的JPEG图像处理开源库,它支持将图像数据压缩编码为JPEG格式和对原有的JPEG图像解压缩,Android系统底层处理图片压缩就是用得libJPEG库。但有一点需要注意的是,为了适配低版本的Android手机,Android系统在内部的压缩算法并没有采用普通的哈夫曼(Huffman)算法,因为哈夫曼算法比较占CPU,从而导致Android在压缩的同时保持较高的图像质量和色彩丰富时,压缩的性能不是很好。基于此,本文将使用AS的Cmake工具编译libJPEG-turbo源码,然后通过JNI/NDK技术编写采样哈夫曼算法压缩接口,以提高在Android中图片压缩质量。

注:libjpeg-turbo是针对libjpeg库的一个优化版本。

1.1 哈夫曼编码

 Haffman编码是Huffman于1952年提出的一种高效的无损压缩编码方法(注:压缩20%~90%),该方法的编码的码长是可变的,它完全依据字符出现概率(频率)来构造异字头的平均长度最短的码字,即对于出现概率(频率)高的信息,编码的码长较短;对于出现概率(频率)高的信息,编码的码长较长。在图像压缩的应用场景中,Haffman编码的基本方法是先对图像数据扫描一遍,计算出各种像素出现的概率,按概率的大小指定不同长度的唯一码字,由此得到一张该图像的Haffman码表。编码后的图像数据记录的是每个像素的码字,而码字与实际像素值的对应关系记录在码表中。

  • Haffman树

 假设有n个权值{w1,w2,…,wn},构造一棵有n个叶子结点的二叉树,每个叶子结点带权为wk,每个叶子的路径长度为lk,则其中树的带权路径长度WPL=∑(wk*lk)最小的二叉树称为赫夫曼树,也称最优二叉树。举个栗子:

 该树的带权路径长度:WPL=∑(wklk) = 110+270+315+3*5=210

  • Haffman算法原理

 假设需要编码的字符集为{d1,d2,…dn},各个字符在电文中出现的次数或频率集合为{w1,w2,…,wn},以d1,d2,…,dn作为叶子结点,以w1,w2,…,wn作为相应叶子结点的权值来构造一棵赫夫曼树。规定:赫夫曼树的左分支代表0,右分支代表1,则从根结点到叶子结点所经过的路径分支组成的0和1的序列便为该结点对应字符的编码,这就是Haffman编码。

举个栗子:对字符串“BADCADFEED”进行Haffman编码?

首先,计算每个字母出现的概率A 27%,B 8%,C 15%,D 15%,E 30%,F 5%;
其次,构造一颗哈夫曼树(左小,右大),并将每个叶子节点的左右权值分别改为0,1;

第三,将每个字母从根节点到叶子结点所经过的路径0或1来编码;

最后,得到字符串的Haffman编码。
即“BADCADFEED”的Haffman编码为“1001010010101001000111100”。

1.2 libjpeg编码与解码

1. 压缩JPEG

(1) 分配和初始化JPEG压缩对象jpeg_compress_struct,并设置错误处理模块。

// JPEG压缩编码的核心结构体,位于源码jpeglib.h
// 它包含了各种压缩参数和指向工作空间的指针(JPEG库所需内存)等
struct jpeg_compress_struct cinfo;
// JPEG错误处理结构体,位于源码jpeglib.h
struct jpeg_error_mgr jerr;
// 设置错误处理对象,以防初始化失败,比如内存溢出
cinfo.err = jpeg_std_error(&jerr);
// 初始化JPEG压缩对象(结构体对象)
jpeg_create_compress(&cinfo);

(2) 指定压缩数据输出。这里假设指定一个文件路径,然后将压缩后的JPEG数据存储到该文件中。

// 打开指定路径的文件
if ((outfile = fopen(filename, "wb")) == NULL) {fprintf(stderr, "can't open %s\n", filename);exit(1);
}
// 指定JPEG压缩数据保存位置
jpeg_stdio_dest(&cinfo, outfile);

(3) 设置压缩参数。当然,首先我们需要填充输入图像的相关信息,比如宽高、颜色空间等。

// 获取输入图像信息
cinfo.image_width = image_width;     // 宽度
cinfo.image_height = image_height;   // 高度
cinfo.input_components = 3;          // 每个像素占的颜色分量数量
cinfo.in_color_space = JCS_RGB;      // 颜色空间,RGB
// 设置压缩参数
cinfo.optimize_coding = true;  // 压缩优化
cinfo.arith_code = false;  // 使用哈夫曼编码
jpeg_set_defaults(&cinfo);
// 设置压缩质量,0~100
jpeg_set_quality(&cinfo, quality, true);

(4) 启动压缩引擎,并按行处理数据。由于图像数据在内存中是以字节为单位按顺序存储的,对于一张尺寸为wxh图像来说,它在内存中是按行存储的,共h行,至于每行占多少个字节由w和每个像素大小决定。假如这里有张分辨率为640x480且颜色空间为RGB的图像(每个像素占三个分量,即R分量、G分量、B分量,每个分量占1个字节),那么,在内存中每行占640x3=1920字节,共480行,因此这张图片在内存中总共占[wx像素)xh]=[(640x3)x480]=921600字节

// 开启压缩引擎
jpeg_start_compress(&cinfo, TRUE);extern JSAMPLE *image_buffer;   // 存储要压缩的图像数据,按R、G、B分量顺序
extern int image_height;        // 图像行数
extern int image_width;         // 图像列数
// 存储行起始地址
JSAMPROW row_pointer[1];
// 图像缓冲区中的物理行宽度,其中,对于RGB来说,每个像素占3个颜色分量
// 每个分量占一个字节,那么图像中一行的宽度为:(width * 3)
// 即cinfo.image_width * cinfo.input_components
int row_stride = cinfo.image_width * cinfo.input_components;
// 按行读取图像数据
// 然后进行压缩,并存储到目的地址中
while (cinfo.next_scanline < cinfo.image_height) {// 从数据源缓存区image_buffer读取一行数据// 并将起始地址赋值给row_pointer[0]row_pointer[0] = &image_buffer[cinfo.next_scanline * row_stride];// 将image_buffer中的数据写到JPEG编码器中(void)jpeg_write_scanlines(&cinfo, row_pointer, 1);
}

(5) 结束压缩,释放资源。

// 停止压缩
jpeg_finish_compress(&cinfo);
// 关闭文件描述符
fclose(outfile);
// 释放引擎所占资源
jpeg_destroy_compress(&cinfo);
2. 解码JPEG

(1) 分配、初始化JPEG解压对象

// JPEG解压结构体
struct jpeg_decompress_struct cinfo;// 1. 设置程序错误处理
// 这里对错误处理做了优化,对标准的error_exit方法做了处理//
// typedef struct my_error_mgr *my_error_ptr;
// METHODDEF(void) my_error_exit(j_common_ptr cinfo) {//      my_error_ptr myerr = (my_error_ptr)cinfo->err;
//      (*cinfo->err->output_message) (cinfo);
//      longjmp(myerr->setjmp_buffer, 1);
// }
struct my_error_mgr {struct jpeg_error_mgr pub;    // 错误处理结构体jmp_buf setjmp_buffer;        // 异常信息,回调给调用者
};
struct my_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr.pub); // 设置错误处理标准程序
jerr.pub.error_exit = my_error_exit;   // 使用自定义的error_exit函数
if (setjmp(jerr.setjmp_buffer)) {      jpeg_destroy_decompress(&cinfo);fclose(infile);return 0;
}
// 2. 初始化JPEG解压对象
jpeg_create_decompress(&cinfo);

(2) 指定数据源(待解压JPEG图像)

// 打开待解压的JPEG文件
if ((infile = fopen(filename, "rb")) == NULL) {fprintf(stderr, "can't open %s\n", filename);return 0;
}
// 将JPEG文件指定为数据源
jpeg_stdio_src(&cinfo, infile);

(3) 读取JPEG图像文件头部参数

(void)jpeg_read_header(&cinfo, TRUE);

(4) 设置解压参数,开始解压。这里我们无需改变JPEG图像文件的头部信息,因此,不设置解压参数。

// 开始解压
(void)jpeg_start_decompress(&cinfo);

(5) 读取图像数据存储到缓存区buffer中。

// 计算图像存储在物理内存中每一行占的大小(字节)
// 即图像的宽*每个像素所占分量数
int row_stride = cinfo.output_width * cinfo.output_components;
// 分配存储解压数据的缓存区
JSAMPARRAY buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1);
// 循环读取output_height行字节数据,存储到buffer缓存区中
while (cinfo.output_scanline < cinfo.output_height) {(void)jpeg_read_scanlines(&cinfo, buffer, 1);// 将得到的解压数据作进一步处理// 这里如要自己实现,如定义一个函数// put_scanline_someplace(buffer[0], row_stride);
}

(6) 结束解压,释放资源

// 停止解压
(void)jpeg_finish_decompress(&cinfo);
// 释放内存资源
jpeg_destroy_decompress(&cinfo);
fclose(infile);
1.3 libjpeg源码分析
  • jpeg_compress_struct结构体

 JPEG压缩编码的核心结构体,它被声明在jpeglib.h头文件中,其包含了存储输入图像参数信息、压缩参数以及指向工作空间的指针(JPEG库所需内存)等。jpeg_compress_struct结构体声明(部分)如下:

struct jpeg_compress_struct {struct jpeg_destination_mgr *dest; // 已压缩数据存储地址JDIMENSION image_width;      // 输入图像宽度,unsigned int类型JDIMENSION image_height;     // 输入图像高度int input_components;        // 输入图像颜色分量数量J_COLOR_SPACE in_color_space; // 输入图像颜色空间,如JCS_RGBint data_precision; // 压缩后图像数据的位精度int num_components; // 压缩后JPEG图像的颜色分量数量J_COLOR_SPACE jpeg_color_space; // 压缩后JPEG图像的颜色空间peg_component_info *comp_info;  boolean raw_data_in;  // 为TRUE,表示向下采样数据boolean arith_code;   // 为FALSE,表示使用Huffman编码,否则为算术编码boolean optimize_coding; // 为TRUE,表示优化熵编码参数int smoothing_factor;      // 1~100,其中,0表示平滑输入J_DCT_METHOD dct_method;   // DCT算法选择器,JDIMENSION next_scanline;  // 逐行扫描图像时下一行行号,0~(image_h-1)...// 与压缩相关的结构体struct jpeg_comp_master *master;struct jpeg_c_main_controller *main;struct jpeg_c_prep_controller *prep;struct jpeg_c_coef_controller *coef;struct jpeg_marker_writer *marker;struct jpeg_color_converter *cconvert;struct jpeg_downsampler *downsample;struct jpeg_forward_dct *fdct;struct jpeg_entropy_encoder *entropy;jpeg_scan_info *script_space;int script_space_size;
};
  • jpeg_decompress_struct结构体

 该结构体是libjpeg解压缩JPEG图像的核心结构体,位于它被声明在jpeglib.h头文件中,其包含了待解压缩JPEG图像的基本信息,如图像的宽高、每个像素所占颜色分量数目、颜色空间等,同时也包括解压所需配置的各种参数等等。jpeg_decompress_struct结构体声明(部分)如下:

struct jpeg_decompress_struct {jpeg_common_fields;          // 与jpeg_compress_strue共用成员变量,包括err等       struct jpeg_source_mgr *src; // 待解压的压缩数据源/** 待解压JPEG图像的基本信息*   通过jpeg_read_header()函数获取填充*/JDIMENSION image_width;     // 图像的宽度JDIMENSION image_height;    // 图像的高度  int num_components;         // 图像每个像素所占分量数量J_COLOR_SPACE jpeg_color_space; // JPEG图像颜色空间/* 解压处理参数,需要调用jpeg_start_decompress()之前设置*   注意:调用jpeg_read_header()函数后这些参数会被赋予初始值*/J_COLOR_SPACE out_color_space; // 输出颜色空间unsigned int scale_num, scale_denom; // 缩放图像factordouble output_gamma;          /* image gamma wanted in output */boolean buffered_image;       /* TRUE=multiple output passes */boolean raw_data_out;         /* TRUE=downsampled data wanted */J_DCT_METHOD dct_method;      // IDCT算法选择器boolean do_fancy_upsampling;  /* TRUE=apply fancy upsampling */boolean do_block_smoothing;   /* TRUE=apply interblock smoothing */boolean quantize_colors;      /* TRUE=colormapped output wanted */.../* 描述输入待解压图像的基本信息,当调用jpeg_start_decompress()* 时,会被自动计算赋值,当然,我们也在调用jpeg_start_decompress()* 之前通过调用jpeg_calc_output_dimensions()*/JDIMENSION output_width;      // 输出图像宽度JDIMENSION output_height;     // 输出图像高度int out_color_components;     // out_color_components中的颜色分量数目int output_components;        // 颜色分量数目// 从jpeg_read_scanlines()中读取下一个扫描行的行索引// 大小为 0 .. output_height-1JDIMENSION output_scanline;...// 与解压缩相关的结构体对象struct jpeg_decomp_master *master;struct jpeg_d_main_controller *main;struct jpeg_d_coef_controller *coef;struct jpeg_d_post_controller *post;struct jpeg_input_controller *inputctl;struct jpeg_marker_reader *marker;struct jpeg_entropy_decoder *entropy;struct jpeg_inverse_dct *idct;struct jpeg_upsampler *upsample;struct jpeg_color_deconverter *cconvert;struct jpeg_color_quantizer *cquantize;
};
  • jpeg_error_mgr结构体

 该结构体用于异常处理,被声明于jpeglib.h头文件中,它的部分声明如下:

struct jpeg_error_mgr {void (*error_exit) (j_common_ptr cinfo);  // 异常退出捕获函数void (*emit_message) (j_common_ptr cinfo, int msg_level);void (*output_message) (j_common_ptr cinfo);void (*format_message) (j_common_ptr cinfo, char *buffer);void (*reset_error_mgr) (j_common_ptr cinfo);...const char * const *jpeg_message_table; // library错误信息const char * const *addon_message_table; // 非library错误信息
};
  • *函数:jpeg_std_error(struct jpeg_error_mgr err)

 该函数实现在jerror.c源文件中,它的作用就是初始化jpeg_error_mgr结构体对象err,即对对象中的成员赋初始值,比如将err对象error_exit字段赋值为error_exit函数,该函数是jerror.c源文件已经实现的函数,作用在于当引擎异常退出(如分配内存失败)时释放引擎所占的资源。jpeg_std_error函数实现如下:

GLOBAL(struct jpeg_error_mgr *)
jpeg_std_error(struct jpeg_error_mgr *err)
{err->error_exit = error_exit; // 异常退出处理函数err->emit_message = emit_message;err->output_message = output_message;err->format_message = format_message;err->reset_error_mgr = reset_error_mgr;err->trace_level = 0;         /* default = no tracing */err->num_warnings = 0;        /* no warnings emitted yet */err->msg_code = 0;            /* may be useful as a flag for "no error" *//* Initialize message table pointers */err->jpeg_message_table = jpeg_std_message_table;err->last_jpeg_message = (int)JMSG_LASTMSGCODE - 1;err->addon_message_table = NULL;err->first_addon_message = 0; /* for safety */err->last_addon_message = 0;return err;
}// error_exit()函数
METHODDEF(void)
error_exit(j_common_ptr cinfo)
{/* Always display the message */(*cinfo->err->output_message) (cinfo);/* Let the memory manager delete any temp files before we die */jpeg_destroy(cinfo);exit(EXIT_FAILURE);
}
  • 函数:jpeg_create_compress(cinfo)

 该函数的作用为结构体jpeg_compress_struct分配内存资源,并初始化相关成员变量,需要注意的是,在调用该函数之前,我们需要设置该结构体的err字段,以便处理初始化失败时异常情况。它被声明在jpeglib.h头文件中,它的具体实现实际上是jpeg_CreateCompress()函数,该函数位于jcapimin.c源文件中。

// #define jpeg_create_compress(cinfo) \
//  jpeg_CreateCompress((cinfo), JPEG_LIB_VERSION, \
//                      (size_t)sizeof(struct jpeg_compress_struct))
GLOBAL(void)jpeg_CreateCompress(j_compress_ptr cinfo, int version, size_t structsize)
{int i;cinfo->mem = NULL;          if (version != JPEG_LIB_VERSION)ERREXIT2(cinfo, JERR_BAD_LIB_VERSION, JPEG_LIB_VERSION, version);if (structsize != sizeof(struct jpeg_compress_struct))ERREXIT2(cinfo, JERR_BAD_STRUCT_SIZE,(int)sizeof(struct jpeg_compress_struct), (int)structsize);{struct jpeg_error_mgr *err = cinfo->err;void *client_data = cinfo->client_data; MEMZERO(cinfo, sizeof(struct jpeg_compress_struct));cinfo->err = err;cinfo->client_data = client_data;}cinfo->is_decompressor = FALSE;// 初始化cinfo对象的内存管理器实例jinit_memory_mgr((j_common_ptr)cinfo);/* Zero out pointers to permanent structures. */cinfo->progress = NULL;cinfo->dest = NULL;cinfo->comp_info = NULL;for (i = 0; i < NUM_QUANT_TBLS; i++) {cinfo->quant_tbl_ptrs[i] = NULL;#if JPEG_LIB_VERSION >= 70cinfo->q_scale_factor[i] = 100;#endif}for (i = 0; i < NUM_HUFF_TBLS; i++) {cinfo->dc_huff_tbl_ptrs[i] = NULL;cinfo->ac_huff_tbl_ptrs[i] = NULL;}#if JPEG_LIB_VERSION >= 80/* Must do it here for emit_dqt in case jpeg_write_tables is used */cinfo->block_size = DCTSIZE;cinfo->natural_order = jpeg_natural_order;cinfo->lim_Se = DCTSIZE2 - 1;#endifcinfo->script_space = NULL;cinfo->input_gamma = 1.0;     /* in case application forgets */// 设置初始化完毕标志cinfo->global_state = CSTATE_START;
}
  • *函数:jpeg_stdio_dest(j_compress_ptr cinfo, FILE outfile)

 该函数的作用是将输入待压缩图像文件流赋值给my_destination_mgr结构体的outfile字段,它被声明在libjpeg.h头文件中,具体实现在jdatadst.c源文件中。jpeg_stdio_dest函数源码如下:

GLOBAL(void)
jpeg_stdio_dest(j_compress_ptr cinfo, FILE *outfile)
{/**my_destination_mgr结构体:** typedef struct {*       struct jpeg_destination_mgr pub; *      FILE *outfile;  // 目标文件流              *     JOCTET *buffer; // buffer缓存              *  } my_destination_mgr;*  typedef my_destination_mgr *my_dest_ptr;*/my_dest_ptr dest;if (cinfo->dest == NULL) {   cinfo->dest = (struct jpeg_destination_mgr *)(*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_PERMANENT,sizeof(my_destination_mgr));} else if (cinfo->dest->init_destination != init_destination) {ERREXIT(cinfo, JERR_BUFFER_SIZE);}// 将cinfo的dest成员变量赋值给结构体destdest = (my_dest_ptr)cinfo->dest;dest->pub.init_destination = init_destination;dest->pub.empty_output_buffer = empty_output_buffer;dest->pub.term_destination = term_destination;// 将文件流存储地址赋值给my_dest_ptr结构体的outfile成员变量dest->outfile = outfile;
}
  • 函数:jpeg_write_scanlines(j_compress_ptr cinfo, JSAMPARRAY scanlines,JDIMENSION num_lines)

 该函数的作用时从待压缩图像数据源缓存区scanlines,读出num_lines行数据写入到编码压缩引擎中,并进行编码压缩处理。当然,在写入数据之前该函数会去判断当前引擎的状态是否为启动,且读取的行数是否超限。jpeg_write_scanlines函数被声明在jpeglib.h中,实现在jcapistd.c源文件中,具体源码如下:

// typedef char JSAMPLE;
// typedef JSAMPLE *JSAMPROW;
// typedef JSAMPROW *JSAMPARRAY;
GLOBAL(JDIMENSION)
jpeg_write_scanlines(j_compress_ptr cinfo, JSAMPARRAY scanlines,JDIMENSION num_lines)
{JDIMENSION row_ctr, rows_left;// 判断编码引擎的状态是否为CSTATE_SCANNINGif (cinfo->global_state != CSTATE_SCANNING)ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);// 判断当前行数是否超过待压缩图像的高度if (cinfo->next_scanline >= cinfo->image_height)WARNMS(cinfo, JWRN_TOO_MUCH_DATA);if (cinfo->progress != NULL) {cinfo->progress->pass_counter = (long)cinfo->next_scanline;cinfo->progress->pass_limit = (long)cinfo->image_height;(*cinfo->progress->progress_monitor) ((j_common_ptr)cinfo);}if (cinfo->master->call_pass_startup)(*cinfo->master->pass_startup) (cinfo);rows_left = cinfo->image_height - cinfo->next_scanline;if (num_lines > rows_left)num_lines = rows_left;row_ctr = 0;// 将num_lines行待压缩数据传入jpeg_c_main_controller结构体的process_data函数中// 至于是如何处理的,我们这里就不继续分析了(*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, num_lines);cinfo->next_scanline += row_ctr;return row_ctr;
}
  • 函数:jpeg_read_scanlines(j_decompress_ptr cinfo, JSAMPARRAY scanlines,
    JDIMENSION max_lines)

 该函数的作用是从解压器中读取max_lines行解压数据存储到scanlines指向的缓存中。它被声明在jpeglib.h头文件中,实现在jdapistd.c源文件,具体源码如下:

GLOBAL(JDIMENSION)
jpeg_read_scanlines(j_decompress_ptr cinfo, JSAMPARRAY scanlines,JDIMENSION max_lines)
{JDIMENSION row_ctr;// 处理边界if (cinfo->global_state != DSTATE_SCANNING)ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state);if (cinfo->output_scanline >= cinfo->output_height) {WARNMS(cinfo, JWRN_TOO_MUCH_DATA);return 0;}if (cinfo->progress != NULL) {cinfo->progress->pass_counter = (long)cinfo->output_scanline;cinfo->progress->pass_limit = (long)cinfo->output_height;(*cinfo->progress->progress_monitor) ((j_common_ptr)cinfo);}// 从解压器中读取解压数据,存储到scanlines缓存中row_ctr = 0;(*cinfo->main->process_data) (cinfo, scanlines, &row_ctr, max_lines);cinfo->output_scanline += row_ctr;return row_ctr;
}

2. libjpeg编译与移植

2.1 使用Cmake编译libJPEG-turbo源码

(1) 新建Android工程libjpeg,并将libjpeg-turbo源码全部拷贝到src/main/cpp目录下;

(3) 修改Android工程的build.gradle,配置libjpeg-turbo的CmakeLists.txt;

android {compileSdkVersion 28defaultConfig {applicationId "com.jiangdg.libjpeg"minSdkVersion 15targetSdkVersion 28versionCode 1versionName "1.0"testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"externalNativeBuild {cmake {cppFlags ""// 配置编译的平台版本abiFilters "armeabi", "armeabi-v7a", "arm64-v8a"}}}buildTypes {release {minifyEnabled falseproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}}externalNativeBuild {cmake {path "src\\main\\cpp\\CMakeLists.txt"//路径改为cpp文件夹下CMakeList的路径}
//        cmake {//            path file('CMakeLists.txt')
//        }}}

(3) 编译Android项目,得到对应平台架构的libjpeg.so文件,以及jconfig.h、jconfigint.h头文件。

Github项目地址:libjpeg4Android,欢迎star or issues.

2.2 使用libjpeg压缩编码JPEG图像

(1) 新建Android项目HandleJpeg,拷贝头文件jconfig.h、jconfigint.h、jpeglib.h和jmorecfg.h到src/main/cpp目录,同时拷贝动态库libjpeg.so到src/main/jniLibs目录下(如果没有创建一个)。

(2) 配置CmakeList.txt,导入libjpeg.so

cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_VERBOSE_MAKEFILE  on)# 设置so输出路径
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY  ${PROJECT_BINARY_DIR}/libs)# 指定libjpeg动态库路径
set(jpeglibs "${CMAKE_SOURCE_DIR}/src/main/jniLibs")# 导入第三方库:libjpeg.so
add_library(libjpeg SHARED IMPORTED)
set_target_properties(libjpeg PROPERTIESIMPORTED_LOCATION "${jpeglibs}/${ANDROID_ABI}/libjpeg.so")set(CMAKE_CXX_FLAGS  "${CMAKE_CXX_FLAGS} -std=gnu++11 -fexceptions -frtti")# 配置、链接动态库
add_library(jpegutilSHAREDsrc/main/cpp/NativeJPEG.cpp)# 查找NDK原生库log,android
find_library(log-lib log)
find_library(android-lib android)# 链接所有库到jpegutil
# AndroidBitmapInfo需要库jnigraphics
target_link_libraries(jpegutillibjpegjnigraphics${log-lib}${android-lib})

(3) 编写Java层native方法

/***  使用libjpeg实现JPEG编码压缩、解压** @author Jiangdg* @since 2019-08-12 09:54:00* */
public class JPEGUtils {static {System.loadLibrary("jpegutil");}public native static int nativeCompressJPEG(Bitmap bitmap, int quality, String outPath);
}

(4) 编写native层实现

// JPEG图形编码压缩、解压
// 采用libjpeg库(libjpeg_turbo版本)实现
//
// Created by Jiangdg on 2019/8/12.
//
#include <jni.h>
#include <android/bitmap.h>
#include <android/log.h>
#include <malloc.h>
#include "jpeglib.h"
#include <stdio.h>
#include <csetjmp>
#include <string.h>
#include <setjmp.h>#define TAG "NativeJPEG"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
#define UNSUPPORT_BITMAP_FORMAT -99
#define FAILED_OPEN_OUTPATH -100
#define SUCCESS_COMPRESS 1typedef uint8_t BYTE;// 自定义error结构体
struct my_error_mgr {struct jpeg_error_mgr pub;jmp_buf setjmp_buffer;
};int compressJPEG(BYTE *data, int width, int height, jint quality, const char *path) {int nComponent = 3;FILE *f = fopen(path, "wb");if(f == NULL) {return FAILED_OPEN_OUTPATH;}// 初始化JPEG对象,为其分配空间struct my_error_mgr my_err;struct jpeg_compress_struct jcs;jcs.err = jpeg_std_error(&my_err.pub);if(setjmp(my_err.setjmp_buffer)) {return 0;}jpeg_create_compress(&jcs);// 指定压缩数据源,设定压缩参数// 使用哈夫曼算法压缩编码jpeg_stdio_dest(&jcs, f);jcs.image_width = width;jcs.image_height = height;jcs.arith_code = false; // false->哈夫曼编码jcs.input_components = nComponent;jcs.in_color_space = JCS_RGB;jpeg_set_defaults(&jcs);jcs.optimize_coding = quality;  // 压缩质量 0~100jpeg_set_quality(&jcs, quality, true);// 开始压缩,一行一行处理jpeg_start_compress(&jcs, true);JSAMPROW row_point[1];int row_stride;row_stride = jcs.image_width * nComponent;while(jcs.next_scanline < jcs.image_height) {row_point[0] = &data[jcs.next_scanline * row_stride];jpeg_write_scanlines(&jcs, row_point, 1);}// 结束压缩,释放资源if(jcs.optimize_coding != 0) {LOGI("使用哈夫曼压缩编码完成");}jpeg_finish_compress(&jcs);jpeg_destroy_compress(&jcs);fclose(f);return SUCCESS_COMPRESS;
}const char *jstringToString(JNIEnv *env, jstring jstr) {char *ret;const char * c_str = env->GetStringUTFChars(jstr, NULL);jsize len = env->GetStringLength(jstr);if(c_str != NULL) {ret = (char *)malloc(len+1);memcpy(ret, c_str, len);ret[len] = 0;}env->ReleaseStringUTFChars(jstr, c_str);return ret;
}extern  "C"
JNIEXPORT jint JNICALL
Java_com_jiangdg_natives_JPEGUtils_nativeCompressJPEG(JNIEnv *env, jclass type, jobject bitmap,jint quality, jstring outPath_) {// 获取bitmap的属性信息int ret;int width, height, format;int color;BYTE r, g, b;BYTE *pixelsColor;BYTE *data, *tmpData;AndroidBitmapInfo androidBitmapInfo;const char *outPath = jstringToString(env, outPath_);LOGI("outPath=%s", outPath);if((ret = AndroidBitmap_getInfo(env, bitmap, &androidBitmapInfo)) < 0) {LOGI("AndroidBitmap_getInfo failed, error=%d", ret);return ret;}if((ret = AndroidBitmap_lockPixels(env, bitmap, reinterpret_cast<void **>(&pixelsColor))) < 0) {LOGI("AndroidBitmap_lockPixels failed, error=%d", ret);return ret;}width = androidBitmapInfo.width;height = androidBitmapInfo.height;format = androidBitmapInfo.format;LOGI("open image:w=%d, h=%d, format=%d", width, height, format);// 将bitmap转换为rgb数据,只处理RGBA_8888格式// 一行一行的处理,每个像素占4个字节,包括a、r、g、b三个分量,每个分量占8位data = (BYTE *)malloc(width * height * 3);tmpData = data;for(int i=0; i<height; ++i) {for(int j=0; j<width; ++j) {if(format == ANDROID_BITMAP_FORMAT_RGBA_8888) {color = *((int *)pixelsColor);b = (color >> 16) & 0xFF;g = (color >> 8) & 0xFF;r = (color >> 0) & 0xFF;*data = r;*(data + 1) = g;*(data + 2) = b;data += 3;// 处理下一个像素,在内存中即占4个字节pixelsColor += 4;} else {return UNSUPPORT_BITMAP_FORMAT;}}}if((ret = AndroidBitmap_unlockPixels(env, bitmap)) < 0) {LOGI("AndroidBitmap_unlockPixels failed,error=%d", ret);return ret;}// 编码压缩图片ret = compressJPEG(tmpData, width, height, quality, outPath);free((void *)tmpData);return ret;
}

 当然,如果你需要使用本工程生成的so运用到其他项目,需要编译本项目,AS会自动在.externalNativeBuild/…/libs目录下生成libjpegtil.so文件,然后,将libjpegtil.so和libjpeg.so同时拷贝到目标工程中即可。

Github项目地址:HandleJpeg,欢迎star or issues.

Android直播开发之旅(15):libjpeg库的编译移植与使用相关推荐

  1. Android直播开发之旅(13):使用FFmpeg+OpenSL ES播放PCM音频

    文章目录 1. OpenSL ES原理 1.1 OpenSL ES核心API讲解 1.1.1 对象(Object)与接口(Interface) 1.1.2 [OpenSL ES的状态机制](https ...

  2. Android直播开发之旅(3):AAC编码格式分析与MP4文件封装(MediaCodec+MediaMuxer)

    Android直播开发之旅(3):AAC编码格式分析与MP4文件封装(MediaCodec+MediaMuxer) (码字不易,转载请声明出处:http://blog.csdn.net/andrexp ...

  3. Android直播开发之旅(25):使用AES算法加密多媒体文件(+RSA+MD5+Base64)

    文章目录 1. AES算法 1.1 AES加密过程 1.1.1 字节代替(SubBytes) 1.1.2 行移位(ShiftRows) 1.1.3 列混合(MixColumns) 1.1.4 加轮密钥 ...

  4. Android直播开发之旅(17):使用FFmpeg提取MP4中的H264和AAC

    最近在开发中遇到了一个问题,即无法提取到MP4中H264流的关键帧进行处理,且保存到本地的AAC音频也无法正常播放.经过调试分析发现,这是由于解封装MP4得到的H264和AAC是ES流,它们缺失解码时 ...

  5. Android直播开发之旅(4):MP3编码格式分析与lame库编译封装

    转载请声明出处:http://blog.csdn.net/andrexpert/article/77683776 一.Mp3编码格式分析 MP3,全称MPEG Audio Layer3,是一种高效的计 ...

  6. Android直播开发之旅(9):OkCamera,Android 相机应用开发通用库

    OkCamera,Android 相机应用开发通用库 转载请声明出处:http://blog.csdn.net/andrexpert/article/details/79302576 明天就可以回家过 ...

  7. Android直播开发之旅(7):Android视频直播核心技术(架构)详解

    (转载请声明出处:http://blog.csdn.net/andrexpert/article/details/76919535) 一.直播架构解析 目前主流的直播架构中主要有两种方案,即流媒体转发 ...

  8. Android直播开发之旅(18):FFmpeg中滤镜(filter)的工作原理

    文章目录 1. 什么是滤镜 1.1 简单滤镜(滤镜链) 1.2 复杂滤镜(滤镜图) 2. 滤镜API介绍与使用 2.1 滤镜API介绍 2.1.1 结构体 2.1.2 功能函数 2.2 滤镜API的使 ...

  9. Android NDK开发之旅(2):一篇文章搞定Android Studio中使用CMake进行NDK/JNI开发

    Android NDK开发之旅(2):一篇文章搞定android Studio中使用CMake进行NDK/JNI开发 (码字不易,转载请声明出处:http://blog.csdn.NET/andrex ...

最新文章

  1. Set the roller speed
  2. Grid R-CNN解读:商汤最新目标检测算法
  3. spirng整合rmi
  4. 2006-我都做了什么!
  5. 大一高数下册笔记整理_高等数学下册知识点总结.doc
  6. 基于python的多光谱影像植被指数计算
  7. BoundsChecker 使用方法
  8. 完全激活office2007
  9. Charles 访问 HTTPS 链接不是私密连接 (完美解决)
  10. Java狐仙ol,狐仙八大职业介绍 简要分为四类
  11. 殿影酒店即将开业,推出电影和酒店的跨界创新模式
  12. Sam Altman 山姆奥特曼:强化学习进展 Reinforcement Learning Progress
  13. request_threaded_irq
  14. 河海大学计算机网络毕业设计,河海大学文件河海大学毕业设计.doc
  15. 读后感《富爸爸穷爸爸》
  16. 微信小程序制作看图工具
  17. 【嵌入式开发板】迅为iTOP-4412开发板板及资料介绍
  18. java 鱼刺图_鱼骨图问题分析法
  19. IV XXSC-10
  20. meta标签及其含义大全

热门文章

  1. Silverlight实用窍门系列:29.Silverlight碰撞测试、检测自定义控件碰撞,雷达扫描图之扫描雷达点状态【附带源码实例】...
  2. 线程初步(四)--小练习
  3. VMware 安装WIN10 WIN7
  4. Python: numpy tile()函数 可实现ndarray的横向纵向复制
  5. 几何分布及其期望与方差
  6. 宝兰德BES安装及Springboot项目打包部署及websocket解决方案
  7. Matlab代码区出现中文乱码的情况
  8. 毫秒级从百亿大表任意维度筛选数据,是怎么做到的.
  9. 物理学/数学中常用的“等号”
  10. 万维钢解读,从数学上解释为什么绝大多数投资者都会输给市场?最可能值,远远小于平均值...