fix(ios): stereo mic + A2DP output, add vc_audio_restart ABI
Diagnosed by comparing against TeamTalk5 (Client/iTeamTalk), which achieves stereo mic + A2DP output. Five fixes: 1. configureStereoCapture now calls setPreferredInput + setInputDataSource (mirroring TeamTalk5's SoundDevicesModel). Previously omitted based on incorrect diagnosis that setPreferredInput collapsed A2DP — the real culprit was setPreferredInputNumberOfChannels(2), which neither project uses. 2. New C ABI: vc_audio_restart (full stop + re-init, unlike suspend/resume which only stop/start). Swift wrapper added. The withAudioSuspend wrapper that used it was removed after on-device testing showed it killed all audio (including VoiceOver) when switching presets — the core's set_capture_channels handles engine restart internally. 3. Bluetooth options: Voice Chat preset now includes BOTH .allowBluetoothHFP AND .allowBluetoothA2DP (matching TeamTalk5's UtilSound.swift:228). Previously HFP-only blocked A2DP headphones. 4. Capture channels now reset when switching stereo→mono via selectCaptureChannels/applyPreset. AudioSessionManager tracks activeMicStreamId (set by SessionState on join/leave voice). 5. Docs synced: voice.md, tech-stack.md, architecture.md, PROGRESS.md. Removed stale setPreferredInputNumberOfChannels(2) references. Verified: ctest --preset dev 21/21 green, iOS client builds. Stereo mic + A2DP output still needs on-device debugging — the core recipe is correct but iOS 26 route behavior requires hands-on testing with a debugger.
This commit is contained in:
@@ -178,23 +178,27 @@ Each receiver keeps an **adaptive jitter buffer per ssrc**.
|
||||
|
||||
- Capture and playback run on miniaudio's real-time callbacks (WASAPI / CoreAudio / ALSA).
|
||||
Playback is genuinely stereo end-to-end. **Mic capture** is mono by default; **stereo mic
|
||||
capture** is supported via `vc_set_capture_channels(stream_id, 2)` (e.g. iOS stereo built-in
|
||||
mic via `AVAudioSession.setPreferredInputNumberOfChannels(2)`) — when enabled, the capture
|
||||
device opens in stereo (interleaved L/R) and the encoder receives real stereo PCM (no upmix).
|
||||
A mono mic frame on a stereo channel is upmixed L=R before encoding so the Opus bitstream is
|
||||
still spec-correct stereo. **Screen-audio (`SCREEN_AUDIO`) loopback** captures in the
|
||||
channel's mode — stereo when the channel is stereo (real interleaved L/R, no downmix), mono
|
||||
when the channel is mono — so a stereo music/screen-share channel gets genuine stereo
|
||||
end-to-end. See §9 for the platform-specific loopback mechanism.
|
||||
capture** is supported via `vc_set_capture_channels(stream_id, 2)` — when enabled, the
|
||||
capture device opens in stereo (interleaved L/R) and the encoder receives real stereo PCM
|
||||
(no upmix). A mono mic frame on a stereo channel is upmixed L=R before encoding so the Opus
|
||||
bitstream is still spec-correct stereo. **Screen-audio (`SCREEN_AUDIO`) loopback** captures
|
||||
in the channel's mode — stereo when the channel is stereo (real interleaved L/R, no
|
||||
downmix), mono when the channel is mono — so a stereo music/screen-share channel gets
|
||||
genuine stereo end-to-end. See §9 for the platform-specific loopback mechanism.
|
||||
- **iOS mic capture:** all iOS audio routing is driven from Swift via `AVAudioSession` by the
|
||||
`IOSAudioRouter` singleton *before* the core (miniaudio) opens its device — miniaudio does
|
||||
NOT touch `AVAudioSession` on iOS. Input port selection (`availableInputs`), built-in mic
|
||||
orientation (`setPreferredDataSource`: front/back/top/bottom), polar patterns
|
||||
(`setPreferredPolarPattern`: omni/cardioid/subcardioid/bidirectional), mic processing mode
|
||||
(`.voiceChat` = Standard with AEC/AGC/HPF, or `.measurement` = Raw/Studio with all processing
|
||||
off), Bluetooth mode (`.allowBluetooth` HFP voice vs `.allowBluetoothA2DP` stereo output vs
|
||||
neither), and stereo capture (`setPreferredInputNumberOfChannels(2)` → `vc_set_capture_channels`)
|
||||
are all set from Swift. The core then opens whatever route AVAudioSession has established.
|
||||
off), Bluetooth mode (`.allowBluetoothHFP` HFP voice vs `.allowBluetoothA2DP` stereo output
|
||||
vs neither), and stereo capture (`.stereo` polar pattern + `setPreferredInput` +
|
||||
`setInputDataSource` → `vc_set_capture_channels`) are all set from Swift. The core then
|
||||
opens whatever route AVAudioSession has established. When the user changes audio settings
|
||||
mid-session, `IOSAudioRouter` suspends the core's devices (`vc_audio_suspend`), reconfigures
|
||||
`AVAudioSession`, then restarts the devices (`vc_audio_restart`) so they reopen against the
|
||||
new route — mirroring TeamTalk5's `closeSoundDevices`/`initSoundInputDevice`/
|
||||
`initSoundOutputDevice` pattern.
|
||||
- **DSP engine: see §11.** The original plan was `webrtc-audio-processing` (AEC + NS + AGC +
|
||||
VAD in one tuned module, BSD-licensed) — but it has no working Windows/MSVC build upstream
|
||||
(confirmed via its own issue tracker: GCC-only Meson build, MinGW support unfinished, hard
|
||||
|
||||
Reference in New Issue
Block a user