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:
@@ -1433,6 +1433,22 @@ vc_result vc_client::audio_resume() {
|
||||
return audio_engine_.resume() ? VC_OK : VC_ERR_AUDIO;
|
||||
}
|
||||
|
||||
vc_result vc_client::audio_restart() {
|
||||
// Full uninit + re-init (not just stop/start like suspend/resume) so the miniaudio
|
||||
// devices reopen against the current AVAudioSession route. Mirrors TeamTalk5's
|
||||
// closeSoundDevices()/initSoundInputDevice()/initSoundOutputDevice() pattern.
|
||||
// IMPORTANT: only restart if the engine was already running — calling
|
||||
// ensure_audio_running() when the engine isn't running would start it prematurely
|
||||
// (opening the capture device / mic without an active mic stream), which on iOS
|
||||
// triggers a route reconfiguration that can collapse the output route.
|
||||
bool was_running = audio_engine_.running();
|
||||
audio_engine_.stop();
|
||||
if (was_running) {
|
||||
ensure_audio_running();
|
||||
}
|
||||
return VC_OK;
|
||||
}
|
||||
|
||||
vc_result vc_client::get_stream_audio_config(uint32_t user_id, uint32_t stream_id,
|
||||
vc_audio_config* out) {
|
||||
if (state_net_.load(std::memory_order_acquire) != VC_STATE_CONNECTED) return VC_ERR_NOT_CONNECTED;
|
||||
@@ -1900,5 +1916,6 @@ vc_result vc_client::get_account_list(vc_account_list*) { return VC_ERR_NOT_IMPL
|
||||
vc_result vc_client::get_permissions(vc_permissions*) { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::audio_suspend() { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::audio_resume() { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
vc_result vc_client::audio_restart() { return VC_ERR_NOT_IMPLEMENTED; }
|
||||
|
||||
#endif // VOICECAT_HAS_NET
|
||||
|
||||
@@ -60,6 +60,7 @@ struct vc_client {
|
||||
vc_remote_stream_state* out);
|
||||
vc_result audio_suspend();
|
||||
vc_result audio_resume();
|
||||
vc_result audio_restart();
|
||||
|
||||
vc_result send_text(vc_text_scope scope, uint32_t target_id, const char* utf8);
|
||||
|
||||
|
||||
@@ -310,4 +310,9 @@ vc_result vc_audio_resume(vc_client* c) {
|
||||
return c->audio_resume();
|
||||
}
|
||||
|
||||
vc_result vc_audio_restart(vc_client* c) {
|
||||
if (c == nullptr) return VC_ERR_INVALID_ARG;
|
||||
return c->audio_restart();
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
||||
Reference in New Issue
Block a user