---
title: Rendering Spatial Audio from Bluetooth headphones
framework: audiotoolbox
role: article
role_heading: Article
path: audiotoolbox/rendering-spatial-audio-from-bluetooth-headphones
---

# Rendering Spatial Audio from Bluetooth headphones

Create a Spatial Audio extension that allows Bluetooth headphones to track the wearer’s head movements for spatial audio playback.

## Overview

Overview To integrate a Bluetooth headphone into the Spatial Audio routing system, implement an audio extension called an audio unit. The system adds the audio unit to the audio signal chain when someone is listening to audio on the headphones and Spatial Audio is active. When the listener disables Spatial Audio or connects to a different audio device, the system removes your audio unit. You deliver this renderer by subclassing AUHeadTrackingBinauralRenderer, which is itself a subclass of AUAudioUnit. Package your subclass as a Spatial Audio App Extension; see Creating an audio unit extension for an example of creating an audio app extension. Your extension runs in a process encapsulated by the system for security and performance. In addition to the audio unit, publish your headphone’s head-tracking capabilities using AudioAccessoryKit so the system can match your device to the correct renderer. The system exclusively controls spatialization state through three KVO properties your audio unit subclass observes: deviceUID identifies the paired device and, together with AUHeadTrackingBinauralRenderer/headTrackingIsEnabled, signals when your renderer begins data retrieval; AUHeadTrackingBinauralRenderer/isBypassed is the system’s safety valve, signaling when it removes your audio unit from the signal chain. important: You can develop and test an app that uses AUHeadTrackingBinauralRenderer on devices in any region. The class currently builds only for development or Ad Hoc testing. The class will support App Store submission and alternative distribution at a later time. Customer installations of your app can use the framework only on devices located in the EU that are signed in with an Apple Account with an EU country or region. Deliver the audio unit Subclassing AUHeadTrackingBinauralRenderer is your entry point into the Spatial Audio routing system. The system identifies your audio unit using the AudioComponentDescription type, which requires the component type kAudioUnitType_HeadTrackingBinauralRenderer, the four-character code 'auht'. Declare your audio unit in your App Extension’s Info.plist under the AudioComponents key. Include the type, a four-character subtype unique to your audio unit, a four-character manufacturer code, a display name, and a version number: <key>AudioComponents</key> <array>     <dict>         <key>type</key>         <string>auht</string>         <key>subtype</key>         <string>abcd</string>         <key>manufacturer</key>         <string>Mfgr</string>         <key>name</key>         <string>AcmeCorp: Spatial Renderer</string>         <key>version</key>         <integer>1</integer>     </dict> </array> The App Extension’s principal class must conform to AUAudioUnitFactory and implement createAudioUnit(with:) to return an instance of your subclass: final class MyAudioUnitFactory: NSObject, AUAudioUnitFactory {     func createAudioUnit(         with desc: AudioComponentDescription     ) throws -> AUAudioUnit {         return try MyBinauralRenderer(componentDescription: desc)     } } Observe head-tracking state Respond when a listener turns head tracking on or off by observing the AUHeadTrackingBinauralRenderer/headTrackingIsEnabled property. The system sets this KVO-compliant property; you don’t have to set it yourself. Set up your observation in your subclass initializer or in allocateRenderResources(), and store the returned token so the observation stays active: var headTrackingObservation: NSKeyValueObservation?

headTrackingObservation = observe(     \.headTrackingIsEnabled,     options: [.new] ) { [weak self] _, change in     guard let self, let enabled = change.newValue else { return }     if enabled {         self.startHeadTracking()     } else {         self.stopHeadTracking()     } } Retrieve head-tracking data To identify the Bluetooth headphone currently matched to your audio unit observe the deviceUID property. The system sets this KVO-compliant property lets you know when to begin or stop retrieving head tracking data; you don’t have to set it yourself. The value is a String? containing a 32-bit unsigned integer in decimal notation, not a UUID, the same value used by AudioAccessoryKit. Don’t store this value; it isn’t a UUID, despite being a string. When the property becomes non-nil, use the value to request head orientation data from AudioAccessoryKit. When the property becomes nil, stop any active data retrieval and release associated resources. Wait until both deviceUID is non-nil and AUHeadTrackingBinauralRenderer/headTrackingIsEnabled is true before starting data retrieval: var deviceUIDObservation: NSKeyValueObservation?

deviceUIDObservation = observe(     \.deviceUID,     options: [.new] ) { [weak self] _, change in     // Begin retrieval only when `headTrackingIsEnabled` is also `true`.     guard let self else { return }     if let uid = change.newValue.flatMap({ $0 }) {         self.beginRetrievingHeadData(deviceUID: uid)     } else {         self.stopRetrievingHeadData()     } } Handle bypass If your Audio Unit exceeds its power budget or causes audio dropouts, the system sets AUHeadTrackingBinauralRenderer/isBypassed to true to remove your Audio Unit from the signal chain. Observe the property and respond gracefully when the system bypasses your audio unit. Release compute-intensive resources, stop head-tracking data retrieval, and reset your renderer state. When ``AUHeadTrackingBinauralRenderer/isBypassedreturns tofalse`, restore your rendering resources and resume normal operation, as shown below. var bypassObservation: NSKeyValueObservation?

bypassObservation = observe(     \.isBypassed,     options: [.new] ) { [weak self] _, change in     // `isBypassed` can be set from any thread; synchronize access     // to shared rendering state appropriately.     guard let self, let bypassed = change.newValue else { return }     if bypassed {         self.stopRetrievingHeadData()         self.releaseRenderResources()     } else {         self.restoreRenderResources()     } } important: The system can set AUHeadTrackingBinauralRenderer/isBypassed from any thread. Keep your observer implementation thread-safe.

## See Also

### Audio Units

- [Creating an audio unit extension](avfaudio/creating-an-audio-unit-extension.md)
- [Creating custom audio effects](avfaudio/creating-custom-audio-effects.md)
- [Incorporating Audio Effects and Instruments](audiotoolbox/incorporating-audio-effects-and-instruments.md)
- [Debugging Out-of-Process Audio Units on Apple Silicon](audiotoolbox/debugging-out-of-process-audio-units-on-apple-silicon.md)
- [AUAudioUnit](audiotoolbox/auaudiounit.md)
- [AUAudioUnitBus](audiotoolbox/auaudiounitbus.md)
- [AUAudioUnitBusArray](audiotoolbox/auaudiounitbusarray.md)
- [AUAudioUnitPreset](audiotoolbox/auaudiounitpreset.md)
- [AUAudioUnitV2Bridge](audiotoolbox/auaudiounitv2bridge.md)
- [AUHeadTrackingBinauralRenderer](audiotoolbox/auheadtrackingbinauralrenderer.md)
- [AudioUnitExtensionCopyComponentList(_:)](audiotoolbox/audiounitextensioncopycomponentlist(_:).md)
- [AudioUnitExtensionSetComponentList(_:_:)](audiotoolbox/audiounitextensionsetcomponentlist(_:_:).md)
- [AUAudioUnitFactory](audiotoolbox/auaudiounitfactory.md)
