Skip to content

feat(venc): SVC-T per-layer FEC split — outgoing.enhancePort + thinEnhance (both backends)#85

Closed
josephnef wants to merge 1 commit into
OpenIPC:masterfrom
josephnef:feat/svct-per-layer-fec-split
Closed

feat(venc): SVC-T per-layer FEC split — outgoing.enhancePort + thinEnhance (both backends)#85
josephnef wants to merge 1 commit into
OpenIPC:masterfrom
josephnef:feat/svct-per-layer-fec-split

Conversation

@josephnef

@josephnef josephnef commented Jun 12, 2026

Copy link
Copy Markdown

Summary

waybeam already encodes SVC-T temporal layers (resilience presets rally/range/fpvMI_VENC_SetRefParam) and marks droppable frames TRAIL_N in the bitstream — but every frame still left on one UDP socket/SHM ring, so wfb-ng could only apply one FEC setting to everything. The README even called this out: "Real-world refPred benefit on a lossy link depends on the sender applying per-layer FEC priority… Without that integration the pyramid is roughly neutral."

This PR is that integration: it splits the stream by temporal layer at the transport level, so the wifibroadcast link can protect each layer differently.

What end-users get

  • Unequal error protection (UEP) over wfb-ng. Base-layer frames (IDR + parameter sets + base-P — the frames that must arrive for the stream to stay decodable) travel on their own port and can ride strong FEC. Enhancement frames (droppable by design — base never references them) travel on a second port with light or no FEC. Same total bitrate buys noticeably more link robustness: redundancy is spent where loss actually hurts.
  • Graceful degradation under congestion. When the link degrades, the weakly-protected enhancement channel dies first — and the result is not corruption/smearing but a clean frame-rate step-down to the base rate (½ at rally, deeper for range/fpv). Video keeps flowing; it just gets smoother→sparser instead of breaking.
  • Operational thinning without touching the encoder. The GS can simply stop tunnelling the enhance stream to halve the air bandwidth — no API call, no re-encode, no IDR storm. For encoder-side thinning there's outgoing.thinEnhance (live toggle): enhancement frames aren't sent at all, while mirror-mode SD recording keeps the full frame rate (record full, stream base).
  • Zero new ground-station logic. Both channels carry one RTP session (same SSRC, same sequence space). The GS forwards both wfb_rx outputs to the same decoder port; standard RTP reordering merges them. Any existing player (pixelpilot, gstreamer, ffplay) just works.
  • Safe by default. enhancePort=0 keeps today's single-channel behavior byte-for-byte. The enhance channel is auxiliary: if it fails to open (or a live server retarget can't host it), waybeam logs a warning and continues single-channel — the base video link never dies because of it.

How FEC with wfb-ng works now (with this PR)

wfb-ng applies FEC per stream (per UDP port → radio_port). The split maps temporal layers onto that model 1:1 — per-layer FEC becomes pure configuration:

# Camera: refPred preset + port split
curl "http://<cam>/api/v1/set?video0.resilience=rally"
curl "http://<cam>/api/v1/set?outgoing.enhancePort=5605"
curl "http://<cam>/api/v1/set?outgoing.server=udp://127.0.0.1:5600"

# Air unit: one wfb_tx per layer, FEC budget spent where it matters
wfb_tx -p 0  -u 5600 -K /etc/drone.key -k 8 -n 12 wlan0   # base    — strong FEC
wfb_tx -p 16 -u 5605 -K /etc/drone.key -k 8 -n 9  wlan0   # enhance — light FEC

# Ground: both rx outputs feed ONE decoder port — RTP seq space merges them
wfb_rx -p 0  -u 5600 -K /etc/gs.key wlan1
wfb_rx -p 16 -u 5600 -K /etc/gs.key wlan1

With shm:// output, a nonzero enhancePort instead creates a second ring /dev/shm/<name>_enh for a second wfb_tx instance (zero-syscall path preserved for both layers).

Companion: single wfb-ng TX process for all streams — implemented, please test

Stock wfb-ng needs one wfb_tx/wfb_rx pair per stream — with this PR a typical camera already runs base + enhance (+ audio + sidecar = up to four pairs), and each FEC encoder schedules its blocks blind to the others. The single-process counterpart is now implemented and merged in a wfb-ng fork:

josephnef/wfb-ng#1 — Multi-stream wfb_tx: per-stream FEC profiles, priority drain, waybeam SHM input (merged into the fork's master; full design, commit-by-commit notes and verification there)

