轉自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就可以了