Raptor Streaming System -- Development Guide

August 15, 2026 · View on GitHub

Quick-start reference for contributing to RSS. This document is optimized for AI-assisted development but applies equally to human contributors. For deep dives, follow the cross-references to other docs in this repo.


1. What Raptor Is

RSS is an embedded streaming platform for Ingenic MIPS IP cameras (T10, T20, T21, T23, T30, T31, T32, T33, T40, T41, and A1). It replaces a monolithic streamer with isolated single-purpose daemons communicating via shared memory rings and Unix control sockets. On SoCs without an ISP (A1), RFS acts as the video source instead of RVD.

Raptor is production quality software meant to be deployed to production cameras. Code quality, memory safety, and long-term reliability are non-negotiable. Every contribution must follow C best standards and practices -- write code you would trust to run unattended for years on hardware you cannot physically access. Do it right the first time.

Key constraints:

  • Target devices have 32-256 MB RAM, no swap. Cache: T20/T21/T30 have 16 KB I + 16 KB D, no L2. T23 has 16 KB I + 16 KB D + 64 KB L2. T31/T32/T33/T41 have 32 KB I + 32 KB D + 128 KB L2. T40 has 32 KB I + 32 KB D + 1 MB L2 (shared with AI engine). A1 has 32 KB I + 32 KB D + 1 MB L2.
  • All code is C11, cross-compiled for mipsel with uclibc or musl
  • Binary size and memory footprint matter -- every kilobyte counts
  • No dynamic allocation in hot paths (frame delivery, RTP packetization)
  • Daemons run 24/7 for months -- leaks, even slow ones, are fatal

Architecture reference: 20-rss-architecture.md Build system: 21-rss-build.md Test infrastructure: 22-rss-testing.md


2. Repository Layout

Five git repos, expected as siblings:

raptor/              Main repo -- all daemons and tools
raptor-hal/          Hardware abstraction layer (Ingenic SDK wrapper)
raptor-ipc/          IPC primitives (SHM ring, OSD double-buffer, control socket)
raptor-common/       Shared utilities (config, logging, JSON, net, TLS)
compy/               RTSP/RTP/SRTP/WebRTC library
raptor-docs/         This documentation

Each daemon lives in its own subdirectory under raptor/:

rvd/   -- Video pipeline (HAL owner, encoder, framesource, IVS, OSD)
rsd/   -- RTSP server (ring consumer, compy-based)
rsd-555/ -- RTSP server (ring consumer, live555-based, static link)
rad/   -- Audio capture and encoding
rhd/   -- HTTP server (snapshots, MJPEG, audio streaming)
rod/   -- OSD rendering (libschrift text, detection boxes)
ric/   -- IR-cut and day/night control
rmr/   -- Recording (fMP4, H.264/H.265/MJPEG; continuous, motion, timelapse)
rmd/   -- Motion detection state machine
rwd/   -- WebRTC server (WHIP signaling, DTLS-SRTP)
rwc/   -- USB webcam gadget (UVC+UAC)
rfs/   -- File source (MP4/Annex B playback into rings)
rsp/   -- RTMP/RTMPS stream push (YouTube, Twitch)
rsr/   -- SRT listener (MPEG-TS over SRT, libsrt)
raptorctl/  -- CLI control tool
ringdump/   -- Ring buffer debug/dump tool
rlatency/   -- End-to-end RTSP latency measurement tool
rac/        -- Audio playback and recording tool
rverify/    -- Signed-recording verification tool (host + target)

Only RVD and RAD link against the HAL. All other daemons are pure ring consumers or control-socket clients. RSD and RSD-555 are alternative RTSP backends reading the same rings -- RSD uses compy (C, custom RTP), RSD-555 uses live555 (C++, statically linked).


3. Code Style

Raptor uses Linux kernel style with tabs. A .clang-format file enforces this -- always run clang-format -i before committing.

Conformity hooks automate the mechanical rules locally. The engine lives once in raptor/tools/conformity/ (see its README); each repo declares its checks in a tracked one-line .conformity manifest. Activate once per clone -- git config core.hooksPath tools/conformity/hooks inside raptor, or ../raptor/tools/conformity/hooks from any sibling -- and commits get diff-scoped format, the hand-written-JSON gate and lab-address refusal at staging time, trailer rejection at message time, and the exact CI range checks at push time, so a push that passes locally cannot fail those CI gates. Hooks are the committer's safety net; CI remains the enforcement for everyone. Commit messages carry no trailers: no Signed-off-by, no co-authors, no review tags.

