Skip to content
Esc

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 only rerinku_* 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 with RERINKU_. 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 is RERINKU_OK). Library-wide functions are rerinku_*. Functions for a device are rerinku_device_*, and their first argument is the device.
  • Errors: Functions that can fail return rerinku_err_t: RERINKU_OK (0) or a negative RERINKU_ERR_*. rerinku_strerror() gives a description. rerinku_device_write() is the exception: it returns rerinku_write_result_t, because most of its outcomes (nobody watching, waiting for a keyframe) are normal.
  • Objects: rerinku_device_t is an opaque handle. create / destroy and start / stop are 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 hexadecimal RERINKU_VERSION_NUMBER). rerinku_version() and rerinku_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 calls on_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_KEY set, only devices with the correct secret can register, where secret = base64(HMAC-SHA256(device key, sn)). For mass production, write each device’s secret into 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_STREAMING at almost no cost, so it’s fine to keep feeding frames all the time. Putting the encoder to sleep in on_stream_stop saves 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 implement on_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.