fix(ios): VPIO silent playback + quiet speaker in stereo/studio presets
Two on-device bugs in the native iOS Voice-Processing path (Swift-only; no core/ABI change). 1. Voice Chat (VPIO) silent playback: doStartMicStream() called audioRestart() BEFORE startStream, so when the engine was already running (a remote stream had started it) it reopened with external_capture=false and opened a hardware miniaudio capture device. The announce-result restart then early-returns (engine already running) so that device was never dropped and fought the AVAudioEngine VPIO unit, silencing playback. Now: setExternalPlayback first, then startStream (stores external_feed synchronously), THEN audioRestart() — the core reopens in full external mode (no hardware devices). Added VPIO diagnostics: graph/route formats at start, ring written/read totals at teardown. 2. Stereo Mic / Studio quiet earpiece: the .builtInMicBtA2dp presets omit .defaultToSpeaker (it breaks A2DP) and skip forceSpeaker, so with no Bluetooth connected output pinned to the quiet receiver. New IOSAudioRouter.applyA2dpSpeakerFallback() overrides to the built-in speaker when no external (A2DP/wired/AirPlay) output is present and clears the override when one is — called after activation and on device-change route changes.
This commit is contained in:
@@ -230,13 +230,16 @@ final class SessionState {
|
||||
// runs in external mode (no hardware mic/playback). The mic stream is started with
|
||||
// externalFeed so the core skips the hardware capture device; setExternalPlayback makes
|
||||
// it skip the hardware playback device and deliver the mix to IOSVoiceProcessingEngine.
|
||||
//
|
||||
// ORDER MATTERS: set the external-playback flag now, but defer audioRestart() until
|
||||
// AFTER startStream (below) so the MIC LocalStream — which carries external_feed=true —
|
||||
// already exists when ensure_audio_running() derives external_capture. Restarting before
|
||||
// the stream exists makes the core reopen a hardware capture device that is never dropped
|
||||
// (the announce-result restart early-returns because the engine is already running); that
|
||||
// lingering miniaudio capture unit then fights the AVAudioEngine VPIO unit on the same
|
||||
// .voiceChat session and silences VPIO playback.
|
||||
let useVPIO = IOSAudioRouter.shared.currentConfigUsesVoiceProcessing
|
||||
if useVPIO {
|
||||
client.setExternalPlayback(true)
|
||||
client.audioRestart() // flip any already-running (pre-join) engine into external mode
|
||||
} else {
|
||||
client.setExternalPlayback(false)
|
||||
}
|
||||
client.setExternalPlayback(useVPIO)
|
||||
|
||||
let desc = StreamDescriptor(kind: .mic, deviceId: voiceState.currentDeviceId, label: "Mic",
|
||||
externalFeed: useVPIO)
|
||||
@@ -259,6 +262,10 @@ final class SessionState {
|
||||
client.setCaptureChannels(streamId: streamId, channels: channels)
|
||||
}
|
||||
if useVPIO {
|
||||
// The external-feed MIC stream now exists, so restart the core into full
|
||||
// external mode (no hardware capture/playback, mixer-timer only) — mic and
|
||||
// speaker are owned entirely by the VPIO engine, which we start right after.
|
||||
client.audioRestart()
|
||||
IOSVoiceProcessingEngine.shared.start(
|
||||
client: client, micStreamId: streamId, captureChannels: channels)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user