音视频入门-16-使用libjpeg-trubo处理JPEG图片

* 音视频入门文章目录 *

RGB-to-JPEG 回顾

上一篇 【手动生成一张JPEG图片】 根据 【JPEG文件格式详解】 一步一步地实现了将 RGB 数据生成了一张 JPEG 图片。

可以感受到,自己来实现 JPEG 的基本系统编码还是有相当的复杂度的,JPEG 压缩编码算法一共分为 11 个步骤:

  1. 颜色模式转换
  2. 采样
  3. 分块
  4. 离散余弦变换(DCT)
  5. 量化
  6. Zigzag 扫描排序
  7. DC 系数的差分脉冲调制编码
  8. DC 系数的中间格式计算
  9. AC 系数的游程长度编码
  10. AC 系数的中间格式计算
  11. 熵编码

下面,我们使用 libjpeg-turbo 来处理 JPEG 图片。

使用 libjpeg-turbo

Building libjpeg-turbo

官方 Build 文档

mkdir libjpeg-turbo/build
cd libjpeg-turbo/build

cmake -G"Unix Makefiles" -DCMAKE_INSTALL_PREFIX:PATH=./ -DCMAKE_INSTALL_BINDIR:PATH=./ -DCMAKE_INSTALL_DATAROOTDIR:PATH=./ -DCMAKE_INSTALL_DOCDIR:PATH=./ -DCMAKE_INSTALL_LIBDIR:PATH=./  -DCMAKE_INSTALL_INCLUDEDIR:PATH=./  -DCMAKE_INSTALL_MANDIR:PATH=./ ..

make
make install

CMakeLists.txt

cmake_minimum_required(VERSION 3.14)

set(CMAKE_C_STANDARD 99)

#aux_source_directory(../3rd/libjpeg-turbo LIBJPEG_TURBO_SRC)

link_directories(../3rd/libjpeg-turbo/build)
include_directories(../3rd/libjpeg-turbo/build)

add_executable(16-rgb-to-jpeg-library rgb-to-jpeg-with-libjpeg-turbo.c util.c)

# 添加链接库
target_link_libraries(16-rgb-to-jpeg-library libturbojpeg.dylib)

libjpeg-turbo 处理 JPEG 图片

JPEG to RGB24

使用 libjpeg-turbo 解码 JPEG 图片成 RGB 格式的数据。

代码中使用的 JPEG 图片

struct ImageData {
    unsigned char *pixels;
    long  width;
    long height;
};

int decode_JPEG_file(char *inJpegName, char *outRgbName) {
    struct jpeg_decompress_struct cinfo;
    struct jpeg_error_mgr jerr;

    FILE * infile;
    FILE * outfile;

    if ((infile = fopen(inJpegName, "rb")) == NULL) {
        fprintf(stderr, "can't open %s\n", inJpegName);
        return -1;
    }
    if ((outfile = fopen(outRgbName, "wb")) == NULL) {
        fprintf(stderr, "can't open %s\n", outRgbName);
        return -1;
    }

    cinfo.err = jpeg_std_error(&jerr);

    jpeg_create_decompress(&cinfo);

    jpeg_stdio_src(&cinfo, infile);

    jpeg_read_header(&cinfo, TRUE);

    printf("image_width = %d\n", cinfo.image_width);
    printf("image_height = %d\n", cinfo.image_height);
    printf("num_components = %d\n", cinfo.num_components);
    printf("enter scale M/N:\n");

    jpeg_start_decompress(&cinfo);

    //输出的图象的信息
    printf("output_width = %d\n", cinfo.output_width);
    printf("output_height = %d\n", cinfo.output_height);
    printf("output_components = %d\n", cinfo.output_components);

    int row_stride = cinfo.output_width * cinfo.output_components;
    /* Make a one-row-high sample array that will go away when done with image */
    JSAMPARRAY buffer = (JSAMPARRAY)malloc(sizeof(JSAMPROW));
    buffer[0] = (JSAMPROW)malloc(sizeof(JSAMPLE) * row_stride);

    struct ImageData imageData = {
        .width =  cinfo.image_width,
        .height = cinfo.image_height,
        .pixels = malloc(row_stride*cinfo.image_height)
    };
    long counter = 0;

    while (cinfo.output_scanline < cinfo.output_height) {
        jpeg_read_scanlines(&cinfo, buffer, 1);
        memcpy(imageData.pixels + counter, buffer[0], row_stride);
        counter += row_stride;
    }

    printf("total size: %ld\n", counter);
    fwrite(imageData.pixels,  counter, 1, outfile);


    jpeg_finish_decompress(&cinfo);

    jpeg_destroy_decompress(&cinfo);

    fclose(infile);
    fclose(outfile);
    free(imageData.pixels);

    return 0;
}

