空间音频

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

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

如果您的内容使用受支持的音频格式,则可以从 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()。 同样,您可以使用 ExoPlayer 的当前参数获取 Player.getTrackSelectionParameters()。例如,如需在播放过程中选择立体声音轨:

Kotlin

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

Java

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

请注意,在播放过程中更改音轨选择参数可能会导致播放中断。如需详细了解如何调整播放器的音轨 选择参数,请参阅 音轨选择 部分。

默认空间化行为

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)