zoukankan      html  css  js  c++  java
  • 记录一次报 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

  • 相关阅读:
    Hashmap
    string字符串分词
    关于链表的几道经典例题
    【C语言】链表(LinkedList)的建立与基本操作
    01迷宫
    2019 数学联赛 题解 / 游记
    一种简单方法构造 n 元有限域
    Python逆向某网站之 AES解密数据
    我发现了一个网站Bug,然后反馈了
    在多线程中使用静态方法是否有线程安全问题
  • 原文地址:https://www.cnblogs.com/oloroso/p/13323527.html
Copyright © 2011-2022 走看看