为 XR 应用添加空间音频

借助 Jetpack SceneCore 中的空间音频功能,您可以在 Android XR 应用中打造沉浸式音频体验。

空间音频可模拟用户在 3D 环境中感知声音的方式。它可以营造出声音从所有方向(包括用户上方和下方)发出的感觉。为此,系统会在 3D 空间的特定位置模拟一个或多个“虚拟音箱”。

未针对 Android XR 设计或修改的现有应用的音频会在 Android XR 中自动进行空间化处理。当用户在其聊天室中移动时,所有应用音频都将从应用界面呈现到的面板中发出。例如,如果闹钟应用的计时器响铃,则音频会像是从应用面板位置发出。Android XR 会自动改变声音,以实现逼真的定位效果。例如,应用面板与用户之间的感知距离会微妙地影响音量,从而增强真实感。

如需详细了解现有应用如何渲染空间音频,请参阅本页面上的向应用添加立体声和环绕声

如果您要针对 XR 优化应用,Jetpack SceneCore 提供了用于进行高级空间音频自定义的工具。您可以在 3D 环境中精确定位声音,使用全方位音频获得逼真的声场,并利用内置环绕声集成功能。

Android XR 中提供的空间音频类型

Android XR 支持定位音频、立体声、环绕声和全景声音频。

定位音频

您可以将声源定位到 3D 空间中的特定位置。 例如,您可以在虚拟环境的角落放置一只狗的 3D 模型,让它吠叫。您可以让多个实体从各自的位置发出声音。如需渲染位置音频,文件必须是单声道或立体声。

空间化立体声和环绕声

支持所有 Android 媒体格式,包括定位声、立体声和环绕声。

立体声音频是指具有两个声道的音频格式,而环绕声是指具有两个以上声道的音频格式,例如 5.1 环绕声7.1 环绕声配置。每个声道中的声音数据都与一位讲者相关联。例如,在立体声模式下播放音乐时,左声道音箱可能发出与右声道不同的乐器轨道。

环绕声通常用于电影和电视节目中,通过使用多个扬声器声道来增强真实感和沉浸感。例如,对话通常从中央扬声器声道播放,而直升机飞行的声音可能会依次使用不同的声道,以营造直升机在 3D 空间中飞行的感觉。

全景声音频

氛围音效(或氛围音效)就像音频的天空盒,可为用户提供身临其境的音景。将氛围声用于背景环境音效,或在您想要重现环绕听众的全球声场等其他场景中使用。Android XR 支持一阶、二阶和三阶环绕声中的 AmbiX 环绕声音频格式。我们建议使用 Opus (.ogg) 和 PCM/Wave (.wav) 文件类型。

将空间音频与 Jetpack SceneCore 搭配使用

使用 Jetpack SceneCore 实现空间音频需要检查空间音频功能并选择用于加载空间音频的 API。

检查空间功能

在使用空间音频功能之前,请检查 Session 是否支持空间音频。在以下部分的所有代码段中,系统都会先检查功能,然后再尝试播放空间化音频。

加载空间音频

您可以使用以下任一 API 加载空间音频,以便在 Jetpack SceneCore 中使用。

  • SoundPool:非常适合大小小于 1 MB 的短音效,这些音效会提前加载,并且可以重复使用。这对于加载位置音频非常有用。
  • ExoPlayer:非常适合加载音乐和视频等立体声和环绕声内容。还支持后台媒体播放。
  • MediaPlayer:提供加载全景声音频的最简单方式。
  • AudioTrack:可对如何加载音频数据进行最大限度的控制。允许直接写入音频缓冲区,或者您可以合成或解码自己的音频文件。

向应用添加位置音频

位置音源由 PointSourceAttributes 和关联的 Entity 定义。Entity 的位置和方向决定了 PointSourceAttribute 在 3D 空间中的渲染位置。

位置音频示例

以下示例会将音效音频文件加载到音效池中,并在 Entity 的位置播放该文件。

// Check spatial capabilities before using spatial audio
if (xrSession.getSpatialCapabilities().hasCapability(SpatialCapabilities.SPATIAL_CAPABILITY_SPATIAL_AUDIO)) {
    // The session has spatial audio capabilities

    val maxVolume = 1F
    val lowPriority = 0
    val infiniteLoop = -1
    val normalSpeed = 1F

    val soundPool = SoundPool.Builder()
        .setAudioAttributes(
            AudioAttributes.Builder()
                .setContentType(CONTENT_TYPE_SONIFICATION)
                .setUsage(USAGE_ASSISTANCE_SONIFICATION)
                .build()
        )
        .build()

    val pointSource = PointSourceAttributes(entity)

    val soundEffect = appContext.assets.openFd("sounds/tiger_16db.mp3")
    val pointSoundId = soundPool.load(soundEffect, lowPriority)

    soundPool.setOnLoadCompleteListener{ soundPool, sampleId, status ->
        //wait for the sound file to be loaded into the soundPool
        if (status == 0){

            SpatialSoundPool.play(
                session = xrSession,
                soundPool = soundPool,
                soundID = pointSoundId,
                attributes = pointSource,
                volume = maxVolume,
                priority = lowPriority,
                loop = infiniteLoop,
                rate = normalSpeed
            )
        }
    }
} else {
    // The session does not have spatial audio capabilities
}

