fix(audio): silent SSB/voice TX on macOS — honour mic native rate + start capture for remote_audio_tx#2930
Conversation
…tart capture for remote_audio_tx Two independent defects left USB/LSB/AM/FM voice TX with no modulating audio on macOS while DAX digital-mode TX worked fine. 1. Mic capture never started without a dax_tx stream The mic_selection=PC handler in MainWindow only started QAudioSource capture when `m_audio->txStreamId() != 0`. txStreamId() is the *dax_tx* stream id, but voice TX flows over *remote_audio_tx*. With no DAX bridge running there is no dax_tx stream, so switching mic_selection to PC for plain SSB never started mic capture — onTxAudioReady never fired and the radio got no PC audio. Add AudioEngine::hasAnyTxStream() (dax_tx OR remote_audio_tx) and gate capture start on that instead. 2. QAudioSource opened at 48 kHz on a device that runs at 16 kHz macTxInputRateCandidates() tried 48000 first. CoreAudio reports isFormatSupported(48000)==true for capture devices that actually run at a lower native rate (e.g. USB webcam mics at 16 kHz), so QAudioSource "opened" successfully — state=Active, error=NoError — but the device delivered zero samples. processedUSecs stayed 0, the push-mode QBuffer never advanced (pos stuck at 0), and TX was silent. Query the device's preferredFormat().sampleRate() and try it first; the existing resampler then converts to 24 kHz radio-native. 48000/44100/24000 remain as fallbacks for devices with no usable preferred rate. Diagnosed by instrumenting onTxAudioReady (mic buffer pos stuck at 0 with QAudioSource state=Active) and QAudioSource::processedUSecs (stuck at 0), then confirming the device's native rate via system_profiler (Current SampleRate: 16000 on the affected webcam mic). Tested on macOS with FLEX-8600 fw 4.x: USB voice TX now produces audio with both a 16 kHz webcam mic and a 48 kHz USB audio interface. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Nice diagnosis on both defects — the processedUSecs=0 with state=Active symptom is exactly the kind of CoreAudio quiet-failure that's hard to track down, and the fix is appropriately surgical.
Defect 1 (hasAnyTxStream()): The new gate correctly mirrors the consumer-side guard in onTxAudioReady() (AudioEngine.cpp:3970 and :3978), which already accepts either stream. Capture-start and capture-consume now agree on which streams matter, which is the right invariant.
One thing worth double-checking on Linux/Windows (already called out in your test plan): the remoteTxStreamReady handler at MainWindow.cpp:2192 only gates on isTxStreaming(), so on those platforms remote_audio_tx becoming ready while mic_selection=PC was already starting capture even before this PR. The new gate in the micStateChanged handler is the symmetric fix for the reverse order (stream ready first, then mic switched to PC). Behavior should be a strict superset of the old gate.
Defect 2 (preferred rate): Ordering puts preferredFormat().sampleRate() after macBluetoothNativeInputRate(), which is the right call — Bluetooth telephony rates are more authoritative for those devices, and macOS sometimes reports preferred=48000 for SCO inputs too. The > 0 && !contains() guard handles the "no usable preferred rate" case cleanly, and the existing m_txResampler (AudioEngine.cpp:4001-4023) does the rate→24kHz conversion regardless of the chosen candidate, so 16 kHz / 32 kHz / etc. all just work.
The change is #ifdef Q_OS_MAC so Linux/Windows are untouched for that piece.
Thanks for the thorough diagnostic write-up — the probe table makes it easy to reproduce and verify. Happy to leave the Linux/Windows voice-TX regression check to a reviewer with that hardware.
|
As mentioned — happy to take a look on the Mac side. Worth a cross-link with #2982 (just opened as a draft): same file (
I've got the same class of webcam-default input device here (Mac mini Apple Silicon, macOS 26.x, FLEX 6300, AE on 73, Nigel G0JKN |
|
Verified on Mac mini Apple Silicon, macOS 26.5.0, FLEX-6300 — both defects fixed end-to-end. Defect #2 (preferred sample rate) — caught directly in our TX-stream startup log: C920's native rate is 32 kHz; Qt/CoreAudio previously claimed Defect #1 (capture-start gate) — also fixed. Capture started despite there being no End-to-end voice TX: slice in USB, Looks ready for review/merge from the macOS angle — happy to provide more detail or test variations on request. 73, Nigel G0JKN |
|
Merged as 07ea5e6. Thanks @pepefrog1234 — the Stale-code audit against current main (today saw merges across the audio path — #2982 TCI mic suppression, #2973 audio summary logging, #2978 BYPASS flag, #2926 audio device prompts, #3004 limiter default) — none touched the buggy site at Independent verification by @nigelfenton on his Mac mini + FLEX-6300 + C920 setup (32 kHz native webcam mic) was the gold-standard test — 9 W forward power into a dummy load with clean SSB modulation, both defects fixed end-to-end. That kind of cross-tester hardware confirmation makes the merge call easy. Linux/Windows TX-voice users on a strict 'no DAX, plain SSB' setup will benefit too — the bug was just much less likely to surface on those platforms because most users have DAX enabled (which set 73, |
Summary
On macOS, USB/LSB/AM/FM voice TX produced no modulating audio (carrier up, dead air) even though DAX digital-mode TX worked fine. Two independent defects stacked together; both are fixed here.
Defect 1 — mic capture never started without a dax_tx stream
MainWindow'smic_selectionhandler only startedQAudioSourcecapture whenm_audio->txStreamId() != 0:txStreamId()is the dax_tx stream id. Voice TX flows over remote_audio_tx (Opus), a different stream. When no DAX bridge is running there is no dax_tx stream, so switchingmic_selectionto PC for plain SSB never started mic capture —onTxAudioReady()never fired and the radio received no PC audio.onTxAudioReady()itself already accepts either stream (if (m_txStreamId == 0 && m_remoteTxStreamId == 0) return;), so only the capture-start gate was wrong.Fix: add
AudioEngine::hasAnyTxStream()(dax_tx or remote_audio_tx) and gate capture start on that.Defect 2 — QAudioSource opened at 48 kHz on a 16 kHz-native device
macTxInputRateCandidates()tried48000first. macOS CoreAudio reportsisFormatSupported(48000) == truefor many capture devices that actually run at a lower native rate (e.g. USB webcam mics at 16 kHz).QAudioSourcethen "opens" successfully —state=Active,error=NoError— but the device delivers zero samples:QAudioSource::processedUSecs()stays0QBufferwrite position never advances (pos()stuck at0)Fix: query
QAudioDevice::preferredFormat().sampleRate()and try it first. The existing resampler converts the device-native rate to 24 kHz radio-native.48000/44100/24000remain as fallbacks for devices that report no usable preferred rate.Diagnosis
Instrumented the TX path on the affected machine:
onTxAudioReady()entrym_micBuffer->pos()stuck at0QAudioSourcehealth @1sstate=Active error=NoError processedUSecs=0system_profiler SPAudioDataType4K SlimFit Cam)Current SampleRate: 16000processedUSecs=0withstate=Activeis the tell-tale of CoreAudio accepting a format the device can't actually source.Test plan
macTxInputRateCandidates()is#ifdef Q_OS_MAC; thehasAnyTxStream()gate change is cross-platform and should be verified on a Windows/Linux voice TX setup.🤖 Generated with Claude Code