int main(int argc, char* argv[]) {
    printf("↓↓↓↓↓↓↓↓↓↓ Decode JPEG to RGB24 ↓↓↓↓↓↓↓↓↓↓\n");
    char *inJpegName1 = "/Users/staff/Desktop/libjpeg-turbo-test-image.jpg";
    char *outRgbName1 = "/Users/staff/Desktop/libjpeg-turbo-test-image.rgb24";
    int flag1 = decode_JPEG_file(inJpegName1, outRgbName1);
    if (flag1 == 0) {
        printf("decode ok!\n");
    } else {
        printf("decode error!\n");
    }
    printf("↑↑↑↑↑↑↑↑↑↑ Decode JPEG to RGB24 ↑↑↑↑↑↑↑↑↑↑\n\n");
}

运行上面的代码,将得到解码后的 RGB 文件 libjpeg-turbo-test-image.rgb24 :

使用 ffplay 查看 RGB24 文件:

ffplay -f rawvideo -pixel_format rgb24  -s 800x800 /Users/staff/Desktop/libjpeg-turbo-test-image.rgb24
jpeg-to-rgb24-example

RGB24 to JPEG

// 彩虹的七种颜色
uint32_t rainbowColors[] = {
        0XFF0000, // 红
        0XFFA500, // 橙
        0XFFFF00, // 黄
        0X00FF00, // 绿
        0X007FFF, // 青
        0X0000FF, // 蓝
        0X8B00FF  // 紫
};

void genRGB24Data(uint8_t *rgbData, int width, int height) {
    for (int i = 0; i < width; ++i) {
        // 当前颜色
        uint32_t currentColor = rainbowColors[0];
        if(i < 100) {
            currentColor = rainbowColors[0];
        } else if(i < 200) {
            currentColor = rainbowColors[1];
        } else if(i < 300) {
            currentColor = rainbowColors[2];
        } else if(i < 400) {
            currentColor = rainbowColors[3];
        } else if(i < 500) {
            currentColor = rainbowColors[4];
        } else if(i < 600) {
            currentColor = rainbowColors[5];
        } else if(i < 700) {
            currentColor = rainbowColors[6];
        }
        // 当前颜色 R 分量
        uint8_t R = (currentColor & 0xFF0000) >> 16;
        // 当前颜色 G 分量
        uint8_t G = (currentColor & 0x00FF00) >> 8;
        // 当前颜色 B 分量
        uint8_t B = currentColor & 0x0000FF;

        for (int j = 0; j < height; ++j) {
            int currentIndex = 3*(i*height+j);
            rgbData[currentIndex] = R;
            rgbData[currentIndex+1] = G;
            rgbData[currentIndex+2] = B;
        }
    }
}

int encode_JPEG_file(char *strImageName,uint8_t *image_buffer, int image_height, int image_width, int quality) {

    struct jpeg_compress_struct cinfo;
    
    struct jpeg_error_mgr jerr;
    /* More stuff */
    FILE * outfile;     /* target file */
    JSAMPROW row_pointer[1];    /* pointer to JSAMPLE row[s] */
    int row_stride;     /* physical row width in image buffer */
    
    cinfo.err = jpeg_std_error(&jerr);
    /* Now we can initialize the JPEG compression object. */
    jpeg_create_compress(&cinfo);

   
    if ((outfile = fopen(strImageName, "wb")) == NULL) {
        fprintf(stderr, "can't open %s\n", strImageName);
        //exit(1);
        return -1;
    }
    jpeg_stdio_dest(&cinfo, outfile);

  
    cinfo.image_width = image_width;    /* image width and height, in pixels */
    cinfo.image_height = image_height;
    cinfo.input_components = 3;     /* # of color components per pixel */
    cinfo.in_color_space = JCS_RGB;     /* colorspace of input image */
 
    jpeg_set_defaults(&cinfo);
   
    jpeg_set_quality(&cinfo, quality, TRUE /* limit to baseline-JPEG values */);
    
    jpeg_start_compress(&cinfo, TRUE);

 
    row_stride = image_width * 3;   /* JSAMPLEs per row in image_buffer */

    int line = 0;
    while (line < cinfo.image_height) {
      
        //row_pointer[0] = &image_buffer[cinfo.next_scanline * row_stride];
        row_pointer[0] = &image_buffer[line * row_stride];
        jpeg_write_scanlines(&cinfo, row_pointer, 1);

        line++;
    }

    /* Step 6: Finish compression */
    jpeg_finish_compress(&cinfo);
    /* After finish_compress, we can close the output file. */
    fclose(outfile);

    /* Step 7: release JPEG compression object */
    /* This is an important step since it will release a good deal of memory. */
    jpeg_destroy_compress(&cinfo);

    return 0;
}

