在嵌入式系统中,由于内存和计算资源有限,选择压缩算法时需要考虑以下因素:

  • 内存占用:算法需要尽量少的 RAM 和 ROM。
  • 计算效率:算法需要快速执行,适合嵌入式处理器。
  • 压缩率:在资源限制下尽可能提高压缩率。

1. Run-Length Encoding (RLE)

  • 特点:简单高效,适合数据中有大量重复值的场景。
  • 优点:实现简单,占用极少的内存。
  • 缺点:对随机数据或无重复数据的压缩率较低。

压缩代码示例:

size_t rle_compress(const uint8_t *input, size_t input_size, uint8_t *output, size_t output_size) {
    size_t input_index = 0, output_index = 0;

    while (input_index < input_size) {
        uint8_t current = input[input_index];
        size_t run_length = 1;

        // 计算连续相同字节的长度
        while (input_index + run_length < input_size && input[input_index + run_length] == current && run_length < 255) {
            run_length++;
        }

        // 写入压缩数据 (字节值 + 运行长度)
        if (output_index + 2 > output_size) {
            return 0; // 输出缓冲区不足
        }
        output[output_index++] = current;
        output[output_index++] = run_length;

        input_index += run_length;
    }

    return output_index; // 返回压缩后的数据大小
}

解压缩代码示例:

size_t rle_decompress(const uint8_t *input, size_t input_size, uint8_t *output, size_t output_size) {
    size_t input_index = 0, output_index = 0;

    while (input_index < input_size) {
        if (input_index + 1 >= input_size) {
            // 输入数据格式错误(值和长度必须成对出现)
            return 0;
        }

        uint8_t value = input[input_index++];
        uint8_t run_length = input[input_index++];

        // 检查输出缓冲区是否足够
        if (output_index + run_length > output_size) {
            return 0; // 输出缓冲区不足
        }

        // 填充解压后的数据
        memset(&output[output_index], value, run_length);
        output_index += run_length;
    }

    return output_index; // 返回解压后的数据大小
}

2. Lightweight Entropy Encoding (如 Delta Encoding)

  • 特点:适合传感器数据等连续变化较小的场景。
  • 优点:实现简单,压缩率高。
  • 缺点:仅适用于特定类型的数据。

压缩代码示例:

size_t delta_compress(const uint8_t *input, size_t input_size, uint8_t *output, size_t output_size) {
    if (input_size == 0) return 0;

    size_t output_index = 0;
    uint8_t prev = input[0];

    // 写入第一个字节
    if (output_index + 1 > output_size) return 0;
    output[output_index++] = prev;

    for (size_t i = 1; i < input_size; i++) {
        int8_t delta = input[i] - prev;
        prev = input[i];

        // 写入差值
        if (output_index + 1 > output_size) return 0;
        output[output_index++] = (uint8_t)delta;
    }

    return output_index; // 返回压缩后的数据大小
}

解压缩代码示例:

size_t delta_decompress(const uint8_t *input, size_t input_size, uint8_t *output, size_t output_size) {
    if (input_size == 0 || output_size == 0) {
        return 0; // 输入或输出缓冲区为空
    }

    size_t output_index = 0;

    // 第一个字节直接复制
    output[output_index++] = input[0];

    for (size_t i = 1; i < input_size; i++) {
        if (output_index >= output_size) {
            return 0; // 输出缓冲区不足
        }

        // 当前值 = 前一个值 + 差值
        output[output_index] = output[output_index - 1] + (int8_t)input[i];
        output_index++;
    }

    return output_index; // 返回解压后的数据大小
}

3.两种解码示例

   3.1 RLE 解码
uint8_t compressed_rle[] = {0xAA, 5, 0xBB, 3}; // 压缩数据:0xAA 重复 5 次,0xBB 重复 3 次
uint8_t decompressed_rle[10];
size_t decompressed_size_rle = rle_decompress(compressed_rle, sizeof(compressed_rle), decompressed_rle, sizeof(decompressed_rle));
// 解压后:decompressed_rle = {0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xBB, 0xBB, 0xBB}
   3.2 Delta 解码
uint8_t compressed_delta[] = {0x10, 2, -1, 3}; // 压缩数据:第一个值为 0x10,后续为差值
uint8_t decompressed_delta[10];
size_t decompressed_size_delta = delta_decompress(compressed_delta, sizeof(compressed_delta), decompressed_delta, sizeof(decompressed_delta));
// 解压后:decompressed_delta = {0x10, 0x12, 0x11, 0x14}

4.注意事项

  1. RLE 解码

    • 输入数据必须是 (值, 长度) 成对出现。
    • 输出缓冲区必须足够大以容纳解压后的数据。
  2. Delta 解码

    • 输入的第一个字节是原始数据的起始值。
    • 后续字节是差值,可能为负数(需要用 int8_t 处理)。

这两个解码函数都适合嵌入式系统,内存占用小,计算效率高,使用时要明确自己的数据是否适合这样的压缩算法,否则可能会让数据得不到压缩,反而会是数据量变大。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