android平台移植jpeg-turbo库达到减小jpeg编码体积的目的

来源:互联网 发布:移动数据打开没有网络 编辑:程序博客网 时间:2024/06/06 02:03

我目前所从事的工作是做公司社交类APP的消息服务模块,其中有一个需求就是压缩传输的图片的体积,因为现在一般的手机拍摄的照片体积都在2M左右,所以想办法减小传输过程中的体积是非常有必要的。


一般的处理过程是这样:设定一个固定的分辨率大小,比如960*960,图片解码成bitmap对象后,如果图片的宽或者高超出了960,那么就对宽和高进行等比例缩放,使得长的那一边刚好等于960,然后再进行jpeg编码。


经过这个过程的处理,一般2M左右的图片就会变为100K左右,这个已经是一个不错的结果了,但是既然交给我们平台架构中心来做这件事,肯定要做出一点不同的效果出来。


我的研究成果如下:

Android平台所使用的jpeg编解码库其实是一个叫IJG的组织开发的一个C库,Android的图形库SKIA对其进行了封装,但是SKIA对jpeg库进行封装的时候没有将其中的两个选项暴露出来:optimize encoding选项和progressive选项,而这两个选项都可以减小编码后的体积,并且progressive选项产生的progressive jpeg的用户体验是好于baseline jpeg的用户体验的(目前还没有找到在ImageView上渐进地显示progressive jpeg的方法),所以我要做的工作是使用NDK移植jpeg编解码库,然后暴露这两个选项出来,让业务来调用我的接口。


第一步:使用Android NDK移植jpeg-turbo库

jpeg-turbo库是对jpeg库的一个优化,此处移植jpeg-turbo库

jpeg-turbo库下载链接:http://download.csdn.net/detail/lihuapinghust/8220993


将下载的zip包解压到android工程的jni目录,在工程的Properties->Builders中新建一个Builder,设置如下:



并且将新建的builder放在第一个位置,然后build,就可以编译so库了


第二部,设计接口

因为我们只在编码的时候做优化,所以我们就要求业务使用Android原生的解码和缩放工具得到要编码的bitmap,然后我们再把bitmap的字节数据拷贝出来,传给我们的接口进行编码操作,所以jni接口部分的代码如下

void write_to_RGB(char * dst, const void* srcRow, int width) {const uint32_t* src = (const uint32_t*)srcRow;    while (--width >= 0) {    uint32_t c = *src++;    dst[0] = SkGetPackedR32(c);    dst[1] = SkGetPackedG32(c);    dst[2] = SkGetPackedB32(c);    dst += 3;    }}JNIEXPORT jint JNICALL Java_com_skynet_compressor_image_NativeJpeg_doJpegCompressBitmap  (JNIEnv * env, jclass cls, jbyteArray byteArray, jint width, jint height, jstring out_file_path, jint quality) {uint8_t *buf = (uint8_t *)(*env)->GetByteArrayElements(env, byteArray, 0);jlong capacity = (*env)->GetArrayLength(env, byteArray);char * c_out_file_path = (char *) (*env)->GetStringUTFChars(env, out_file_path, NULL);struct jpeg_compress_struct cinfo;struct jpeg_error_mgr jerr;FILE * output_file;cinfo.err = jpeg_std_error(&jerr);jpeg_create_compress(&cinfo);/* Add some application-specific error messages (from cderror.h) */jerr.addon_message_table = cdjpeg_message_table;jerr.first_addon_message = JMSG_FIRSTADDONCODE;jerr.last_addon_message = JMSG_LASTADDONCODE;cinfo.image_width = width;cinfo.image_height = height;cinfo.input_components = 3;cinfo.in_color_space = JCS_RGB;cinfo.optimize_coding = TRUE;cinfo.input_gamma = 1;cinfo.dct_method = JDCT_IFAST;jpeg_set_defaults(&cinfo);jpeg_set_quality(&cinfo, quality, TRUE);jpeg_simple_progression(&cinfo);if ((output_file = fopen(c_out_file_path, "wb")) == NULL) {__android_log_print(ANDROID_LOG_INFO, "native-method", "open outfile failed");    return -1;}jpeg_stdio_dest(&cinfo, output_file);jpeg_start_compress(&cinfo, TRUE);uint8_t* oneRowP = (uint8_t*)malloc(width * 3);const void*      srcRow = (const void *)buf;while (cinfo.next_scanline < cinfo.image_height) {JSAMPROW row_pointer[1];    /* pointer to JSAMPLE row[s] */write_to_RGB(oneRowP, srcRow, width);    row_pointer[0] = oneRowP;(void) jpeg_write_scanlines(&cinfo, row_pointer, 1);srcRow = (const void*)((const char*)srcRow + width * 4);}free(oneRowP);jpeg_finish_compress(&cinfo);jpeg_destroy_compress(&cinfo);fclose(output_file);return 0;}

代码是参考jpeg库本身代码和Android平台源码写的,

然后在Java层声明jni方法:

public static native int doJpegCompressBitmap(byte[] bytes, int width,int height, String outFilePath, int quality);

然后java层就可以使用我们的接口进行jpeg编码了

得到的文件大小会比使用Androdi原生的编码方法得到的文件小6%左右,虽然提升性能不是特别明显,但是在以用户体验为中心的互联网时代,即使是1K的流量也要为用户节省

0 0