代码要点

  • 第 1 步是使用 getSpatialCapabilities() 检查空间音频功能目前是否可用。
  • 将 contentType 设置为 CONTENT_TYPE_SONIFICATION 并将 usage 设置为 USAGE_ASSISTANCE_SONIFICATION 后,系统会将此音频文件视为音效。
  • 为了简单起见,上例会在使用音频文件之前立即将其加载到池中,以便将代码放在一起。理想情况下,您应在加载应用时异步加载所有音效,以便在需要时池中包含所有音频文件。

为应用添加立体声和环绕声

建议您使用 Exoplayer 向应用添加立体声和环绕声。如需详细了解如何将空间音频与 Exoplayer 搭配使用,请参阅空间音频指南

立体声和环绕声音箱位置

在环绕声音箱定位中,虚拟环绕声音箱的定位和方向相对于中置音箱,采用标准 ITU 配置,环绕用户。

默认情况下,中心声道音箱位于应用的 mainPanelEntity 上。这包括由 Android XR 自动进行音频空间化处理的移动应用。

对于立体声,音箱放置方式与环绕声类似,只不过左声道和右声道分别位于面板的左侧和右侧。

如果您有多个面板,并且想要选择哪个面板发出音频,或者您希望立体声或环绕声音频相对于另一个 Entity 进行渲染,则可以使用 PointSourceAttributes 来定义中置声道的音频位置。其余频道将按照前面所述的方式放置。在这些情况下,您还必须使用 MediaPlayer

当用户在空间中移动时,立体声和环绕声虚拟音箱也会移动和调整,以确保音箱始终处于最佳位置。

如果您已将 MediaPlayerExoPlayer 配置为在后台继续播放立体声或环绕声,则当应用进入后台时,虚拟音箱位置将发生变化。由于空间中没有面板或其他点可将声音固定,因此空间音频会随用户移动(换句话说,它会“锁定到头部”)。

环绕声示例

以下示例使用 MediaPlayer 加载 5.1 音频文件,并将文件的中置声道设置为 Entity

// Check spatial capabilities before using spatial audio
if (xrSession.getSpatialCapabilities().hasCapability(SpatialCapabilities.SPATIAL_CAPABILITY_SPATIAL_AUDIO)) {
    // The session has spatial audio capabilities

    val pointSourceAttributes = PointSourceAttributes(xrSession.mainPanelEntity)

    val mediaPlayer = MediaPlayer()

    val fivePointOneAudio = appContext.assets.openFd("sounds/aac_51.ogg")
    mediaPlayer.reset()
    mediaPlayer.setDataSource(fivePointOneAudio)

    val audioAttributes =
        AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            .setUsage(AudioAttributes.USAGE_MEDIA)
            .build()

    SpatialMediaPlayer.setPointSourceAttributes(
        xrSession,
        mediaPlayer,
        pointSourceAttributes)

    mediaPlayer.setAudioAttributes(audioAttributes)
    mediaPlayer.prepare()
    mediaPlayer.start()

} else {
    // The session does not have spatial audio capabilities
}

代码要点

向应用添加全景声音场

播放全景声音场的最简单方法是使用 MediaPlayer 加载文件。由于全景声音效适用于整个声景,因此您无需指定 Entity 即可提供位置。而是使用适当的氛围声顺序创建 SoundFieldAttributes 实例,以指定声道数量。

Ambionics 示例

以下示例使用 MediaPlayer 播放了全景声音场。

// Check spatial capabilities before using spatial audio
if (xrSession.getSpatialCapabilities().hasCapability(SpatialCapabilities.SPATIAL_CAPABILITY_SPATIAL_AUDIO)) {
    // The session has spatial audio capabilities

    val soundFieldAttributes =
        SoundFieldAttributes(SpatializerConstants.AMBISONICS_ORDER_FIRST_ORDER)

    val mediaPlayer = MediaPlayer()

    val soundFieldAudio = appContext.assets.openFd("sounds/foa_basketball_16bit.wav")

    mediaPlayer.reset()
    mediaPlayer.setDataSource(soundFieldAudio)

    val audioAttributes =
        AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
            .setUsage(AudioAttributes.USAGE_MEDIA)
            .build()

    SpatialMediaPlayer.setSoundFieldAttributes(
        xrSession,
        mediaPlayer,
        soundFieldAttributes)

    mediaPlayer.setAudioAttributes(audioAttributes)
    mediaPlayer.prepare()
    mediaPlayer.start()

} else {
    // The session does not have spatial audio capabilities
}

代码要点

  • 与前面的代码段一样,第一步是使用 getSpatialCapabilities() 检查空间音频功能目前是否可用。
  • contentType 和 usage 仅供参考。
  • AMBISONICS_ORDER_FIRST_ORDER 会向 SceneCore 发出信号,指示声场文件定义了四个声道。