Fix RADE RX not decoding on Windows: create dax_rx stream in activateRADE()#1820
Conversation
…RADE() On platforms without a DAX audio bridge (Windows, Linux without PipeWire), startDax() is not compiled in so stream create type=dax_rx is never sent to the radio. RADEEngine::feedRxAudio() was consequently never called because PanadapterStream::daxAudioReady only fires when the radio is actively sending dax_rx VITA-49 packets. Fix: when RADE activates on a non-bridge platform, send stream create type=dax_rx for the RADE slice's DAX channel directly. Register the returned stream ID with PanadapterStream on the statusReceived response. On deactivate, remove the stream only if TCI has no active clients (TCI may have borrowed our stream via the same daxStreamIdForChannel reuse pattern it already uses with DaxBridge streams). If a dax_rx stream for this channel already exists (created by TCI), reuse it rather than creating a duplicate that would deliver double audio to RADEEngine. RADE TX is unaffected — it uses the remote_audio_tx Opus path with dax=0 mic routing, which is platform-independent. Fixes: RADE mode shows "activated" but produces no decoded audio on Windows or Linux builds without HAVE_PIPEWIRE. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Good catch @NF0T — this is a real bug and the fix approach is solid. The root cause analysis is correct: startDax() is compiled out on Windows/non-PipeWire Linux, so the dax_rx VITA-49 stream never gets created and feedRxAudio is never called.
The implementation follows the existing TciServer borrow-vs-create pattern well, and the files changed are tightly scoped to the stated fix.
A few observations:
1. Race between stream create response and RADE deactivation
If the user toggles RADE off very quickly after activation, deactivateRADE() runs disconnect(m_radeDaxStreamConn) — good. But the stream create command is already in-flight. The radio will respond with a status message containing the new stream ID, and since the one-shot connection is now disconnected, m_radeDaxStreamId stays 0. The stream is created on the radio but never removed by us.
This is a narrow window and unlikely to hit in practice, but worth noting. A possible mitigation: in deactivateRADE(), if m_radeDaxStreamId == 0 and the connection was still live (not yet fired), you could set a flag to send stream remove when the response arrives, or just accept the leak as a known limitation documented in the TODO.
2. m_tciServer access without #ifdef HAVE_TCI guard
The deactivation path does m_tciServer && m_tciServer->clientCount() > 0. If TCI support is compiled out, m_tciServer would still be nullptr (it's initialised to nullptr in the header), so the null check saves you. This works, but it relies on the member always being declared — if m_tciServer were ever put behind #ifdef HAVE_TCI in the header, this would break. A comment noting the assumption would be defensive.
3. The daxCh range check (>= 1 && <= 4) is correct — FlexRadio DAX channels are 1–4, matching the existing codebase conventions.
4. Clean scoping — the { } block inside the #if keeps radeSlice and daxCh from leaking into the surrounding scope. Nice.
Overall this looks correct and well-reasoned. The PR description is exemplary — clear root cause, explanation of the TCI interaction, and honest test coverage gaps noted. Thank you for the contribution, and for tracking down a platform-specific issue that's hard to discover without Windows hardware.
Reviewed by AetherClaude
|
Thanks for the thorough review @aethersdr-agent. On the race window: Acknowledged — this is a real edge case. The window is the radio's round-trip latency for the On |
|
Reviewed the diff. This is a clean fix — the root cause analysis is correct (no A few notes after reading through: The one-shot connection pattern is solid. On One thing to watch: If LGTM overall. The borrow-vs-create pattern with TCI and the cleanup guard on |
|
Hey Ryan, thanks for all of the contributions you're making. We have a discord space where all of the AetherSDR users are discussing roadmap plans and ideas. We'd love for you to join in the conversation. |
ten9876
left a comment
There was a problem hiding this comment.
Verified all helper APIs exist (daxStreamIdForChannel, registerDaxStream, clientCount). Correct platform guard matches startDax() gating. TCI-safe cleanup logic is sound. Tested on real Windows 11 + FLEX-8400 hardware. LGTM.
) Cuts a community-contribution-heavy point release. Eight community PRs landed plus three AetherClaude crash fixes. Highlights: Community: - Fix RADE RX not decoding on Windows (#1820, NF0T) - Clamp stale DAX RX backlog on macOS — fixes +1.5s FT8 DT bias (#1822, jensenpat) - Fix TCI DAX resampler crosstalk between slices (#1815, Chaosuk97) - Fix RX applet pan slider with NR active (#1799, Chaosuk97) - Fix TCI RX gain default 1.0 → 0.5 to match applet (#1811, NF0T) - Fix HAVE_SERIALPORT guard (#1812, NF0T) - Seamless ADIF logbook auto-reload (#1801, Chaosuk97) - RAC Canada band-plan corrections (#1817, VE3NEM) AetherClaude crash fixes: - Applet reorder with floating containers (#1745 → #1746) - Lazy-build RadioSetupDialog tabs, dodges Wayland FFmpeg/VDPAU crash (#1776 → #1777) - PanAdapter float-freeze — show-after-reset + direct reparent (#1668 → #1669) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix DAX IQ silently broken on Windows / non-PipeWire Linux DAX IQ has never delivered samples on Windows or non-PipeWire Linux: neither the GUI's DaxIqApplet level meter nor any TCI IQ client receives any data, even though stream creation succeeds and the radio sends UDP packets. Same class of bug as #1820 (RADE RX on Windows): startDax() is gated by #if defined(Q_OS_MAC) || defined(HAVE_PIPEWIRE) and contains the DAX IQ stream-status registration handler. On platforms without an audio bridge it's compiled out entirely. As a result, when iq_start arrives or the user toggles a DAX IQ channel 'On' in the applet: - 'stream create type=dax_iq daxiq_channel=N' is sent OK - The radio creates the stream and sends VITA-49 IQ packets - PanadapterStream receives them but never registers them to a channel (registerIqStream() lives inside startDax()) - PanadapterStream::iqDataReady never fires - DaxIqModel::feedRawIqPacket never runs → GUI meter silent - TciServer::onIqDataReady never runs → TCI clients get nothing Mirror the DAX IQ wiring (status handler + registration + level meter + enable/disable hooks) outside the audio-bridge gate, conditional on the inverse so we don't double-connect on Mac/PipeWire builds where startDax() still does its own wiring lazily when DAX audio is toggled. Verified on Windows (Flex 6700) by capturing 30s of frames into a TCI IQ client. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(dax): restore qCDebug(lcDax) lines in Windows/Linux IQ wiring Mirror the three diagnostic logs the original startDax() block has — removed, ignoring-non-owner, and per-status — so the new ctor-time path on Windows / non-PipeWire Linux produces the same trail when debugging future stream-routing issues. These were exactly the lines that diagnosed this bug in the first place; keeping them on the new path matches the pattern the next debugger will expect. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Nigel Fenton <nigelfenton@me.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Jeremy Fielder <kk7gwy@aethersdr.com>
Fixes #1819
Problem
RADE mode activates successfully on Windows (log shows "RADE mode activated") but produces no decoded audio. The sync indicator never appears and
RADEEngine::feedRxAudiois never called.Root cause:
PanadapterStream::daxAudioReadyonly fires when the radio is sendingdax_rxVITA-49 packets, which only start after the client sendsstream create type=dax_rx dax_channel=N. That command lives insidestartDax(), which is compiled out on non-Mac/non-PipeWire platforms (#if defined(Q_OS_MAC) || defined(HAVE_PIPEWIRE)). On Windows, the stream is never created, sofeedRxAudionever gets called.RADE TX is unaffected — it uses the
remote_audio_txOpus path withdax=0mic routing, which is platform-independent.Fix
When RADE activates on a non-bridge platform, send
stream create type=dax_rxfor the RADE slice's DAX channel directly and register the returned stream ID withPanadapterStreamvia a one-shotstatusReceivedconnection.Follows the same borrow-vs-create pattern as
TciServer:RADEEnginem_radeDaxStreamIdis only set when we created the stream, sodeactivateRADE()knows whether to sendstream removestream removeif TCI has active clients (TCI may have borrowed our stream; removing it would silently kill TCI audio)Test Plan
RADE registered dax_rx ch 1 stream 0x400000bon activationNotes
dax_rxstream lifecycle safety concern (TCI borrowed streams not re-registered if their creator removes them)🤖 Generated with Claude Code