Rules

  • Tabs for indentation, 8-wide. No spaces for indentation.
  • 100-column line limit. Break long lines at logical boundaries.
  • Linux brace style. Opening brace on same line as control statement; functions get the brace on the next line.
  • Pointer alignment right: char *buf, not char* buf.
  • No single-line blocks. Always use braces even for one-line if/for/while bodies, or put the body on the next line without braces only when it fits the existing file's pattern.
  • Sort includes by group: system headers first, then library headers, then project headers. Do not auto-sort (disabled in clang-format).

Naming

  • Functions: snake_case. Prefix with module: rvd_stream_stop(), rss_ring_read(), hal_enc_start().
  • Types: snake_case_t for typedefs: rvd_state_t, rss_ring_t.
  • Macros/constants: UPPER_SNAKE: RVD_MAX_STREAMS, RSS_OK.
  • Local variables: short, descriptive: chn, ret, idx, s.
  • No Hungarian notation. No m_ or g_ prefixes.

Comments

  • Default to no comments. Well-named functions and variables are self-documenting.
  • Comment the why, never the what. If removing the comment wouldn't confuse a reader, don't write it.
  • File-level block comments are fine for describing the module's role (see any daemon's main .c file).
  • Never reference tickets, PRs, or session context in comments -- those belong in the commit message.

4. C Standards and Safety

Memory Safety

  • No unbounded operations. Use snprintf, never sprintf. Use rss_strlcpy, never strcpy/strcat.
  • Check every allocation. malloc/calloc/cJSON_Create* can return NULL. cJSON_PrintUnformatted can return NULL. Handle it or propagate the error.
  • Free exactly once, in the right order. Every malloc has a matching free on all code paths (including error paths). Use goto-based cleanup for multi-resource functions.
  • No use-after-free. When you hold a pointer into a cJSON object or detached node, ensure the parent outlives your use.
  • Stack buffers have bounded lifetimes. Never return pointers to stack-allocated arrays.
  • No VLAs. Use fixed-size stack arrays or malloc.

Thread Safety

  • Atomic operations for simple flags: _Atomic bool, atomic_load, atomic_store. Used extensively for stream_active[], ivs_active, pipeline_ready.
  • pthread_mutex_t for complex shared state (detection results, OSD regions, client lists).
  • Control socket handlers run synchronously in the main thread via epoll -- no mutex needed for state accessed only from the ctrl handler.
  • Each encoder channel runs in a dedicated thread. Never access another channel's thread-local state without synchronization.

Error Handling

  • Functions return int (0 = success, negative = error) or a pointer (NULL = failure).
  • Use RSS_OK / RSS_ERR / RSS_ERR_INVAL / RSS_ERR_NOTSUP from rss_common.h.
  • HAL calls go through the RSS_HAL_CALL() macro which handles NULL function pointers gracefully.
  • IPC control handlers write errors via rss_ctrl_resp_error() and return the response length. Never leave resp unwritten.

Defensive Patterns

  • Idempotent operations. Stop an already-stopped stream? Return OK. Start an already-running stream? Return OK. Never crash on redundant operations.
  • Validate at system boundaries. Check JSON input from control sockets. Check channel indices. Check config values. Trust internal state between validated boundaries.
  • Rollback on failure. If a multi-step operation fails midway, restore the previous state. See set-codec and set-resolution in rvd_ctrl.c for the pattern.
  • Don't mask errors. If a HAL call fails, log it and propagate. Don't silently continue with corrupt state.
  • JSON is built by serializers and read by parsers — never by hand. Requests go through rss_ctrl_cmd/rss_ctrl_cmd_int/ rss_ctrl_cmd_str, responses through rss_ctrl_resp_ok/ rss_ctrl_resp_error/rss_ctrl_resp_json, and answers are checked with rss_ctrl_resp_is_ok or cJSON_Parse — never strstr/snprintf. A %s into a JSON literal is one edit away from injection, and a substring check ties correctness to the peer's formatting (a spaced "status": "ok" once parked a whole feature). test-all.sh stage 0 enforces this by grep; the two documented exemptions are raptorctl_help.c (display text showing -j example syntax) and raptor-ipc's transport error frame in rss_ctrl.c (a dependency-free layer emitting a constant shape with one integer).

