Repository navigation
Jaguar1 async RX ring: residual ~1-2% RX loss on MediaTek (PixelPilot#6 follow-up) — hypothesis + dead ends, needs device validation #330
Description
Summary
Follow-up to the now-closed OpenIPC/PixelPilot#6 (no video on MediaTek). The no-video regression was fixed and merged (#317, 16 KB RX URBs restored). But on-device validation surfaced a separate, residual RX-quality problem that is not fixed: video comes up but is "not perfect — artifacts when the screen changes." This issue documents the investigation so the next person doesn't repeat the dead ends.
Status: root cause is a hypothesis (async RX ring under transient load); NOT reproduced at realistic load on a bench rig; no validated fix. Needs validation on a real MediaTek device or a clean-RF setup.
Symptom
- Reporter: RTL8812AU @ USB2 (480 Mbit), MediaTek Dimensity 810 (Redmi Note 11 5G).
- floppyhammer's older
fix-no-videobuild (pre-refactor devourer) = "perfect." Current master = "not perfect, artifacts when the screen changes." - Logcat from the imperfect run:
wfb-ng PKT_LOST≈ 1–2% (steady drips of 1–3 pkts),FecChangeController: bumping FECrepeatedly, NALU pipeline shows ~2% of NALUs dropped after FEC couldn't recover. The MediaTek HW decoder itself reportsDrop: 0— so the loss is upstream, in RX delivery, not decode. "Screen changes" = video keyframes = a sudden burst of radio packets.
Hypothesis (unproven)
The RX path was rewritten from a synchronous single bulk-IN read (floppyhammer's era) to an async multi-URB ring (UsbTransport::rx_loop / devourer_rx_cb in src/UsbTransport.cpp). In the async ring the caller's consumer (on_data → FrameParser::recvbuf2recvframe → in PixelPilot, wfb-ng FEC+AES+UDP) runs inline inside devourer_rx_cb, on the single libusb event-pump thread, BEFORE the URB is resubmitted. Under a keyframe burst the inline cost can outrun URB reposting, the posted-URB depth collapses, and the chip RX-FIFO overflows → the 1–3-pkt drips. The synchronous model had no per-URB-resubmit dependency (the chip FIFO absorbed between-read gaps).
This is plausible but was not demonstrated to be the cause at realistic load (see below).
What was built to investigate
A bench harness (2 adapters, one as TX flood, one as the 8812AU RX on a USB2 hub):
- A per-frame busy-spin injected at the stable
recvbuf2recvframeentry (DEVOURER_RX_SINK_SPIN_US, scaled by the aggregated packet count) to model the inline consumer cost (wfb-ng ≈ 20–50 µs/frame). - Dual-era RX binaries: the synchronous build (floppyhammer era) vs current async build.
- A keyframe-burst emulation added to
txdemo(DEVOURER_TX_BURST_ON_MS/_OFF_MS: flood ON ms, idle OFF ms, repeat). - Delivery measured as received-frames / aired-frames.
(Scripts + the experimental fix branch are preserved; ping if you want them.)
What was found
- The mechanism is possible. At an extreme inline cost (spin = 100 000 µs/frame) the async ring's delivery craters (≈18 700 → 800 frames). So the ring can be starved by a slow inline consumer.
- But the rig does not reproduce it at realistic load. At ≤ 3000 µs/frame (≈60–150× the real wfb-ng cost) there is zero delivery loss. A fast x86 + xhci host has ~100× more USB-completion/CPU headroom than the phone.
- The rig is fundamentally unsuitable to see a 1–2% effect. Near-field (two adapters ~cm apart) forces a lossy link: MCS rates won't decode at any TX power (EVM saturation), so the only usable rate is 6M legacy at ~62% delivery. 38% baseline RF loss buries the reporter's 1–2% signal.
What did NOT help (dead ends — don't repeat)
- Decoupling the consumer from the pump thread (bounded queue in
devourer_rx_cb+ a single worker thread runningon_data, resubmitting the URB immediately). Implemented and A/B-tested. It regressed delivery in both steady-state and burst tests (e.g. under bursts: old inline held ~62% across spin 0/300/1000; decoupled = 59% / 62% / 25%). Root of the regression: the inline ring's slower reaping produces fat RX-agg buffers the chip FIFO absorbs, while the decoupled fast-reaping produces thin buffers + hard queue drops. Do not ship this fix on current evidence. - Deeper URB ring (
DEVOURER_RX_URBS8 → 32 → 128): null in steady state (all ~23 000). Depth can't help a consumer-limited steady state; it might help transients, but the rig couldn't show it.
Recommended next steps
- Validate on the actual MediaTek device (Dimensity/Helio/MT6765) — the only environment where the 1–2% transient effect is visible. Instrument per-commit or A/B the sync vs async RX on-device.
- Or reproduce on a clean-RF setup (real antenna separation + attenuators) so a high-delivery, high-MCS link is possible and a 1–2% effect is detectable — the near-field 2-adapter bench cannot do this.
- Do not change the RX architecture on bench evidence alone. The merged Jaguar USB RX: 16 KB URBs + matching agg caps — restore the MediaTek host fix #317 already fixed the real no-video regression; the residual quality gap is unproven and no rig-validated fix exists.
References
- No Video on MediaTek Device (Samsung Galaxy A15)? PixelPilot#6 (closed — no-video, fixed)
- Jaguar USB RX: 16 KB URBs + matching agg caps — restore the MediaTek host fix #317 (merged,
b7c50ec— 16 KB RX URBs) - Restore the MediaTek no-video fix: 16 KB RX URBs via devourer + heap RX ring PixelPilot#107 (open — submodule bump + heap RX ring)
- RX ring:
src/UsbTransport.cpp(rx_loop,devourer_rx_cb); parser:src/jaguar1/FrameParser.cpp(recvbuf2recvframe)
Activity
rxdemo Android USB-fd path + autonomous Quest RX-ring harness (issue #…
josephnef commentedon Aug 6, 2026
Update — likely root-caused on the TX side, needs a MediaTek retest.
Since this was filed, the same class of "MediaTek ground-station artifacts under load" was chased down on a different link and root-caused on the transmit path, not the RX ring this issue hypothesizes:
DEVOURER_TX_RETRY_LIMIT(default 0, no MAC retries; reliability comes from FEC). Shipped into PixelPilot via tx: programmatic runtime TX-mode API; drop DEVOURER_TX_HT_MCS gate #111, hardware-confirmed by the docs/bench: on-air TX throughput vs wfb-ng (svpcom) + SDR duty tool #109 reporter.Why this is relevant here: the bench investigation documented above modeled the RX half (inline consumer cost + incoming keyframe bursts) and never reproduced the 1–2% loss — plausibly because the real mechanism was the uplink retry flood, which the rig never ran. The RX-ring dead-ends recorded in this issue still stand as documented (don't re-run them); they just may not have been the cause.
@493505110 — could you retest on your Dimensity 810 (Redmi Note 11 5G) with current PixelPilot master (devourer with the retry-limit fix, default 0)? The "not perfect, artifacts when the screen changes" behaviour is the thing to check. If it's gone we'll close this; if it recurs, we have a genuinely separate RX-side residual and this stays open with a real repro.
493505110 commentedon Aug 9, 2026
Thank you for your works!
I retest it, the behaviour was disappeared,video perfect
log if needed: pixelpilot_log_20260809_113145.txt