Docs menu
Device SDK (C)
On this page
The device SDK connects camera firmware to Rerinku View. The device registers with Rerinku View, can receive commands over MQTT at any time, and pushes its hardware encoder’s output over WebRTC (WHIP) whenever someone watches or records. The SDK doesn’t capture or encode; the firmware just hands it encoded frames.
- Plain C (C11). You include just one header,
<rerinku/rerinku_device.h>. - Ships as a static library,
librerinku_device.a(every dependency inside), and a shared library,librerinku_device.so. Both export onlyrerinku_*symbols and depend on nothing but the C library. - Supports CMake (
find_package(rerinku_device)) and pkg-config (rerinku_device.pc). - Cross-compiles with the chip vendor’s toolchain. Tested on Rockchip RV1103 (Luckfox Pico Plus: Cortex-A7, uClibc, GCC 8.3, 64 MB RAM).
- Open source under Apache-2.0. Every dependency is a C library, pinned to a fixed version and built from source.
Get and build
You need CMake 3.21 or later and a C11 compiler. Only Linux and other POSIX systems are supported (pthreads, BSD sockets). The first configure downloads the pinned dependencies.
git clone https://github.com/akarineren/relink.git rerinku-device-sdk
cd rerinku-device-sdk
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
ctest --test-dir build --output-on-failure
cmake --install build --component sdk --strip --prefix "$PWD/sdk"
The installed sdk/ directory is the deliverable. You can copy the whole directory to another machine and use it there:
include/rerinku/rerinku_device.h API
include/rerinku/rerinku_version.h Version macros (generated at build time)
lib/librerinku_device.a The SDK and every dependency merged into one static library
lib/librerinku_device.so* The same, as a shared library (soname librerinku_device.so.0.5)
lib/cmake/rerinku_device/ CMake package: find_package(rerinku_device)
lib/pkgconfig/rerinku_device.pc pkg-config file (relocatable)
share/rerinku_device/examples/ quickstart/ and device_example.c
share/doc/rerinku_device/ README, CHANGELOG, LICENSE, NOTICE
librerinku_device.a is the SDK and all its dependencies merged into a single object file whose only global symbols are rerinku_*. The firmware links just this one file, and its own copies of mbedTLS, cJSON and so on won’t clash with the versions inside the SDK.
Cross-compiling
Pass a CMake toolchain file for the target platform. The repository includes one for Rockchip RV1103/RV1106 (vendor toolchain arm-rockchip830-linux-uclibcgnueabihf):
cmake -S . -B build-rv1106 -DCMAKE_BUILD_TYPE=MinSizeRel \
-DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/rv1106-uclibc.cmake \
-DRV1106_TOOLCHAIN_DIR=<sdk>/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf
cmake --build build-rv1106 -j
cmake --install build-rv1106 --component sdk --strip --prefix "$PWD/sdk-rv1106"
For other chips, copy that file and change the compiler prefix and CPU flags. The SDK must be built with the firmware’s own toolchain, so that the C library and floating-point ABI match.
Quick start
The full program is examples/quickstart/quickstart.c in the repository. It uses a video file in place of an encoder, so it runs directly on a computer. On a camera, replace its next_frame() with the encoder’s output. The core of it:
#include <rerinku/rerinku_device.h>
rerinku_device_config_t config;
rerinku_device_config_init(&config); /* defaults + struct_size */
config.endpoint = "http://192.168.1.10:8080"; /* Rerinku View's address */
config.sn = "CAM-0001"; /* this device's serial number */
rerinku_device_callbacks_t callbacks = {
.struct_size = sizeof(callbacks),
.on_status = on_status, /* connecting / online / offline */
.on_stream_start = on_stream_start, /* someone is watching: start the encoder */
.on_stream_stop = on_stream_stop, /* nobody is watching: the encoder may sleep */
.on_keyframe_request = on_keyframe_request, /* e.g. RK_MPI_VENC_RequestIDR */
};
rerinku_device_t *device;
rerinku_err_t err = rerinku_device_create(&config, &callbacks, &device);
if (err != RERINKU_OK)
fprintf(stderr, "%s\n", rerinku_strerror(err));
rerinku_device_channel_t video = {
.struct_size = sizeof(video),
.channel_id = 0,
.kind = RERINKU_MEDIA_KIND_VIDEO,
.codec = RERINKU_CODEC_H264,
.stream_id = "main",
};
rerinku_device_add_channel(device, &video);
rerinku_device_start(device); /* returns at once; registration and reconnection run in the background */
/* encoder thread: call once for every access unit */
rerinku_device_write(device, 0, data, len, pts_us);
/* at shutdown */
rerinku_device_stop(device);
rerinku_device_destroy(device);
To try it on a computer, first generate a test video with ffmpeg (with an access unit delimiter before each frame), then run the quick start program:
ffmpeg -f lavfi -i testsrc2=size=1280x720:rate=25 -t 10 -c:v libx264 \
-profile:v baseline -g 50 -bf 0 -x264-params aud=1 clip.h264
./quickstart http://192.168.1.10:8080 QUICKSTART-001 clip.h264
Once the device registers, it shows up in Rerinku View’s device list automatically; open it to see the video. A fuller example is examples/device_example.c: an H.264/H.265 file plus G.711 audio, and it handles commands and snapshot requests.
Linking into your firmware
CMake:
find_package(rerinku_device 0.5 CONFIG REQUIRED) # -DCMAKE_PREFIX_PATH=<sdk dir>
target_link_libraries(camera PRIVATE rerinku::device) # static library
# or rerinku::device_shared # shared library
Cross-compiling toolchain files usually restrict find_package to the sysroot. In that case, point to the SDK directory explicitly:
find_package(rerinku_device 0.5 CONFIG REQUIRED
PATHS "${RERINKU_SDK_DIR}" NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)
pkg-config (Makefiles, Buildroot, chip vendor SDKs):
export PKG_CONFIG_PATH=<sdk dir>/lib/pkgconfig
# Static linking: the static library already contains every dependency; just add pthread
cc -std=c11 $(pkg-config --cflags rerinku_device) camera.c \
"$(pkg-config --variable=libdir rerinku_device)/librerinku_device.a" -pthread
# Dynamic linking
cc -std=c11 $(pkg-config --cflags rerinku_device) camera.c $(pkg-config --libs rerinku_device)
examples/quickstart/ has a ready-made CMakeLists.txt and Makefile for both approaches.
Using the source directly in a CMake project: call add_subdirectory(rerinku-device-sdk), then link rerinku::device. This is how Android uses it: the SDK source is built directly with the NDK and wrapped in a JNI layer.
API conventions
The API follows the conventions of the C libraries it’s built on (mbedTLS, libjuice, libsrtp):
- Naming: Functions and types start with
rerinku_, and macros and enum values withRERINKU_. Types end in_t, and callback types in_cb. Enum values carry their type’s name (rerinku_stream_state_t→RERINKU_STREAM_STATE_*; the only exception isRERINKU_OK). Library-wide functions arererinku_*. Functions for a device arererinku_device_*, and their first argument is the device. - Errors: Functions that can fail return
rerinku_err_t:RERINKU_OK(0) or a negativeRERINKU_ERR_*.rerinku_strerror()gives a description.rerinku_device_write()is the exception: it returnsrerinku_write_result_t, because most of its outcomes (nobody watching, waiting for a keyframe) are normal. - Objects:
rerinku_device_tis an opaque handle.create/destroyandstart/stopare used in pairs. - Versioned structs: The first field of the config, channel and callback structs is
struct_size. A program compiled against an older header passes a smaller struct, and the missing fields keep their defaults. Fields added in newer versions are ignored by an older library. - Version numbers:
<rerinku/rerinku_version.h>provides the header’s version (RERINKU_VERSION_MAJOR,_MINOR,_PATCH,_STRING, and the hexadecimalRERINKU_VERSION_NUMBER).rerinku_version()andrerinku_version_number()return the version of the library actually linked. Before 1.0, the soname changes whenever the minor version changes (librerinku_device.so.0.5). - Exports: Public functions are marked
RERINKU_API. Every other symbol is hidden.
Threads: Every function can be called from any thread. Callbacks run on the SDK’s own threads, so return quickly. Don’t call rerinku_device_stop() or rerinku_device_destroy() from a callback.
Memory: Strings passed in are always copied. The caller keeps ownership of all data it passes in (including frame data), which only needs to stay valid during the call.
Device lifecycle
rerinku_device_start() returns immediately. Registration, the MQTT connection and failure recovery all happen in the background, and failures are retried forever with backoff (starting at 1 second and doubling up to 30 seconds). So even a device that starts before the network or Rerinku View comes online automatically later. on_status reports three states: connecting, online and offline.
- Streaming: When someone watches or records, the platform sends a stream command. The SDK calls
on_stream_start, sets up a WHIP session and requests a keyframe. When nobody needs the stream any more, it callson_stream_stop. Sessions the server drops (restart, network loss) are rebuilt by the SDK automatically. - Tokens: At registration, the platform issues a device token (valid for 24 hours by default on Rerinku View). The SDK registers again when three quarters of the validity period have passed. Timing follows the server’s clock, so it doesn’t matter if the device clock is still at 1970. When an HTTP request is refused (401) or the MQTT login is refused, the SDK registers again right away.
- Device key: When Rerinku View has
DEVICE_KEYset, only devices with the correctsecretcan register, wheresecret = base64(HMAC-SHA256(device key, sn)). For mass production, write each device’ssecretinto it at the factory. Don’t put the device key itself in the firmware.
Feeding frames
Each rerinku_device_write() call passes one complete access unit (all the NAL units of one picture) or one audio frame, along with its capture time (in microseconds, monotonically increasing, from any starting point; Rockchip MPI’s u64PTS, for example, can be used as is).
- H.264/H.265 channels default to
RERINKU_BITSTREAM_FORMAT_AUTO, which recognizes the format frame by frame: Annex-B with 3-byte or 4-byte start codes, 1/2/4-byte length prefixes, or a single NAL. Data is validated first, then normalized to 4-byte start codes. - SPS/PPS (and VPS for H.265) are cached and added automatically to keyframes that lack them, so a viewer who has just joined always gets a decodable picture.
- The SDK caches the current GOP (by default, up to 1 MiB per video channel), so a new viewer starts playing from a keyframe at once, without waiting for the next IDR.
- Writes are never blocked by the network. When nobody is watching, the call returns
RERINKU_WRITE_RESULT_NOT_STREAMINGat almost no cost, so it’s fine to keep feeding frames all the time. Putting the encoder to sleep inon_stream_stopsaves power. - When the network can’t keep up, the call returns
RERINKU_WRITE_RESULT_DROPPED: the frame is dropped and a new keyframe is requested, and the picture continues from there.
Supported codecs: video H.264 and H.265 (H.265 can’t be played in browsers that don’t support it, so push H.264 for web viewing); audio Opus and G.711 µ-law/A-law.
Requests from the platform
| Callback / function | Use |
|---|---|
on_command → rerinku_device_reply_command() / rerinku_device_fail_command() |
Commands the SDK doesn’t handle itself (reboot, PTZ…). params_json holds the command’s parameters. You can reply later, from any thread |
on_snapshot → rerinku_device_reply_snapshot() |
The platform wants a thumbnail: capture a JPEG and hand it to the SDK, whenever the hardware JPEG encoder has it ready |
on_runtime_info |
While someone has the device details open, the platform asks every few seconds: fill in a JSON object, such as temperature, CPU and memory |
rerinku_device_send_event() |
The device reports an event on its own (motion detection, alarms…). Returns RERINKU_ERR_NETWORK while offline. The SDK does not queue the event and send it later, so save important events yourself and send them once the device is back online |
rerinku_device_upload_recording() |
Hands the platform an MP4 the device recorded on its own while it was offline; the platform files it in its recordings by recording time. Blocks until the upload finishes, so call it from your own thread |
The device’s static information (SoC, CPU, memory, sensor…) is reported on every connection through ext_json in the config, and the platform stores it and shows it in the device info. Live data (temperature, CPU usage…) is answered on demand by on_runtime_info. Keys the platform knows are shown with their matching names, and other keys are listed under their own names, so a device can add its own fields without waiting for a platform update.
Protocols and networking
| Step | Protocol | Description |
|---|---|---|
| Registration | HTTP(S) POST <endpoint>/ |
Submits the sn and device info, and gets back the WHIP URL, MQTT address, ICE servers and device token |
| Commands | MQTT 3.1.1 | The username is the SN, the password is the device token, and the client ID is rerinku-dev-<sn>. Rerinku View’s built-in broker lets a device use only its own topics |
| Video | WebRTC (WHIP) | One POST completes the SDP exchange. ICE is handled by libjuice, with DTLS-SRTP encryption |
| Snapshots, recording uploads | HTTP(S) | With the device token |
The device must be able to reach Rerinku View’s HTTP port and MQTT port, and outbound UDP must be allowed (WebRTC media). https:// and mqtts:// verify the server certificate, and certificate verification depends on a correct clock (NTP). You can use ca_file to point to a PEM file that contains only the root certificates you need, which saves memory.
Resource usage
Measured on RV1103 (Cortex-A7 1.2 GHz), MinSizeRel, 640×360 H.264 at about 0.8 Mbit/s plus G.711A:
librerinku_device.so (stripped) |
0.39 MB, depends only on the C library |
| Memory when idle (registered, MQTT online) | About 0.8 MB |
| Memory while streaming | About 1.8 MB |
| CPU while streaming | About 6% of one core |
| Threads | 3 idle, 5 while streaming |
Memory you can tune in rerinku_device_config_t:
gop_cache_max_bytes: the GOP cache per video channel, 1 MiB by default. Small-memory devices can set it to 0 and implementon_keyframe_request, so the picture starts from the encoder’s IDR.nack_history_packets: sent packets kept for retransmission (about 1.2 KB each), 512 by default. For devices with 64 MB of RAM, 128–256 is recommended.
Known limitations
- Push only (WHIP), no receiving. No trickle ICE or DataChannel.
- POSIX systems only. No Windows version yet.
- Events aren’t queued: save events from while the device was offline yourself and send them later.
For the full change history and a table of old and new names, see CHANGELOG.md in the repository.