Protocol Compliance

Raptor implements real-world streaming protocols. Where an RFC or standard defines the behavior, follow it -- don't invent ad-hoc alternatives. Clients, NVRs, and browsers expect standards-compliant implementations. A creative shortcut that works with one client will break with the next.

Applicable standards by component:

ComponentStandards
compy (RTSP core)RFC 2326 (RTSP/1.0), RFC 7826 (RTSP/2.0)
compy (RTP/RTCP)RFC 3550 (RTP), RFC 4585 (RTCP FB), RFC 5104 (codec control)
compy (SRTP)RFC 3711 (SRTP/SRTCP)
compy (SDP)RFC 4566 (SDP), RFC 3264 (offer/answer)
compy (payloads)RFC 6184 (H.264), RFC 7798 (H.265), RFC 3640 (AAC), RFC 7587 (Opus)
RWD (WebRTC)RFC 8445 (ICE), RFC 8489 (STUN), RFC 5764 (DTLS-SRTP), WHIP (draft-ietf-wish-whip)
RMR (recording)ISO 14496-12 (ISOBMFF/fMP4), MJPEG in MP4 (FourCC jpeg)
RSD / RSD-555 (RTSP)RFC 2326 (RTSP/1.0), RFC 3550 (RTP §5.1), RFC 4566 (SDP)
RSR (SRT/TS)ISO 13818-1 (MPEG-TS), SRT protocol (Haivision spec / draft-sharabayko-srt)

Compliance items enforced across RSD, RSD-555, and compy:

  • Random initial RTP seq and timestamp (RFC 3550 §5.1): both compy and RSD use /dev/urandom for initial sequence numbers and RTP-Info rtptime values. Zero-based values cause client calibration failures (e.g., mpv "No video PTS").
  • SDP o= origin (RFC 4566 §5.2): session ID from monotonic clock, server IP from getsockname. Not 0 0 ... 0.0.0.0.
  • SDP b=AS: (RFC 4566 §5.8): per-media bandwidth hints.
  • SDP direction (RFC 4566 §6): streaming server sends, not receives. Do not use a=recvonly (incorrect for a camera).
  • RTP-Info URL (RFC 2326 §12.33): strip credentials from the URL -- ffmpeg strips them internally and fails to match if they're present.

Rules:

  • Verify RFC compliance when touching protocol code. Before modifying any code that implements RFC behavior, read the relevant sections of the RFC and confirm your changes conform. Don't assume the existing code is correct -- verify both.
  • Cite the RFC in commit messages when implementing or fixing protocol behavior. Example: compy: fix RTCP SR timestamp per RFC 3550 Section 6.4.1.
  • Don't subset silently. If you intentionally skip a MUST or SHOULD from an RFC, document it in the code with the specific section number and the reason (hardware limitation, scope, etc.).
  • Use RFC terminology. MUST, SHOULD, MAY have precise meanings (RFC 2119). Match the RFC's field names and state machine names in code where practical -- it makes cross-referencing trivial.
  • Test against real clients. VLC, ffplay, and browser WebRTC are the minimum. NVR compatibility (Blue Iris, Frigate) matters for RTSP. Don't assume your own raptorctl exercises the same code paths a third-party client would.

5. Architecture Patterns

Adding a Control Command

Every daemon exposes a JSON-over-Unix-socket control interface. To add a new command:

  1. Daemon side -- add an if (strcmp(cmd, "your-cmd") == 0) block in the daemon's ctrl handler function. Parse input with rss_json_get_int()/rss_json_get_str(). Respond with rss_ctrl_resp_ok() or rss_ctrl_resp_error().

  2. raptorctl side -- add a help entry in help_entries[] (raptorctl_help.c) and a dispatch table entry in raptorctl_dispatch.c. Simple no-arg commands fall through to the generic pass-through -- no dispatch entry needed.

  3. JSON mode -- commands added this way automatically work with raptorctl -j since it sends raw JSON directly.

Pattern reference: stream-stop/stream-start in rvd_ctrl.c, mute/unmute in rad_main.c, ai-disable/ai-enable and ao-disable/ao-enable in rad_main.c (pipeline teardown/bringup with rollback on failure).

Adding a Simple Encoder Parameter

Simple encoder params (single int/uint/bool value, channel-scoped) use the table-driven system. To add one:

  1. Add a row to enc_params[] in rvd_ctrl.c with the param name, type (EP_INT/EP_U32/EP_BOOL), and offsetof into rss_hal_ops_t for the setter and getter.

That's it -- no raptorctl changes needed. The param is immediately available via enc-set, enc-get, and enc-list.

For struct-based params (multi-field args like ROI, super-frame), add an explicit handler in handle_encoder_advanced_cmd() plus a raptorctl dispatch entry.

Adding a New Daemon

Consumer daemons follow a template:

  1. Create raptor/<name>/ with Makefile and source files.
  2. Link librss_ipc.a + librss_common.a (not the HAL).
  3. Call rss_daemonize() for PID file + signal handling.
  4. Open ring buffers with rss_ring_open(), read with rss_ring_read().
  5. Listen on /var/run/rss/<name>.sock for control commands.
  6. Add build target in the top-level Makefile.
  7. Add the daemon name to the daemons[] array in raptorctl.c.

IPC Primitives

  • SHM ring buffers -- lock-free single-producer multi-consumer frame transport. Zero-copy in refmode (producer writes a pointer, consumers mmap the same physical memory).
  • OSD double-buffer -- BGRA bitmap shared between ROD (writer) and RVD (reader). Dirty flag + heartbeat for crash detection.
  • Control sockets -- length-prefixed JSON over Unix domain sockets. Synchronous request/response. Used for configuration, status queries, and runtime parameter changes.

Details: 20-rss-architecture.md sections 3-5.

Logging: raptor-ipc has its own log macros (RSS_IPC_ERROR, RSS_IPC_WARN, RSS_IPC_INFO, RSS_IPC_DEBUG) defined in rss_ipc.h. Use these instead of printf/fprintf in IPC code. rss_daemon_init() automatically wires them through the daemon's rss_log() via a weak symbol -- no per-daemon setup needed. Standalone tools and tests get stderr fallback.

HAL Layer

The HAL wraps Ingenic's libimp.so SDK with a clean C API. It abstracts cross-SoC differences (9 SoC families, 3 SDK generations). A1 has no ISP -- it skips the HAL entirely and uses RFS as the video source.

  • Never call IMP_ functions directly* from daemon code. Always go through the HAL ops vtable.
  • HAL functions take void *ctx as first argument (opaque context).
  • Capabilities are runtime-queryable via ops->get_caps().
  • Audio and video HAL are separate static libraries.

HAL API reference: 10-hal-api.md HAL internals: 11-hal-internals.md Platform capabilities: 12-hal-caps.md


6. Build and Test

Every change must build clean, pass all tests, and run clean under AddressSanitizer and ThreadSanitizer before committing. Sanitizer warnings are treated as bugs -- do not suppress or ignore them. Memory leaks, data races, and undefined behavior are not acceptable in production code that runs 24/7 on devices you cannot physically access.

Standalone Build (no Buildroot)

make distclean
./build-standalone.sh t31 --local --static

make distclean removes stale build artifacts. The build script downloads the toolchain and all dependencies, then builds everything, no need to specify CROSS_COMPILE manually. Use --local to use sibling repo checkouts instead of cloning. Output binaries go to build/.

Build Individual Targets

# Using the standalone deps
XB=.deps/toolchain/bin/mipsel-linux-
make PLATFORM=T31 CROSS_COMPILE=$XB raptorctl rvd rsd

Note: daemon binaries that link the HAL (rvd, rad) will fail at link time without the Ingenic SDK libs. Compilation succeeding (all .c files compile clean) is sufficient to verify your changes.

ASAN Build (x86, mock HAL)

./build-asan.sh        # AddressSanitizer + UBSan
./build-asan.sh tsan   # ThreadSanitizer

Builds all 14 daemons + tools for x86 with a mock HAL (tests/mock_hal.c). Output goes to asan-out/. RVD and RAD use the mock HAL; all other daemons build natively (no HAL dependency).

