Spatial Audio is an immersive audio experience that puts your users at the center of the action, making your content sound more realistic. The sound is "spatialized" to create a multi-speaker effect, similar to a surround sound setup, but through headphones instead.
For example, in a movie, the sound from a car might start behind the user, move forward, and trail off into the distance. In a video chat, voices can be separated and placed around the user, making it easier to identify speakers.
If your content uses a supported audio format, you can add spatial audio to your app starting with Android 13 (API level 33).
Query for capabilities
Use the Spatializer
class to
query the device's spatialization capabilities and behavior. Start by retrieving
an instance of the Spatializer
from the
AudioManager
:
Kotlin
val spatializer = audioManager.spatializer
Java
Spatializer spatializer = AudioManager.getSpatializer();
After you get the Spatializer
, check for the four conditions that must hold
true for the device to output spatialized audio:
Criteria | Check |
---|---|
Does the device support spatialization? |
getImmersiveAudioLevel() is not SPATIALIZER_IMMERSIVE_LEVEL_NONE
|
Is spatialization available? Availability depends on compatibility with the current audio output routing. |
isAvailable() is true |
Is spatialization enabled? | isEnabled() is true |
Can an audio track with the given parameters be spatialized? | canBeSpatialized() is true |
These conditions might not be met, for example, if spatialization is unavailable for the current audio track or disabled on the audio output device altogether.
Head tracking
With supported headsets, the platform can adjust the audio's
spatialization based on the user's head position. To check if a head tracker is
available for the current audio output routing, call
isHeadTrackerAvailable()
.
Compatible content
Spatializer.canBeSpatialized()
indicates whether audio with the given properties can be spatialized with the
current output device routing. This method takes an AudioAttributes
and an AudioFormat
, both of which are
described in more detail below.
AudioAttributes
An AudioAttributes
object
describes the usage of an
audio stream (for example, game audio
or standard media),
along with its playback behaviors and content type.
When calling canBeSpatialized()
, use the same
AudioAttributes
instance as set for your Player
. For example, if
you're using the Jetpack Media3 library and haven't customized the
AudioAttributes
, use AudioAttributes.DEFAULT
.
Disabling spatial audio
To indicate that your content has already been spatialized, call
setIsContentSpatialized(true)
so that the audio isn't double-processed. Alternatively, adjust the
spatialization behavior to disable spatialization altogether by calling
setSpatializationBehavior(AudioAttributes.SPATIALIZATION_BEHAVIOR_NEVER)
.
AudioFormat
An AudioFormat
object describes
details about the format and channel configuration of an audio track.
When instantiating the AudioFormat
to pass into canBeSpatialized()
,
set the encoding
to the same as the output format expected from the decoder. You should also set
a channel mask
that matches your content's channel configuration. Refer to the
Default spatialization behavior section for guidance on
specific values to use.
Listen for changes to the Spatializer
To listen for changes in the Spatializer
’s state, you can add a listener
with Spatializer.addOnSpatializerStateChangedListener()
.
Similarly, to listen for changes in the availability of a head tracker,
call Spatializer.addOnHeadTrackerAvailableListener()
.
This can be useful if you want to adjust your track selection during playback
using the listener’s callbacks. For example, when a user connects or disconnects their
headset from the device, the onSpatializerAvailableChanged
callback indicates whether the spatializer effect is available for the new
audio output routing. At this point, you may consider updating your player's
track selection logic to match the device's new capabilities. For details on
ExoPlayer's track selection behavior, refer to the ExoPlayer and spatial audio
section.
ExoPlayer and spatial audio
Recent releases of ExoPlayer make it easier to adopt spatial audio. If you use
the standalone ExoPlayer library (package name com.google.android.exoplayer2
),
version 2.17 configures the platform to output spatialized audio, and version
2.18 introduces audio channel count constraints.
If you use the ExoPlayer module from the Media3 library, (package name
androidx.media3
), versions 1.0.0-beta01
and newer include these same updates.
After updating your ExoPlayer dependency to the latest release, your app just needs to include content that can be spatialized.
Audio channel count constraints
When all four conditions for spatial audio are met, ExoPlayer picks
a multi-channel audio track. If not, ExoPlayer chooses a stereo track instead.
If the Spatializer
properties change, ExoPlayer
will trigger a new track selection to select an audio track that matches the
current properties. Note that this new track selection may cause a short
rebuffering period.
To disable audio channel count constraints, set the track selection parameters on the player as shown below:
Kotlin
exoPlayer.trackSelectionParameters = DefaultTrackSelector.Parameters.Builder(context) .setConstrainAudioChannelCountToDeviceCapabilities(false) .build()
Java
exoPlayer.setTrackSelectionParameters( new DefaultTrackSelector.Parameters.Builder(context) .setConstrainAudioChannelCountToDeviceCapabilities(false) .build() );
Similarly, you can update an existing track selector's parameters to disable audio channel count constraints as follows:
Kotlin
val trackSelector = DefaultTrackSelector(context) ... trackSelector.parameters = trackSelector.buildUponParameters() .setConstrainAudioChannelCountToDeviceCapabilities(false) .build()
Java
DefaultTrackSelector trackSelector = new DefaultTrackSelector(context); ... trackSelector.setParameters( trackSelector .buildUponParameters() .setConstrainAudioChannelCountToDeviceCapabilities(false) .build() );
With audio channel count constraints disabled, if content has multiple audio tracks, ExoPlayer initially selects the track that has the highest number of channels and is playable from the device. For example, if the content contains a multi-channel audio track and a stereo audio track, and the device supports playback of both, ExoPlayer selects the multi-channel track. See the Audio track selection for details on how to customize this behavior.
Audio track selection
When ExoPlayer’s audio channel count constraints behavior is disabled, ExoPlayer does not automatically select an audio track that matches the properties of the device's spatializer. Instead, you can customize ExoPlayer's track selection logic by setting track selection parameters before or during playback. By default, ExoPlayer selects audio tracks that are the same as the initial track with regards to MIME type (encoding), channel count, and sample rate.
Changing the track selection parameters
To change the ExoPlayer’s track selection parameters, use
Player.setTrackSelectionParameters()
.
Likewise, you can get ExoPlayer’s current parameters with
Player.getTrackSelectionParameters()
.
For example, to select a stereo audio track mid-playback:
Kotlin
exoPlayer.trackSelectionParameters = exoPlayer.trackSelectionParameters .buildUpon() .setMaxAudioChannelCount(2) .build()
Java
exoPlayer.setTrackSelectionParameters( exoPlayer.getTrackSelectionParameters() .buildUpon() .setMaxAudioChannelCount(2) .build() );
Note that changing the track selection parameters mid-playback may cause an interruption in the playback. More information on tuning the player’s track selection parameters is available in the track selection section of the ExoPlayer docs.
Default spatialization behavior
The default spatialization behavior in Android includes the following behaviors that may be customized by OEMs:
Only multi-channel content is spatialized, not stereo content. If you do not use ExoPlayer, depending on the format of your multi-channel audio content, you may need to configure the maximum number of channels that can be output by an audio decoder to a large number. This ensures that the audio decoder outputs multi-channel PCM for the platform to spatialize.
Kotlin
val mediaFormat = MediaFormat() mediaFormat.setInteger(MediaFormat.KEY_MAX_OUTPUT_CHANNEL_COUNT, 99)
Java
MediaFormat mediaFormat = new MediaFormat(); mediaFormat.setInteger(MediaFormat.KEY_MAX_OUTPUT_CHANNEL_COUNT, 99);
For an example in action, see ExoPlayer's
MediaCodecAudioRenderer.java
. To turn spatialization off yourself, regardless of OEM customization, see Disabling spatial audio.AudioAttributes
: Audio is eligible for spatialization if theusage
is set to eitherUSAGE_MEDIA
orUSAGE_GAME
.AudioFormat
: Use a channel mask that contains at least theAudioFormat.CHANNEL_OUT_QUAD
channels (front-left, front-right, back-left, and back-right) for the audio to be eligible for spatialization. In the example below, we useAudioFormat.CHANNEL_OUT_5POINT1
for a 5.1 audio track. For a stereo audio track, useAudioFormat.CHANNEL_OUT_STEREO
.If you are using Media3, you can use
Util.getAudioTrackChannelConfig(int channelCount)
to convert a channel count to a channel mask.In addition, set the encoding to
AudioFormat.ENCODING_PCM_16BIT
if you have configured the decoder to output multi-channel PCM.Kotlin
val audioFormat = AudioFormat.Builder() .setEncoding(AudioFormat.ENCODING_PCM_16BIT) .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1) .build()
Java
AudioFormat audioFormat = new AudioFormat.Builder() .setEncoding(AudioFormat.ENCODING_PCM_16BIT) .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1) .build();
Test spatial audio
Ensure that spatial audio is enabled on your test device:
- For wired headsets, go to System settings > Sound & vibration > Spatial audio.
- For wireless headsets, go to System settings > Connected devices > Gear icon for your wireless device > Spatial audio.
To check for Spatial Audio availability for the current routing, run the
adb shell dumpsys audio
command on your device. You should see the following
parameters in the output while playback is active:
Spatial audio:
mHasSpatializerEffect:true (effect present)
isSpatializerEnabled:true (routing dependent)