为您的 XR 应用添加空间音频

Jetpack SceneCore 中的空间音频功能使您能够在 Android XR 应用中打造沉浸式音频体验。

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

未为 Android XR 设计或修改的现有应用,其音频会在 Android XR 中自动空间化。当用户在空间中移动时,所有应用音频都将从应用 UI 渲染到的面板发出。例如,如果时钟应用中的计时器响起,音频听起来就像来自应用面板的位置。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:提供对如何加载音频数据的最大控制。允许直接写入音频缓冲区,或者在您合成或解码自己的音频文件时使用。

为您的应用添加位置音频

位置声源由 PointSourceParams 和相关的 Entity 定义。Entity 的位置和方向决定了 PointSourceParams 在 3D 空间中的渲染位置。

位置音频示例

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

// Check spatial capabilities before using spatial audio
if (session.scene.spatialCapabilities
    .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 = 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.hasCapability(SpatialCapabilities.SPATIAL_CAPABILITY_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
}

代码要点

  • 位置音频示例中一样,第一步是使用 spatialCapabilities 检查空间音频功能是否可用。
  • contentType 设置为 (/reference/android/media/AudioAttributes#CONTENT_TYPE_MUSIC)18 并将 usage 设置为 (/reference/android/media/AudioAttributes#USAGE_MEDIA)16 允许系统将此音频文件视为环绕声。

为您的应用添加全景声场

播放全景声场最简单的方法是使用 MediaPlayer。由于全景声适用于整个声景,因此您无需指定 Entity 来提供位置。相反,您可以创建一个 SoundFieldAttributes 实例,其中包含适当的全景声阶数并指定声道数量。

全景声示例

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

// Check spatial capabilities before using spatial audio
if (session.scene.spatialCapabilities.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(
        session,
        mediaPlayer,
        soundFieldAttributes
    )

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

代码要点

  • 与之前的代码片段一样,第一步是使用 hasCapability() 检查空间音频功能是否可用。
  • The contentType and usage are purely informational.
  • The AMBISONICS_ORDER_FIRST_ORDER signals to SceneCore that the sound field file defines four channels.