Running Tests

Full suite (recommended):

./tests/test-all.sh                   # quick pass (~2 min)
./tests/test-all.sh --soak 300        # with 5-min leak soak
./tests/test-all.sh --tsan            # ThreadSanitizer mode
./tests/test-all.sh --tsan --soak 300 # full TSAN + soak

Runs all four stages: build, unit tests, integration tests, leak/race detection. Fails fast if any stage fails.

Individual test suites:

# Unit tests (204 tests across 17 suites, ASAN)
cd tests && make test

# Integration tests (65 tests — raptorctl, HTTP, RTSP, multi-client,
# SEI timecode, signed-recording verification)
./tests/test-integration.sh

# RIC behavior suite (stub rvd + fake sysfs GPIO in a user
# namespace; RIC_SUITE_STRICT=1 turns skips into failures)
./tests/test-ric.sh

# RAC beep suite (no rad needed; ring capture verified sample-exact)
./tests/test-rac.sh

# IPv6-first / IPv4-fallback proof for every listening daemon
./tests/test-net-fallback.sh

# Leak detection (lifecycle soak under LeakSanitizer)
./tests/test-leak.sh
./tests/test-leak.sh --duration 300        # 5-min soak
./tests/test-leak.sh --tsan                # data race detection
./tests/test-leak.sh --tsan --duration 300 # TSAN + soak

# Sibling repo tests
cd raptor-ipc/tests && make test      # IPC tests (29 tests)
cd raptor-common/tests && make test   # common tests (86 tests)

Fuzz targets (requires clang): these cover the parsers that read untrusted network input before authentication, so test-all runs them time-boxed and CI pins clang for it. Give them longer and a corpus for an actual hunt -- 20 seconds is a smoke test, not a search.

./tests/test-fuzz.sh                          # as test-all runs it (20s each)
./tests/test-fuzz.sh --seconds 600 --corpus ~/fuzz-corpus   # real hunt
make -C fuzz                     # build all fuzzers by hand
./fuzz/fuzz_stun corpus/stun/    # STUN parser (calls production rwd_ice_process)
./fuzz/fuzz_sdp corpus/sdp/      # SDP parser
./fuzz/fuzz_http_auth corpus/auth/  # HTTP auth

Sanitizer and language flags are shared. sanitizer-flags.mk is sourced by build-asan.sh and included by tests/Makefile, so the 24 sources both compile see the same instrumentation. Edit flags there, not in either consumer, and keep values space- and quote-free -- the file has to parse as both sh and make.

Rebuilds are not optional. test-all reuses asan-out/ only when the recorded sanitizer matches, no source is newer than the binaries, and the previous build finished. Before those checks existed, --tsan on a warm tree ran ASan binaries under a TSan banner, and a build that died partway looked complete -- so a broken TSan build and a live data race both hid for weeks. If you are ever unsure what you are testing, rm -rf asan-out/ costs one build.

CI

GitHub Actions (tests.yml, manual dispatch):

  • asan job: full suite + 5-min soak under AddressSanitizer
  • tsan job: full suite + 5-min soak under ThreadSanitizer

Both jobs build from scratch, run 287 unit tests + 142 integration checks + lifecycle soak (concurrent clients, ring reconnect, clean shutdown). Logs are uploaded as artifacts on failure.

A separate Format workflow runs on every pull request and push to main: git-clang-format (pinned to clang-format-19) judges only the lines a change touches against the merge base, so pre-existing drift in untouched files blocks nobody while every new line must be clean. The same gate runs in raptor-hal, raptor-ipc and raptor-common. Its first real catch was a help-string line in the very change that documented the gate's URL schemes.

On-Device Testing via NFS

Test devices mount the build host's home directory at /mnt/nfs. No scp needed -- edit, build, run directly:

# On device:
cd /mnt/nfs/projects/thingino/raptor
./run.sh    # launches configured daemons

clang-format

Always format before committing:

clang-format -i path/to/changed/files.c

The .clang-format in the repo root enforces the project style.

Pre-Commit Checklist

Before every commit:

  1. clang-format -i on all changed .c files
  2. ./build-standalone.sh t31 --local --static -- clean build, zero warnings
  3. ./tests/test-all.sh -- unit + integration + leak check pass
  4. On-device smoke test if touching daemon logic (NFS mount, run binary, exercise via raptorctl)

