You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ESP32上使用miniz的tinfl_decompress处理流式解压的问题

关于miniz库tinfl_decompress流式解压的问题及解决方案

我在ESP32上使用miniz库的tinfl_decompress函数处理网络流式压缩数据,函数签名如下:

tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags);

为适配流式场景做了如下设置:

  • 输出缓冲区pOut_buf_start初始化大小为TINFL_LZ_DICT_SIZE
  • 每次仅向输入缓冲区传入最多512字节数据,避免一次性生成过多解压数据

但在pOut_buf_next接近输出缓冲区末尾时遇到问题:

  1. 继续解压会触发TINFL_STATUS_HAS_MORE_OUTPUT状态,该状态是否真的无法恢复?
  2. 将输出缓冲区回绕(设置pOut_buf_next = pOut_buf_start,pOut_buf_size=TINFL_LZ_DICT_SIZE)后,下一次调用tinfl_decompress会导致输出损坏。

核心原因

tinfl_decompress依赖输出缓冲区的历史数据完成LZ77格式解压(压缩算法会引用之前已解压的数据块)。直接回绕覆盖旧数据会破坏解压器需要的历史字典,导致输出损坏。而TINFL_STATUS_HAS_MORE_OUTPUT并非无法恢复,它只是提示当前输出缓冲区剩余空间不足,需处理已输出数据后继续。

正确处理流程

1. 处理TINFL_STATUS_HAS_MORE_OUTPUT状态

  • 触发该状态时立即停止当前解压调用
  • 取出pOut_buf_next到pOut_buf_start + TINFL_LZ_DICT_SIZE之间的已解压数据(如发送到应用层、写入存储)
  • 保留解压器需要的历史数据,而非直接回绕:
    • 计算剩余需保留的历史数据长度:TINFL_LZ_DICT_SIZE - (pOut_buf_next - pOut_buf_start)
    • 将缓冲区末尾的历史数据移动到起始位置
    • 更新pOut_buf_next为起始位置加上剩余历史数据的长度
    • 更新pOut_buf_size为TINFL_LZ_DICT_SIZE - (pOut_buf_next - pOut_buf_start)(即剩余可用空间)

2. 允许回绕输出缓冲区的时机

只有当当前解压上下文不再需要之前的历史数据时,才能完全回绕缓冲区:

  • 整个压缩数据流解压完成(返回TINFL_STATUS_DONE)
  • 切换到全新的压缩数据流(需重新初始化tinfl_decompressor结构体)

3. 流式解压循环示例

// 初始化解压上下文
tinfl_decompressor decomp_ctx;
memset(&decomp_ctx, 0, sizeof(decomp_ctx));

// 输出缓冲区(大小为TINFL_LZ_DICT_SIZE)
uint8_t out_buf[TINFL_LZ_DICT_SIZE];
uint8_t *out_next = out_buf;
size_t out_size = sizeof(out_buf);

// 输入缓冲区(每次最多512字节)
uint8_t in_buf[512];
size_t in_size;
const uint8_t *in_next;

while (/* 还有网络数据可读 */) {
    // 读取网络数据到in_buf,获取实际读取长度in_size
    in_size = read_network_data(in_buf, sizeof(in_buf));
    in_next = in_buf;

    while (in_size > 0) {
        mz_uint32 status = tinfl_decompress(&decomp_ctx, in_next, &in_size, out_buf, out_next, &out_size, TINFL_FLAG_PARSE_ZLIB_HEADER);

        if (status == TINFL_STATUS_HAS_MORE_OUTPUT) {
            // 取出已解压数据:out_next - out_buf 字节
            process_decompressed_data(out_buf, out_next - out_buf);

            // 保留历史数据,移动缓冲区内容
            size_t history_len = out_next - out_buf;
            if (history_len > 0) {
                memmove(out_buf, out_next, sizeof(out_buf) - history_len);
            }
            out_next = out_buf + (sizeof(out_buf) - history_len);
            out_size = history_len; // 剩余可用空间为原已使用长度
        } else if (status == TINFL_STATUS_DONE) {
            // 处理剩余已解压数据
            process_decompressed_data(out_buf, out_next - out_buf);
            // 当前数据流完成,可重置输出缓冲区
            out_next = out_buf;
            out_size = sizeof(out_buf);
            break;
        } else if (status != TINFL_STATUS_OKAY) {
            // 处理解压错误
            handle_decompress_error(status);
            goto exit_loop;
        } else {
            // TINFL_STATUS_OKAY:输入数据处理完毕,读取下一批网络数据
            out_next += (sizeof(out_buf) - out_size);
            out_size = sizeof(out_buf) - (out_next - out_buf);
            break;
        }
    }
}
exit_loop:
// 清理资源

关键注意事项

  • 永远不要丢弃解压器依赖的历史数据,miniz的LZ解压最多需要TINFL_LZ_DICT_SIZE字节的历史数据还原内容
  • 处理TINFL_STATUS_HAS_MORE_OUTPUT时,仅移动数据而非覆盖,确保解压上下文能访问到所需历史字典
  • 处理多个独立压缩数据流时,每次新流开始前必须重新初始化tinfl_decompressor结构体,此时可安全回绕输出缓冲区

内容的提问来源于stack exchange,提问作者Nitek

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 19:41:14