libjpeg學習2:記憶體篇
前面文章說到到libjpeg的使用示例,裡面的例子實際上是檔案的操作,即解壓JPEG檔案,因為libjpeg有對FILE操作的函式,所以程式碼直接用jpeg_stdio_src(&cinfo, fp);就行了,這個庫會去讀取JPEG檔案。但是實際應用場合中,很多都不是檔案,比如從網路傳輸過來的是JPEG資料,需要解壓為RGB或YUV;又或者傳輸RGB資料要轉換成JPEG。總之,是基於記憶體的操作的。往往用檔案的方式來處理很簡單,而換成了記憶體操作就有點措手不及了。正如上個月搞的FFMPEG轉換,儲存為一個視訊檔案是很簡單的,但我要將轉換後的資料儲存到某一片記憶體中傳輸到上位機,我不可能先儲存為檔案再讀檔案,這太沒職業道德了。幸運的是,我花了1周多的時間搞定了這個問題。
更幸運的是,libjpeg新版本已經實現了記憶體的讀寫。我依稀記得當年使用一個很舊的版本,裡面是沒有記憶體的介面的,因此上網找,竟然也能找到有人自己實現了記憶體介面。但後來發現新版本庫就有了,於是我就不用畫蛇添足了。
解壓記憶體中的JPEG裡,使用jpeg_mem_src函式,如jpeg_mem_src(&cinfo, jpeg_buffer, jpeg_size)。而要將記憶體中的RGB壓縮成JPEG,則使用jpeg_mem_dest介面,如jpeg_mem_src(&cinfo, jpeg_buffer, jpeg_size)。注意,jpeg_buffer是二級指標,不用使用者申請,由libjpeg申請空間,但要使用者自行釋放。
標頭檔案宣告如下:
/** * 利用libjpeg將緩衝區的JPEG轉換成RGB 解壓JPEG * * @param[IN] jpeg_buffer JPEG圖片緩衝區 * @param[IN] jpeg_size JPEG圖片緩衝區大小 * @param[IN] rgb_buffer RGB緩衝區 * @param[IN/OUT] size RGB緩衝區大小 * @param[OUT] width 圖片寬 * @param[OUT] height 圖片高 * * @return * 0:成功 * -1:開啟檔案失敗 * @note * jpeg、rgb空間由呼叫者申請,size為輸入輸出引數,傳入為rgb空間大小,轉出為rgb實際大小 */ int jpeg2rgb(unsigned char* jpeg_buffer, int jpeg_size, unsigned char* rgb_buffer, int* size, int* width, int* height); /** * 利用libjpeg將緩衝區的RGB轉換成JPEG 壓縮為JPEG * * @param[IN] rgb_buffer JPEG圖片RGB資料 * @param[IN] width 圖片寬 * @param[IN] height 圖片高 * @param[IN] quality 圖片質量 * @param[OUT] jpeg_buffer JPEG緩衝區指標 * @param[OUT] jpeg_size JPEG緩衝區大小 * * @return * 0:成功 * -1:開啟檔案失敗 * @note * jpeg_buffer為二級指標,無須呼叫者申請空間,由libjpeg申請,但呼叫者要自行釋放 */ int rgb2jpeg(unsigned char* rgb_buffer, int width, int height, int quality, unsigned char** jpeg_buffer, unsigned long* jpeg_size);
程式碼實現如下:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <setjmp.h>
#include <math.h>
#include <sys/time.h>
#include <time.h>
// jpeg庫標頭檔案必須放到stdio.h後面
#include "libjpeg/include/jpeglib.h"
#include "libjpeg/include/jerror.h"
typedef struct my_error_mgr * my_error_ptr;
void my_error_exit (j_common_ptr cinfo)
{
my_error_ptr myerr = (my_error_ptr) cinfo->err;
(*cinfo->err->output_message) (cinfo);
longjmp(myerr->setjmp_buffer, 1);
}
int jpeg2rgb(unsigned char* jpeg_buffer, int jpeg_size, unsigned char* rgb_buffer, int* size, int* width, int* height)
{
struct jpeg_decompress_struct cinfo;
struct my_error_mgr jerr;
JSAMPARRAY buffer;
int row_stride = 0;
unsigned char* tmp_buffer = NULL;
int rgb_size;
if (jpeg_buffer == NULL)
{
printf("no jpeg buffer here.\n");
return -1;
}
if (rgb_buffer == NULL)
{
printf("you need to alloc rgb buffer.\n");
return -1;
}
cinfo.err = jpeg_std_error(&jerr.pub);
jerr.pub.error_exit = my_error_exit;
if (setjmp(jerr.setjmp_buffer))
{
jpeg_destroy_decompress(&cinfo);
return -1;
}
jpeg_create_decompress(&cinfo);
jpeg_mem_src(&cinfo, jpeg_buffer, jpeg_size);
jpeg_read_header(&cinfo, TRUE);
//cinfo.out_color_space = JCS_RGB; //JCS_YCbCr; // 設定輸出格式
jpeg_start_decompress(&cinfo);
row_stride = cinfo.output_width * cinfo.output_components;
*width = cinfo.output_width;
*height = cinfo.output_height;
rgb_size = row_stride * cinfo.output_height; // 總大小
if (*size < rgb_size)
{
printf("rgb buffer to small, we need %d but has only: %d\n", rgb_size, *size);
}
*size = rgb_size;
buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);
printf("debug--:\nrgb_size: %d, size: %d w: %d h: %d row_stride: %d \n", rgb_size,
cinfo.image_width*cinfo.image_height*3,
cinfo.image_width,
cinfo.image_height,
row_stride);
tmp_buffer = rgb_buffer;
while (cinfo.output_scanline < cinfo.output_height) // 解壓每一行
{
jpeg_read_scanlines(&cinfo, buffer, 1);
// 複製到記憶體
memcpy(tmp_buffer, buffer[0], row_stride);
tmp_buffer += row_stride;
}
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
return 0;
}
int rgb2jpeg(unsigned char* rgb_buffer, int width, int height, int quality, unsigned char** jpeg_buffer, unsigned long* jpeg_size)
{
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
int row_stride = 0;
JSAMPROW row_pointer[1];
if (jpeg_buffer == NULL)
{
printf("you need a pointer for jpeg buffer.\n");
return -1;
}
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_compress(&cinfo);
jpeg_mem_dest(&cinfo, jpeg_buffer, jpeg_size);
cinfo.image_width = width;
cinfo.image_height = height;
cinfo.input_components = 3;
cinfo.in_color_space = JCS_RGB;
jpeg_set_defaults(&cinfo);
jpeg_set_quality(&cinfo, quality, 1); // todo 1 == true
jpeg_start_compress(&cinfo, TRUE);
row_stride = width * cinfo.input_components;
while (cinfo.next_scanline < cinfo.image_height)
{
row_pointer[0] = &rgb_buffer[cinfo.next_scanline * row_stride];
jpeg_write_scanlines(&cinfo, row_pointer, 1);
}
jpeg_finish_compress(&cinfo);
jpeg_destroy_compress(&cinfo);
return 0;
}
個別引數還有優化地方,等有空了再深入研究一下。
時隔多年,又碰到了libjpeg加速版本,名為turbo-libjpeg。雖驚訝於其宣稱的速度,但老夫著實不信,待用程式碼驗證了再寫幾篇文章。