Implemented streaming Huffman compression in chunks of 256 bytes
This commit is contained in:
parent
213d9d976d
commit
b0cc2b670d
|
@ -7,6 +7,8 @@ COMMON_SRC = \
|
||||||
common/bitarray.c \
|
common/bitarray.c \
|
||||||
common/encoding.c \
|
common/encoding.c \
|
||||||
common/filter.c \
|
common/filter.c \
|
||||||
|
common/huffman.c \
|
||||||
|
common/huffman_table.c \
|
||||||
common/maths.c \
|
common/maths.c \
|
||||||
common/printf.c \
|
common/printf.c \
|
||||||
common/streambuf.c \
|
common/streambuf.c \
|
||||||
|
|
|
@ -18,6 +18,10 @@
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "platform.h"
|
||||||
|
|
||||||
|
#ifdef USE_HUFFMAN
|
||||||
|
|
||||||
#include "huffman.h"
|
#include "huffman.h"
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,15 +32,18 @@ int huffmanEncodeBuf(uint8_t *outBuf, int outBufLen, const uint8_t *inBuf, int i
|
||||||
uint8_t *outByte = outBuf;
|
uint8_t *outByte = outBuf;
|
||||||
*outByte = 0;
|
*outByte = 0;
|
||||||
uint8_t outBit = 0x80;
|
uint8_t outBit = 0x80;
|
||||||
|
|
||||||
for (int ii = 0; ii < inLen; ++ii) {
|
for (int ii = 0; ii < inLen; ++ii) {
|
||||||
const int huffCodeLen = huffmanTable[*inBuf].codeLen;
|
const int huffCodeLen = huffmanTable[*inBuf].codeLen;
|
||||||
const uint16_t huffCode = huffmanTable[*inBuf].code;
|
const uint16_t huffCode = huffmanTable[*inBuf].code;
|
||||||
++inBuf;
|
++inBuf;
|
||||||
uint16_t testBit = 0x8000;
|
uint16_t testBit = 0x8000;
|
||||||
|
|
||||||
for (int jj = 0; jj < huffCodeLen; ++jj) {
|
for (int jj = 0; jj < huffCodeLen; ++jj) {
|
||||||
if (huffCode & testBit) {
|
if (huffCode & testBit) {
|
||||||
*outByte |= outBit;
|
*outByte |= outBit;
|
||||||
}
|
}
|
||||||
|
|
||||||
testBit >>= 1;
|
testBit >>= 1;
|
||||||
outBit >>= 1;
|
outBit >>= 1;
|
||||||
if (outBit == 0) {
|
if (outBit == 0) {
|
||||||
|
@ -45,7 +52,8 @@ int huffmanEncodeBuf(uint8_t *outBuf, int outBufLen, const uint8_t *inBuf, int i
|
||||||
*outByte = 0;
|
*outByte = 0;
|
||||||
++ret;
|
++ret;
|
||||||
}
|
}
|
||||||
if (ret >= outBufLen) {
|
|
||||||
|
if (ret >= outBufLen && ii < inLen - 1 && jj < huffCodeLen - 1) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -57,3 +65,40 @@ int huffmanEncodeBuf(uint8_t *outBuf, int outBufLen, const uint8_t *inBuf, int i
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int huffmanEncodeBufStreaming(huffmanState_t *state, const uint8_t *inBuf, int inLen, const huffmanTable_t *huffmanTable)
|
||||||
|
{
|
||||||
|
uint8_t *savedOutBytePtr = state->outByte;
|
||||||
|
uint8_t savedOutByte = *savedOutBytePtr;
|
||||||
|
|
||||||
|
for (const uint8_t *pos = inBuf, *end = inBuf + inLen; pos < end; ++pos) {
|
||||||
|
const int huffCodeLen = huffmanTable[*pos].codeLen;
|
||||||
|
const uint16_t huffCode = huffmanTable[*pos].code;
|
||||||
|
uint16_t testBit = 0x8000;
|
||||||
|
|
||||||
|
for (int jj = 0; jj < huffCodeLen; ++jj) {
|
||||||
|
if (huffCode & testBit) {
|
||||||
|
*state->outByte |= state->outBit;
|
||||||
|
}
|
||||||
|
|
||||||
|
testBit >>= 1;
|
||||||
|
state->outBit >>= 1;
|
||||||
|
if (state->outBit == 0) {
|
||||||
|
state->outBit = 0x80;
|
||||||
|
++state->outByte;
|
||||||
|
*state->outByte = 0;
|
||||||
|
++state->bytesWritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if buffer is filled and we haven't finished compressing
|
||||||
|
if (state->bytesWritten >= state->outBufLen && (pos < end - 1 || jj < huffCodeLen - 1)) {
|
||||||
|
// restore savedOutByte
|
||||||
|
*savedOutBytePtr = savedOutByte;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
|
@ -25,6 +25,20 @@ typedef struct huffmanTable_s {
|
||||||
uint16_t code;
|
uint16_t code;
|
||||||
} huffmanTable_t;
|
} huffmanTable_t;
|
||||||
|
|
||||||
|
typedef struct huffmanState_s {
|
||||||
|
uint16_t bytesWritten;
|
||||||
|
uint8_t *outByte;
|
||||||
|
uint16_t outBufLen;
|
||||||
|
uint8_t outBit;
|
||||||
|
} huffmanState_t;
|
||||||
|
|
||||||
extern const huffmanTable_t huffmanTable[HUFFMAN_TABLE_SIZE];
|
extern const huffmanTable_t huffmanTable[HUFFMAN_TABLE_SIZE];
|
||||||
|
|
||||||
|
struct huffmanInfo_s {
|
||||||
|
uint16_t uncompressedByteCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
#define HUFFMAN_INFO_SIZE sizeof(struct huffmanInfo_s)
|
||||||
|
|
||||||
int huffmanEncodeBuf(uint8_t *outBuf, int outBufLen, const uint8_t *inBuf, int inLen, const huffmanTable_t *huffmanTable);
|
int huffmanEncodeBuf(uint8_t *outBuf, int outBufLen, const uint8_t *inBuf, int inLen, const huffmanTable_t *huffmanTable);
|
||||||
|
int huffmanEncodeBufStreaming(huffmanState_t *state, const uint8_t *inBuf, int inLen, const huffmanTable_t *huffmanTable);
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
#include "common/color.h"
|
#include "common/color.h"
|
||||||
#include "common/maths.h"
|
#include "common/maths.h"
|
||||||
#include "common/streambuf.h"
|
#include "common/streambuf.h"
|
||||||
|
#include "common/huffman.h"
|
||||||
|
|
||||||
#include "config/config_eeprom.h"
|
#include "config/config_eeprom.h"
|
||||||
#include "config/feature.h"
|
#include "config/feature.h"
|
||||||
|
@ -287,7 +288,12 @@ static void serializeDataflashSummaryReply(sbuf_t *dst)
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef USE_FLASHFS
|
#ifdef USE_FLASHFS
|
||||||
static void serializeDataflashReadReply(sbuf_t *dst, uint32_t address, const uint16_t size, bool useLegacyFormat)
|
enum compressionType_e {
|
||||||
|
NO_COMPRESSION,
|
||||||
|
HUFFMAN
|
||||||
|
};
|
||||||
|
|
||||||
|
static void serializeDataflashReadReply(sbuf_t *dst, uint32_t address, const uint16_t size, bool useLegacyFormat, bool allowCompression)
|
||||||
{
|
{
|
||||||
BUILD_BUG_ON(MSP_PORT_DATAFLASH_INFO_SIZE < 16);
|
BUILD_BUG_ON(MSP_PORT_DATAFLASH_INFO_SIZE < 16);
|
||||||
|
|
||||||
|
@ -297,19 +303,25 @@ static void serializeDataflashReadReply(sbuf_t *dst, uint32_t address, const uin
|
||||||
readLen = bytesRemainingInBuf;
|
readLen = bytesRemainingInBuf;
|
||||||
}
|
}
|
||||||
// size will be lower than that requested if we reach end of volume
|
// size will be lower than that requested if we reach end of volume
|
||||||
if (readLen > flashfsGetSize() - address) {
|
const uint32_t flashfsSize = flashfsGetSize();
|
||||||
|
if (readLen > flashfsSize - address) {
|
||||||
// truncate the request
|
// truncate the request
|
||||||
readLen = flashfsGetSize() - address;
|
readLen = flashfsSize - address;
|
||||||
}
|
}
|
||||||
sbufWriteU32(dst, address);
|
sbufWriteU32(dst, address);
|
||||||
|
|
||||||
|
// legacy format does not support compression
|
||||||
|
const uint8_t compressionMethod = (!allowCompression || useLegacyFormat) ? NO_COMPRESSION : HUFFMAN;
|
||||||
|
|
||||||
|
if (compressionMethod == NO_COMPRESSION) {
|
||||||
if (!useLegacyFormat) {
|
if (!useLegacyFormat) {
|
||||||
// new format supports variable read lengths
|
// new format supports variable read lengths
|
||||||
sbufWriteU16(dst, readLen);
|
sbufWriteU16(dst, readLen);
|
||||||
sbufWriteU8(dst, 0); // placeholder for compression format
|
sbufWriteU8(dst, 0); // placeholder for compression format
|
||||||
}
|
}
|
||||||
|
|
||||||
// bytesRead will equal readLen
|
|
||||||
const int bytesRead = flashfsReadAbs(address, sbufPtr(dst), readLen);
|
const int bytesRead = flashfsReadAbs(address, sbufPtr(dst), readLen);
|
||||||
|
|
||||||
sbufAdvance(dst, bytesRead);
|
sbufAdvance(dst, bytesRead);
|
||||||
|
|
||||||
if (useLegacyFormat) {
|
if (useLegacyFormat) {
|
||||||
|
@ -318,6 +330,47 @@ static void serializeDataflashReadReply(sbuf_t *dst, uint32_t address, const uin
|
||||||
sbufWriteU8(dst, 0);
|
sbufWriteU8(dst, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
#ifdef USE_HUFFMAN
|
||||||
|
// compress in 256-byte chunks
|
||||||
|
const uint16_t READ_BUFFER_SIZE = 256;
|
||||||
|
uint8_t readBuffer[READ_BUFFER_SIZE];
|
||||||
|
|
||||||
|
huffmanState_t state = {
|
||||||
|
.bytesWritten = 0,
|
||||||
|
.outByte = sbufPtr(dst) + MSP_PORT_DATAFLASH_INFO_SIZE + HUFFMAN_INFO_SIZE,
|
||||||
|
.outBufLen = readLen - HUFFMAN_INFO_SIZE,
|
||||||
|
.outBit = 0x80,
|
||||||
|
};
|
||||||
|
*state.outByte = 0;
|
||||||
|
|
||||||
|
uint16_t bytesReadTotal = 0;
|
||||||
|
// read until output buffer overflows or flash is exhausted
|
||||||
|
while (state.bytesWritten < state.outBufLen && address + bytesReadTotal < flashfsSize) {
|
||||||
|
const int bytesRead = flashfsReadAbs(address + bytesReadTotal, readBuffer,
|
||||||
|
MIN(sizeof(readBuffer), flashfsSize - address - bytesReadTotal));
|
||||||
|
|
||||||
|
const int status = huffmanEncodeBufStreaming(&state, readBuffer, bytesRead, huffmanTable);
|
||||||
|
if (status == -1) {
|
||||||
|
// overflow
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesReadTotal += bytesRead;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.outBit != 0x80) {
|
||||||
|
++state.bytesWritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
// header
|
||||||
|
sbufWriteU16(dst, sizeof(uint16_t) + state.bytesWritten);
|
||||||
|
sbufWriteU8(dst, compressionMethod);
|
||||||
|
// payload
|
||||||
|
sbufWriteU16(dst, bytesReadTotal);
|
||||||
|
sbufAdvance(dst, state.bytesWritten);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif // USE_FLASHFS
|
#endif // USE_FLASHFS
|
||||||
#endif // USE_OSD_SLAVE
|
#endif // USE_OSD_SLAVE
|
||||||
|
@ -1186,16 +1239,20 @@ static void mspFcDataFlashReadCommand(sbuf_t *dst, sbuf_t *src)
|
||||||
const unsigned int dataSize = sbufBytesRemaining(src);
|
const unsigned int dataSize = sbufBytesRemaining(src);
|
||||||
const uint32_t readAddress = sbufReadU32(src);
|
const uint32_t readAddress = sbufReadU32(src);
|
||||||
uint16_t readLength;
|
uint16_t readLength;
|
||||||
|
bool allowCompression = false;
|
||||||
bool useLegacyFormat;
|
bool useLegacyFormat;
|
||||||
if (dataSize >= sizeof(uint32_t) + sizeof(uint16_t)) {
|
if (dataSize >= sizeof(uint32_t) + sizeof(uint16_t)) {
|
||||||
readLength = sbufReadU16(src);
|
readLength = sbufReadU16(src);
|
||||||
|
if (sbufBytesRemaining(src)) {
|
||||||
|
allowCompression = sbufReadU8(src);
|
||||||
|
}
|
||||||
useLegacyFormat = false;
|
useLegacyFormat = false;
|
||||||
} else {
|
} else {
|
||||||
readLength = 128;
|
readLength = 128;
|
||||||
useLegacyFormat = true;
|
useLegacyFormat = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
serializeDataflashReadReply(dst, readAddress, readLength, useLegacyFormat);
|
serializeDataflashReadReply(dst, readAddress, readLength, useLegacyFormat, allowCompression);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -56,12 +56,11 @@
|
||||||
//#define SD_CS_PIN PB12
|
//#define SD_CS_PIN PB12
|
||||||
//#define SD_SPI_INSTANCE SPI2
|
//#define SD_SPI_INSTANCE SPI2
|
||||||
|
|
||||||
//#define USE_FLASHFS
|
#define USE_FLASHFS
|
||||||
//#define USE_FLASH_M25P16
|
#define USE_FLASH_M25P16
|
||||||
|
|
||||||
//#define M25P16_CS_GPIO GPIOB
|
#define M25P16_CS_PIN PB12
|
||||||
//#define M25P16_CS_PIN GPIO_Pin_12
|
#define M25P16_SPI_INSTANCE SPI2
|
||||||
//#define M25P16_SPI_INSTANCE SPI2
|
|
||||||
// SPI1
|
// SPI1
|
||||||
// PB5 SPI1_MOSI
|
// PB5 SPI1_MOSI
|
||||||
// PB4 SPI1_MISO
|
// PB4 SPI1_MISO
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
F3_TARGETS += $(TARGET)
|
F3_TARGETS += $(TARGET)
|
||||||
FEATURES = VCP SDCARD
|
FEATURES = VCP SDCARD ONBOARDFLASH
|
||||||
|
|
||||||
TARGET_SRC = \
|
TARGET_SRC = \
|
||||||
drivers/accgyro/accgyro_adxl345.c \
|
drivers/accgyro/accgyro_adxl345.c \
|
||||||
|
|
|
@ -125,6 +125,7 @@
|
||||||
#define VTX_SMARTAUDIO
|
#define VTX_SMARTAUDIO
|
||||||
#define VTX_TRAMP
|
#define VTX_TRAMP
|
||||||
#define USE_CAMERA_CONTROL
|
#define USE_CAMERA_CONTROL
|
||||||
|
#define USE_HUFFMAN
|
||||||
|
|
||||||
#ifdef USE_SERIALRX_SPEKTRUM
|
#ifdef USE_SERIALRX_SPEKTRUM
|
||||||
#define USE_SPEKTRUM_BIND
|
#define USE_SPEKTRUM_BIND
|
||||||
|
|
|
@ -235,6 +235,13 @@ rcsplit_unitest_DEFINES := \
|
||||||
USE_UART3 \
|
USE_UART3 \
|
||||||
USE_RCSPLIT \
|
USE_RCSPLIT \
|
||||||
|
|
||||||
|
huffman_unittest_SRC := \
|
||||||
|
$(USER_DIR)/common/huffman.c \
|
||||||
|
$(USER_DIR)/common/huffman_table.c
|
||||||
|
|
||||||
|
huffman_unittest_DEFINES := \
|
||||||
|
USE_HUFFMAN
|
||||||
|
|
||||||
# Please tweak the following variable definitions as needed by your
|
# Please tweak the following variable definitions as needed by your
|
||||||
# project, except GTEST_HEADERS, which you can use in your own targets
|
# project, except GTEST_HEADERS, which you can use in your own targets
|
||||||
# but shouldn't modify.
|
# but shouldn't modify.
|
||||||
|
|
|
@ -417,6 +417,92 @@ TEST(HuffmanUnittest, TestHuffmanEncode)
|
||||||
EXPECT_EQ(0xd8, (int)outBuf[4]);
|
EXPECT_EQ(0xd8, (int)outBuf[4]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(HuffmanUnittest, TestHuffmanEncodeStreaming)
|
||||||
|
{
|
||||||
|
#define INBUF_LEN1 3
|
||||||
|
#define INBUF_LEN1_CHUNK1 2
|
||||||
|
#define INBUF_LEN1_CHUNK2 (INBUF_LEN1 - INBUF_LEN1_CHUNK1)
|
||||||
|
const uint8_t inBuf1[INBUF_LEN1] = {0,1,1};
|
||||||
|
// 11 101 101
|
||||||
|
// 1110 1101
|
||||||
|
// e d
|
||||||
|
huffmanState_t state1 = {
|
||||||
|
.bytesWritten = 0,
|
||||||
|
.outByte = outBuf,
|
||||||
|
.outBufLen = OUTBUF_LEN,
|
||||||
|
.outBit = 0x80,
|
||||||
|
};
|
||||||
|
*state1.outByte = 0;
|
||||||
|
int status = huffmanEncodeBufStreaming(&state1, inBuf1, INBUF_LEN1_CHUNK1, huffmanTable);
|
||||||
|
EXPECT_EQ(0, status);
|
||||||
|
status = huffmanEncodeBufStreaming(&state1, inBuf1 + INBUF_LEN1_CHUNK1, INBUF_LEN1_CHUNK2, huffmanTable);
|
||||||
|
EXPECT_EQ(0, status);
|
||||||
|
if (state1.outBit != 0x80) {
|
||||||
|
++state1.bytesWritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_EQ(1, state1.bytesWritten);
|
||||||
|
EXPECT_EQ(0xed, (int)outBuf[0]);
|
||||||
|
|
||||||
|
#define INBUF_LEN2 4
|
||||||
|
#define INBUF_LEN2_CHUNK1 1
|
||||||
|
#define INBUF_LEN2_CHUNK2 1
|
||||||
|
#define INBUF_LEN2_CHUNK3 2
|
||||||
|
const uint8_t inBuf2[INBUF_LEN2] = {0,1,2,3};
|
||||||
|
// 11 101 1001 10001
|
||||||
|
// 1110 1100 1100 01
|
||||||
|
// e c c 8
|
||||||
|
huffmanState_t state2 = {
|
||||||
|
.bytesWritten = 0,
|
||||||
|
.outByte = outBuf,
|
||||||
|
.outBufLen = OUTBUF_LEN,
|
||||||
|
.outBit = 0x80,
|
||||||
|
};
|
||||||
|
*state2.outByte = 0;
|
||||||
|
status = huffmanEncodeBufStreaming(&state2, inBuf2, INBUF_LEN2_CHUNK1, huffmanTable);
|
||||||
|
EXPECT_EQ(0, status);
|
||||||
|
status = huffmanEncodeBufStreaming(&state2, inBuf2 + INBUF_LEN2_CHUNK1, INBUF_LEN2_CHUNK2, huffmanTable);
|
||||||
|
EXPECT_EQ(0, status);
|
||||||
|
status = huffmanEncodeBufStreaming(&state2, inBuf2 + INBUF_LEN2_CHUNK1 + INBUF_LEN2_CHUNK2, INBUF_LEN2_CHUNK3, huffmanTable);
|
||||||
|
EXPECT_EQ(0, status);
|
||||||
|
if (state2.outBit != 0x80) {
|
||||||
|
++state2.bytesWritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_EQ(2, state2.bytesWritten);
|
||||||
|
EXPECT_EQ(0xec, (int)outBuf[0]);
|
||||||
|
EXPECT_EQ(0xc4, (int)outBuf[1]);
|
||||||
|
|
||||||
|
#define INBUF_LEN3 8
|
||||||
|
#define INBUF_LEN3_CHUNK1 4
|
||||||
|
#define INBUF_LEN3_CHUNK2 (INBUF_LEN3 - INBUF_LEN3_CHUNK1)
|
||||||
|
const uint8_t inBuf3[INBUF_LEN3] = {0,1,2,3,4,5,6,7};
|
||||||
|
// 11 101 1001 10001 10000 011101 011100 011011
|
||||||
|
// 1110 1100 1100 0110 0000 1110 1011 1000 1101 1
|
||||||
|
// e c c 6 0 e b 8 d 8
|
||||||
|
huffmanState_t state3 = {
|
||||||
|
.bytesWritten = 0,
|
||||||
|
.outByte = outBuf,
|
||||||
|
.outBufLen = OUTBUF_LEN,
|
||||||
|
.outBit = 0x80,
|
||||||
|
};
|
||||||
|
*state3.outByte = 0;
|
||||||
|
status = huffmanEncodeBufStreaming(&state3, inBuf3, INBUF_LEN3_CHUNK1, huffmanTable);
|
||||||
|
EXPECT_EQ(0, status);
|
||||||
|
status = huffmanEncodeBufStreaming(&state3, inBuf3 + INBUF_LEN3_CHUNK1, INBUF_LEN3_CHUNK2, huffmanTable);
|
||||||
|
EXPECT_EQ(0, status);
|
||||||
|
if (state3.outBit != 0x80) {
|
||||||
|
++state3.bytesWritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPECT_EQ(5, state3.bytesWritten);
|
||||||
|
EXPECT_EQ(0xec, (int)outBuf[0]);
|
||||||
|
EXPECT_EQ(0xc6, (int)outBuf[1]);
|
||||||
|
EXPECT_EQ(0x0e, (int)outBuf[2]);
|
||||||
|
EXPECT_EQ(0xb8, (int)outBuf[3]);
|
||||||
|
EXPECT_EQ(0xd8, (int)outBuf[4]);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(HuffmanUnittest, TestHuffmanDecode)
|
TEST(HuffmanUnittest, TestHuffmanDecode)
|
||||||
{
|
{
|
||||||
int len;
|
int len;
|
||||||
|
|
Loading…
Reference in New Issue