Make the ground-station uplink configurable, and stop it starving video RX creating color blocks - #110
Conversation
The adapter is half duplex, so every uplink frame is a receive blackout.
The uplink was hardcoded to k=1/n=5 at MCS0 and Transmitter::sendPacket
emits a block's fragments back-to-back, measuring ~102 frames/s in bursts
of 10-12 inside 1-2 ms. A burst blinds the receiver long enough to drop
CONSECUTIVE video fragments, the one loss pattern the downlink FEC cannot
repair -- it sits pinned at its maximum setting and still fails, the
decoder gets partial frames, and the unwritten part of the output buffer
shows as colour blocks.
This was latent until devourer 5f099ee ("drop DEVOURER_TX_HT_MCS gate")
made the uplink actually reach the air: before it, fixed_rate stayed at
MGN_1M, and CCK is illegal on the 5 GHz channels this link runs on, so the
frames were never transmitted. The uplink was silently dead and the radio
was effectively receive-only.
Expose the uplink's airtime as settings rather than constants, because the
right trade-off depends on the airframe: a short-range quad can spend
uplink margin freely, a long-range wing cannot, and a ground station with a
separate RC/telemetry radio wants no uplink here at all. Adaptive ->
Uplink... offers enable/MCS/redundancy, stored in SharedPreferences and
applied on the next link start, matching the existing LDPC/STBC lifecycle.
Out-of-range values are rejected rather than clamped: transmitting at a
rate the operator did not choose is worse than keeping the previous one.
Defaults move one MCS step and leave redundancy alone. Measured at 5 GHz
ch161/20 MHz on an RTL8812AU, in fragments/s the downlink FEC could not
recover:
mcs 0, n 5 9.78 - 15.82 (previous hardcoded values)
mcs 1, n 5 2.18 <- new default
mcs 3, n 2 0.12
uplink off 0.00
~3 dB of uplink margin buys a 7x reduction. Redundancy is deliberately not
the knob turned by default -- losing mavlink is worse than losing video.
The gain is superlinear in airtime because shorter blackouts stop clipping
runs of fragments, and the downlink FEC absorbs isolated losses.
Spacing the five fragments 8 ms apart was tried and is worse: total lost
fragments fall (15.8 -> 10.4) but artefacts become more frequent, as the
same damage spreads over more downlink FEC blocks and one damaged block is
one visible glitch. The lever is total airtime, not smoother airtime.
Each measurement was validated as actually transmitting (TxFrame
countPIncoming > 0 plus the expected fixed_rate in the devourer log) before
its numbers were used; the uplink can die silently, which invalidates a run
that otherwise looks clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015LuniKiNRWAcTvaRV8ZhH8
|
@kkbin505 you are right to point this out, there seems to be a regression after devourer update. However the old devourer + alink worked for me on rtl8812au, so I think it's not the MCS issue. increasing the MCS decreases the air time, so the issue is less visible.
since the PR can help to mitigate the issue and adds a useful feature (MCS switch for uplink in the menu), I would like to keep the code, but the PR and commit description should be updated. WDYT? |
|
P.S. please keep the default as is, I think the TX issue is unrelated to MCS |
|
I think this should be a correct fix for the TX issue (+related change in PixelPilot to disable retries). Currently devourer retries every packet 12 times which leads to the channel overflow. |
Make the ground-station uplink configurable, and stop it starving video RX
Fixes the persistent colour-block corruption on RTL8812AU ground stations (see Issue #109 ).
The problem
The adapter is half duplex: every uplink frame is a receive blackout. The uplink was hardcoded to
k=1, n=5at MCS0, andTransmitter::sendPacketemits a block's fragments back-to-back, which measures as ~102 frames/s in bursts of 10-12 inside 1-2 ms. Those bursts blind the receiver long enough to drop video fragments — the one loss pattern the downlinkso the decoder gets partial frames and paints the rest of the buffer as colour blocks. That is the H.265 encoding, the compressor block is dynamic, so the colour blocks are random.
This was latent until devourer
5f099ee("drop DEVOURER_TX_HT_MCS gate") made the uplink actually reach the air. Before thatfixed_ratestayed atMGN_1M, and CCK is illegal on the 5 GHz channels this link runs on, so the frames were never transmitted — the uplink was silently dead on my device. (So there is no issue with the official release 0.23)The change
The uplink airtime is configurable through the menu rather than fixed as a hardcoded constant, because the optimal trade-off depends on the airframe. A short-range quad can afford to allocate more airtime to the uplink, whereas a long-range fixed-wing aircraft cannot. A ground station using a separate RC or telemetry radio may not need an uplink here at all. Until a dynamic optimization algorithm is implemented, this is the most practical compromise I have found.
Adaptive → Uplink… exposes three controls, stored in
SharedPreferencesand applied on the next link start (same lifecycle as the existing LDPC/STBC options):New JNI entry points
nativeSetUplinkEnabled/nativeSetUplinkMcs/nativeSetUplinkFecNmirror the existingnativeSetUseLdpcpattern. Out-of-range values are rejected rather than clamped — silently transmitting at a rate the operator did not choose is worse than keeping the previous one.Choice of default
Measured on an RTL8812AU at 5 GHz ch161/20 MHz. The metric is
lost_last_secondfrom the adaptive-link telemetry: video fragments the downlink FEC could not recover.The baseline was measured twice, 9.78 and 15.82, which is the honest spread for a config that is losing 5-15 fragments a second — the exact figure moves with how much video the drone is sending and with ambient traffic. Every other row is a single run. What is not ambiguous is the ordering: each step down in uplink airtime moves the loss by roughly an order of magnitude, and
muting the uplink removes it entirely.
T
Colour blocks were still occasionally visible at the default in my testing. It is a large improvement, not a complete fix — the honest end of the range is the uplink-off row, and everything between is a trade.
Note the improvement is superlinear in airtime — halving per-frame transmit time cut loss 7×, not 2×. Shorter blackouts stop clipping runs of fragments, and the downlink FEC absorbs isolated losses fine.
What I tried that did not work
Keeping the burst and spacing the five fragments 8 ms apart. Total lost fragments dropped (15.8 → 10.4) but the artefacts became visibly more frequent — the same damage spread over more downlink FEC blocks, and one damaged block is one visible glitch. Pacing is the wrong instinct here; the lever is total airtime.
Testing
Ground station: Samsung Galaxy S22 (SM-S901U1, Android 16), RTL8812AU (
0bda:881a), 5 GHz channel 161 @ 20 MHz. Each configuration run 30+ s, 200-1200 telemetry samples, statistics from logcat. Every run was validated as actually transmitting (TxFramecountPIncoming > 0and the expectedfixed ratecode in the devourer log) before its numbers were used — the uplink can die silently, which invalidates a run that otherwise looks clean.Also tested on Google Pixel 1, same issue and same fix.
Not tested at long range; the MCS default trades uplink margin, so that is the axis to check before trusting it on a distant airframe.
Fixes #109