flipperzero-firmware/firmware/targets/f6/furi_hal/furi_hal_compress.c
SG 274c12fc56
[FL-2274] Inventing streams and moving FFF to them (#981)
* Streams: string stream
* String stream: updated insert/delete api
* Streams: generic stream interface and string stream implementation
* Streams: helpers for insert and delete_and_insert
* FFF: now compatible with streams
* MinUnit: introduced tests with arguments
* FFF: stream access violation
* Streams: copy data between streams
* Streams: file stream
* FFF: documentation
* FFStream: documentation
* FFF: alloc as file
* MinUnit: support for nested tests
* Streams: changed delete_and_insert, now it returns success flag. Added ability dump stream inner parameters and data to cout.
* FFF: simplified file open function
* Streams: unit tests
* FFF: tests
* Streams: declare cache_size constant as define, to allow variable modified arrays
* FFF: lib moved to a separate folder
* iButton: new FFF
* RFID: new FFF
* Animations: new FFF
* IR: new FFF
* NFC: new FFF
* Flipper file format: delete lib
* U2F: new FFF
* Subghz: new FFF and streams
* Streams: read line
* Streams: split
* FuriCore: implement memset with extra asserts
* FuriCore: implement extra heap asserts without inventing memset
* Scene manager: protected access to the scene id stack with a size check
* NFC worker: dirty fix for issue where hal_nfc was busy on app start
* Furi: update allocator to erase memory on allocation. Replace furi_alloc with malloc.
* FuriCore: cleanup memmgr code.
* Furi HAL: furi_hal_init is split into critical and non-critical parts. The critical part is currently clock and console.
* Memmgr: added ability to track allocations and deallocations through console.
* FFStream: some speedup
* Streams, FF: minor fixes
* Tests: restore
* File stream: a slightly more thread-safe version of file_stream_delete_and_insert

Co-authored-by: Aleksandr Kutuzov <alleteam@gmail.com>
2022-02-18 22:53:46 +03:00

265 lines
8.7 KiB
C

#include <furi_hal_compress.h>
#include <furi.h>
#include <lib/heatshrink/heatshrink_encoder.h>
#include <lib/heatshrink/heatshrink_decoder.h>
#define TAG "FuriHalCompress"
#define FURI_HAL_COMPRESS_ICON_ENCODED_BUFF_SIZE (2 * 512)
#define FURI_HAL_COMPRESS_ICON_DECODED_BUFF_SIZE (1024)
#define FURI_HAL_COMPRESS_EXP_BUFF_SIZE (1 << FURI_HAL_COMPRESS_EXP_BUFF_SIZE_LOG)
typedef struct {
uint8_t is_compressed;
uint8_t reserved;
uint16_t compressed_buff_size;
} FuriHalCompressHeader;
typedef struct {
heatshrink_decoder* decoder;
uint8_t
compress_buff[FURI_HAL_COMPRESS_EXP_BUFF_SIZE + FURI_HAL_COMPRESS_ICON_ENCODED_BUFF_SIZE];
uint8_t decoded_buff[FURI_HAL_COMPRESS_ICON_DECODED_BUFF_SIZE];
} FuriHalCompressIcon;
struct FuriHalCompress {
heatshrink_encoder* encoder;
heatshrink_decoder* decoder;
uint8_t* compress_buff;
uint16_t compress_buff_size;
};
static FuriHalCompressIcon* icon_decoder;
static void furi_hal_compress_reset(FuriHalCompress* compress) {
furi_assert(compress);
heatshrink_encoder_reset(compress->encoder);
heatshrink_decoder_reset(compress->decoder);
memset(compress->compress_buff, 0, compress->compress_buff_size);
}
void furi_hal_compress_icon_init() {
icon_decoder = malloc(sizeof(FuriHalCompressIcon));
icon_decoder->decoder = heatshrink_decoder_alloc(
icon_decoder->compress_buff,
FURI_HAL_COMPRESS_ICON_ENCODED_BUFF_SIZE,
FURI_HAL_COMPRESS_EXP_BUFF_SIZE_LOG,
FURI_HAL_COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG);
heatshrink_decoder_reset(icon_decoder->decoder);
memset(icon_decoder->decoded_buff, 0, sizeof(icon_decoder->decoded_buff));
FURI_LOG_I(TAG, "Init OK");
}
void furi_hal_compress_icon_decode(const uint8_t* icon_data, uint8_t** decoded_buff) {
furi_assert(icon_data);
furi_assert(decoded_buff);
FuriHalCompressHeader* header = (FuriHalCompressHeader*)icon_data;
if(header->is_compressed) {
size_t data_processed = 0;
heatshrink_decoder_sink(
icon_decoder->decoder,
(uint8_t*)&icon_data[4],
header->compressed_buff_size,
&data_processed);
while(1) {
HSD_poll_res res = heatshrink_decoder_poll(
icon_decoder->decoder,
icon_decoder->decoded_buff,
sizeof(icon_decoder->decoded_buff),
&data_processed);
furi_assert((res == HSDR_POLL_EMPTY) || (res == HSDR_POLL_MORE));
if(res != HSDR_POLL_MORE) {
break;
}
}
heatshrink_decoder_reset(icon_decoder->decoder);
memset(icon_decoder->compress_buff, 0, sizeof(icon_decoder->compress_buff));
*decoded_buff = icon_decoder->decoded_buff;
} else {
*decoded_buff = (uint8_t*)&icon_data[1];
}
}
FuriHalCompress* furi_hal_compress_alloc(uint16_t compress_buff_size) {
FuriHalCompress* compress = malloc(sizeof(FuriHalCompress));
compress->compress_buff = malloc(compress_buff_size + FURI_HAL_COMPRESS_EXP_BUFF_SIZE);
compress->encoder = heatshrink_encoder_alloc(
compress->compress_buff,
FURI_HAL_COMPRESS_EXP_BUFF_SIZE_LOG,
FURI_HAL_COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG);
compress->decoder = heatshrink_decoder_alloc(
compress->compress_buff,
compress_buff_size,
FURI_HAL_COMPRESS_EXP_BUFF_SIZE_LOG,
FURI_HAL_COMPRESS_LOOKAHEAD_BUFF_SIZE_LOG);
return compress;
}
void furi_hal_compress_free(FuriHalCompress* compress) {
furi_assert(compress);
heatshrink_encoder_free(compress->encoder);
heatshrink_decoder_free(compress->decoder);
free(compress->compress_buff);
free(compress);
}
bool furi_hal_compress_encode(
FuriHalCompress* compress,
uint8_t* data_in,
size_t data_in_size,
uint8_t* data_out,
size_t data_out_size,
size_t* data_res_size) {
furi_assert(compress);
furi_assert(data_in);
furi_assert(data_in_size);
size_t sink_size = 0;
size_t poll_size = 0;
HSE_sink_res sink_res;
HSE_poll_res poll_res;
HSE_finish_res finish_res;
bool encode_failed = false;
size_t sunk = 0;
size_t res_buff_size = sizeof(FuriHalCompressHeader);
// Sink data to encoding buffer
while((sunk < data_in_size) && !encode_failed) {
sink_res = heatshrink_encoder_sink(
compress->encoder, &data_in[sunk], data_in_size - sunk, &sink_size);
if(sink_res != HSER_SINK_OK) {
encode_failed = true;
break;
}
sunk += sink_size;
do {
poll_res = heatshrink_encoder_poll(
compress->encoder,
&data_out[res_buff_size],
data_out_size - res_buff_size,
&poll_size);
if(poll_res < 0) {
encode_failed = true;
break;
}
res_buff_size += poll_size;
} while(poll_res == HSER_POLL_MORE);
}
// Notify sinking complete and poll encoded data
finish_res = heatshrink_encoder_finish(compress->encoder);
if(finish_res < 0) {
encode_failed = true;
} else {
do {
poll_res = heatshrink_encoder_poll(
compress->encoder,
&data_out[res_buff_size],
data_out_size - 4 - res_buff_size,
&poll_size);
if(poll_res < 0) {
encode_failed = true;
break;
}
res_buff_size += poll_size;
finish_res = heatshrink_encoder_finish(compress->encoder);
} while(finish_res != HSER_FINISH_DONE);
}
bool result = true;
// Write encoded data to output buffer if compression is efficient. Else - write header and original data
if(!encode_failed && (res_buff_size < data_in_size + 1)) {
FuriHalCompressHeader header = {
.is_compressed = 0x01, .reserved = 0x00, .compressed_buff_size = res_buff_size};
memcpy(data_out, &header, sizeof(header));
*data_res_size = res_buff_size;
} else if(data_out_size > data_in_size) {
data_out[0] = 0x00;
memcpy(&data_out[1], data_in, data_in_size);
*data_res_size = data_in_size + 1;
} else {
*data_res_size = 0;
result = false;
}
furi_hal_compress_reset(compress);
return result;
}
bool furi_hal_compress_decode(
FuriHalCompress* compress,
uint8_t* data_in,
size_t data_in_size,
uint8_t* data_out,
size_t data_out_size,
size_t* data_res_size) {
furi_assert(compress);
furi_assert(data_in);
furi_assert(data_out);
furi_assert(data_res_size);
bool result = false;
bool decode_failed = false;
HSD_sink_res sink_res;
HSD_poll_res poll_res;
HSD_finish_res finish_res;
size_t sink_size = 0;
size_t res_buff_size = 0;
size_t poll_size = 0;
FuriHalCompressHeader* header = (FuriHalCompressHeader*)data_in;
if(header->is_compressed) {
// Sink data to decoding buffer
size_t compressed_size = header->compressed_buff_size;
size_t sunk = sizeof(FuriHalCompressHeader);
while(sunk < compressed_size && !decode_failed) {
sink_res = heatshrink_decoder_sink(
compress->decoder, &data_in[sunk], compressed_size - sunk, &sink_size);
if(sink_res < 0) {
decode_failed = true;
break;
}
sunk += sink_size;
do {
poll_res = heatshrink_decoder_poll(
compress->decoder, &data_out[res_buff_size], data_out_size, &poll_size);
if(poll_res < 0) {
decode_failed = true;
break;
}
res_buff_size += poll_size;
} while(poll_res == HSDR_POLL_MORE);
}
// Notify sinking complete and poll decoded data
if(!decode_failed) {
finish_res = heatshrink_decoder_finish(compress->decoder);
if(finish_res < 0) {
decode_failed = true;
} else {
do {
poll_res = heatshrink_decoder_poll(
compress->decoder, &data_out[res_buff_size], data_out_size, &poll_size);
res_buff_size += poll_size;
finish_res = heatshrink_decoder_finish(compress->decoder);
} while(finish_res != HSDR_FINISH_DONE);
}
}
*data_res_size = res_buff_size;
result = !decode_failed;
} else if(data_out_size >= data_in_size - 1) {
memcpy(data_out, &data_in[1], data_in_size);
*data_res_size = data_in_size - 1;
result = true;
} else {
result = false;
}
furi_hal_compress_reset(compress);
return result;
}