音视频编解码:JPEG格式2 代码走读

上一篇文章中我们了解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-mainexample.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

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-mainjcapimin.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。我是一枚爱跑步的程序猿,维护公众号和知乎专栏《MediaStack》,有兴趣可以关注,一起学习音视频知识,时不时分享实战经验。

版权声明:本文内容转自互联网,本文观点仅代表作者本人。本站仅提供信息存储空间服务,所有权归原作者所有。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至1393616908@qq.com 举报,一经查实,本站将立刻删除。

(0)

相关推荐

发表回复

登录后才能评论