risc-v中文社区

 找回密码
 立即注册
查看: 3104|回复: 0

LibJpeg解码内存中的Jpeg数据

[复制链接]

347

主题

564

帖子

2237

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
2237
发表于 2022-5-18 11:24:35 | 显示全部楼层 |阅读模式
一般人都知道,ST官网提供了libjpeg库,目前还没有研究ST的代码,我从网上看到有这样的帖子,感觉有记录价值:
熟悉libjpeg的朋友都知道libjpeg是一个开源的库。Linux和Android都是用libjpeg来支持jpeg文件的,可见其功能多么强大。但是默认情况下libjpeg只能处理jpeg文件的解码,或者把图像编码到jpeg文件。在嵌入式设备中没有文件系统也是很正常的事情,难道我们就不能利用libjpeg的强大功能了吗?当然不是!本文将会介绍怎样扩展libjpeg让其能够解码内存中的jpeg数据。
     在介绍主题之前,请允许我讨论一下公共代码库的数据输入的一些问题。因为一个公共代码库是开放给大家用的,这个世界的输入方式也是多种多样的,比如可以通过文件输入,shell用户手工输入,内存缓存输入,网络socket输入等等。所以实现库的时候,千万不要假定用户只有一种输入方式。

     通用的做法是实现一个输入的中间层。如果库是以支持面向对象语言实现的话,可以实现一套流机制,实现各式各样的流(文件流,缓存流,socket流等)。公共代码库的输入为流对象。这样库就可以实现各式各样的输入了。一个例子请参考Android图形引擎Skia的实现。

     假如库是用非面向对象的语言实现的话,那么怎样来实现多种输入方式呢?可以通过定义输入对象的数据结构,该数据结构中让用户注册读写数据的函数和数据。因为只有调用者最清楚他的数据来源,数据读取方式。在公共代码库中,只需要调用用户注册的回调函数对数据进行读写就可以了。这样的话,也可以实现公共代码库对多种输入方式的支持。

     回到本文的主题,libjpeg对多种输入的支持就不好,它假设了用户只会用文件作为输入,没有考虑其他的输入方式。经过研究他的源代码发现其内部也是非常容易扩展,进而实现对多种输入的支持的,但是libjpeg没有更这样做,不明白为什么。请看jpeglib.h中如下定义:

/* Data source object for decompression */
struct jpeg_source_mgr {
  const JOCTET * next_input_byte; /* => next byte to read from buffer */
  size_t bytes_in_buffer;        /* # of bytes remaining in buffer */

  JMETHOD(void, init_source, (j_decompress_ptr cinfo));
  JMETHOD(boolean, fill_input_buffer, (j_decompress_ptr cinfo));
  JMETHOD(void, skip_input_data, (j_decompress_ptr cinfo, long num_bytes));
  JMETHOD(boolean, resync_to_restart, (j_decompress_ptr cinfo, int desired));
  JMETHOD(void, term_source, (j_decompress_ptr cinfo));
};
可以看出source manager对象可以注册多个回调函数来对数据进行读写。在看jdatasrc.c中的代码:

typedef struct {
  struct jpeg_source_mgr pub;        /* public fields */

  FILE * infile;                /* source stream */
  JOCTET * buffer;                /* start of buffer */
  boolean start_of_file;        /* have we gotten any data yet? */
} my_source_mgr;
该文件为jpeglib的source manger初始化和管理的地方。上面的数据结构是内部使用的源数据。可以看出其源数据只支持文件输入(infile变量),并提供缓存功能(buffer变量)。

     其对source manager初始化的接口定义子jpeglib.h中,定义如下:

EXTERN(void) jpeg_stdio_src JPP((j_decompress_ptr cinfo, FILE * infile));
通过这个接口我们可以看出它的source manager只能接收文件作为输入。该函数的实现在jdatasrc.c文件中。

     为了支持内存jpeg数据输入,我的设计是在jdatasrc.c中实现一个新的接口来初始化jpeglib的source manger对象。并完成注册其读写的回调函数给source manager。

     说干就干,首先我们需要让source manager对象支持内存数据。修改my_source_mgr数据结构如下:

typedef struct{
  UINT8* img_buffer;
  UINT32 buffer_size;
  UINT32 pos;
}BUFF_JPG;
/* Expanded data source object for stdio input */

typedef struct {
  struct jpeg_source_mgr pub;        /* public fields */
  union{
    BUFF_JPG jpg;                /* jpeg image buffer */
    VFS_FILE * infile;                /* source stream */
  };
  JOCTET * buffer;                /* start of buffer */
  boolean start_of_file;        /* have we gotten any data yet? */
} my_source_mgr;
可以看出我们通过union来支持内存数据(jpg变量)或者文件输入。因为需要负责读写必须要标识出当前内存读写的位置,所以必须要在BUFF_JPG数据结构中定义pos变量。

     下一步我们需要实现读写内存jpeg数据的回调函数了。经过分析对文件数据读写的回调函数,发现我们只需要实现jpeg_source_mgr数据结构中的fill_input_buffer回调函数就可以了,其他的回调函数可以延用对文件读取的回调函数。在jdatasrc.c文件中,定义回调函数如下:

