空间音频

空间音频是一种沉浸式音频体验,可以让您的用户置身于事件中心,使您的内容听起来更逼真。声音经过“空间化”处理,可以产生多扬声器效果,类似于环绕声设置,但通过耳机实现。

例如,在电影中,汽车的声音可能从用户身后开始,然后向前移动,最后逐渐消失在远处。在视频聊天中,可以将声音分开并放置在用户周围,从而更容易识别说话者。

如果您的内容使用受支持的音频格式,您可以从 Android 13(API 级别 33)开始向您的应用添加空间音频。

查询功能

使用 Spatializer 类查询设备的空间化功能和行为。首先从 AudioManager 中检索 Spatializer 的实例。

Kotlin

val spatializer = audioManager.spatializer

Java

Spatializer spatializer = AudioManager.getSpatializer();

获取 Spatializer 后,检查设备输出空间化音频必须满足的四个条件。

条件 检查
设备是否支持空间化? getImmersiveAudioLevel() 不为 SPATIALIZER_IMMERSIVE_LEVEL_NONE
空间化是否可用?
可用性取决于与当前音频输出路由的兼容性。
isAvailable()true
空间化是否已启用 isEnabled()true
具有给定参数的音频轨道是否可以空间化? canBeSpatialized()true

例如,如果当前音频轨道不支持空间化或音频输出设备完全禁用空间化,则可能无法满足这些条件。

头部追踪

使用支持的耳机,平台可以根据用户的头部位置调整音频的空间化。要检查当前音频输出路由是否可以使用头部追踪器,请调用 isHeadTrackerAvailable()

兼容内容

Spatializer.canBeSpatialized() 指示是否可以使用当前输出设备路由对具有给定属性的音频进行空间化处理。此方法采用 AudioAttributesAudioFormat,这两个对象将在下面详细介绍。

AudioAttributes

AudioAttributes 对象描述音频流的用途(例如,游戏音频标准媒体),以及其播放行为和内容类型

调用 canBeSpatialized() 时,请使用与为您的 Player 设置的相同的 AudioAttributes 实例。例如,如果您使用的是 Jetpack Media3 库并且没有自定义 AudioAttributes,请使用 AudioAttributes.DEFAULT

禁用空间音频

要指示您的内容已进行空间化处理,请调用 setIsContentSpatialized(true),以便不会对音频进行二次处理。或者,通过调用 setSpatializationBehavior(AudioAttributes.SPATIALIZATION_BEHAVIOR_NEVER) 来调整空间化行为以完全禁用空间化。

AudioFormat

AudioFormat 对象描述了音频轨道的格式和声道配置的详细信息。

实例化要传递到 canBeSpatialized()AudioFormat 时,请将 编码 设置为与解码器预期的输出格式相同。您还应该设置与内容声道配置匹配的 声道掩码。有关要使用的特定值的指导,请参阅默认空间化行为部分。

侦听 Spatializer 的更改

要侦听 Spatializer 状态的更改,您可以使用 Spatializer.addOnSpatializerStateChangedListener() 添加侦听器。同样,要侦听头部追踪器可用性的更改,请调用 Spatializer.addOnHeadTrackerAvailableListener()

如果您想使用侦听器的回调在播放期间调整音轨选择,这将非常有用。例如,当用户连接或断开耳机与设备的连接时,onSpatializerAvailableChanged 回调会指示空间化效果对于新的音频输出路由是否可用。此时,您可以考虑更新播放器的音轨选择逻辑以匹配设备的新功能。有关 ExoPlayer 音轨选择行为的详细信息,请参阅ExoPlayer 和空间音频部分。

ExoPlayer 和空间音频

最新版本的 ExoPlayer 使采用空间音频变得更加容易。如果您使用的是独立 ExoPlayer 库(包名称 com.google.android.exoplayer2),版本 2.17 会将平台配置为输出空间化音频,版本 2.18 引入了音频声道数约束。如果您使用的是 Media3 库中的 ExoPlayer 模块(包名称 androidx.media3),版本 1.0.0-beta01 和更高版本包含这些相同的更新。

更新您的 ExoPlayer 依赖项到最新版本后,您的应用只需包含可以空间化的内容即可。

音频声道数约束

满足空间音频的所有四个条件后,ExoPlayer 会选择多声道音频轨道。如果没有,ExoPlayer 会选择立体声轨道。如果 Spatializer 属性发生更改,ExoPlayer 将触发新的音轨选择以选择与当前属性匹配的音频轨道。请注意,此新的音轨选择可能会导致短暂的重新缓冲时间。

