前面文章说到到libjpeg的使用示例,里面的例子实际上是文件的操作,即解压JPEG文件,因为libjpeg有对FILE操作的函数,所以代码直接用jpeg_stdio_src(&cinfo, fp);就行了,这个库会去读取JPEG文件。但是实际应用场合中,很多都不是文件,比如从网络传输过来的是JPEG数据,需要解压为RGB或YUV;又或者传输RGB数据要转换成JPEG。总之,是基于内存的操作的。往往用文件的方式来处理很简单,而换成了内存操作就有点措手不及了。正如上个月搞的FFMPEG转换,保存为一个视频文件是很简单的,但我要将转换后的数据保存到某一片内存中传输到上位机,我不可能先保存为文件再读文件,这太没职业道德了。幸运的是,我花了1周多的时间搞定了这个问题。
更幸运的是,libjpeg新版本已经实现了内存的读写。我依稀记得当年使用一个很旧的版本,里面是没有内存的接口的,因此上网找,竟然也能找到有人自己实现了内存接口。但后来发现新版本库就有了,于是我就不用画蛇添足了。
解压内存中的JPEG里,使用jpeg_mem_src函数,如jpeg_mem_src(&cinfo, jpeg_buffer, jpeg_size)。而要将内存中的RGB压缩成JPEG,则使用jpeg_mem_dest接口,如jpeg_mem_src(&cinfo, jpeg_buffer, jpeg_size)。注意,jpeg_buffer是二级指针,不用用户申请,由libjpeg申请空间,但要用户自行释放。
头文件声明如下:
/**
* 利用libjpeg将缓冲区的JPEG转换成RGB 解压JPEG
*
* @param[IN] jpeg_buffer JPEG图片缓冲区
* @param[IN] jpeg_size JPEG图片缓冲区大小
* @param[IN] rgb_buffer RGB缓冲区
* @param[IN/OUT] size RGB缓冲区大小
* @param[OUT] width 图片宽
* @param[OUT] height 图片高
*
* @return
* 0:成功
* -1:打开文件失败
* @note
* jpeg、rgb空间由调用者申请,size为输入输出参数,传入为rgb空间大小,转出为rgb实际大小
*/
int jpeg2rgb(unsigned char* jpeg_buffer, int jpeg_size, unsigned char* rgb_buffer, int* size, int* width, int* height);
/**
* 利用libjpeg将缓冲区的RGB转换成JPEG 压缩为JPEG
*
* @param[IN] rgb_buffer JPEG图片RGB数据
* @param[IN] width 图片宽
* @param[IN] height 图片高
* @param[IN] quality 图片质量
* @param[OUT] jpeg_buffer JPEG缓冲区指针
* @param[OUT] jpeg_size JPEG缓冲区大小
*
* @return
* 0:成功
* -1:打开文件失败
* @note
* jpeg_buffer为二级指针,无须调用者申请空间,由libjpeg申请,但调用者要自行释放
*/
int rgb2jpeg(unsigned char* rgb_buffer, int width, int height, int quality, unsigned char** jpeg_buffer, unsigned long* jpeg_size);
代码实现如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include <math.h>
#include <sys/time.h>
#include <time.h>
// jpeg库头文件必须放到stdio.h后面
#include "libjpeg/include/jpeglib.h"
#include "libjpeg/include/jerror.h"
typedef struct my_error_mgr * my_error_ptr;
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);
}
int jpeg2rgb(unsigned char* jpeg_buffer, int jpeg_size, unsigned char* rgb_buffer, int* size, int* width, int* height)
{
struct jpeg_decompress_struct cinfo;
struct my_error_mgr jerr;
JSAMPARRAY buffer;
int row_stride = 0;
unsigned char* tmp_buffer = NULL;
int rgb_size;
if (jpeg_buffer == NULL)
{
printf("no jpeg buffer here.\n");
return -1;
}
if (rgb_buffer == NULL)
{
printf("you need to alloc rgb buffer.\n");
return -1;
}
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = my_error_exit;
if (setjmp(jerr.setjmp_buffer))
{
jpeg_destroy_decompress(&cinfo);
return -1;
}
jpeg_create_decompress(&cinfo);
jpeg_mem_src(&cinfo, jpeg_buffer, jpeg_size);
jpeg_read_header(&cinfo, TRUE);
//cinfo.out_color_space = JCS_RGB; //JCS_YCbCr; // 设置输出格式
jpeg_start_decompress(&cinfo);
row_stride = cinfo.output_width * cinfo.output_components;
*width = cinfo.output_width;
*height = cinfo.output_height;
rgb_size = row_stride * cinfo.output_height; // 总大小
if (*size < rgb_size)
{
printf("rgb buffer to small, we need %d but has only: %d\n", rgb_size, *size);
}
*size = rgb_size;
buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
printf("debug--:\nrgb_size: %d, size: %d w: %d h: %d row_stride: %d \n", rgb_size,
cinfo.image_width*cinfo.image_height*3,
cinfo.image_width,
cinfo.image_height,
row_stride);
tmp_buffer = rgb_buffer;
while (cinfo.output_scanline < cinfo.output_height) // 解压每一行
{
jpeg_read_scanlines(&cinfo, buffer, 1);
// 复制到内存
memcpy(tmp_buffer, buffer[0], row_stride);
tmp_buffer += row_stride;
}
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
return 0;
}
int rgb2jpeg(unsigned char* rgb_buffer, int width, int height, int quality, unsigned char** jpeg_buffer, unsigned long* jpeg_size)
{
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
int row_stride = 0;
JSAMPROW row_pointer[1];
if (jpeg_buffer == NULL)
{
printf("you need a pointer for jpeg buffer.\n");
return -1;
}
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
jpeg_mem_dest(&cinfo, jpeg_buffer, jpeg_size);
cinfo.image_width = width;
cinfo.image_height = height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, quality, 1); // todo 1 == true
jpeg_start_compress(&cinfo, TRUE);
row_stride = width * cinfo.input_components;
while (cinfo.next_scanline < cinfo.image_height)
{
row_pointer[0] = &rgb_buffer[cinfo.next_scanline * row_stride];
jpeg_write_scanlines(&cinfo, row_pointer, 1);
}
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
return 0;
}
个别参数还有优化地方,等有空了再深入研究一下。
时隔多年,又碰到了libjpeg加速版本,名为turbo-libjpeg。虽惊讶于其宣称的速度,但老夫着实不信,待用代码验证了再写几篇文章。