转:
一. 前言
在嵌入式项目中,有时候需要进行压缩解压,此时希望能在代码中简单的方式就能嵌入压缩解压算法库,并且关注代码大小,RAM需求,执行速度和使用方便性等。这里就推荐一个知名的开源压缩库miniz。
Miniz 是一个仅单源文件就实现无损、高性能的数据压缩库。遵循 zlib(RFC 1950)和 Deflate(RFC 1951)压缩数据格式规范标准。支持 zlib 库中{BANNED}最佳常用的导出函数,且是完全独立的实现,因此不受 zlib 许可条款的约束。Miniz 还包含用于写入 .PNG 格式图像文件以及读取/写入/追加 .ZIP 格式存档的简单易用的函数。Miniz 的压缩速度经过优化,可与 zlib 相媲美,并且它还具有专门的实时压缩器函数,可与 fastlz/minilzo 相媲美。
Miniz具备以下特点
lMIT 许可证。
l纯 C 语言编写,单c./h文件库。已在 GCC、Clang 和 Visual Studio 中测试。
l通过宏定义轻松调整和裁剪。
l可直接替代 zlib {BANNED}最佳常用的 API(已在多个使用 zlib 的开源项目中测试,例如 libpng 和 libzip)。
l填补了 zlib 与几个流行的实时压缩器在单线程性能与压缩比之间的差距。例如,在级别 1 时,miniz.c 的压缩比比 minilzo 高约 5% - 9%,但速度慢约 35%。在级别 2 - 9 时,miniz.c 的设计旨在在压缩比和速度方面优于 zlib。性能对比见:
l不是基于块的压缩器:miniz.c 完全支持使用协程式实现的流式处理。如果只有单个字节,也可以逐字节调用 zlib 风格的 API 函数。
l易于使用。底层压缩器(tdefl)和解压缩器(tinfl)具有简单的状态结构,可根据需要通过简单的 memcpy 进行保存/恢复。底层编解码器 API 完全不使用堆。
l整个inflater (包括可选的 zlib 头解析和 Adler-32 校验)在一个单独的协程函数中实现,该函数单独存在于一个小型(约 550 行)的源文件中:miniz_tinfl.c
l一套相当完整的(但完全可选的).ZIP 存档操作和提取 API。存档功能旨在解决嵌入式、移动或游戏开发环境中常见的问题。(存档 API 有意设计得足够强大,只需添加一些额外的高级逻辑即可编写整个存档器。)
l不支持加密归档。
l目前没有什么文档熟悉基本的zlib API则容易入手。可以参考代码前的注释,以及examples程序。
二. 源码
Release下下载的文件源码已经组合成miniz.c/miniz.h
miniz.c/miniz.h,添加到自己的项目即可。
Git直接clone的代码
git clone
如下
执行./amalgamate.sh可以使用命令生成miniz.c和miniz.h
即将各种文件放在一起。类似于sqlite的做法。生成的位于amalgamation下,和release下载的就是一样的了。
三. 嵌入式平台适配
miniz.h中
如果有标准库的堆管理直接使用
没有就需要替换以下接口
{BANNED}最佳好是专门实现miniz_port.c/h去实现相关需要配置的结果。
如果只需要基于buffer的压缩解压只需要适配这几个接口即可。
如果还需要进行文件的解压压缩操作则还需要实现对应的接口。
四. 测试
复制miniz.c和mniz.h到自己的项目。
添加miniz_test.c内容如下
点击(此处)折叠或打开
-
#include
-
#include
-
#include "miniz.h"
-
#include "miniz_port.h" // The string to compress.
-
static const char *s_pStr = "Good morning Dr. Chandra. This is Hal. I am ready for my first lesson." \
-
"Good morning Dr. Chandra. This is Hal. I am ready for my first lesson." \
-
"Good morning Dr. Chandra. This is Hal. I am ready for my first lesson."\
-
"Good morning Dr. Chandra. This is Hal. I am ready for my first lesson." \
-
"Good morning Dr. Chandra. This is Hal. I am ready for my first lesson."\
-
"Good morning Dr. Chandra. This is Hal. I am ready for my first lesson." \
-
"Good morning Dr. Chandra. This is Hal. I am ready for my first lesson.";
-
int miniz_test(int argc, char *argv[]);
-
int miniz_test(int argc, char *argv[])
-
{
-
uint32_t step = 0;
-
int cmp_status;
-
uLong src_len = (uLong)strlen(s_pStr);
-
uLong cmp_len = compressBound(src_len);
-
uLong uncomp_len = src_len;
-
uint8_t *pCmp, *pUncomp;
-
uint32_t total_succeeded = 0;
-
(void)argc, (void)argv;
-
printf("miniz.c version: %s\n", MZ_VERSION);
-
do { // Allocate buffers to hold compressed and uncompressed data.
-
pCmp = (mz_uint8 *)miniz_port_malloc(0, (size_t)cmp_len);
-
pUncomp = (mz_uint8 *)miniz_port_malloc(0, (size_t)src_len);
-
if ((!pCmp) || (!pUncomp)) {
-
printf("Out of memory!\n"); // Compress the string.
-
return EXIT_FAILURE;
-
}
-
cmp_status = compress(pCmp, &cmp_len, (const unsigned char *)s_pStr, src_len);
-
if (cmp_status != Z_OK) {
-
printf("compress() failed!\n");
-
miniz_port_free(pCmp);
-
miniz_port_free(pUncomp);
-
return EXIT_FAILURE;
-
}
-
printf("Compressed from %u to %u bytes\n", (mz_uint32)src_len, (mz_uint32)cmp_len);
-
if (step) { // Purposely corrupt the compressed data if fuzzy testing (this is a very crude fuzzy test). uint3
-
2_t n = 1 + (rand() % 3);
-
while (n--) {
-
uint32_t i = rand() % cmp_len;
-
pCmp[i] ^= (rand() & 0xFF);
-
}
-
} // Decompress.
-
cmp_status = uncompress(pUncomp, &uncomp_len, pCmp, cmp_len);
-
total_succeeded += (cmp_status == Z_OK);
-
if (step) {
-
printf("Simple fuzzy test: step %u total_succeeded: %u\n", step, total_succeeded);
-
} else {
-
if (cmp_status != Z_OK) {
-
printf("uncompress failed!\n");
-
miniz_port_free(pCmp);
-
miniz_port_free(pUncomp);
-
return EXIT_FAILURE;
-
}
-
printf("Decompressed from %u to %u bytes\n", (mz_uint32)cmp_len, (mz_uint32)uncomp_len); // Ensure uncompress() returned the expected data.
-
if ((uncomp_len != src_len) || (memcmp(pUncomp, s_pStr, (size_t)src_len))) {
-
printf("Decompression failed!\n");
-
miniz_port_free(pCmp);
-
miniz_port_free(pUncomp);
-
return EXIT_FAILURE;
-
}
-
}
-
miniz_port_free(pCmp);
-
miniz_port_free(pUncomp);
-
step++; // Keep on fuzzy testing if there's a non-empty command line.
-
} while (argc >= 2);
-
printf("Success.\n");
-
return EXIT_SUCCESS;
- }
根据实际替换malloc和free和printf等函数。我这里专门实现了miniz_port.c/h去做。
调用
miniz_test(0,0);
看到打印如下
五. 资源消耗
主要关注程序大小和RAM占用
我们通过malloc/free接口记录下{BANNED}最佳大堆使用量并打印出来,可以看到压缩大概需要320KB动态内存。
miniz_port.c如下
点击(此处)折叠或打开
-
#include
-
#include
-
#include "miniz_port.h"
-
static size_t s_max_used = 0;
-
static size_t s_cur_used = 0;
-
void *miniz_port_malloc(size_t size)
-
{
-
void *p = malloc(size + 4); /* malloc默认配置是8字节对齐,所以预留的4字节强制类型转换不会有对齐问题 */
-
memset(p, 0, size);
-
*(uint32_t *)p = size; /* 记录分配的大小 用于调试使用*/
-
s_cur_used += size;
-
if (s_max_used < s_cur_used) {
-
s_max_used = s_cur_used;
-
}
-
printf("malloc:max:%u,cur:%u\r\n", s_max_used, s_cur_used);
-
return (uint8_t *)p + 4;
-
}
-
void miniz_port_free(void *ptr)
-
{
-
free((uint8_t *)ptr - 4);
-
s_cur_used -= *(uint32_t *)((uint8_t *)ptr - 4);
-
printf("free:max:%u,cur:%u\r\n", s_max_used, s_cur_used);
-
}
-
void *miniz_port_realloc(void *ptr, size_t size)
-
{
-
uint32_t len;
-
void *p = malloc(0, size + 4);
-
*(uint32_t *)p = size; /* 记录分配的大小 */
-
s_cur_used += size;
-
if (s_max_used < s_cur_used) {
-
s_max_used = s_cur_used;
-
}
-
len = *(uint32_t *)((uint8_t *)ptr - 4);
-
memcpy((uint8_t *)p + 4, ptr, len);
-
free((uint8_t *)ptr - 4);
-
s_cur_used -= len;
-
printf("realloc:max:%u,cur:%u\r\n", s_max_used, s_cur_used);
-
return (uint8_t *)p + 4;
- }
上述测试代码在某个嵌入式平台编译看到rom空间如下
rodata大概2KB
text大概16K
六. 推荐阅读
七. 总结
通过上述测试可以看到miniz ROM大概是需要20KB左右,RAM需要320KB左右,所以不太适合MCU平台,比较适合资源丰富点的SOC/MPU,或者有扩展RAM的MCU平台。后面我们再继续推荐适合MCU平台使用的压缩算法库。
miniz的代码全部放在了一起,包括不同平台的适配也是通过宏去实现,都在miniz.c中,对于非linux和windows等嵌入式平台其实移植性不是很好,{BANNED}最佳好是对平台依赖接口,比如堆管理,时间接口,文件操作接口等单独剥离出来通过port文件移植实现{BANNED}最佳好。这样miniz.c里面不应该再出现和平台相关的代码和宏等,全部由port去适配。