要禁用音频声道数约束,请按如下所示设置播放器上的音轨选择参数。

Kotlin

exoPlayer.trackSelectionParameters = DefaultTrackSelector.Parameters.Builder(context)
  .setConstrainAudioChannelCountToDeviceCapabilities(false)
  .build()

Java

exoPlayer.setTrackSelectionParameters(
  new DefaultTrackSelector.Parameters.Builder(context)
    .setConstrainAudioChannelCountToDeviceCapabilities(false)
    .build()
);

同样,您可以更新现有音轨选择器的参数以禁用音频声道数约束,如下所示。

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()
);

禁用音频声道数约束后,如果内容有多个音频轨道,ExoPlayer 最初会选择声道数最多且设备可播放的轨道。例如,如果内容包含多声道音频轨道和立体声音频轨道,并且设备支持播放两者,则 ExoPlayer 将选择多声道轨道。有关如何自定义此行为的详细信息,请参阅音频音轨选择

音频音轨选择

禁用 ExoPlayer 的音频声道数约束行为后,ExoPlayer 不会自动选择与设备空间化程序属性匹配的音频轨道。相反,您可以在播放之前或期间通过设置音轨选择参数来自定义 ExoPlayer 的音轨选择逻辑。默认情况下,ExoPlayer 选择与初始音轨在 MIME 类型(编码)、声道数和采样率方面相同的音频音轨。

更改音轨选择参数

要更改 ExoPlayer 的音轨选择参数,请使用 Player.setTrackSelectionParameters()。同样,您可以使用 Player.getTrackSelectionParameters() 获取 ExoPlayer 的当前参数。例如,要在播放过程中选择立体声音频轨道。

Kotlin

exoPlayer.trackSelectionParameters = exoPlayer.trackSelectionParameters
  .buildUpon()
  .setMaxAudioChannelCount(2)
  .build()

Java

exoPlayer.setTrackSelectionParameters(
  exoPlayer.getTrackSelectionParameters()
    .buildUpon()
    .setMaxAudioChannelCount(2)
    .build()
);

请注意,在播放过程中更改音轨选择参数可能会导致播放中断。有关调整播放器音轨选择参数的更多信息,请参阅 ExoPlayer 文档的音轨选择部分。

默认空间化行为

Android 中的默认空间化行为包括以下可能由 OEM 自定义的行为。

  • 仅对多声道内容进行空间化处理,不对立体声内容进行空间化处理。如果您不使用 ExoPlayer,则根据多声道音频内容的格式,您可能需要将音频解码器可以输出的最大声道数配置为较大的数字。这可确保音频解码器为平台输出多声道 PCM 以进行空间化处理。

    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);

    例如,请参见 ExoPlayer 的 MediaCodecAudioRenderer.java。要自行关闭空间音频,无论是否存在 OEM 定制,请参见 禁用空间音频

  • AudioAttributes:如果 usage 设置为 USAGE_MEDIAUSAGE_GAME,则音频有资格进行空间音频处理。

  • AudioFormat:使用至少包含 AudioFormat.CHANNEL_OUT_QUAD 通道(前左、前右、后左和后右)的通道掩码,才能使音频有资格进行空间音频处理。在下面的示例中,我们为 5.1 音频轨道使用 AudioFormat.CHANNEL_OUT_5POINT1。对于立体声音频轨道,请使用 AudioFormat.CHANNEL_OUT_STEREO

    如果您使用的是 Media3,则可以使用 Util.getAudioTrackChannelConfig(int channelCount) 将通道数转换为通道掩码。

    此外,如果您已将解码器配置为输出多声道 PCM,请将编码设置为 AudioFormat.ENCODING_PCM_16BIT

    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();

测试空间音频

确保您的测试设备已启用空间音频

  • 对于有线耳机,请转到**系统设置 > 声音和振动 > 空间音频**。
  • 对于无线耳机,请转到**系统设置 > 已连接设备 > 无线设备的齿轮图标 > 空间音频**。

要检查当前路由的空间音频可用性,请在设备上运行 adb shell dumpsys audio 命令。播放过程中,您应该在输出中看到以下参数

Spatial audio:
mHasSpatializerEffect:true (effect present)
isSpatializerEnabled:true (routing dependent)