为 XR 应用添加空间音频

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

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

空间音频可模拟用户在 3D 环境中感知声音的方式。它能营造出声音来自四面八方(包括用户上方和下方)的感觉。为此,系统会在 3D 空间中的特定位置模拟一个或多个“虚拟扬声器”。

对于并非专为 Android XR 设计或未针对 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 设备可能支持其他格式,例如 Dolby Atmos。如需查询媒体格式支持情况,请使用 ExoPlayer 的 AudioCapabilities:

val audioCapabilities = AudioCapabilities.getCapabilities(
    context,
    androidx.media3.common.AudioAttributes.DEFAULT,
    null,
    emptyList()
)

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

    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,
                entity = entity,
                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。

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

如果您已配置 MediaPlayer 或 ExoPlayer,以便在后台继续播放立体声或环绕声,则当应用在后台运行时,虚拟扬声器定位将会发生变化。由于没有面板或其他空间点可供锚定声音,空间音频会随用户移动(换句话说,它是“头部锁定”的)。

环绕声示例

以下示例使用 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()

    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,
        entity = session.scene.mainPanelEntity,
    )

    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 (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
}

代码要点