/*
* This function will read the jpeg memery block to fill the library buffer.
*/
METHODDEF(boolean)
jpg_fill_input_buffer (j_decompress_ptr cinfo)
{
  my_src_ptr src = (my_src_ptr) cinfo->src;
  size_t nbytes;

  if(src->jpg.img_buffer == NULL || src->jpg.pos >= src->jpg.buffer_size){
    nbytes = -1;
  }
  else {
    nbytes = (src->jpg.pos + INPUT_BUF_SIZE > src->jpg.buffer_size ?         \
           src->jpg.buffer_size - src->jpg.pos : INPUT_BUF_SIZE);
    MEMCPY(src->buffer, src->jpg.img_buffer + src->jpg.pos, nbytes);
    src->jpg.pos += nbytes;
  }

  if (nbytes <= 0) {
    if (src->start_of_file)        /* Treat empty input file as fatal error */
      ERREXIT(cinfo, JERR_INPUT_EMPTY);
    WARNMS(cinfo, JWRN_JPEG_EOF);
    /* Insert a fake EOI marker */
    src->buffer[0] = (JOCTET) 0xFF;
    src->buffer[1] = (JOCTET) JPEG_EOI;
    nbytes = 2;
  }

  src->pub.next_input_byte = src->buffer;
  src->pub.bytes_in_buffer = nbytes;
  src->start_of_file = FALSE;

  return TRUE;
}
可以看出我们读取数据都是从内存缓存中读取,如果到达缓存末尾就返回-1。

     经过调试分析还发现jdatasrc.c文件中skip_input_data函数有一个不严谨的地方。原来代码中如下:

METHODDEF(void)
skip_input_data (j_decompress_ptr cinfo, long num_bytes)
{
  my_src_ptr src = (my_src_ptr) cinfo->src;

  /* Just a dumb implementation for now.  Could use fseek() except
   * it doesn't work on pipes.  Not clear that being smart is worth
   * any trouble anyway --- large skips are infrequent.
   */
  if (num_bytes > 0) {
    while (num_bytes > (long) src->pub.bytes_in_buffer) {
      num_bytes -= (long) src->pub.bytes_in_buffer;
      (void) fill_input_buffer(cinfo);
      /* note we assume that fill_input_buffer will never return FALSE,
       * so suspension need not be handled.
       */
    }
    src->pub.next_input_byte += (size_t) num_bytes;
    src->pub.bytes_in_buffer -= (size_t) num_bytes;
  }
}
请注意显示地调用了fill_input_buffer,而不是调用注册给source manager的回调函数。这样做是不严谨的,虽然只支持文件输入的情况下,这样写没有任何问题,但是如果我们增加其他的输入方式的话(比如内存数据输入),这样写将不会调用到我们注册给Source manager的fill_input_buffer回调函数。所以如上的代码修改为:

METHODDEF(void)
skip_input_data (j_decompress_ptr cinfo, long num_bytes)
{
  my_src_ptr src = (my_src_ptr) cinfo->src;

  /* Just a dumb implementation for now.  Could use fseek() except
   * it doesn't work on pipes.  Not clear that being smart is worth
   * any trouble anyway --- large skips are infrequent.
   */
  if (num_bytes > 0) {
    while (num_bytes > (long) src->pub.bytes_in_buffer) {
      num_bytes -= (long) src->pub.bytes_in_buffer;
      //(void) fill_input_buffer(cinfo);
      (void) src->pub.fill_input_buffer(cinfo);
      /* note we assume that fill_input_buffer will never return FALSE,
       * so suspension need not be handled.
       */
    }
    src->pub.next_input_byte += (size_t) num_bytes;
    src->pub.bytes_in_buffer -= (size_t) num_bytes;
  }
}
调用我们注册的回调函数来读取数据。

     最好我们需要实现一个供用户用内存jpeg数据初始化source manager的接口。我的定义如下:

/*
* This function improve the library can use the jpeg memory block as source.
*/
GLOBAL(void)
jpeg_stdio_buffer_src (j_decompress_ptr cinfo, UINT8 * buffer, UINT32 size)
{
  my_src_ptr src;

  if (cinfo->src == NULL) {        /* first time for this JPEG object? */
    cinfo->src = (struct jpeg_source_mgr *)
      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
                                  SIZEOF(my_source_mgr));
    src = (my_src_ptr) cinfo->src;
    src->buffer = (JOCTET *)
      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
                                  INPUT_BUF_SIZE * SIZEOF(JOCTET));
  }

  src = (my_src_ptr) cinfo->src;
  src->pub.init_source = init_source;
  src->pub.fill_input_buffer = jpg_fill_input_buffer;
  src->pub.skip_input_data = skip_input_data;
  src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
  src->pub.term_source = term_source;
  //src->infile = infile;
  src->jpg.img_buffer = buffer;
  src->jpg.buffer_size = size;
  src->jpg.pos = 0;
  src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
  src->pub.next_input_byte = NULL; /* until buffer loaded */
}
通过该函数会发现:我们用户输入的buffer初始化了my_source_mgr,并用我们实现的回调函数jpg_fill_input_buffer初始化了jpeg_source_mgr数据结构中的fill_input_buffer。这样每次libjpeg读取数据就将会调用jpg_fill_input_buffer来读取内存jpeg数据了。

     最后把jpeg_stdio_buffer_src接口暴露给最终用户。在jpeglib.h中增加如下定义:

EXTERN(void) jpeg_stdio_buffer_src JPP((j_decompress_ptr cinfo, UINT8 * buffer, UINT32 size));
    至此libjpeg已经可以支持内存jpeg数据的解码了。只需要在调用jpeg_stdio_src接口的地方改调用jpeg_stdio_buffer_src就可以了。

    以上代码仅供参考,以上代码初步测试没有问题,但是未经过严格测试。如果你经过测试发现代码中的错误或者有改进的方法,请你和我联系。我的联系方式请看《关于云中漫步》。


原文地址:

http://my.unix-center.net/~Simon_fu/?p=565



回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则



Archiver|手机版|小黑屋|risc-v中文社区

GMT+8, 2024-4-19 17:43 , Processed in 0.020501 second(s), 17 queries .

risc-v中文社区论坛 官方网站

Copyright © 2018-2021, risc-v open source

快速回复 返回顶部 返回列表