为 XR 应用添加空间音频

适用的 XR 设备
本指南可帮助您为以下类型的 XR 设备打造优质体验。
扩展现实头戴设备
有线扩展现实眼镜

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

空间音频可以模拟用户在 3D 环境中感知声音的方式。它可以营造出声音从各个方向(包括用户上方和下方)发出的感觉。系统通过在 3D 空间中的特定位置模拟一个或多个“虚拟扬声器”来实现此目的。

对于尚未针对 Android XR 设计或修改的现有应用,其音频会在 Android XR 中自动空间化。当用户在空间中移动时,所有应用音频都将从应用界面呈现到的面板发出。例如,如果时钟应用中的计时器响起,音频听起来就像是从应用面板位置发出的。Android XR 会自动更改声音,以实现位置真实感。例如,应用面板与用户之间的感知距离会微妙地影响音频音量,从而增强真实感。

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

如果您要针对 XR 优化应用,Jetpack SceneCore 会提供用于高级空间音频自定义的工具。您可以精确地在 3D 环境中定位声音,使用环绕声音频来营造逼真的声场,并利用内置的环绕声集成。

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

Android XR 支持位置音频、立体声音频、环绕声音频和环绕声音频。

位置音频

位置音频可以定位为从 3D 空间中的特定点播放。 例如,您可以让虚拟环境角落的 3D 狗模型发出吠叫声。您可以让多个实体从各自的位置发出声音。如需呈现位置音频,文件必须是单声道或立体声。

空间化立体声和环绕声

所有 Android 媒体格式 都支持位置音频、立体声音频和 环绕声音频。除了这些格式之外,Android XR 设备可能还支持 杜比全景声杜比数字杜比数字+音频格式。

立体声音频是指具有两个声道的音频格式,而环绕声音频是指具有两个以上声道的音频格式,例如 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:提供对如何加载音频数据的最大控制权。 允许直接写入音频缓冲区,或者如果您合成了或解码了自己的音频文件。

检查媒体格式支持情况

某些媒体格式受 Android 平台支持。不过,特定的 Android XR 设备可能支持其他格式,例如 杜比全景声。如需查询媒体格式支持情况,请使用 ExoPlayer 的 AudioCapabilities

val audioCapabilities = AudioCapabilities.getCapabilities(context, androidx.media3.common.AudioAttributes.DEFAULT, null)
if (audioCapabilities.supportsEncoding(C.ENCODING_AC3)) {
    // Device supports playback of the Dolby Digital media format.
}
if (audioCapabilities.supportsEncoding(C.ENCODING_E_AC3)) {
    // Device supports playback of the Dolby Digital Plus media format.
}
if (audioCapabilities.supportsEncoding(C.ENCODING_E_AC3_JOC)) {
    // Device supports playback of the Dolby Digital Plus with Dolby Atmos media format.
}

检查这些功能可能涉及阻塞调用,并且 不应在主线程上调用

向应用添加位置音频

位置声音源由 PointSourceParams 和关联的 Entity 定义。Entity 的位置和方向决定了 PointSourceParams 在 3D 空间中的呈现位置。在 SpatialMediaPlayer上调用 setPointSourceParams,以在MediaPlayer实例上设置PointSourceParams及其关联的entity

位置音频示例

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

// Check spatial capabilities before using spatial audio
if (session.scene.spatialCapabilities.contains(SpatialCapability.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 = PointSourceParams(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 = session,
                soundPool = soundPool,
                soundID = pointSoundId,
                params = pointSource,
                volume = maxVolume,
                priority = lowPriority,
                loop = infiniteLoop,
                rate = normalSpeed
            )
        }
    }
} else {
    // The session does not have spatial audio capabilities
}

有关代码的要点

  • 第一步是检查空间音频功能是否 可用,方法是使用 spatialCapabilities
  • 将 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 (session.scene.spatialCapabilities.contains(SpatialCapability.SPATIAL_AUDIO)) {
    // The session has spatial audio capabilities

    val pointSourceAttributes = PointSourceParams(session.scene.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.setPointSourceParams(
        session,
        mediaPlayer,
        pointSourceAttributes
    )

    mediaPlayer.setAudioAttributes(audioAttributes)
    mediaPlayer.prepare()
    mediaPlayer.start()
} else {
    // The session does not have spatial audio capabilities
}

有关代码的要点

向应用添加环绕声声场

播放环绕声声场的最简单方法是使用 MediaPlayer 加载文件。由于环绕声适用于整个音景,因此您无需指定 Entity 来提供位置。相反,您可以使用适当的环绕声 顺序(指定声道数)创建 SoundFieldAttributes 的 实例。

环绕声示例

以下示例使用 MediaPlayer 播放环绕声声场。

// Check spatial capabilities before using spatial audio
if (session.scene.spatialCapabilities.contains(SpatialCapability.SPATIAL_AUDIO)) {
    // The session has spatial audio capabilities

    val soundFieldAttributes =
        SoundFieldAttributes(SpatializerConstants.AmbisonicsOrder.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(
        session,
        mediaPlayer,
        soundFieldAttributes
    )

    mediaPlayer.setAudioAttributes(audioAttributes)
    mediaPlayer.prepare()
    mediaPlayer.start()
} else {
    // The session does not have spatial audio capabilities
}

有关代码的要点

  • 与之前的代码段一样,第一步是使用 hasCapability() 检查空间音频 功能是否可用。
  • contentType 和 usage 纯粹是信息性的。
  • AMBISONICS_ORDER_FIRST_ORDER 向 SceneCore 发出信号,表明声场 文件定义了四个声道。