1. 程式人生 > >libjpeg學習2:記憶體篇

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。雖驚訝於其宣稱的速度,但老夫著實不信,待用程式碼驗證了再寫幾篇文章。