問題簡述
這個問題的具體現象大概是這么回事。我們的程序使用了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
。