int main(int argc, char* argv[]) {
    printf("↓↓↓↓↓↓↓↓↓↓ Encode RGB24 to JPEG ↓↓↓↓↓↓↓↓↓↓\n");
    int width = 700, height = 700;
    char *outJpegName2 = "/Users/staff/Desktop/rainbow-rgb24.jpeg";
    //uint8_t rgbBuffer[width*height*3];
    uint8_t *rgbBuffer = malloc(width*height*3);
    genRGB24Data(rgbBuffer, width, height);
    int flag2 = encode_JPEG_file(outJpegName2, rgbBuffer, width, height, 80);
    if (flag2 == 0) {
        printf("encode ok!\n");
    } else {
        printf("encode error!\n");
    }
    free(rgbBuffer);
    printf("↑↑↑↑↑↑↑↑↑↑ Encode RGB24 to JPEG ↑↑↑↑↑↑↑↑↑↑\n\n");
}

仍然是生成彩虹图,运行上面的代码,将得到编码后的 JPEG 文件 rainbow-rgb24.jpeg :

rainbow-rgb24.jpeg

JPEG to YUV

使用 libjpeg-turbo 解码 JPEG 图片成 YUV 格式的数据。

int tjpeg2yuv(unsigned char* jpeg_buffer, int jpeg_size, unsigned char** yuv_buffer, int* yuv_size, int* yuv_type)
{
    tjhandle handle = NULL;
    int width, height, subsample, colorspace;
    int flags = 0;
    int padding = 1; // 1或4均可,但不能是0
    int ret = 0;

    handle = tjInitDecompress();
    tjDecompressHeader3(handle, jpeg_buffer, jpeg_size, &width, &height, &subsample, &colorspace);

    printf("w: %d h: %d subsample: %d color: %d\n", width, height, subsample, colorspace);

    flags |= 0;

    *yuv_type = subsample;
    // 注:经测试,指定的yuv采样格式只对YUV缓冲区大小有影响,实际上还是按JPEG本身的YUV格式来转换的
    *yuv_size = tjBufSizeYUV2(width, padding, height, subsample);
    *yuv_buffer =(unsigned char *)malloc(*yuv_size);
    if (*yuv_buffer == NULL)
    {
        printf("malloc buffer for rgb failed.\n");
        return -1;
    }
    ret = tjDecompressToYUV2(handle, jpeg_buffer, jpeg_size, *yuv_buffer, width,
                             padding, height, flags);
    if (ret < 0)
    {
        printf("compress to jpeg failed: %s\n", tjGetErrorStr());
    }
    tjDestroy(handle);

    return ret;
}

int main(int argc, char* argv[]) {
    printf("↓↓↓↓↓↓↓↓↓↓ Decode JPEG to YUV ↓↓↓↓↓↓↓↓↓↓\n");
    char *inJpegName3 = "/Users/staff/Desktop/libjpeg-turbo-test-image.jpg";
    FILE *jpegFile = fopen(inJpegName3, "rb");
    
    struct stat statbuf;
    stat(inJpegName3, &statbuf);
    int fileLen=statbuf.st_size;
    printf("fileLength2: %d\n", fileLen);

    uint8_t *jpegData = malloc(fileLen);
    fread(jpegData, fileLen, 1, jpegFile);
    fclose(jpegFile);

    uint8_t *yuvData;
    int yuvSize;
    int yuvType;
    tjpeg2yuv(jpegData, fileLen, &yuvData, &yuvSize, &yuvType);
    
    printf("size: %d; type: %d\n", yuvSize, yuvType);
    
    char *yuvSuffix;
    if(yuvType == TJSAMP_444) {
        yuvSuffix = ".yuv444";
    } else if(yuvType == TJSAMP_422) {
        yuvSuffix = ".yuv422";
    } else if(yuvType == TJSAMP_420) {
        yuvSuffix = ".yuv420";
    } else if(yuvType == TJSAMP_GRAY) {
        yuvSuffix = ".yuv-gray";
    } else if(yuvType == TJSAMP_440) {
        yuvSuffix = ".yuv440";
    } else if(yuvType == TJSAMP_411) {
        yuvSuffix = ".yuv411";
    } else {
        printf("Unsupported type!");
        return -1;
    }
    printf("yuv samp: %s\n", yuvSuffix);
    
    char yuvFileName[100];
    sprintf(yuvFileName, "/Users/staff/Desktop/libjpeg-turbo-test-image%s", yuvSuffix);
    FILE *yuvFile = fopen(yuvFileName, "wb");
    fwrite(yuvData, yuvSize, 1, yuvFile);

    free(jpegData);
    free(yuvData);
    fflush(yuvFile);
    fclose(yuvFile);
    printf("↑↑↑↑↑↑↑↑↑↑ Decode JPEG to YUV ↑↑↑↑↑↑↑↑↑↑\n\n");
}

