记录一次报 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_VERSION62

posted @ 2020-07-16 16:53  乌合之众  阅读(2123)  评论(0编辑  收藏  举报
clear