记录一次报 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
。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 用 C# 插值字符串处理器写一个 sscanf
· Java 中堆内存和栈内存上的数据分布和特点
· 开发中对象命名的一点思考
· .NET Core内存结构体系(Windows环境)底层原理浅谈
· 为什么说在企业级应用开发中,后端往往是效率杀手?
· 本地部署DeepSeek后,没有好看的交互界面怎么行!
· DeepSeek 解答了困扰我五年的技术问题。时代确实变了!
· 趁着过年的时候手搓了一个低代码框架
· 推荐一个DeepSeek 大模型的免费 API 项目!兼容OpenAI接口!