Dolby Atmos is an object-based audio technology designed to provide immersive audio experiences in cinemas, home theaters, and mobile devices. Use Dolby Atmos to create spatial audio that enhances immersion in your app.
Set up Dolby Atmos spatial audio
1. Detect device support
Determine if your device supports the Dolby Digital Plus (E-AC-3) audio format using the code in the Determining the Dolby decoding capabilities of the device section of Enabling Dolby Atmos in Android Mobile media apps. AC-4 is not supported.
2. Play audio with ExoPlayer
Load the media you checked in the previous step into an ExoPlayer instance. If you want the ExoPlayer to be visible, it must included in a panel. If you’re new to ExoPlayer, see the official Getting started guide.
3. Enable SpatialAudioFeature
In the registerFeatures() method of your app’s main activity file, register the spatial audio feature to connect the audio from ExoPlayer.
override fun registerFeatures(): List<SpatialFeature> {
return listOf(VRFeature(this), SpatialAudioFeature())
}
4. Connect the audio session ID
Steps 1 through 3 are the same for every app. This step connects ExoPlayer’s audio session ID to the panel entity, and there are two approaches. Pick the one that matches where your ExoPlayer is created:
Option A: Read the ID from the player. Use this when your ExoPlayer is created in your immersive activity. You read audioSessionId from the player after playback is ready. Follow this approach if you completed steps 1 through 3 with the player and panel in the same activity.
Option B: Provide the ID before playback. Use this when your ExoPlayer is created in a separate panel activity, or when you want to register the ID without waiting for a player event. You generate the ID in your immersive activity and pass it to the activity that builds the player.
Both approaches produce the same spatialized result. Option A is covered next; Option B follows it.
Option A: Read the ID from the player
In your main activity file, add this method to listen for player events. It gets the audio session ID from ExoPlayer when the content is ready and connects the audio from ExoPlayer to the Spatial SDK panel entity.
Register your AudioSessionId with the SpatialAudioFeature using the registerAudioSessionId method.
override fun onPlaybackStateChanged(playbackState: Int) {
if (playbackState == Player.STATE_READY) {
val registeredAudioSessionId = 1
val audioSessionId = player.audioSessionId
spatialAudioFeature.registerAudioSessionId(registeredAudioSessionId, audioSessionId)
panelEntity.setComponent(AudioSessionId(registeredAudioSessionId, AudioType.SOUNDFIELD))
}
}
When you move around the scene or when the Entity emitting audio moves, the audio continues to emit from the Entity’s current location.
Option B: Provide the ID before playback
This approach reverses the order of Option A: instead of reading audioSessionId from the ExoPlayer after playback reaches Player.STATE_READY, you generate the audio session ID in your immersive activity, register it up front, and pass it to the activity that builds the player. This removes the need to route the ID back from the player to your immersive activity, so it fits apps where the ExoPlayer runs in a separate panel activity.
1. Generate and register the audio session ID. In your immersive activity, generate an audio session ID with AudioManager and register it with SpatialAudioFeature. Because you own the ID, you do not need to wait for a player event to register it.
import android.content.Context
import android.media.AudioManager
val audioSessionId = (getSystemService(Context.AUDIO_SERVICE) as AudioManager).generateAudioSessionId()
val registeredAudioSessionId = 1
spatialAudioFeature.registerAudioSessionId(registeredAudioSessionId, audioSessionId)
panelEntity.setComponent(AudioSessionId(registeredAudioSessionId, AudioType.SOUNDFIELD))
2. Pass the audio session ID to the panel activity. Register the media panel with IntentPanelRegistration and add the audio session ID to the launch Intent as an extra. IntentPanelRegistration hosts an activity from an Intent, which lets you pass data to the activity that builds the ExoPlayer. For the full set of registration options, see Register and configure panels.
Add FLAG_ACTIVITY_NEW_TASK to the intent, and give each panel its own intent so their launches stay separate.
const val EXTRA_AUDIO_SESSION_ID = "audio_session_id"
IntentPanelRegistration(
registrationId = R.id.media_panel,
intentCreator = { entity ->
Intent(this, MediaPlayerActivity::class.java).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
putExtra(EXTRA_AUDIO_SESSION_ID, audioSessionId)
}
},
settingsCreator = { entity ->
/* Return your PanelSettings. See the panel registration guide. */
},
)
3. Apply the audio session ID in the panel activity. In the panel activity, read the extra from the launch intent and set it on the ExoPlayer when you build it. Use the same EXTRA_AUDIO_SESSION_ID key in both activities.
const val AUDIO_SESSION_ID_UNSET = -1
val injectedSessionId = intent.getIntExtra(EXTRA_AUDIO_SESSION_ID, AUDIO_SESSION_ID_UNSET)
val player =
ExoPlayer.Builder(this).build().apply {
if (injectedSessionId != AUDIO_SESSION_ID_UNSET) {
setAudioSessionId(injectedSessionId)
}
}
Because the audio session ID is registered before playback starts, the panel’s audio is spatialized once the player begins to play.
Audio types
The AudioSessionId component supports several audio types:
STEREO: Standard audio with left and right (LR) channels.
Using the AudioType.SOUNDFIELD enum works with any multichannel audio file. If you use ambisonic audio, play it using the other enum options.
Setting stereo audio offsets
Use the AudioSessionStereoOffsets component to set stereo audio offsets for the left and right channels, each represented by a Vector3.
The AudioSessionStereoOffsets component has two different modes.
The WORLD_SPACE mode positions stereo offsets in absolute world space. The offsets do not take into account the Entity’s position.
The LOCAL_SPACE mode positions stereo offsets relative to the Entity’s local space. If the Entity has no Transform, the offsets are positioned in world space.
This example builds on Option A by adding stereo channel positioning. Notice two key differences: the AudioType is set to STEREO instead of SOUNDFIELD, and the AudioSessionStereoOffsets component positions the left and right channels in 3D space.
override fun onPlaybackStateChanged(playbackState: Int) {
if (playbackState == Player.STATE_READY) {
val registeredAudioSessionId = 1
val audioSessionId = player.audioSessionId
spatialAudioFeature.registerAudioSessionId(registeredAudioSessionId, audioSessionId)
panelEntity.setComponent(AudioSessionId(registeredAudioSessionId, AudioType.STEREO))
panelEntity.setComponent(
AudioSessionStereoOffsets(
left = Vector3(-1f, 0f, 0f),
right = Vector3(1f, 0f, 0f)
)
)
}
}
The Vector3(-1f, 0f, 0f) and Vector3(1f, 0f, 0f) values position the left and right audio channels one unit apart on the X-axis, creating proper stereo separation when the user faces the panel.