Make your audio unit app container for core audio sandbox safe

12th Aug, 2022 | coreaudio

A typical audio extension for core audio is split into 3 targets:

  • A framework that contains the audio code, some/all UI code, AUAudioUnit and AUViewController
  • The audio extension, which embeds the framework
  • A container app that registers the audio extension, and either demonstrates the audio unit functionality or provides a useful standalone audio application. It embeds the framework and extension.

While developing an audio extension you may have ignored sandboxing for these targets. This is fine for development but when it comes to submitting the container application to the app store the container app and extension must have sandbox enabled.

Enable sandbox mode with an entitlements file

This is the easy bit. It's likely that there is already a .entitlements file in the container app and extension targets.

If not you can create this file and check in XCode in the build settings for the target. Filter for entitlement and look at the Code Signing Entitlements value. This should show the filepath XCode is expecting or if it's not there you can set it to the file you created.

Then right click the file in the XCode file inspector and select Open as > Source code and edit to look like this:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.app-sandbox</key>
    <true/>
</dict>
</plist>

Of course if you had other entitlements defined keep them!

Now you can archive your application and submit to the store. Right? Well maybe not...

NSOSStatusErrorDomain Code=-3000 "invalidComponentID"

The container app has 2 main purposes:

  • Register the audio extension so it can be used by any host software like Garageband or Logic Audio Pro
  • Create an instance of the audio unit inside a AVAudioEngine session to either demonstrate or use the audio unit functionality

After enabling sandbox mode I found the second part was not working.

To create the AVAudioUnit instance required for AVAudioEngine we use

AVAudioUnit.instantiate(
    with: AudioComponentDescription, 
    options: AudioComponentInstantiationOptions = [], 
    completionHandler: @escaping (AVAudioUnit?, Error?
) -> Void

The completion handler error was set to Error Domain=NSOSStatusErrorDomain Code=-3000 "invalidComponentID" and the audio unit was nil.

What happened?

The fix

My AudioComponentDescription was the normal combination of type, subtype and manufacturer plus the zero flags:

    func auComponentDescription() -> AudioComponentDescription {
        AudioComponentDescription(
            componentType: kAudioUnitType_MusicDevice,
            componentSubType: fourCharCodeFrom("abcd"),
            componentManufacturer: fourCharCodeFrom("wxyz"),
            componentFlags: 0,
            componentFlagsMask: 0
        )
    }

I had never paid much attention to the flags because the audio unit just had standard features, but it turns out there is a flag that is ESSENTIAL: AudioComponentFlags.sandboxSafe.rawValue

    func auComponentDescription() -> AudioComponentDescription {
        AudioComponentDescription(
            componentType: kAudioUnitType_MusicDevice,
            componentSubType: fourCharCodeFrom("abcd"),
            componentManufacturer: fourCharCodeFrom("wxyz"),
            componentFlags: AudioComponentFlags.sandboxSafe.rawValue,
            componentFlagsMask: 0
        )
    }

With this the AVAudioUnit.instantiate again gave me my AVAudioUnit instance and all was well again.

And if you are wondering about the fourCharCodeFrom function it is a little utility to convert strings to FourCharCode

    func fourCharCodeFrom(_ string : String) -> FourCharCode {
        assert(string.count == 4, "String length must be 4")
        var result : FourCharCode = 0
        for char in string.utf16 {
            result = (result << 8) + FourCharCode(char)
        }
        return result
    }