One wfb_tx serves all streams: per-stream radio_port + FEC k/n + session key + radiotap (per-stream MCS/BW), strict drain priority (base > enhance — under radio saturation the enhancement stream sheds load first), direct venc_wfb SHM ring input (vendored venc_ring, survives waybeam respawns), and per-stream runtime control (wfb_tx_cmd ... set_fec_stream -r <radio_port>). Without -y the binary behaves exactly like upstream.

How to test with this PR

# build the fork (air unit)
git clone https://github.com/josephnef/wfb-ng && cd wfb-ng && make all_bin

# waybeam: SVC-T preset + SHM output + per-layer split
curl "http://<cam>/api/v1/set?video0.resilience=rally"
curl "http://<cam>/api/v1/set?outgoing.server=shm://venc_wfb"
curl "http://<cam>/api/v1/set?outgoing.enhancePort=1"   # any nonzero arms the venc_wfb_enh ring

# ONE wfb_tx for both layers (UDP inputs u=5600/u=5605 work too)
./wfb_tx -K drone.key -i <link_id> -C 7003 \
    -y "shm=venc_wfb,p=0,k=8,n=12,T=15" \
    -y "shm=venc_wfb_enh,p=1,k=8,n=9,T=15" \
    wlan0

# ground: stock wfb_rx per stream, both into ONE decoder port (shared RTP seq space)
wfb_rx -p 0 -u 5600 -K gs.key -i <link_id> wlan1
wfb_rx -p 1 -u 5600 -K gs.key -i <link_id> wlan1

# live per-layer FEC retune of the droppable layer only
./wfb_tx_cmd 7003 set_fec_stream -r 1 -k 8 -n 10

Verified so far: full wfb-ng test suite (37/38, incl. 6 new multi-stream cases), loopback runs, SHM producer kill/respawn re-attach. Real-radio reports very welcome — especially the saturation behavior (enhancement stream should shed first; watch the per-stream PKT_S lines) and decoder-side merge quality.

Also in the fork: josephnef/wfb-ng#2 (draft) — upstream wfb-ng PR #450 (driver TX-buffer traffic shaper, by tipoman9) rebased onto the multi-stream base, exploring how buffer-level backpressure composes with priority drain.

Upstreaming to svpcom/wfb-ng is a separate step (their policy is Telegram-first discussion with bench results); test reports from this PR feed exactly that.

Other follow-ups carried from the plan: 3-way per-sublayer split (T1 vs T2 → three FEC classes), per-layer split for the dual-stream ch1, enhance-channel stats in /api/v1/transport/status, and sidecar layer-tagging.

Implementation notes

  • outgoing.enhancePort (uint16, MUT_RESTART, default 0=off): second Star6eOutput/MarukoOutput instance derived from the base URI (venc_config_derive_enhance_uri() — UDP port override / SHM _enh suffix / unix:// rejected). Gated on ref_base > 0 (without refPred no frame classifies as enhancement — logged). Validated against collisions with server/audio/sidecar ports (HTTP 409). Live outgoing.server retargets follow on both channels or disable the split.
  • outgoing.thinEnhance (bool, MUT_LIVE): gates the send before packetization — RTP seq not consumed (receiver sees a gapless base stream), recorder writes are downstream of the gate and unaffected. IDR on resume so the re-appearing layer starts from a clean reference chain.
  • include/venc_svct.h: shared refType constants + venc_svct_frame_is_enhance()/_is_notforref() classifiers replace the duplicated per-backend *_REFTYPE_ENHANCE_P_NOTFORREF defines. TRAIL_N rewriting semantics unchanged (NOTFORREF only).
  • Per-frame routing in both frame loops; the RTP session state lives above the output instance (Star6eVideoState/MarukoStreamRuntime), which is what keeps one SSRC/seq space across both ports for free. Sidecar pressure observation + transport trailer now describe the channel each frame actually used.
  • All six config layers updated per AGENTS.md sync rules (struct/parser/printer/g_fields[]+aliases/WebUI/default JSON); HTTP API contract bumped to 0.11.0.

Validation

  • Build/test commands run:
    • make build SOC_BUILD=star6e
    • make build SOC_BUILD=maruko
    • make test1699 passed, 0 failed (new: classifier truth table, URI derivation, config load/save/roundtrip incl. byte-equal layout, collision 409s, thinEnhance 501/200 live-set, maruko config mirror)
    • make verify (both backends + webui-check) and make pre-pr
  • Runtime smoke checks performed:
    • Star6E — bench offline (192.168.1.13 unreachable); validation steps documented below
    • Maruko — bench offline (192.168.2.12 unreachable)

Pending bench validation (devices were offline): rally + enhancePort=5605 → tcpdump expects ~50/50 frame split, base port only IDR/base frames; merged-forwarding decodes at full rate, base-only at ½ rate; thinEnhance live toggle + IDR-on-resume; live server retarget follows on both ports; enhancePort=0 regression. ⚠️ One open assumption: refType==3 (ENHANCE_P_REFBYENHANCE) follows the vendor enum layout but only appears with ref_enhance > 1 (range/fpv) — flagged in code; if base frames ever classify as enhance on the bench, the classifier narrows to ==4 only.

Checklist

  • VERSION updated for this PR (0.16.0 → 0.17.0).
  • HISTORY.md updated with this PR's user-visible changes.
  • Documentation updated for changed behavior (README per-layer FEC section + outgoing table).

HTTP API Contract (required when HTTP behavior changes)

  • documentation/HTTP_API_CONTRACT.md updated in this PR (new subsection + capability rows + changelog).
  • Endpoint/payload/status changes match the contract exactly (409 collision messages, 501-without-callback, live/restart semantics — unit-tested).
  • Contract version updated appropriately: 0.11.0 (non-breaking, additive).

HTTP API Design Guardrails

  • Endpoints are lean and focused on direct operational value (two fields on the existing /api/v1/set, no new endpoints).
  • JSON payloads remain simple and descriptive.
  • No unnecessary endpoint proliferation or generic/ambiguous fields were introduced.

Notes

  • Star6E-first policy: both backends are implemented symmetrically in this PR (the feature is output-layer, no new SDK calls); bench validation will run Star6E first, then Maruko, once the devices are back online.

🤖 Generated with Claude Code

…hance (both backends)

Route SVC-T enhancement-layer frames (refType ENHANCE_*) through a second
transport channel — same host, dedicated UDP port, or a "<name>_enh" SHM
ring — while base frames stay on outgoing.server. Both channels carry ONE
RTP session (shared SSRC + seq space), so the ground station forwards both
wfb_rx outputs to a single decoder port and RTP reordering merges them.
This enables unequal error protection on wifibroadcast links: wfb-ng
applies FEC per stream, so the always-decodable base layer can ride strong
FEC while the droppable enhancement layer rides light FEC.

- outgoing.enhancePort (MUT_RESTART, 0=off): gated on a refPred resilience
  preset (rally/range/fpv); non-fatal bring-up; port-collision validation;
  live outgoing.server retargets follow on both channels or disable the
  split with a warning.
- outgoing.thinEnhance (MUT_LIVE): drop enhancement frames pre-
  packetization (seq not consumed, mirror-mode recorder keeps full rate);
  IDR issued on resume.
- include/venc_svct.h: shared refType constants + classifiers replace the
  duplicated per-backend NOTFORREF defines; TRAIL_N rewrite unchanged.
- Sidecar transport trailer now describes the channel each frame used.
- HTTP API contract 0.11.0; README per-layer FEC example; tests for the
  classifier, URI derivation, config layers, collision 409s, live toggle.

Host tests: 1699 passed. make verify + pre-pr green. Bench validation
pending (devices offline); refType==3 classification flagged for device
confirmation on enhance>1 presets.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@josephnef

Copy link
Copy Markdown
Author

The single-process wfb-ng counterpart mentioned in the description is no longer a future item — it's implemented and merged in josephnef/wfb-ng#1 (multi-stream wfb_tx: per-stream FEC profiles, strict base>enhance drain priority, direct venc_wfb/venc_wfb_enh SHM ring input, per-stream runtime FEC control). The PR description now has a How to test with this PR section with the exact waybeam + wfb_tx commands.

Real-radio test reports are very welcome — especially saturation behavior (the enhancement stream should shed load first; per-stream PKT_S stdout lines show it) and decoder-side merge quality with both wfb_rx outputs on one port.

@snokvist

Copy link
Copy Markdown
Collaborator

Thanks for the PR and the suggestion, this is out of scope for waybeam venc to implement .

@snokvist snokvist closed this Jun 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants