diff --git a/apps/chanora_flutter/ios/Runner/AppDelegate.swift b/apps/chanora_flutter/ios/Runner/AppDelegate.swift index b3d7415..ce11818 100644 --- a/apps/chanora_flutter/ios/Runner/AppDelegate.swift +++ b/apps/chanora_flutter/ios/Runner/AppDelegate.swift @@ -153,6 +153,28 @@ import AVFoundation do { try AVAudioSession.sharedInstance().setActive(true, options: []) NSLog("chanora_flutter: AVAudioSession activated on foreground") + // Read back the ACTUAL session state. preferredSampleRate / + // preferredIOBufferDuration are hints; iOS may pick something + // else depending on hardware + currently-engaged effects. + // Without these we can't tell whether VPIO is running at + // 48 kHz mono (what our render callback assumes) or at e.g. + // 44.1 kHz (which would explain the user's broken playback + // \u2014 our render callback would be writing samples at the + // wrong rate, causing pitch + timing artifacts). + let s = AVAudioSession.sharedInstance() + let route = s.currentRoute + let outs = route.outputs.map { "\($0.portType.rawValue)/\($0.portName)" }.joined(separator: ",") + let ins = route.inputs.map { "\($0.portType.rawValue)/\($0.portName)" }.joined(separator: ",") + NSLog( + "chanora_flutter: AVAudioSession actual: " + + "category=\(s.category.rawValue) " + + "mode=\(s.mode.rawValue) " + + "sampleRate=\(s.sampleRate) " + + "ioBufferDuration=\(String(format: "%.4f", s.ioBufferDuration)) " + + "outputs=[\(outs)] " + + "inputs=[\(ins)] " + + "outputVolume=\(s.outputVolume)" + ) } catch { NSLog("chanora_flutter: AVAudioSession setActive failed: \(error)") } diff --git a/apps/chanora_flutter/pubspec.yaml b/apps/chanora_flutter/pubspec.yaml index 71e63ab..2ca8cf0 100644 --- a/apps/chanora_flutter/pubspec.yaml +++ b/apps/chanora_flutter/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.0-rc.8+70 +version: 1.0.0-rc.8+71 environment: sdk: ^3.11.5 diff --git a/crates/chanora_audio/src/ios_voice_unit.rs b/crates/chanora_audio/src/ios_voice_unit.rs index bcb5884..d95397e 100644 --- a/crates/chanora_audio/src/ios_voice_unit.rs +++ b/crates/chanora_audio/src/ios_voice_unit.rs @@ -579,6 +579,48 @@ impl IosVoiceUnit { "ios VPIO audio unit started" ); + // Read back the ACTUAL stream format VPIO accepted on each + // bus (iOS sometimes substitutes its own format if the + // hardware can't satisfy our preference) and the actual + // AVAudioSession sample rate + IO buffer duration. Without + // these we can't tell whether our 48 kHz Int16 mono format + // was honoured or silently downgraded to e.g. 44.1 kHz + // Float32 (which would cause our render callback to write + // i16 values into a buffer iOS interprets as f32 = severe + // distortion). Diagnostic prompted by external review + // pointing out that 'preferredSampleRate' is a hint, not + // a guarantee \u2014 must verify post-init. + match unit.output_stream_format() { + Ok(fmt) => info!( + target: "chanora_audio", + sample_rate = fmt.sample_rate, + channels = fmt.channels, + sample_format = ?fmt.sample_format, + flags = ?fmt.flags, + "ios VPIO actual OUTPUT stream format (post-init)" + ), + Err(e) => warn!( + target: "chanora_audio", + error = %e, + "ios VPIO output_stream_format read failed" + ), + } + match unit.input_stream_format() { + Ok(fmt) => info!( + target: "chanora_audio", + sample_rate = fmt.sample_rate, + channels = fmt.channels, + sample_format = ?fmt.sample_format, + flags = ?fmt.flags, + "ios VPIO actual INPUT stream format (post-init)" + ), + Err(e) => warn!( + target: "chanora_audio", + error = %e, + "ios VPIO input_stream_format read failed" + ), + } + Ok(Self { unit }) } }