Fix DAX coexistence stream ownership#2266
Closed
jensenpat wants to merge 2 commits intoten9876:mainfrom
Closed
Conversation
d6a2a3f to
a3e9728
Compare
This was
linked to
issues
May 1, 2026
a3e9728 to
1ff6716
Compare
This was referenced May 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #2194. Follows up on #2222 and #2226, and covers the DAX/TCI coexistence cluster around #2203, #2210, #2223, and #2224. #2221 is intentionally out of scope because it is packaging/runtime related rather than radio/DAX flow behavior.
This PR stabilizes AetherSDR coexistence with SmartSDR DAX2 and FlexRadio API/firmware 4.2.18 ownership semantics. AetherSDR no longer claims DAX TX at GUI attach time, keeps Windows external-DAX2 paths route-only, preserves hosted DAX on macOS/PipeWire, keeps TCI TX explicit, and makes LAN VITA UDP startup resilient when another Flex client is already using a radio-side IP/port tuple.
Architecture Changes
Explicit DAX TX Policy
DAX TX stream creation is now reason-gated through a small policy helper instead of being an implicit side effect of generic audio setup.
Reasons are explicit:
HostedDaxBridgefor AetherSDR-owned hosted DAX on macOS/PipeWire.TciTxAudiofor TCI TX paths that truly need AetherSDR to own adax_txstream.ExternalDaxRouteOnlyfor Windows/SmartSDR DAX2 routing where AetherSDR may settransmit dax=1/0but must not create or steal the stream.GenericAudioRecreatefor PC audio recreation, which must not createdax_tx.RadioModel::ensureDaxTxStream()now requires a reason, and there are no argless call sites left. GenericcreateAudioStream()no longer creates DAX TX. GUI attach still does not create DAX TX.Windows External DAX2 Coexistence
On Windows, AetherSDR is treated as external-DAX2 route-only. It keeps the radio-side TX DAX flag aligned with the active TX slice mode so SmartSDR DAX2 can route digital TX audio, but it does not create an AetherSDR-owned
dax_txstream for GUI attach, PC Audio, generic audio recreation, or route-only mode.Foreign SmartSDR-owned DAX TX/RX streams are logged and ignored rather than adopted into AetherSDR state.
Hosted DAX and TCI
Hosted DAX platforms still create an AetherSDR-owned
dax_txstream when the hosted bridge actually needs one. TCI TX now requests DAX TX explicitly asTciTxAudio, so the dependency is visible in code and logs instead of being hidden behind startup or generic audio paths.A follow-up bug from local testing was fixed here too: bare Flex stream-create responses such as
84000000are parsed as hexadecimal for DAX TX create responses. Without that, AetherSDR could store the wrong TX stream ID and send outbound DAX/TCI packets to a non-existent stream.TCI TX slice handoff now also preserves the current DAX TX route while the radio status stream is between
tx=0on the old slice andtx=1on the new slice. This avoids briefly sendingtransmit set dax=0during a TCI PTT edge.LAN VITA UDP Registration
LAN VITA UDP sockets now bind to an OS-assigned port on the selected local IPv4 address using
DontShareAddress. The selected address order remains:AnyIPv4After binding, AetherSDR primes
radio:4992, sendsclient udpport <localPort>, and if the radio returns0x500000A9orPort/IP pair already in use, the LAN path closes, rebinds to a fresh ephemeral port, primes again, and retriesclient udpport.The retry policy is LAN-only. SmartLink/WAN
startWan, routed UDP behavior,client udp_register, and user port-forwarding assumptions were not changed.Diagnostics
The patch adds concise logs for:
client_handle=0 ip=0.0.0.0.client udpportrequest, registration, collision, retry registration, and retry failure.Compatibility Notes
Older radio/API behavior is preserved where ownership data is missing. Missing
client_handleremains legacy-compatible where intended.client_handle=0remains compatible unless paired with the explicit dead endpointip=0.0.0.0.AetherSDR-hosted DAX still owns its streams on hosted-DAX platforms. Windows SmartSDR DAX2 is allowed to own the actual DAX streams while AetherSDR only maintains radio-side route state.
Validation
Local macOS validation:
git diff --checkpasses.cmake --build build --target AetherSDR -- -j10passes.ctest --test-dir build --output-on-failure -j10passes.Test coverage added/updated:
Windows validation is next: this branch will be pulled on Windows, built there, and tested with SmartSDR DAX2 coexistence.
👨🏼💻 Generated with OpenAI Codex (GPT-5.5 Pro 4/23) and tested by @jensenpat