<轉>libjpeg解碼內存中的jpeg數據


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM