libjpeg学习2:内存篇

前面文章说到到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申请空间,但要用户自行释放。
头文件声明如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/**
* 利用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);

代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
#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。虽惊讶于其宣称的速度,但老夫着实不信,待用代码验证了再写几篇文章。