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

熟悉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就可以了

<转>libjpeg解码内存中的jpeg数据的更多相关文章

  1. C# 操作地址 从内存中读取写入数据(初级)

    本示例以植物大战僵尸为例, 实现功能为 每1秒让阳光刷新为 9999.本示例使用的游戏版本为 [植物大战僵尸2010年度版], 使用的辅助查看内存地址的工具是  CE. 由于每次启动游戏, 游戏中阳光 ...

  2. Linux 下V4l2摄像头采集图片,实现yuyv转RGB,RGB转BMP,RGB伸缩,jpeglib 库实现压缩RGB到内存中,JPEG经UDP发送功(转)

    ./configure CC=arm-linux-gnueabihf-gcc LD=arm-linux-gnueabihf-ld --host=arm-linux --prefix=/usr/loca ...

  3. 【FFMPEG】从内存中获取H264数据并进行decode

    版权声明:本文为博主原创文章,未经博主允许不得转载. 使用ffmpeg解码h264数据其实相对使用x264进行视频编码是简单了许多的,因为ffmpeg提供了一个decoding_encoding.c的 ...

  4. 嵌入式 linux 移植修改后的libjpeg 实现内存中解码

    1.修改libjpeg源码,使之实现内存解码. 修改libjpeg中读取或者输出jpeg文件的函数接口文件jdatadst.c和jdatasrc.c见下面这篇帖子. http://blog.163.c ...

  5. 如何将内存中的位图数据绘制在DC上

    假如你定义了一个位图类,里面包含位图头,位图信息头,调色板,位图数据.然后你按照位图的格式将位图文件读入你的类中,现在你知道了位图的全部信息了.主要信息包含在位图信息头里面,数据则在位图数据缓冲里面. ...

  6. EXTJS4.2 内存中操作表格数据时,删除表格数据,行号不连续解决

    需要重新刷新下表格的view => grid.view.refresh();

  7. VC++使用CImage在内存中Jpeg转换Bmp图片

    VC++中Jpeg与Bmp图片格式互转应该是会经常遇到,Jpeg相比Bmp在图片大小上有很大优势. 本文重点介绍使用现有的CImage类在内存中进行转换,不需要保存为文件,也不需要引入第三方库. Li ...

  8. C/C++数据在内存中的存储方式

    目录 1 内存地址 2 内存空间   在学习C/C++编程语言时,免不了和内存打交道,在计算机中,我们存储有电影,文档,音乐等数据,这些数据在内存中是以什么形式存储的呢?下面做一下简单介绍. 本文是学 ...

  9. Java WEB中的HttpServletResponse数据传递

    1.什么是HttpServletResponse 2.使用HttpServletResponse向浏览器发送数据及相关实例. 实例1:实现文件下载功能 实例2:实现验证码注册 实例3:实现页面3秒后跳 ...

随机推荐

  1. LTTng调试: 一个系统软件工程师的随手涂鸦

    http://nanxiao.me/install-lttng/ http://packages.efficios.com/ http://lttng.org/ http://lttng.org/do ...

  2. visul svn+花生壳

    1.服务器端 工具:visul svn+花生壳 花色壳:注册域名 visul svn:配置http://www.cnblogs.com/bluewelkin/p/3479105.html 外网访问,端 ...

  3. RT:How HTTP use TCP connection

    In HTTP/0.9 (not used anymore), each request uses a separate TCP connection, and the end of a respon ...

  4. JavaScript--动态更改CSS样式

    JavaScript太强大了,虽然是弱语言,不过一点都不输于Java 可以自行设置随机数,来动态更改CSS样式,每一次都是不一样的感觉,这个小功能挺实用的 <!DOCTYPE html> ...

  5. Linq101-Grouping Operators

    using System; using System.Collections.Generic; using System.Linq; namespace Linq101 { class Groupin ...

  6. Wpf控件ListBox使用实例2

    2.Xaml绑定选择结果 <StackPanel Orientation="Vertical"> <TextBlock Margin="10,10,10 ...

  7. 新建android系统服务

    一.Android系统服务 Android提供了很多系统服务:如ActivityManger,PowerManger,WindowManger,WifiManger等等. 这些服务都是系统启动开始就一 ...

  8. 一个tomcat部署俩个java web项目

    2.发布的时候可以发布成war包,用项目名称右键export,选择项目名称,还有发布的路径,即tomcat下的路径,参考http://zhidao.baidu.com/link?url=imOu0Uu ...

  9. 牛顿法与拟牛顿法,DFP法,BFGS法,L-BFGS法

    牛顿法 考虑如下无约束极小化问题: $$\min_{x} f(x)$$ 其中$x\in R^N$,并且假设$f(x)$为凸函数,二阶可微.当前点记为$x_k$,最优点记为$x^*$. 梯度下降法用的是 ...

  10. oc总结

    OC10天大纲 一.类和对象 1.什么是类? 同一种对象的抽象就是类. 2.什么是对象? 世界上的任何事物都可以称为对象,每个对象都有他自己的属性和行为. 3.如何创建一个类(请把一个.h和一个.m粘 ...