运行上面的代码,将得到解码后的 YUV 文件 libjpeg-turbo-test-image.yuv420 :

使用 ffplay 查看 YUV 文件:

ffplay -f rawvideo -pixel_format yuv420p  -s 800x800 /Users/staff/Desktop/libjpeg-turbo-test-image.yuv420
jpeg-to-yuv-example

YUV to JPEG

利用上一步获得的 YUV 文件,再次编码成 JPEG 文件。

int tyuv2jpeg(unsigned char* yuv_buffer, int yuv_size, int width, int height, int subsample, unsigned char** jpeg_buffer, unsigned long* jpeg_size, int quality) {
    tjhandle handle = NULL;
    int flags = 0;
    int padding = 1; // 1或4均可,但不能是0
    int need_size = 0;
    int ret = 0;

    handle = tjInitCompress();

    flags |= 0;

    need_size = tjBufSizeYUV2(width, padding, height, subsample);
    if (need_size != yuv_size) {
        printf("we detect yuv size: %d, but you give: %d, check again.\n", need_size, yuv_size);
        return 0;
    }

    ret = tjCompressFromYUV(handle, yuv_buffer, width, padding, height, subsample, jpeg_buffer, jpeg_size, quality, flags);
    if (ret < 0) {
        printf("compress to jpeg failed: %s\n", tjGetErrorStr());
    }

    tjDestroy(handle);

    return ret;
}

int main(int argc, char* argv[]) {

    printf("↓↓↓↓↓↓↓↓↓↓ Encode YUV to JPEG ↓↓↓↓↓↓↓↓↓↓\n");
    char *yuv420FileName = "/Users/staff/Desktop/libjpeg-turbo-test-image.yuv420";
    FILE *yuv420File = fopen(yuv420FileName, "rb");
    int yuv420Width = 800, yuv420Height = 800;
    int yuvSubsample = TJSAMP_420;
    uint8_t *yuv2jpegBuffer;
    unsigned long yuv2JpegSize;

    struct stat yuv420FileStat;
    stat(yuv420FileName, &yuv420FileStat);
    int yuv420FileLen = yuv420FileStat.st_size;
    printf("yuv420 file length: %d\n", yuv420FileLen);

    uint8_t * yuv420Data = malloc(yuv420FileLen);
    fread(yuv420Data, yuv420FileLen, 1, yuv420File);
    printf("yuv420 read finish!\n");

    tyuv2jpeg(yuv420Data, yuv420FileLen, yuv420Width, yuv420Height, yuvSubsample, &yuv2jpegBuffer, &yuv2JpegSize, 80);
    printf("jpeg data size: %ld\n", yuv2JpegSize);

    FILE *yuv2JpegOutFile = fopen("/Users/staff/Desktop/libjpeg-turbo-yuv-to-jpeg.jpeg", "wb");
    fwrite(yuv2jpegBuffer, yuv2JpegSize, 1, yuv2JpegOutFile);

    fclose(yuv420File);
    fflush(yuv2JpegOutFile);
    fclose(yuv2JpegOutFile);
    free(yuv420Data);

    printf("↑↑↑↑↑↑↑↑↑↑ Encode YUV to JPEG ↑↑↑↑↑↑↑↑↑↑\n\n");
    return 0;
}

运行上面的代码,将得到编码后的 JPEG 文件 libjpeg-turbo-yuv-to-jpeg.jpeg :

libjpeg-turbo-yuv-to-jpeg.jpeg

Congratulations!

至此,我们学会了使用 libjpeg-turbo 处理 JPEG 图片。


代码:
16-rgb-to-jpeg-library

参考资料:

Main libjpeg-turbo repository

JPEG图像压缩算法流程详解

libjpeg学习4:libjpeg-turbo之YUV

关于在Linux下使用TurboJPEG库

FFmpeg & FFPlay 命令基本用法


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容