iosaudioiokotlin-multiplatformavaudiosession

How can I set my iOS AVAudioSession to return input buffers of <0.1 s?


I'm trying to get short audio samples into my iOS app (in Kotlin Multiplatform). Here's my relatively simple setup (sampleRateInHz is defined elsewhere):

val bufferSize: AVAudioFrameCount = 512u

val session = AVAudioSession.sharedInstance()

session.setCategory(AVAudioSessionCategoryPlayAndRecord, null)
session.setMode(AVAudioSessionModeMeasurement, null)

session.setPreferredSampleRate(sampleRateInHz.toDouble(), null)
session.setPreferredIOBufferDuration(bufferSize.toDouble() / session.sampleRate, null)
session.setActive(true, null)

val engine = AVAudioEngine()
val input = engine.inputNode
val bus: AVAudioNodeBus = 0u
val format = input.inputFormatForBus(bus)

input.installTapOnBus(bus, bufferSize, format) { buffer, time ->
    // ...use the data
}

val success = engine.startAndReturnError(null)

I'm using a sample rate of 48 kHz (which seems to be the default anyways). I'm targeting a buffer size of 512 frames which comes out to be a duration about 0.0107 s. No matter what I do it seems to not let me go below 4800 frames (= 0.1s). According to Apple's docs, it seems that the buffer duration should be able to go well below 0.1 s but that's not what I'm seeing here.

Logging the sample rate and IO buffer duration shows that the values are being correctly set (48 kHz and ~0.0107 s) but my AVAudioEngine insists on giving me a minimum of 4800 frames. Increasing the desired buffer size above 4800 frames works great but I still can't go lower. I've tried other devices as well as the iOS simulator but I've been seeing the same results everywhere.


Solution

  • Without saying why you want a smaller buffer size I could simply recommend you slice the larger buffers into smaller ones, so I'll assume you want a smaller buffer size to lower capture latency.

    Your problem is that AVAudioNode.installTap() is documented to not do what you want:

    Supported range is [100, 400] ms.

    AVAudioEngine taps are designed for low/medium frequency updates. If you want higher frequency updates you can try inserting your own subclass of AVAudioSourceNode into the audio AVAudioEngine graph or replacing AVAudioEngine with a remote-IO audio unit.

    With either of these solutions you will likely receive ~10ms buffers without even modifying the AVAudioSession preferredIOBufferDuration, except during screen lock. In any case iOS and AVAudioSession are free to ignore your buffer duration chnage request, so don't depend on it working.