空间音频

空间音频是一种沉浸式音频体验,可让用户全身心地沉浸其中,让内容听起来更加逼真。这种音频具有“空间化”的特点,可以通过头戴式耳机营造多扬声器效果(类似于环绕声设置)。

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

如果您的内容使用受支持的音频格式,则可以从 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 对象用于描述音频轨道的格式和声道配置的详细信息。

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

监听 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 声道(前左、前右、后左和后右)的声道掩码,以便音频符合空间化条件。在下面的示例中,我们使用 AudioFormat.CHANNEL_OUT_5POINT1 表示 5.1 声道音轨。对于立体声音轨,请使用 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)