You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
floppyhammer's older fix-no-video build (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 FEC repeatedly, NALU pipeline shows ~2% of NALUs dropped after FEC couldn't recover. The MediaTek HW decoder itself reports Drop: 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 recvbuf2recvframe entry (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 running on_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_URBS 8 → 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.
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
fix-no-videobuild (pre-refactor devourer) = "perfect." Current master = "not perfect, artifacts when the screen changes."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_cbinsrc/UsbTransport.cpp). In the async ring the caller's consumer (on_data→FrameParser::recvbuf2recvframe→ in PixelPilot, wfb-ng FEC+AES+UDP) runs inline insidedevourer_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):
recvbuf2recvframeentry (DEVOURER_RX_SINK_SPIN_US, scaled by the aggregated packet count) to model the inline consumer cost (wfb-ng ≈ 20–50 µs/frame).txdemo(DEVOURER_TX_BURST_ON_MS/_OFF_MS: flood ON ms, idle OFF ms, repeat).(Scripts + the experimental fix branch are preserved; ping if you want them.)
What was found
What did NOT help (dead ends — don't repeat)
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.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
References
b7c50ec— 16 KB RX URBs)src/UsbTransport.cpp(rx_loop,devourer_rx_cb); parser:src/jaguar1/FrameParser.cpp(recvbuf2recvframe)