記錄一次報 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM