Yes, exactly, the maximumFramesToRender is overwritten by the system anyway.
Juts a few precisions to be sure I explained clearly.
First a bit of context. I've been working on iOS apps with real-time audio synthesis, processing and analysis code for several years. Until recently, I was using AUGraph with the render callback mechanism. I've been playing with AVAudioEngine for a while and started to switch my apps to it last year, when Apple annonced they will soon deprecate AudioToolbox and AUGraph. So now I'm using AVAudioEngine mainly with "home-made" audio units to embed my real-time code, and only in online mode.
I've allways believed you're supposed to set the maxFramePerSlice of the output audio unit yourself, and that this is the one which makes sense for the whole graph. But I found the previsouly mentionned page recently, stating that you're not supposed to set maxFramePerSlice on the output node (or remoteIO within the good old AUGraph).
Here is what I observed during my tests with AUAudioUnits inside AVAudioEngine. To see it happen, you can override your AUAudioUnit's maximumFramesToRender setter with the following line:
super.maximumFramesToRender = maximumFramesToRender;
and put a NSLog or breakpoint inside it.
- The maximumFramesToRender property of AUAudioUnits is set every time the AVAudioEngine prepares, or starts if not previously prepared, just before allocateRenderResourcesAndReturnError is called.
- The value of maximumFramesToRender set to all AUAudioUnits inside the AVAudioEngine seems to the one from the OutputNode.
- This value can be modified live by the system, even if you set it with the previously mentionned workaround. It happens especially when the input is enabled or disabled, or when the audio I/O configuration change (ConfigurationChange notification of AVAudioEngine). This behaviour does not seem to be the same on every iPhone and iPad, I think it depends on the audio hardware chip.
One example of the weird things that can happen to illustrate this behaviour:
- Set the AVAudioSession to standard 48kHz sample rate with the PreferedIOBufferDuration you need. I say 48kHz because this has been the new standard for a few years, since the iPhone 6s I think. Audio hardware of some of the newest phones don't even support 44.1kHz anymore. Build an AVAudioEngine with some nodes, custom AUAudioUnit etc, and connect everything with a 48kHz format. When you start the AVAudioEngine, the standard maximumFramesToRender used for every nodes is usually (always?) 4096.
- While the app is running, connect a bluetooth audio speaker.
- The AVAudioSession switches to 44.1kHz, the AVAudioEngine is reset (meaning the ressources of all nodes are deallocated) and the ConfigurationChange notification is triggered. If you don't change the internal connections and start back the AVAudioEngine as is, the nodes will keep working at 48kHz, and the output node will take care of the 48Khz to 44.1kHz sample rate conversion. The thing is that the output node still has a 4096 maximumFramesToRender, but at 44.4kHz this time. In order to be able to satisfy that, the AVAudioEngine will then set the maximumFramesToRender of each nodes working at 48khz to something around 4460 when it starts (or prepares) again after the configuration changed, before reallocating the ressources.
To conclude:
- It seems that you're not supposed to control the maximumFramesToRender of the output node, and therefore of the whole graph.
- You have to consider in your programming that maximumFramesToRender will change sometimes and that the ressources will be deallocated and reallocted each time the configuration changes. The maximumFramesToRender can have weird values if there are sample rate conversions involved.
- All you can do is specify a preferedIOBufferDuration in the AVAudioSession to control the buffer size and therefore the audio latency of your app. While your app stays in foreground, it seems the system is quite obedient about that.
- When your app goes in background or when the screen goes to sleep, the system can decide to use large audio buffers and it seems we can't do much about it...
I may be wrong about some of these assumptions and conclusions, but unfortunately there are not much precise answers out there about all these questions...