Skip to content

unrecoverable loss / colour blocks #109

Description

@kkbin505

RTL8812AU: ground-station uplink starves the video receiver (unrecoverable loss / colour blocks)

Symptom

On an RTL8812AU receiver, current master shows persistent video corruption: the top
~1/3 of the frame decodes, the rest is large pink/blue blocks. It is not intermittent — it is
present continuously, at any signal strength.

The picture is what an H.26* decoder produces from a partially delivered frame: the macroblock
rows it received are drawn, and the rest of the output buffer is never written.

Measured on a Samsung Galaxy S22 (SM-S901U1, Android 16) and Google Pixel 1 (), RTL8812AU (0bda:881a), 5 GHz

Ground station A Ground station B
Device Samsung Galaxy S22 (SM-S901U1) Google Pixel (1st gen, sailfish)
SoC Snapdragon 8 Gen 1 (taro) Snapdragon 821 (msm8996)
Android 16 (SDK 36) 10 (SDK 29)
ABI arm64-v8a arm64-v8a

channel 161, 20 MHz, at ~60 dB SNR.

Image Image

Root cause

The ground-station uplink transmit is starving the receiver. The adapter is half duplex, so
every uplink frame is a receive blackout.

WfbngLink.cpp configures the uplink as k=1, n=5 — every message is put on the air five
times — and Transmitter::sendPacket emits a block's FEC fragments in a tight loop with no
spacing. Combined with the ~10 Hz adaptive-link cadence plus mavlink, the measured result is:

~102 uplink frames/second, in bursts of 10-12 frames inside 1-2 ms

From logcat (07:40:30, one adaptive-link period):

07:40:30.007  message 1785249630:2000:2000:13:11:2000:60.956657:0:-1:5:phbe
07:40:30.007  Packet sent successfully  (x8   <- 8 frames inside one millisecond)
07:40:30.008  Packet sent successfully  (x4)
07:40:30.108  message 1785249630:...              <- next period, 100 ms later
07:40:30.108  Packet sent successfully  (x8)
07:40:30.109  Packet sent successfully  (x2)

Because the burst is contiguous, the video fragments lost are consecutive — the one loss
pattern the downlink FEC cannot repair. The link reports FEC already bumped to its maximum
setting (5) and still failing.

Why this only appeared recently

This is not a devourer regression, though a devourer change is what exposed it.

Bisecting the devourer submodule (14468b6 … 73f1cb4, 163 commits) against a fixed PixelPilot
tree landed on devourer 5f099ee — "tx: programmatic runtime TX-mode API; drop
DEVOURER_TX_HT_MCS gate" (2026-06-27)
. That commit made send_packet honour the radiotap
HT-MCS index unconditionally:

-      static const bool ht_mcs_enabled = std::getenv("DEVOURER_TX_HT_MCS") != nullptr;
-      if (ht_mcs_enabled && (mcs_known & IEEE80211_RADIOTAP_MCS_HAVE_MCS)) {
+      if (mcs_known & IEEE80211_RADIOTAP_MCS_HAVE_MCS) {

Before it, fixed_rate stayed at the MGN_1M default. CCK is a 2.4 GHz-only modulation, and
this link runs on 5 GHz channel 161
— so the chip never actually transmitted those frames.

The uplink was silently dead. The radio was effectively receive-only, which is why older builds
show no loss. 5f099ee made the uplink real for the first time, and 102 frames/s of transmit
started costing the receiver its packets.

The corollary is worth stating plainly: on release_0_23 and earlier, adaptive-link feedback
and the mavlink uplink were not working at all
on a 5 GHz channel. Pinning devourer back
"fixes" the colour blocks by silencing the uplink again.

Evidence

Same RF environment, same session, metric is lost_last_second from the adaptive-link telemetry
(WfbngLink.cpp) — video fragments the downlink FEC could not recover.

Build SNR lost/s zero-loss samples
master as-is (n=5, MCS0, back-to-back) 59.8 dB 15.82 4.9 %
n=5, fragments spaced 8 ms apart 61.3 dB 10.42 9.0 %
n=2 53.3 dB 2.15 62 %
n=2 + uplink MCS3 57.0 dB 0.12 94 %
uplink muted entirely (control) 56.3 dB 0.00 100 %
devourer pinned pre-5f099ee (control) 60.0 dB 0.26 93 %

The muted-uplink control is the decisive one: with RtlJaguarDevice::send_packet returning
early and nothing else changed, loss goes to exactly zero and the colour blocks disappear.

Spacing the burst makes it worse, not better

Spreading the five fragments over 40 ms lowered total lost fragments (15.8 → 10.4) but made the
artefacts visibly more frequent. The same damage is spread across more downlink FEC blocks,
and one damaged block is one visible glitch. The lever that works is less airtime, not
smoother airtime — worth knowing before anyone reaches for pacing.

Ruled out

Each of these was tested on S22:

  • SIMD/NEON FEC (ZFEX_USE_ARM_NEON etc.) — a differential harness compiling zfex.c twice
    into one binary, scalar vs NEON, ran 60 000 randomised encode/decode trials on the target SoC
    with byte-identical results.
  • Concurrent bulk-IN URBs (devourer 7701ec5) — measured 0.02 lost/s.
  • Jaguar1 async URB RX queue (bad37a8), RX URB infinite timeout (d31f915).
  • RX aggregation thresholds and RX buffer size (bbc7385 reduced both; measured 0.00 lost/s).
  • Renderer / decoder path — the AI-feature branch on a release_0_23 base shows no blocks.
  • wfb-ng submodule — identical in both good and bad configurations.

Environment

  • Samsung Galaxy S22 (SM-S901U1), Google Pixel 1
  • RTL8812AU, 0bda:881a, USB 2.0 high speed
  • PixelPilot 34837ed, devourer 73f1cb4, wfb-ng 0da5279
  • 5 GHz channel 161, 20 MHz

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions