Skip to content

Fix iOS AVAudioSession before engine init#22

Open
txbrown wants to merge 1 commit intomainfrom
fix/ios-audio-session-setup
Open

Fix iOS AVAudioSession before engine init#22
txbrown wants to merge 1 commit intomainfrom
fix/ios-audio-session-setup

Conversation

@txbrown
Copy link
Copy Markdown
Collaborator

@txbrown txbrown commented May 5, 2026

iOS defaults to AVAudioSessionCategorySoloAmbient with a system-chosen buffer size (~4096 frames on device). Elementary's runtime is initialised with a fixed block size of 512, causing a mismatch that produces garbage audio (audible as a low-frequency square wave) immediately on app launch.

Fix: set AVAudioSessionCategoryPlayback with MixWithOthers and AllowBluetoothA2DP, request a 512-frame preferred buffer duration, activate the session before creating AVAudioEngine, then derive the actual block size from session.IOBufferDuration so the runtime always matches what the engine delivers per callback.

I didn't notice this issue on Midicircuit because it still has its own AVAudioEngine setup and some custom native code to bridge between it and react-native-elementary-audio.

…er mismatch

iOS defaults to AVAudioSessionCategorySoloAmbient with a system-chosen buffer
size (~4096 frames on device). Elementary's runtime is initialized with a fixed
block size of 512, causing a mismatch that produces garbage audio (audible as a
low-frequency square wave) immediately on app launch.

Fix: set AVAudioSessionCategoryPlayback with MixWithOthers and AllowBluetoothA2DP,
request a 512-frame preferred buffer duration, activate the session before creating
AVAudioEngine, then derive the actual block size from session.IOBufferDuration so
the runtime always matches what the engine delivers per callback.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the iOS native audio startup path so AVAudioSession is configured before AVAudioEngine is created, with the goal of preventing the startup-time buffer-size mismatch that currently produces corrupted audio on launch.

Changes:

  • Configure AVAudioSession during Elementary initialization, including playback category, options, preferred I/O buffer duration, and activation.
  • Extend init logging to include the granted IOBufferDuration.
  • Derive the Elementary runtime block size from the active session’s actual buffer duration instead of hard-coding 512.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ios/Elementary.mm
Comment on lines +25 to +29
NSError *sessionError;
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback
mode:AVAudioSessionModeDefault
options:AVAudioSessionCategoryOptionMixWithOthers |
Comment thread ios/Elementary.mm
Comment on lines +23 to +42
// Configure session before engine: iOS default (SoloAmbient, ~4096-frame buffers)
// mismatches Elementary's block size and produces garbage audio on launch.
NSError *sessionError;
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryPlayback
mode:AVAudioSessionModeDefault
options:AVAudioSessionCategoryOptionMixWithOthers |
AVAudioSessionCategoryOptionAllowBluetoothA2DP
error:&sessionError];
if (sessionError) {
NSLog(@"[Elementary] Failed to set audio session category: %@", sessionError);
}
[session setPreferredIOBufferDuration:(512.0 / 48000.0) error:&sessionError];
if (sessionError) {
NSLog(@"[Elementary] Failed to set preferred IO buffer duration: %@", sessionError);
}
[session setActive:YES error:&sessionError];
if (sessionError) {
NSLog(@"[Elementary] Failed to activate audio session: %@", sessionError);
}
Comment thread ios/Elementary.mm
Comment on lines +107 to 111
// Use granted IOBufferDuration — iOS may not honor the preferred value exactly.
int bufferSize = (int)round(outputFormat.sampleRate * session.IOBufferDuration);
if (bufferSize <= 0 || bufferSize > 4096) bufferSize = 512; // safety fallback
NSLog(@"[Elementary] Runtime block size: %d frames", bufferSize);
self.runtime = std::make_shared<elem::Runtime<float>>(outputFormat.sampleRate, bufferSize);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants