AVAudioEngine thread-safety

I've been wrestling with a problem related to AVAudioEngine. I've posted a couple questions recently to other forums, but haven't had much luck, so I'm guessing not many people are encountering this, or the questions are unclear, or perhaps I'm not asking in the most appropriate subforums. So, I thought I'd try here in the 'Concurrency' forum, as using concurrency would be one way to solve the problem.


The specific problem is that AVAudioPlayerNode.play() takes a long time to execute. The execution time seems to be proportional to the value of AVAudioSession.ioBufferDuration, and can be from a few milliseconds for low buffer durations to over 20 milliseconds at the default buffer duration. These execution times can be an issue in real-time applications such as games.


An obvious solution would be to move such operations to a background thread using GCD, and I've seen various posts and articles that do this. Here's a code example showing what I mean:


import AVFoundation
import UIKit

class ViewController: UIViewController {
    private let engine = AVAudioEngine()
    private let player = AVAudioPlayerNode()
    private let queue = DispatchQueue(label: "", qos: .userInitiated)

    override func viewDidLoad() {
        super.viewDidLoad()

        engine.attach(player)
        engine.connect(player, to: engine.mainMixerNode, format: nil)

        try! engine.start()
    }

    override func touchesBegan(_ touches: Set, with event: UIEvent?) {
        queue.async {
            if self.player.isPlaying {
                self.player.stop()
            } else {
                self.player.play()
            }
        }
    }
}


In this scenario all AVAudioEngine-related operations would be serial and never concurrent/parallel. The audio system would never be accessed simultaneously from multiple threads, only serially.


My concern is that I don't know whether it's safe to use AVAudioEngine in this way. More generally, I'm not sure what should be assumed about any API for which nothing specific is said about thread safety. In such cases, can it be assumed that access from multiple threads is safe as long as only one thread is active at any given time? (The 'Thread Programming Guide' touches on this, but doesn't appear to address audio frameworks specifically.)


The narrowest version of my question is whether it's safe to use GCD with AVAudioEngine, provided all access is serial. The broader question would be what assumptions should or should not be made about APIs and thread safety when it's not specifically addressed in the documentation.


Any input on either of these issues would be greatly appreciated.

Post not yet marked as solved Up vote post of Jesse1 Down vote post of Jesse1
1.9k views

Replies

I've just written code which makes the same assumption about AVAudioEngine thread safety, and I'd like to know the answer as well.

I share the similar concerns about the general lack of documentation about thread safety.

When I had less iOS experience, I created a project based on GameKit and struggled with unreliable behavior. Years later I wondered if the problems might have been caused by various GameKit network request completion handlers being called on threads other than the main thread, and I didn't know enough yet to consider this possibility, as it was not mentioned in the documentation. Now I write assert(Thread.isMainThread) everywhere I suspect there's even the slightest chance an API completion handler might not be called on the main thread.

Based on my past experience with other parts of AVFoundation, my instincts tell me that it might be safe to restrict all interaction with a single AVAudioEngine object to a background thread.

If my code blows up in the next few months, I'll post again here.