上一篇文章中我们了解JPEG图像的基础知识,以及核心的几个步骤,接下来,我们针对源码深入学习一下相关原理,废话不多说,直接开始。
JPEG官网推荐了两个多媒体开源库,如下链接:
https://jpeg.org/jpeg/software.html
libjpeg-turbo 是一种 JPEG 图像编解码器,它使用 SIMD 指令(MMX、SSE2、NEON、AltiVec)在 x86、x86-64、ARM 和 PowerPC 系统上加速基线 JPEG 压缩和解压缩。在此类系统上,libjpeg-turbo 通常是 libjpeg 的 2-6 倍,其他条件相同。在其他类型的系统上,libjpeg-turbo 凭借其高度优化的霍夫曼编码例程,仍然可以在很大程度上胜过 libjpeg。在许多情况下,libjpeg-turbo 的性能可与专有的高速 JPEG 编解码器媲美。
libjpeg-turbo 实现了传统的 libjpeg API 以及功能较弱但更直接的 TurboJPEG API。libjpeg-turbo 还具有色彩空间扩展功能,允许它从/解压缩到 32 位和大端像素缓冲区(RGBX、XBGR 等),以及一个全功能的 Java 接口。

特征

  • 在 x86、x86-64 和 ARM 平台上,速度是 libjpeg 的 2-6 倍
  • 为流行的 Linux 发行版、Windows、OS X 和 iOS 提供的 32 位和 64 位二进制文件
  • 可用于 GPL 和专有应用程序
  • 供行业标准的 libjpeg API/ABI(可以模拟 libjpeg v6b、v7 或 v8,尽管 libjpeg-turbo 不支持 libjpeg v8 中引入的非标准 SmartScale 格式)
  • 提供 VirtualGL 和 TurboVNC 使用的 TurboJPEG API
  • 商业/闭源加速 JPEG 编解码器的性能相似
  • 能齐全的 Java 包装器
因此JPEG代码走读以 libjpeg-turbo为例,其基本调用流程在libjpeg-turbo-main\example.c 中给出明确实例:
  1. 初始化压缩对象jpeg_create_compress;
  2. 设置压缩后的数据的输出形式jpeg_stdio_dest,比如输出到文件设置压缩的参数jpeg_set_defaults。 这里最重要,也就是我们需要把optimize_coding设置为true。 因为默认是false。
  3. 始压缩jpeg_start_compress;
  4. 行循环写入,jpeg_write_scanlines;
  5. 结束压缩,jpeg_finish_compress;
  6. 释放压缩对象。
    jpeg_destroy_compress
至于如何进行编译,如何使用,其他大佬整理的文档也非常详细,可以参考如下几篇:

https://www.jianshu.com/p/20902ca448ae?utm_source=oschina-app
https://blog.csdn.net/ta893115871/article/details/109890976
https://glumes.com/post/opengl/libjpeg-turbo-compile-and-practice/
接下来我们继续深入函数内部了解其相关实
jpeg_create_compress 做了一次宏定义:
jpeg_CreateCompress
目录:libjpeg-turbo-main\jcapimin.c,主要作用:JPEG 压缩对象的初始化。 
jpeg_stdio_dest
设置JPEG编码器的输出目标,即将编码后的图像数据写入文件。具体实现是通过定义一个名为jpeg_stdio_dest的函数,并传入一个指向已打开的文件的指针作为参数。
依据cinfo->dest是否为空决定是否重新alloc一段内存,其目的就是为了不用多次执行jpeg_stdio_dest。
在设置完目标位置之后,代码接着将dest指针转化为my_dest_ptr类型,并设置其结构体的三个函数指针:init_destination、empty_output_buffer和term_destination,它们分别表示初始化目标位置、清空输出缓存和结束输出操作。
最后,将outfile指针保存到my_dest_ptr结构体的outfile字段中,以备后续写入数据时使用。
jpeg_set_defaults
设置压缩所需要的默认参数集,
jpeg_start_compress
startcompressor依据不同的位深有不同的调用函数,本文以8bit为例,

第一,会检查cinfo->global_state的值是否为CSTATE_START,如果不是,则会通过ERREXIT1函数发出一个错误消息(JERR_BAD_STATE)。
第二,如果write_all_tables为真,则会将所有的表标记为要写入。
第三,该函数会调用cinfo->err->reset_error_mgr函数和cinfo->dest->init_destination函数来重新初始化错误管理器和目标模块。
第四,通过调用jinit_compress_master函数,选择并初始化所有必要的压缩模块。
第五,调用cinfo->master->prepare_for_pass函数来设置第一遍扫描的参数和状态。
最后,将cinfo->next_scanline设置为0,表示还没有扫描过任何扫描线,将cinfo->global_state设置为CSTATE_RAW_OK或CSTATE_SCANNING,表示已经准备好开始第一遍压缩操作了。
jpeg_write_scanlines
将一些扫描行的数据写入到JPEG压缩器中,函数的返回值是实际写入的行数。如果写入的行数小于提供的num_lines,则只有在数据目标模块请求暂停压缩器的情况下才会发生;或者如果传递了超过图像高度的扫描线数。
jpeg_finish_compress
jpeg_finish_compress完成JPEG压缩操作。如果选择了多通道操作模式,则可能需要进行大量的工作,包括实际输出的大部分内容。
第一,会根据global_state进行不同模式判断,判断压缩状态并终止第一次遍历(pass),如果需要进行更多的遍历,则执行它们,最后写入文件尾并清理资源。
第二,执行pass判断,调用 cinfo->master->prepare_for_pass(),准备进行下一轮压缩。并依据位深精度不同,分别调用compress_data接口
第三,完成一些必要的清理工作:调用了标记处理模块(marker)中的函数,用于在图像流的结尾写入EOI(End of Image)标记,并完成一些与标记相关的清理工作;调用了目标数据源(destination)中的函数,用于完成输出图像的最终写入操作;调用了jpeg_abort函数,释放内存并重置全局状态。
jpeg_destroy_compress
JPEG压缩器销毁函数jpeg_destroy_compress,它调用了通用的jpeg_destroy函数来销毁压缩器对象cinfo。通用的jpeg_destroy函数会释放该对象所使用的所有资源,并将对象本身的内存也释放掉。
我是一枚爱跑步的程序猿,维护公众号和知乎专栏《MediaStack》,有兴趣可以关注,一起学习音视频知识,时不时分享实战经验。
继续阅读
阅读原文