Skip to content

Commit 8dd9f13

Browse files
authored
Merge pull request #296 from brilliantlabsAR/compression-library
Compression library
2 parents 396e5ec + 88639fa commit 8dd9f13

File tree

6 files changed

+228
-3
lines changed

6 files changed

+228
-3
lines changed

source/application/Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ C_FILES += \
3939
watchdog.c \
4040
lua_libraries/bluetooth.c \
4141
lua_libraries/camera.c \
42+
lua_libraries/compression.c \
4243
lua_libraries/display.c \
4344
lua_libraries/file.c \
4445
lua_libraries/imu.c \

source/application/compression.c

+48-3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,45 @@
2525
#include <stdlib.h>
2626
#include "compression.h"
2727
#include "lz4.h"
28+
#include "nrfx_log.h"
29+
30+
#define LZ4F_MAGICNUMBER 0x184D2204U
31+
#define LZ4F_MAGIC_SKIPPABLE_START 0x184D2A50U
32+
#define LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH 5
33+
#define LZ4F_HEADER_SIZE_MIN 7
34+
35+
static uint32_t LZ4F_readLE32(const void *src)
36+
{
37+
const uint8_t *const srcPtr = (const uint8_t *)src;
38+
uint32_t value32 = srcPtr[0];
39+
value32 |= ((uint32_t)srcPtr[1]) << 8;
40+
value32 |= ((uint32_t)srcPtr[2]) << 16;
41+
value32 |= ((uint32_t)srcPtr[3]) << 24;
42+
return value32;
43+
}
44+
45+
size_t LZ4F_headerSize(const void *src, size_t srcSize)
46+
{
47+
/* minimal srcSize to determine header size */
48+
if (srcSize < LZ4F_MIN_SIZE_TO_KNOW_HEADER_LENGTH)
49+
return -20;
50+
51+
/* special case : skippable frames */
52+
if ((LZ4F_readLE32(src) & 0xFFFFFFF0U) == LZ4F_MAGIC_SKIPPABLE_START)
53+
return 8;
54+
55+
/* control magic number */
56+
if (LZ4F_readLE32(src) != LZ4F_MAGICNUMBER)
57+
return -21;
58+
59+
/* Frame Header Size */
60+
{
61+
uint8_t const FLG = ((const uint8_t *)src)[4];
62+
uint32_t const contentSizeFlag = (FLG >> 3) & 0x01;
63+
uint32_t const dictIDFlag = FLG & 0x01;
64+
return LZ4F_HEADER_SIZE_MIN + (contentSizeFlag ? 8 : 0) + (dictIDFlag ? 4 : 0);
65+
}
66+
}
2867

2968
int compression_decompress(size_t destination_size,
3069
const void *source,
@@ -34,18 +73,24 @@ int compression_decompress(size_t destination_size,
3473
{
3574
int status = 0;
3675

76+
size_t header_size = LZ4F_headerSize(source, source_size);
77+
78+
if (header_size < 0)
79+
{
80+
return header_size;
81+
}
82+
3783
char *output_buffer = malloc(destination_size);
84+
3885
if (output_buffer == NULL)
3986
{
4087
return -1;
4188
}
4289

43-
// TODO the frame header might not be 7
44-
char *block_pointer = (char *)source + 7;
90+
char *block_pointer = (char *)source + header_size;
4591

4692
while (1)
4793
{
48-
4994
int current_block_size = ((uint8_t)block_pointer[0]) +
5095
((uint8_t)block_pointer[1] << 8) +
5196
((uint8_t)block_pointer[2] << 16) +
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* This file is a part of: https://github.com/brilliantlabsAR/frame-codebase
3+
*
4+
* Authored by: Raj Nakarja / Brilliant Labs Ltd. ([email protected])
5+
* Rohit Rathnam / Silicon Witchery AB ([email protected])
6+
* Uma S. Gupta / Techno Exponent ([email protected])
7+
*
8+
* ISC Licence
9+
*
10+
* Copyright © 2025 Brilliant Labs Ltd.
11+
*
12+
* Permission to use, copy, modify, and/or distribute this software for any
13+
* purpose with or without fee is hereby granted, provided that the above
14+
* copyright notice and this permission notice appear in all copies.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
17+
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
18+
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
19+
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
20+
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
21+
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
22+
* PERFORMANCE OF THIS SOFTWARE.
23+
*/
24+
25+
#include <string.h>
26+
#include "compression.h"
27+
#include "frame_lua_libraries.h"
28+
#include "lauxlib.h"
29+
#include "lua.h"
30+
#include "watchdog.h"
31+
32+
static int registered_function = 0;
33+
static uint8_t decompression_buffer[4096];
34+
static size_t decompression_buffer_size = 0;
35+
36+
static void decompression_lua_handler(lua_State *L, lua_Debug *ar)
37+
{
38+
sethook_watchdog(L);
39+
40+
if (registered_function != 0)
41+
{
42+
lua_rawgeti(L, LUA_REGISTRYINDEX, registered_function);
43+
44+
lua_pushlstring(L,
45+
(char *)decompression_buffer,
46+
decompression_buffer_size);
47+
48+
if (lua_pcall(L, 1, 0, 0) != LUA_OK)
49+
{
50+
luaL_error(L, "%s", lua_tostring(L, -1));
51+
}
52+
}
53+
}
54+
55+
static void process_function_callback(void *context,
56+
void *data,
57+
size_t data_size)
58+
{
59+
decompression_buffer_size = data_size;
60+
memcpy(decompression_buffer, data, data_size);
61+
62+
lua_sethook(L_global,
63+
decompression_lua_handler,
64+
LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE | LUA_MASKCOUNT,
65+
1);
66+
}
67+
68+
static int lua_compression_register_process_function(lua_State *L)
69+
{
70+
if (lua_isnil(L, 1))
71+
{
72+
registered_function = 0;
73+
return 0;
74+
}
75+
76+
if (lua_isfunction(L, 1))
77+
{
78+
registered_function = luaL_ref(L, LUA_REGISTRYINDEX);
79+
return 0;
80+
}
81+
82+
luaL_error(L, "expected nil or function");
83+
84+
return 0;
85+
}
86+
87+
static int lua_compression_decompress(lua_State *L)
88+
{
89+
size_t length;
90+
const char *data = luaL_checklstring(L, 1, &length);
91+
92+
lua_Integer block_size = luaL_checkinteger(L, 2);
93+
94+
if (block_size <= 0)
95+
{
96+
luaL_error(L, "bytes must be greater than 0");
97+
}
98+
99+
int status = compression_decompress(block_size,
100+
data,
101+
length,
102+
process_function_callback,
103+
NULL);
104+
105+
if (status)
106+
{
107+
luaL_error(L, "decompression failed");
108+
}
109+
110+
return 0;
111+
}
112+
113+
void lua_open_compression_library(lua_State *L)
114+
{
115+
lua_getglobal(L, "frame");
116+
117+
lua_newtable(L);
118+
119+
lua_pushcfunction(L, lua_compression_register_process_function);
120+
lua_setfield(L, -2, "process_function");
121+
122+
lua_pushcfunction(L, lua_compression_decompress);
123+
lua_setfield(L, -2, "decompress");
124+
125+
lua_setfield(L, -2, "compression");
126+
127+
lua_pop(L, 1);
128+
}

source/application/lua_libraries/frame_lua_libraries.h

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ void lua_bluetooth_data_interrupt(uint8_t *data, size_t length);
3333

3434
void lua_open_bluetooth_library(lua_State *L);
3535
void lua_open_camera_library(lua_State *L);
36+
void lua_open_compression_library(lua_State *L);
3637
void lua_open_display_library(lua_State *L);
3738
void lua_open_imu_library(lua_State *L);
3839
void lua_open_led_library(lua_State *L);

source/application/luaport.c

+1
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ void run_lua(bool is_paired)
9999
lua_open_imu_library(L);
100100
lua_open_time_library(L);
101101
lua_open_led_library(L);
102+
lua_open_compression_library(L);
102103

103104
lua_open_file_library(L, !is_paired);
104105

tests/test_compression.py

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import asyncio
2+
from frameutils import Bluetooth
3+
4+
5+
async def main():
6+
7+
b = Bluetooth()
8+
9+
await b.connect(print_response_handler=lambda s: print(s))
10+
11+
# Upload the Lua script
12+
lua_script = """
13+
-- Decompression function
14+
function decomp_func(data)
15+
print(data)
16+
end
17+
18+
-- Register the decompression function
19+
frame.compression.process_function(decomp_func)
20+
21+
-- Function to handle the compressed data received from Bluetooth
22+
function ble_func(data)
23+
frame.compression.decompress(data, 1024)
24+
end
25+
26+
-- Register the Bluetooth receive callback
27+
frame.bluetooth.receive_callback(ble_func)
28+
29+
"""
30+
31+
await b.upload_file(lua_script, "main.lua")
32+
await b.send_reset_signal()
33+
34+
await asyncio.sleep(1)
35+
36+
# Send the compressed data. Here the total size of the data is is pretty small,
37+
# but usually you would want to split the data into MTU sized chunks and stitch
38+
# them together on the device side before decompressing.
39+
compressed_data = bytearray(
40+
b"\x04\x22\x4d\x18\x64\x40\xa7\x6f\x00\x00\x00\xf5\x3d\x48\x65\x6c\x6c\x6f\x21\x20\x49\x20\x77\x61\x73\x20\x73\x6f\x6d\x65\x20\x63\x6f\x6d\x70\x72\x65\x73\x73\x65\x64\x20\x64\x61\x74\x61\x2e\x20\x49\x6e\x20\x74\x68\x69\x73\x20\x63\x61\x73\x65\x2c\x20\x73\x74\x72\x69\x6e\x67\x73\x20\x61\x72\x65\x6e\x27\x74\x20\x70\x61\x72\x74\x69\x63\x75\x6c\x61\x72\x6c\x79\x3b\x00\xf1\x01\x69\x62\x6c\x65\x2c\x20\x62\x75\x74\x20\x73\x70\x72\x69\x74\x65\x49\x00\xa0\x20\x77\x6f\x75\x6c\x64\x20\x62\x65\x2e\x00\x00\x00\x00\x5f\xd0\xa3\x47"
41+
)
42+
43+
await b.send_data(compressed_data)
44+
45+
await b.send_break_signal()
46+
await b.disconnect()
47+
48+
49+
asyncio.run(main())

0 commit comments

Comments
 (0)