If any step fails, fix before committing. Do not commit with known sanitizer warnings or test failures.


7. Commit and Push Guidelines

  • Commit messages are concise and imperative. Example: rvd: add stream-stop and stream-start IPC commands
  • Prefix with the component: rvd:, rad:, raptorctl:, hal:, rsd:, build:, tests:, etc.
  • No signatures, no trailers. No Co-Authored-By, no Signed-off-by, nothing after the message body. External PRs are landed with the author preserved and any trailer stripped.
  • Don't push without explicit instruction. Build and test locally first. Confirm with the maintainer before pushing.
  • Don't create releases without explicit instruction.
  • One logical change per commit. A new feature is one commit. A bug fix is one commit. Don't bundle unrelated changes.
  • Behavior changes arrive test-first. Reproduce the defect as a failing test, then fix it. A field bug earns its missing scenario class, not just a regression leg.
  • Diagnostics are mutation-checked. A warning or counter that no test can force to fire is not tested: break the diagnostic in the ways that matter (removed, unthrottled, unreset) and know which leg catches each. State plainly which mutations the legs do not pin.
  • Networking is IPv6-first. Listeners bind the v6 wildcard dual-stack and fall back to v4 only on the no-IPv6 errnos, via the rss_net.h helpers -- never open-coded per daemon. Outbound uses AF_UNSPEC getaddrinfo. tests/test-net-fallback.sh enforces both directions.

8. Common Pitfalls

Embedded-Specific

  • No printf debugging in production code. Use RSS_INFO, RSS_WARN, RSS_ERROR, RSS_DEBUG, RSS_TRACE from rss_common.h. Log levels are filterable at runtime.
  • -Os globally. Never change to -O2/-O3 -- I-cache pressure on MIPS32 makes larger code slower, not faster. Use __attribute__((optimize("O3"))) on specific hot functions if profiling proves it helps.
  • Musl/uclibc differences. free(NULL) is safe. dlopen behavior differs. Page alignment must be 4KB (not 64KB default). The build system handles this via -Wl,-z,max-page-size=0x1000.
  • No filesystem writes in hot paths. Flash storage is slow and has limited write endurance. Rings are in /dev/shm (tmpfs). Config saves are explicit, not automatic.

SDK-Specific

  • Encoder channels share groups. Creating/destroying one channel can affect others in the same group. Always stop the paired JPEG channel before touching its parent video channel.
  • IVS requires FS streaming before start. The framesource channel must be enabled and producing frames before calling IVS_StartRecvPic. The bind chain must include IVS before the framesource is enabled.
  • SDK calls are not thread-safe. All IMP_* calls for a given channel must come from the same thread, or be serialized externally.
  • IVDC mode restrictions. Direct-connect mode changes memory layout and disables certain IPU features. See 27-multi-sensor.md.

IPC-Specific

  • Ring readers must handle overflow. If a consumer falls behind, the producer overwrites old data. Readers detect this via sequence gap and must re-seek to the next keyframe.
  • Control socket responses must always be written. A handler that returns without writing to resp causes the client to hang waiting for a response.
  • rss_ctrl_send_command has a timeout. Default 5 seconds. Don't perform blocking operations (network I/O, disk writes) inside a ctrl handler -- it blocks all other ctrl clients.

9. Cross-Reference Index

TopicDocument
System architecture, daemon roles, IPC20-rss-architecture.md
Build system, repo layout, Buildroot21-rss-build.md
Test suites, ASAN, integration tests22-rss-testing.md
Configuration reference (all options)23-rss-config.md
Ring buffer consumer API and examples24-ring-consumer-guide.md
HAL API (all ops, all SoCs)10-hal-api.md
HAL internals and porting11-hal-internals.md
SoC capabilities matrix12-hal-caps.md
SDK system/encoder/framesource/ISP/audio/OSD01 through 08
WebRTC design (RWD)25-rwd-webrtc-design.md
Day/night IR-cut design (RIC)26-ric-daynight-design.md
Multi-sensor support27-multi-sensor.md
IVS/motion detection28-ivs-detection.md
FPS troubleshooting30-fps-troubleshooting.md