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

本文详细介绍了如何通过优化JPEG编码库,实现对APP消息服务模块中图片体积的有效压缩,包括使用NDK移植jpeg-turbo库、设计接口并提供性能提升达6%的解决方案,旨在提升用户体验。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

我目前所从事的工作是做公司社交类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.youkuaiyun.com/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的流量也要为用户节省

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值