嵌入式设备采集的数据,变化比较平缓,长时间趋于稳定的状态,其采样结果会有大量连续且相同的数值,对应这些数据,常规的压缩算法在硬件资源有限的嵌入式设备无法运行,代码空间和RAM要求不能满足的情况下,简单有效的行程编码不失为一种最佳选择。
行程编码(Run Length Encoding,RLE), 又称游程编码,主要思路是将一个相同值的连续串用一个代表值和串长来代替。例如字符串“AAABBBBCCCCCJJJJJJ”,可以用“3A4B5C6J”来记录,原本长度18字节的字符串使用8个字节即可无损表达。
为便于直观显示,举例的编码是进行了特殊描述,“3A4B5C6J”实际应该是 3‘A’ 4‘B’ 5‘C’ 6‘J’,字符个数的数字转为字符显示,例如3变成了‘3’显示更易理解。
以下举例也是这个规则,正确方式是应该是以%X查看,如果不明白3和‘3’的区别,请先补习C基础。
行程编码的原理简单,但算法需结合实际,统计数据出现概率与次数,有针对性的改进编码,提高压缩比。
45 FF 41 //行程编码 255 个’A’
45 2D 41 //行程编码 45 个’A’
选择字数据串中出现概率最小的为标记。如纯ASCII字符串(0-0x7F),选择0x80则避开该问题。
#include "stdio.h"
#include "string.h"
//配置为数据源中出现概率最小的数
#define FLAG_ENCODE_HEAD 0x80
void log(char *head,unsigned char *data,unsigned int len)
{
unsigned int i;
printf("%s:",head);
for(i=0;i<len;i++)
{
printf("%02X ",data[i]);
}
printf("\r\n");
}
//行程编码
//输入待编码的数据及长度,提供输出编码数据的缓冲区
//返回编码后的长度
int compress_encode(unsigned char *dect, unsigned char *src, unsigned int len)
{
unsigned char last = 0, count = 0;
unsigned char *dd = dect;
unsigned char flag = 1;
while(len)
{
if(flag)
{
flag = 0;
}
else if(last == FLAG_ENCODE_HEAD)
{
*dect++ = FLAG_ENCODE_HEAD;
*dect++ = 0x00;
}
else if(last == *src)
{
if(count < 0xFF)
{
count++;
}
else
{
*dect++ = FLAG_ENCODE_HEAD;
*dect++ = count;
*dect++ = last;
count = 1;
}
}
else
{
count++;
if(count > 3)
{
*dect++ = FLAG_ENCODE_HEAD;
*dect++ = count;
*dect++ = last;
count = 0;
}
else
{
while(count)
{
*dect++ = last;
count--;
}
}
}
last = *src;
src++;
len--;
if(len == 0)
{
count++;
if(count > 3)
{
*dect++ = FLAG_ENCODE_HEAD;
*dect++ = count;
*dect++ = last;
count = 0;
}
else
{
while(count)
{
if(last == FLAG_ENCODE_HEAD)
{
*dect++ = FLAG_ENCODE_HEAD;
*dect++ = 0x00;
}
else
{
*dect++ = last;
}
count--;
}
}
}
}
return (dect - dd);
}
//解码
//输入待解码的数据及长度,提供输出解码数据的缓冲区
//返回解码后的长度
int compress_decode(unsigned char *dect, unsigned char *src, unsigned int len)
{
unsigned char cc = 0;
unsigned char *dd = dect, *src2 = src;
while(len)
{
if(*src == FLAG_ENCODE_HEAD)
{
if(len == 1 || (src[1] == 0 && len < 2) || (src[1] != 0 && len < 3))
{
return -2;
}
cc = src[1];
if(!cc)
{
*dect++ = FLAG_ENCODE_HEAD;
src += 1;
len -= 1;
}
else
{
while(cc)
{
*dect++ = src[2];
cc--;
}
src += 2;
len -= 2;
}
}
else
{
*dect++ = *src;
}
src++;
len--;
}
return (dect - dd);
}
int main(int argc, char *argv[])
{
unsigned char input[100]={"AAAAAAAAABBBBBBBBBCCCCCCCCCDDDEFGGGGGGGGGGG#"};
//最差情况下编码后的长度是源数据的2倍,这种情况下应该调整编码规则
unsigned char encode_buff[200]={0};
int encode_len;
unsigned char decode_buff[100]={0};
int decode_len;
encode_len=compress_encode(encode_buff,input,strlen((char *)input));
log("encode",encode_buff,encode_len);
printf("len=%d->%d\r\n",strlen((char *)input),encode_len);
decode_len=compress_decode(decode_buff,encode_buff,encode_len);
log("decode",decode_buff,decode_len);
printf("output:[%s] , ratio=%f\r\n",decode_buff,(float)encode_len/decode_len);
if(memcmp(input,decode_buff,decode_len)==0)
{
printf("test ok\r\n");
}
else
{
printf("test fail\r\n");
}
return 0;
}
运行输出
encode:80 09 41 80 09 42 80 09 43 44 44 44 45 46 80 0B 47 23
len=44->18
decode:41 41 41 41 41 41 41 41 41 42 42 42 42 42 42 42 42 42 43 43 43 43 43 43 43 43 43 44 44 44 45 46 47 47 47 47 47 47 47 47 47 47 47 23
output:AAAAAAAAABBBBBBBBBCCCCCCCCCDDDEFGGGGGGGGGGG#] , ratio=0.409091
test ok
游程编码适用于数据本身具有大量连续重复出现的内容,可以结合实际数据源,有针对性的调整编码规则,提高压缩比。