2

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.

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you! I believe you because that's exactly what I'm experiencing but where are you seeing that the supported range is [100, 400] ms? Apple docs online don't seem to mention it (I'm looking here). I want high frequency updates because I want an oscilloscope type thing with the least latency I can get. Getting longer buffers during screen lock shouldn't be an issue. Do you have a preference between AVAudioSourceNode and remote-IO? I don't have experience with either.
The range is documented when I look at the header file by command clicking installTap, Xcode 16.2. AVAudioSourceNode is perhaps slightly simpler than the remote io audio unit, however neither should be written in Swift as it can't respect the realtime audio rules. So that means AVAudioSourceNode in Objective-C.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.