Atmos, spatial and multichannel audio for Dark Cosmos
Atmos, spatial and multichannel audio for Dark Cosmos
Section titled “Atmos, spatial and multichannel audio for Dark Cosmos”Research date 2026-10-08. Read-only; nothing in bullet-heaven or godot-tvos was changed.
Fork inspected at ~/Claude/godot-tvos/godot @ efa009712e. SDK headers checked: AppleTVOS27.0.sdk.
- True Dolby Atmos (objects) from real-time game audio: not reachable. [Likely] Your prior belief holds: no public tvOS/iOS API takes real-time PCM plus object metadata and emits Atmos. The only Atmos output path is AVPlayer playing pre-encoded E-AC-3 JOC (or AC-4/TrueHD sources), and that can’t take a live game mix.
- Discrete 5.1/7.1 LPCM over HDMI from Apple TV: reachable, but not with Godot as it is. [Likely] tvOS exposes up to 8 output channels via
AVAudioSession.setPreferredOutputNumberOfChannels. Godot’s iOS/tvOS CoreAudio driver never asks for them, so today it is always stereo on Apple TV. - AirPods head-tracked spatial audio for a game: reachable on tvOS 18+/iOS 18+/macOS 15+ through the AudioToolbox Spatial Mixer AU (
kAudioUnitProperty_SpatialMixerEnableHeadTracking) plus the self-servicecom.apple.developer.coremotion.head-poseentitlement. [Likely] That is a few days of fork work. - The bigger gap is the game, not the engine. [Certain]
audio/audio_manager.gdplays everything through non-positionalAudioStreamPlayerwith no panning at all, so a surround-capable driver would carry the same centred stereo mix to the front L/R pair. Godot’sAudioStreamPlayer2Donly ever writes channel pair 0 (front L/R) even in 5.1/7.1 mode (scene/2d/audio_stream_player_2d.cpp~L182). Real surround needs game-side positioning work too.
1. Dolby Atmos from real-time audio
Section titled “1. Dolby Atmos from real-time audio”- [Likely] AVPlayer is the only observed Atmos path on iOS/tvOS. A developer on the Apple forums (Nov 2024): “the only way I’ve seen an iOS/tvOS device output Dolby Atmos is through AVPlayer”. They had filed a feature request because even
AVSampleBufferAudioRendererfed E-AC-3 JOC doesn’t output Atmos. No Apple staff contradicted this. (forum 750372) - [Certain] The tvOS 27 SDK has no Atmos-encoding or object-output API. The only “Atmos” symbol in AVFAudio/AudioToolbox/CoreAudioTypes is the read-only
AVAudioSession.renderingModeenum valueAVAudioSessionRenderingModeDolbyAtmos(tvOS 17.2+). It reports what the system is doing with loaded content; you can’t set it. There’s anec-3format ID but no evidence of an on-device E-AC-3/JOC encoder. Real-time JOC encoding is Dolby-licensed tech that Apple doesn’t expose. - [Likely] AVAudioEngine/PHASE/spatial mixer do not render to Atmos over HDMI. They render to the route’s channel layout (stereo, 5.1 or 7.1 PCM) or binaural. A 2022 tvOS thread found SceneKit/AVAudioEngine
outputNodestuck at 2 channels on an Atmos system, with no answer. (forum 700337) PHASE has been on tvOS since 17.0, but nothing in its docs mentions Atmos output. - [Likely] Trap for device testing: the AVR may say “Dolby Atmos” anyway. With Atmos enabled and Continuous Audio Connection on, Apple TV 4K wraps all output, including stereo and multichannel PCM, in Dolby MAT. Apple says older receivers “might indicate an Atmos signal” while audio plays “in its original format”. (Apple support) An Atmos light proves nothing; check the receiver’s per-channel input display.
2. Multichannel LPCM 5.1/7.1 over HDMI
Section titled “2. Multichannel LPCM 5.1/7.1 over HDMI”What tvOS offers
Section titled “What tvOS offers”- [Certain]
setPreferredOutputNumberOfChannels(_:)has been available on tvOS since 9.0. Apple’s docs: requests abovemaximumOutputNumberOfChannelsfail, “only certain devices and peripherals support this feature”, and it must be called “only after setting the audio session’s category and mode, and activating the session”. Read backoutputNumberOfChannelsto check it worked. (docs) - [Certain] An Apple evangelist (2015) said Apple TV “does support multichannel PCM audio as well as AC3 passthrough over HDMI”. Typical output is 24-bit/48 kHz, depending on the sink. (forum 17202)
- [Likely] It’s fiddly in practice. One developer saw max=6, preferred=6 return success, and output still 2 (unresolved; Apple asked whether a multichannel receiver was attached). (17202) With the Apple TV Surround setting on “Auto”,
maximumOutputNumberOfChannelsreturned 8 against a 5.1 DD receiver, and channel mapping came out scrambled on the AVAudioPlayer path. (forum 24961) - [Likely] Precedent that it works: SDL3. Its CoreAudio backend calls
setPreferredOutputNumberOfChannelsunder#ifdef SDL_PLATFORM_TVOSonly, with the comment “Calling setPreferredOutputNumberOfChannels seems to break audio output on iOS”. It also sets an explicit layout:kAudioChannelLayoutTag_DVD_12for 5.1 andkAudioChannelLayoutTag_WAVE_7_1for 7.1. (SDL_coreaudio.m) VLC’s iOS/tvOS AudioUnit output does the same request-then-verify, and builds the layout from the HDMI route’sAVAudioSessionChannelDescription.channelLabels. (VLC commit, VLC MR 2786) - [Likely] AirPlay routes report max 2 channels, so this is HDMI-only.
What Godot 4.7 / the fork does today
Section titled “What Godot 4.7 / the fork does today”- [Certain]
drivers/coreaudio/audio_driver_coreaudio.mminit()useskAudioUnitSubType_RemoteIOon iOS/tvOS. It reads the output-scope stream format and accepts 2, 4, 6 or 8 channels (anything else becomes stereo). It then maps channels throughget_speaker_mode_by_total_channels: 4 =SPEAKER_SURROUND_31, 6 =SPEAKER_SURROUND_51, 8 =SPEAKER_SURROUND_71. - [Certain] No AVAudioSession call anywhere in the fork requests channels. A grep for
setPreferredOutputNumberOfChannels,maximumOutputNumberOfChannelsandsetActiveindrivers/andplatform/finds nothing. The session is only configured indrivers/apple_embedded/godot_app_delegate_service_apple_embedded.mm, bysetCategoryfromaudio/general/ios/session_category. Dark Cosmos doesn’t set that, so it gets the default Ambient. No channel layout is set on the AU, and there’s no route-change listener on iOS/tvOS (the device-change listener isMACOS_ENABLEDonly). - [Likely] So the session stays at its 2-channel default and Godot runs
SPEAKER_MODE_STEREOon Apple TV even with a 7.1 AVR attached.AudioServer.get_speaker_mode()on the device would confirm this in one line. - [Likely] On Mac, multichannel probably already works. The HAL path reads the default device’s channel count, and a 5.1/7.1 device gives Godot surround mode.
- [Likely] Godot interleaves channel pairs: pair 0 = FL/FR, 1 = C/LFE, 2 = rear L/R, 3 = side L/R. That matches
WAVE_7_1(L R C LFE Rls Rrs Ls Rs) and DVD_12 for 5.1, which is the layout SDL sets.
What would need changing (fork, tvOS-only)
Section titled “What would need changing (fork, tvOS-only)”- In the tvOS app-delegate session setup (or driver
init()before reading the format): set category Playback on tvOS (test whether Ambient also works) andsetActive:YES. Then callsetPreferredOutputNumberOfChannels(min(max, 8))and readoutputNumberOfChannelsback. Gate this#ifdef TVOS_ENABLED, per SDL’s iOS breakage note. - Re-read the RemoteIO output format after that (the existing switch then picks 6 or 8). Set
kAudioUnitProperty_AudioChannelLayouton the input scope, or swizzle from the route’s channel labels the way VLC does, so Godot’s pair order lands on the right speakers. [Guessing] whether RemoteIO honours the layout property; the label-swizzle fallback is safe. - Add an
AVAudioSessionRouteChangeNotificationhandler that re-initialises on HDMI/AVR changes.AudioServeralready handles a channel-count change viainit_channels_and_buffers(). - Effort: ~80-150 lines of Obj-C++ in the fork, about 1 day plus device iteration [Guessing]. This also needs a fork rebuild and normal-lib swap, the same process as the HDR work.
Game side (needed to hear any benefit): route positional SFX through AudioStreamPlayer3D with a listener mapped to the arena plane, because 3D players pan across all pairs. Alternatively, patch AudioStreamPlayer2D::_update_panning in the fork to write pairs 1-3. Music/ambience can use MIX_TARGET_SURROUND. There’s also a design call: on a top-down screen, what does “behind you” mean? Off-screen threats, boss telegraphs, ambience bed. 2-4 days [Guessing].
3. AirPods spatial audio with head tracking
Section titled “3. AirPods spatial audio with head tracking”- [Certain] APIs and availability:
CMHeadphoneMotionManager: iOS 14 / macOS 14 / watchOS 7, not tvOS. It needsNSMotionUsageDescription. (docs)- The newer automatic route is tvOS 18 / iOS 18 / macOS 15. Entitlement
com.apple.developer.coremotion.head-pose(Xcode “Head Pose” capability, no request process documented) enables head-pose-driven orientation forAVAudioEnvironmentNode.isListenerHeadTrackingEnabled, AudioToolboxkAudioUnitProperty_SpatialMixerEnableHeadTracking, and PHASEPHASEListener.automaticHeadTrackingFlags. (entitlement, PHASE updates June 2024) - Personalised HRTF needs
com.apple.developer.spatial-audio.profile-access.AVGameBypassSystemSpatialAudio(Info.plist) stops PHASE from fighting the Control Center spatial toggle (same doc).
- [Certain] The tvOS 27 SDK header has
kAudioUnitProperty_SpatialMixerEnableHeadTrackingavailabletvos(18.0). It also haskSpatialMixerSourceMode_AmbienceBed, which spatialises a multichannel bus around the listener “as far-field sources” using the bus’s AudioChannelLayout, andkSpatialMixerOutputType_Headphones/BuiltInSpeakers/ExternalSpeakers. - Best Godot integration [Likely]: keep Godot’s mixer and have it render a 7.1 bed. Feed that into an AUSpatialMixer instance (AmbienceBed source mode, Headphones output, head tracking on), then into RemoteIO. Only do this when the route is Bluetooth headphones. On HDMI, send the 7.1 directly (section 2); on iPhone speakers, optionally use the BuiltInSpeakers output type. This reuses the section 2 work: once Godot can mix 7.1, binaural is one more AU in the chain. Effort: 2-4 days on top of section 2 [Guessing]. PHASE/AVAudioEnvironmentNode would mean replacing Godot’s mixer per source, which is far more work and not recommended.
- [Guessing] Free option: the system “Spatialize Stereo” (Control Center on iOS) can already spatialise any stereo app output on AirPods. Whether tvOS applies this to a third-party game’s stereo, and whether it head-tracks, needs a device check. It also has no positional meaning, just a widened stereo field.
- [Guessing]
setSupportsMultichannelContent(true)(tvOS/iOS 15+) is documented for “Now Playing” apps. It may affect how the system spatialises multichannel to AirPods, but it doesn’t fit a game.
4. Recommendation
Section titled “4. Recommendation”| Option | Reachable | Effort | Payoff |
|---|---|---|---|
| Atmos objects from live mix | No [Likely] | n/a | n/a |
| 7.1 LPCM over HDMI (Apple TV + AVR) | Yes [Likely] | fork ~1 day + game 2-4 days | Real for AVR owners only (small share of players) |
| AirPods head-tracked binaural (tvOS/iOS 18+) | Yes [Likely] | +2-4 days on top of 7.1 | Good on iPhone/iPad; on Apple TV, AirPods users only |
| System Spatialize Stereo | Possibly already [Guessing] | 0 | Width only, not positional |
Suggested order: a 30-minute probe build first (below), then fork 7.1 if the probe shows 8 channels and a correct mapping, then game-side positioning, then spatial mixer for AirPods. Don’t start any of it until the game has positional audio worth spreading. Today’s mix is non-positional, so the probe is the only cheap step.
Device test against Chris’s AVR (what must be confirmed)
Section titled “Device test against Chris’s AVR (what must be confirmed)”Probe: a tiny fork patch or debug-only Obj-C hook that logs, on Apple TV, maximumOutputNumberOfChannels, outputNumberOfChannels before and after the preferred-channels request, the current route port type and every channelLabel, renderingMode, and Godot’s AudioServer.get_speaker_mode(). Then:
- Channel ID test: play a voice/tone on each of the 8 channels in turn (FL, FR, C, LFE, RL, RR, SL, SR). On the AVR, confirm each lands on the right speaker, and that its signal-info page shows “Multi-ch PCM 7.1” (or MAT with the right input-channel indicators). Don’t trust the Atmos badge alone.
- Repeat with Apple TV Audio Format set to Auto / Dolby Digital 5.1 / Change Format on/off and Continuous Audio Connection on/off. Forum evidence says “Auto” can over-report channels and scramble the map, and Dolby Digital mode re-encodes to AC-3 5.1 (lossy, with added latency).
- Session category Ambient vs Playback: which one actually gets more than 2 channels.
- Latency and hitches: AV sync on a hit SFX, and
hitchestelemetry, since 8-channel mixing costs Godot’s mixer about 4x the per-voice work of stereo. - Hot-plug: switch AVR input / power-cycle mid-run and confirm no silent stall (route-change handling).
- AirPods (later): Apple TV with AirPods Pro on tvOS 18+. Check that head-pose entitlement head tracking moves the bed, and check the result against the Control Center spatial toggle.
Sources
Section titled “Sources”- Apple forums 750372 — AVAudioEngine Dolby Atmos
- Apple forums 700337 — tvOS multichannel with SceneKit
- Apple forums 17202 — Surround sound for apps
- Apple forums 24961 — speaker mapping vs Dolby setting
- setPreferredOutputNumberOfChannels docs
- WWDC15 507 What’s New in Core Audio (PDF)
- PHASE updates (June 2024) · PHASE availability
- head-pose entitlement · CMHeadphoneMotionManager · AVAudioEnvironmentOutputType
- Apple TV audio settings / Continuous Audio Connection
- SDL CoreAudio backend · VLC iOS AU channel layout commit · VLC MR 2786
- Local: fork
drivers/coreaudio/audio_driver_coreaudio.mm,drivers/apple_embedded/godot_app_delegate_service_apple_embedded.mm,servers/audio/audio_server.cpp,scene/2d/audio_stream_player_2d.cpp; AppleTVOS27.0.sdkAudioUnitProperties.h,AVAudioSession.h,AVAudioSessionTypes.h; gameaudio/audio_manager.gd.