记录一次报 Bogus input colorspace 错误(GDAL、libjpeg-turbo)
问题简述
这个问题的具体现象大概是这么回事。我们的程序使用了libjpeg-turbo
实现了一个编码图像数据为 jpeg
格式的函数,只要调用这个函数就会报错 Bogus input colorspace
,然后程序退出。
查找原因
通过查看 libjpeg-turbo
源码找到了相关的内容
// libjpeg-turbo/jerror.h(63)
JMESSAGE(JERR_BAD_IN_COLORSPACE, "Bogus input colorspace")
// libjpeg-turbo/jccolor.c(542,557,562,568,573)
// jinit_color_converter 函数
ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE);
// libjpeg-turbo/jerror.h(230-232)
#define ERREXIT(cinfo, code) \
((cinfo)->err->msg_code = (code), \
(*(cinfo)->err->error_exit) ((j_common_ptr)(cinfo)))
这里可以确定导致程序退出的原因是与这个有关的。应该是程序在进行编码过程中遇到了错误,打印了错误消息之后退出的。应该是调用了 (cinfo)->err->error_exit
指向的函数退出。
只能从我们的实现的函数去找在哪里间接调用了 jinit_color_converter
来推断错误的位置。通过调试发现是在调用了 jpeg_set_defaults
() 之后退出的,通过查看代码,找到了报错的位置。
// 1、我们的程序调用了 jpeg_set_defaults 函数,这个函数定义在
// libjpeg-turbo/jcparam.c(182)
jpeg_set_defaults(j_compress_ptr cinfo)
// 2、jpeg_set_defaults 里面又调用了 jpeg_default_colorspace 在同文件的 273 行
// 这个函数定义在 libjpeg-turbo/jcparam.c(282)
jpeg_default_colorspace(j_compress_ptr cinfo)
// 3、这个函数调用了 如下代码,在 314 行
314 ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE);
// 通过之前的源码来看,错误消息就是这里输出的,对于 cinfo->err->error_exit 的赋值
// 是在我们的代码中通过 cinfo.err = jpeg_std_error(&jerr); 赋值的
// 4、找到 jpeg_std_error 的定义,里面对 error_exit 进行了赋值
// libjpeg-turbo/jerror.c(230-251)
230 jpeg_std_error(struct jpeg_error_mgr *err)
231 {
232 err->error_exit = error_exit;
// 5、找到 error_exit 的定义,可以看到里面调用了 exit 函数退出进程
// libjpeg-turbo/jerror.c(68-78)
68 METHODDEF(void)
69 error_exit(j_common_ptr cinfo)
70 {
71 /* Always display the message */
72 (*cinfo->err->output_message) (cinfo);
73
74 /* Let the memory manager delete any temp files before we die */
75 jpeg_destroy(cinfo);
76
77 exit(EXIT_FAILURE);
78 }
整个调用链条是理清楚了,原因也找了,就是在调用 jpeg_default_colorspace
的时候,传入的 cinfo->in_color_space
参数不在支持的颜色空间枚举值范围内。但是我们的代码中实际是传入的 JCS_EXT_RGBA
应该是有效的才对。
使用gdb
调试下程序,通过对 jpeg_default_colorspace
函数设置断点,发现了一点端倪。
> gdb ./enjpeg
GNU gdb (Ubuntu 8.3-0ubuntu1) 8.3
... ... 删除大段版权说明等 ... ...
Reading symbols from ./enjpeg...
(gdb) b jpeg_default_colorspace # 添加断点
Function "jpeg_default_colorspace" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (jpeg_default_colorspace) pending.
(gdb) r # 运行程序
Starting program: /tmp/enjpeg
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
开始
Breakpoint 1, 0x00007ffff66d62f0 in jpeg_default_colorspace () from /tmp/lib/libgdal.so.27
(gdb) n
Single stepping until exit from function jpeg_default_colorspace,
which has no line number information.
Bogus input colorspace
[Inferior 1 (process 32133) exited with code 01]
(gdb) q
这里显示断点函数 jpeg_default_colorspace
在动态库 /tmp/lib/libgdal.so.27
中,而不是我们需要的 libjpeg-turbo
中。看到这里很清楚了,因为我们的程序还调用了 gdal
库,这里用到了 gdal
中自带的 libjpeg
,而不是我们需要的 libjpeg-turbo
,通过查看 gdal/frmts/jpeg/libjpeg/jcparam.c
中对 jpeg_default_colorspace
函数的实现,可以发现问题就出在这里,这里的实现是不支持 RGBA
的。
// gdal/frmts/jpeg/libjpeg/jcparam.c 中实现 GLOBAL(void) jpeg_default_colorspace (j_compress_ptr cinfo) { switch (cinfo->in_color_space) { case JCS_GRAYSCALE: jpeg_set_colorspace(cinfo, JCS_GRAYSCALE); break; case JCS_RGB: jpeg_set_colorspace(cinfo, JCS_YCbCr); break; case JCS_YCbCr: jpeg_set_colorspace(cinfo, JCS_YCbCr); break; case JCS_CMYK: jpeg_set_colorspace(cinfo, JCS_CMYK); /* By default, no translation */ break; case JCS_YCCK: jpeg_set_colorspace(cinfo, JCS_YCCK); break; case JCS_UNKNOWN: jpeg_set_colorspace(cinfo, JCS_UNKNOWN); break; default: ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); } } |
// libjpeg-turbo/jcparam.c 中实现 GLOBAL(void) jpeg_default_colorspace(j_compress_ptr cinfo) { switch (cinfo->in_color_space) { case JCS_GRAYSCALE: jpeg_set_colorspace(cinfo, JCS_GRAYSCALE); break; case JCS_RGB: case JCS_EXT_RGB: case JCS_EXT_RGBX: case JCS_EXT_BGR: case JCS_EXT_BGRX: case JCS_EXT_XBGR: case JCS_EXT_XRGB: case JCS_EXT_RGBA: case JCS_EXT_BGRA: case JCS_EXT_ABGR: case JCS_EXT_ARGB: jpeg_set_colorspace(cinfo, JCS_YCbCr); break; case JCS_YCbCr: jpeg_set_colorspace(cinfo, JCS_YCbCr); break; case JCS_CMYK: jpeg_set_colorspace(cinfo, JCS_CMYK); /* By default, no translation */ break; case JCS_YCCK: jpeg_set_colorspace(cinfo, JCS_YCCK); break; case JCS_UNKNOWN: jpeg_set_colorspace(cinfo, JCS_UNKNOWN); break; default: ERREXIT(cinfo, JERR_BAD_IN_COLORSPACE); } } |
解决办法
找到了原因就好解决了。因为用到了 gdal
库,gdal
库又是用的内部自带的 libjpeg
,导致链接的时候,jpeg_xxxx
相关的函数链接到 libgdal.so
上去了。
这里可以采用两个办法
- 一个是重新编译
gdal
,不使用它自带的libjpeg
,使用另外编译的libjpeg-turbo
库。也可以修改gdal/frmts/jpeg/libjpeg/jmorecfg.h
中的#define GLOBAL(type) type
改成#define GLOBAL(type) __attribute__((visibility("hidden"))) type
进行重新编译,将自带的libjpeg
库限制仅在gdal
内部使用。 - 二是修改我们程序链接过程中链接库的顺序,先链接
libjpeg-turbo
后链接libgdal
。这样链接的时候在libjpeg-turbo
里面找到了相关的定义,就不会再去libgdal
里面找了。
相关代码等
编码 jpeg
关键代码
// 创建编码使用的信息结构体
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
// 编码到内存缓冲区,这里libjpeg_turbo会申请内存,用完后但需要记得释放
unsigned char* dstbuffer = NULL;
unsigned long dstbufferlen = 0;
jpeg_mem_dest(&cinfo, &dstbuffer, &dstbufferlen);
// 设置编码的图像的相关信息
cinfo.image_width = nWidth;
cinfo.image_height = nHeight;
cinfo.input_components = 4; // 这个与下面 RGBA 搭配
cinfo.in_color_space = JCS_EXT_RGBA;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, (int)quality, true);
// 开始编码
jpeg_start_compress(&cinfo, true);
JSAMPROW row_pointer[1];
int row_stride = cinfo.image_width * 4; // 两行像素数据间隔的字节数
// 逐行编码
while (cinfo.next_scanline < cinfo.image_height) {
row_pointer[0] = &(((unsigned char*)pRgbaData)[cinfo.next_scanline*row_stride]);
jpeg_write_scanlines(&cinfo, row_pointer, 1);
}
// 完成编码
jpeg_finish_compress(&cinfo);
// 释放
jpeg_destroy_compress(&cinfo);
// 输出缓冲要进行释放
if (dstbuffer != NULL) {
// 相关操作
free(dstbuffer); // 释放内存
}
这里还碰到一个小问题,与前面的无关。也做一个简单的记录。
会报一个 Wrong JPEG library version: library is 80, caller expects 62
的信息,这个是因为程序中调用了 jpeg_create_compress
的时候,实际宏替换为了 jpeg_CreateCompress((cinfo), JPEG_LIB_VERSION,...
链接的库是 libjpeg.so.8
(可以 gdb
调试查看),而编译时候的头文件里面的 JPEG_LIB_VERSION
是62
。