# 空间音频 (/zh-CN/solutions/meta-world/build/spatial-audio)

> For AI agents: see the complete documentation index at [llms.txt](/llms.txt).

<_PlatformTabsGroup>
<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="android" platform="android" />
本文介绍如何使用空间音频功能以增强元宇宙音频体验。

在元宇宙中，空间音频可以为用户带来更加真实的虚拟体验。例如，在一个虚拟的 3D 旅游场景中，空间音频可以让用户宛如身临其境般听到旅游中路人聊天声、海浪声、风声，让用户更沉浸式体验。

![元宇宙空间音频示意](https://web-cdn.agora.io/docs-files/1679566933312)

## 技术原理 [#技术原理]

空间音频功能基于声学原理，模拟声音在不同空间环境中的传播、反射、吸收效果。通过在不同位置放置音源和听众，模拟现实中的声音传播效果，使得听众可以听到更真实自然的声音。

结合 `IMetaSceneEventHandler` 提供的用户位置信息回调和 `ILocalSpatialAudioEngine` 提供的空间音频系列方法，你可以实现带空间音频的元语聊。

## 示例项目 [#示例项目]

声网在 GitHub 上提供开源 [Agora-MetaWorld](https://github.com/AgoraIO-Community/Agora-MetaWorld/tree/dev_metasdk1.0) 示例项目供你参考。

如果你还需了解 Unity 部分的工程文件和功能指南，请联系 sales@shengwang.cn 获取。

## 前提条件 [#前提条件]

实现该进阶功能前，请确保你已实现基础的元语聊或元直播功能，如创建 Meta 服务、获取并下载场景资源、创建场景、设置虚拟形象的信息并进入场景。详见[基础功能](/zh-CN/solutions/meta-world/build/metachat)。

## 实现步骤 [#实现步骤]

用户在元宇宙中听到的声音分为两类：

- 远端用户的音频
- 本地媒体播放器播放的音频

因此，实现空间音频时，你需要按照如下步骤对两种音频都进行处理。

### 用户空间音频 [#用户空间音频]

下图介绍实现用户空间音频的 API 时序：

![用户空间音频 API 时序图](https://web-cdn.agora.io/docs-portal/img/meta-world/spatial-audio-user-android.svg)

#### 1 创建和初始化空间音频引擎 [#1-创建和初始化空间音频引擎]

使用空间音频前，你需要调用 [`create`](/zh-CN/api-reference/rtc/android/audio/audio-prenpost/spatial-audio#create) 和 [`initialize`](/zh-CN/api-reference/rtc/android/audio/audio-prenpost/spatial-audio#initialize) 创建和初始化空间音频引擎。

:::warning[注意]
- 请在加入 RTC 频道前调用 `create` 和 `initialize` 方法。
- 当使用空间音频时，请确保在 `RtcEngine` 中停止发布和订阅音频流。
:::

```java
// 创建和初始化空间音频引擎
spatialAudioEngine = ILocalSpatialAudioEngine.create();
LocalSpatialAudioConfig config = new LocalSpatialAudioConfig() {{
    mRtcEngine = rtcEngine;
}};
spatialAudioEngine.initialize(config);
```

#### 2 设置空间音频接收范围 [#2-设置空间音频接收范围]

调用 [`setAudioRecvRange`](/zh-CN/api-reference/rtc/android/audio/audio-prenpost/spatial-audio#setaudiorecvrange) 设置空间音频接收范围，当远端用户相对本地用户的距离超出这个范围，本地用户就会听不到远端用户的声音。

你可以通过 `ILocalSpatialAudioEngine` 处理是否停止发布和订阅音频流。

```java
// 设置空间音频的音频接收范围
// 如果超过设置的值，那么本地用户听不见远端用户的声音
spatialAudioEngine.setAudioRecvRange(100);
// ILocalSpatialAudioEngine 设置恢复发布本地音频流
// 此时需要额外检查本地用户角色不是观众，否则无法发流
spatialAudioEngine.muteLocalAudioStream(false);
// ILocalSpatialAudioEngine 设置恢复订阅所有远端音频流
spatialAudioEngine.muteAllRemoteAudioStreams(false);
```

#### 3 处理用户位置变化 [#3-处理用户位置变化]

通过 [`onUserPositionChanged`](/zh-CN/solutions/meta-world/reference/meta-api#onuserpositionchanged) 回调监听用户的位置变化。如果当前用户是本地用户，调用 [`updateSelfPosition`](/zh-CN/api-reference/rtc/android/audio/audio-prenpost/spatial-audio#updateselfposition) 更新本地用户位置信息；如果当前用户是远端用户，调用 [`updateRemotePosition`](/zh-CN/api-reference/rtc/android/audio/audio-prenpost/spatial-audio#updateremoteposition) 更新远端用户的信息位置。

:::warning[注意]
请在加入 RTC 频道后监听和处理用户位置变化。
:::

```java
// 处理用户位置的变化
@Override
public void onUserPositionChanged(String uid, MetaUserPositionInfo posInfo) {
    Log.d(TAG, String.format("onUserPositionChanged %s %s %s %s %s", uid,
            Arrays.toString(posInfo.mPosition),
            Arrays.toString(posInfo.mForward),
            Arrays.toString(posInfo.mRight),
            Arrays.toString(posInfo.mUp)
    ));

    if (spatialAudioEngine != null) {
        try {
            int userId = Integer.parseInt(uid);
            // 如果当前用户是本地用户
            // 那么使用 updateSelfPosition 更新本地用户的位置信息
            // 否则使用 updateRemotePosition 更新远端用户的位置信息
            if (KeyCenter.RTC_UID == userId) {
                spatialAudioEngine.updateSelfPosition(
                        posInfo.mPosition, posInfo.mForward, posInfo.mRight, posInfo.mUp
                );
            } else {
                spatialAudioEngine.updateRemotePosition(userId, new RemoteVoicePositionInfo() {{
                    position = posInfo.mPosition;
                    forward = posInfo.mForward;
                }});
            }
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
    }
}
```

#### 4 销毁空间音频引擎 [#4-销毁空间音频引擎]

不需要使用空间音频时，调用 [`destroy`](/zh-CN/api-reference/rtc/android/audio/audio-prenpost/spatial-audio#destroy) 销毁空间音频引擎。

:::warning[注意]
请在离开 RTC 频道后和销毁 RTC 引擎前调用 `destroy`。
:::

```java
// 销毁空间音频引擎
if (spatialAudioEngine != null) {
    ILocalSpatialAudioEngine.destroy();
    spatialAudioEngine = null;
}
```

### 音乐空间音频 [#音乐空间音频]

下图介绍实现媒体播放器空间音频的 API 时序：

![音乐空间音频 API 时序图](https://web-cdn.agora.io/docs-portal/img/meta-world/spatial-audio-media-android.svg)

由于空间音频是基于 NPC 或物体的位置驱动，因此在进入 Unity 场景后，无论 NPC 或物体是否移动，都需要 Unity 脚本主动向 App 发送一次 NPC 或物体的位置信息。这样可以确保空间音频引擎始终基于最新的位置数据提供空间音频。

#### 1 实现媒体播放器 [#1-实现媒体播放器]

参考[媒体播放器](/zh-CN/realtime-media/rtc/build/audio/media-player)功能指南创建一个 `IMediaPlayer` 对象，然后打开、播放媒体资源。

```java
mRtcEngine = RtcEngine.create(config);
// 创建 IMediaPlayer 对象
mediaPlayer = engine.createMediaPlayer();
mediaPlayer.open(url, 0);
// 请确保在收到 onPlayerStateChanged 回调报告 PLAYER_STATE_OPEN_COMPLETED 后再调用 play
mediaPlayer.play();
```

#### 2 设置播放器空间音频 [#2-设置播放器空间音频]

处理 Unity 场景的回调信息，解析得到物体的三维坐标位置和朝向，并将这些信息传入 [`updatePlayerPositionInfo`](/zh-CN/api-reference/rtc/android/audio/audio-prenpost/spatial-audio#updateplayerpositioninfo) 方法，设置播放器的空间音频。

```java
// 处理 Unity 场景回调信息
@Override
public void onRecvMessageFromScene(byte[] message) {
    // 解析 message，得到物体在场景里面的位置信息，包含 position 和 forward
    // position 是物体的三维坐标位置
    // forward 是物体朝向
    // 注意：message 协议格式需要由你们的 Unity 开发人员和 Native 开发人员协商规定
    ...

    // 创建 RemoteVoicePositionInfo 对象，设置位置和朝向信息
    RemoteVoicePositionInfo posInfo = new RemoteVoicePositionInfo();
    posInfo.position = pos;  // pos 为解析得到的物体位置
    posInfo.forward = forward;  // forward 为解析得到的物体朝向

    // 设置播放器的空间音频
    spatialAudioEngine.updatePlayerPosition(mMediaPlayer.getMediaPlayerId(), posInfo);
}
```
<_PlatformProcessedMarker close="true" />

<_PlatformProcessedMarker groupMode="structured" canonicalPlatform="android" platform="ios" />
本文介绍如何使用空间音频功能以增强元宇宙音频体验。

在元宇宙中，空间音频可以为用户带来更加真实的虚拟体验。例如，在一个虚拟的 3D 旅游场景中，空间音频可以让用户宛如身临其境般听到旅游中路人聊天声、海浪声、风声，让用户更沉浸式体验。

![元宇宙空间音频示意](https://web-cdn.agora.io/docs-files/1679566933312)

## 技术原理 [#技术原理-1]

空间音频功能基于声学原理，模拟声音在不 同空间环境中的传播、反射、吸收效果。通过在不同位置放置音源和听众，模拟现实中的声音传播效果，使得听众可以听到更真实自然的声音。

结合 `AgoraMetaSceneEventDelegate` 提供的用户位置信息回调和 `AgoraLocalSpatialAudioKit` 提供的空间音频系列方法，你可以实现带空间音频的元语聊。

## 示例项目 [#示例项目-1]

声网在 GitHub 上提供开源 [Agora-MetaWorld](https://github.com/AgoraIO-Community/Agora-MetaWorld/tree/dev_metasdk1.0) 示例项目供你参考。

如果你还需了解 Unity 部分的工程文件和功能指南，请联系 sales@shengwang.cn 获取。

## 前提条件 [#前提条件-1]

实现该进阶功能前，请确保你已实现基础的元语聊或元直播功能，如创建 Meta 服务、获取并下载场景资源、创建场景、设置虚拟形象的信息并进入场景。详见[基础功能](/zh-CN/solutions/meta-world/build/metachat)。

## 实现步骤 [#实现步骤-1]

用户在元宇宙中听到的声音分为两类：

- 远端用户的音频
- 本地媒体播放器播放的音频

因此，实现空间音频时，你需要按照如下步骤对两种音频都进行处理。

### 用户空间音频 [#用户空间音频-1]

下图介绍实现用户空间音频的 API 时序：

![用户空间音频 API 时序图](https://web-cdn.agora.io/docs-portal/img/meta-world/spatial-audio-user-ios.svg)

#### 1. 创建和初始化空间音频引擎 [#1-创建和初始化空间音频引擎-1]

使用空间音频前，你需要调用 [`sharedLocalSpatialAudioWithConfig`](/zh-CN/api-reference/rtc/ios/audio/audio-prenpost/spatial-audio#sharedlocalspatialaudiowithconfig) 创建和初始化空间音频引擎。

:::warning[注意]
- 请在加入 RTC 频道前调用 `sharedLocalSpatialAudioWithConfig`。
- 当使用空间音频时，请确保在 `AgoraRtcEngineKit` 中停止发布和订阅音频流。
:::

```swift
// 创建和初始化空间音频引擎
let localSpatialConfig = AgoraLocalSpatialAudioConfig()
localSpatialConfig.rtcEngine = rtcEngine
agoraLocalSpatialAudioKit = AgoraLocalSpatialAudioKit.sharedLocalSpatialAudio(with: localSpatialConfig)
```

#### 2 设置空间音频接收范围 [#2-设置空间音频接收范围-1]

调用 [`setAudioRecvRange`](/zh-CN/api-reference/rtc/ios/audio/audio-prenpost/spatial-audio#setaudiorecvrange) 设置空间音频接收范围，当远端用户相对本地用户的距离超出这个范围，本地用户就会听不到远端用户的声音。

你可以通过 `AgoraLocalSpatialAudioKit` 处理是否停止发布和订阅音频流。

```swift
// 设置空间音频的音频接收范围
// 如果超过设置的值，那么本地用户听不见远端用户的声音
agoraLocalSpatialAudioKit.setAudioRecvRange(100)
// AgoraLocalSpatialAudioKit 设置恢复发布本地音频流
// 此时需要额外检查本地用户角色不是观众，否则无法发流
agoraLocalSpatialAudioKit.muteLocalAudioStream(false)
// AgoraLocalSpatialAudioKit 设置恢复订阅所有远端音频流
agoraLocalSpatialAudioKit.muteAllRemoteAudioStreams(false)
```

#### 3 处理用户位置变化 [#3-处理用户位置变化-1]

通过 [`onUserPositionChanged`](/zh-CN/solutions/meta-world/reference/meta-api#onuerpositionchanged) 回调监听用户的位置变化。如果当前用户是本地用户，调用 [`updateSelfPosition`](/zh-CN/api-reference/rtc/ios/audio/audio-prenpost/spatial-audio#api_ibasespatialaudioengine_updateselfposition) 更新本地用户位置信息；如果当前用户是远端用户，调用 [`updateRemotePosition`](/zh-CN/api-reference/rtc/ios/audio/audio-prenpost/spatial-audio#api_ilocalspatialaudioengine_updateremoteposition) 更新远端用户的信息位置。

:::warning[注意]
请在加入 RTC 频道后监听和处理用户位置变化。
:::

```swift
// 处理用户位置的变化
func metaScene(_ scene: AgoraMetaScene, onUserPositionChanged uid: String, posInfo: AgoraMetaPositionInfo) {
    if (uid.compare(KeyCenter.RTM_UID) == .orderedSame) || (uid.compare("") == .orderedSame) {
        // 如果当前用户是本地用户
        // 那么使用 updateSelfPosition 更新本地用户的位置信息
        // 否则使用 updateRemotePosition 更新远端用户的位置信息
        agoraLocalSpatialAudioKit.updateSelfPosition(
            posInfo.position as! [NSNumber],
            axisForward: posInfo.forward as! [NSNumber],
            axisRight: posInfo.right as! [NSNumber],
            axisUp: posInfo.up as! [NSNumber]
        )
        DLog("position = \(posInfo.position), forword = \(posInfo.forward), right = \(posInfo.right), up = \(posInfo.up)")
    } else {
        let remotePositionInfo = AgoraRemoteVoicePositionInfo()
        remotePositionInfo.position = posInfo.position as! [NSNumber]
        remotePositionInfo.forward = posInfo.forward as? [NSNumber]

        agoraLocalSpatialAudioKit.updateRemotePosition(
            UInt(uid) ?? 0,
            positionInfo: remotePositionInfo
        )
    }
}
```

#### 4 销毁空间音频引擎 [#4-销毁空间音频引擎-1]

不需要使用空间音频时，调用 [`destroy`](/zh-CN/api-reference/rtc/ios/audio/audio-prenpost/spatial-audio#destroy) 销毁空间音频引擎。

:::warning[注意]
请在离开 RTC 频道后和销毁 RTC 引擎前调用 `destroy`。
:::

```swift
// 销毁空间音频引擎
if agoraLocalSpatialAudioKit != nil {
    AgoraLocalSpatialAudioKit.destroy()
    agoraLocalSpatialAudioKit = nil
}
```

### 音乐空间音频 [#音乐空间音频-1]

下图介绍实现媒体播放器空间音频的 API 时序：

![音乐空间音频 API 时序图](https://web-cdn.agora.io/docs-portal/img/meta-world/spatial-audio-media-ios.svg)

由于空间音频是基于 NPC 或物体的位置驱动，因此在进入 Unity 场景后，无论 NPC 或物体是否移动，都需要 Unity 脚本主动向 App 发送一次 NPC 或物体的位置信息。这样可以确保空间音频引擎始终基于最新的位置数据提供空间音频。

#### 1 实现媒体播放器 [#1-实现媒体播放器-1]

参考[媒体播放器](/zh-CN/realtime-media/rtc/build/audio/media-player)功能指南创建一个 `AgoraMediaPlayerProtocol` 对象，然后打开、播放媒体资源。

```swift
// 创建 AgoraRtcEngineKit 对象
rtcEngine = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self)
// 创建 AgoraMediaPlayerProtocol 对象
mediaPlayerKit = rtcEngine.createMediaPlayer(with: self)
// 打开 URL
mediaPlayerKit.open(url, startPos: 0)
// 请确保在收到 didChangedToState 回调报告 AgoraMediaPlayerStateOpenCompleted 后再调用 play
mediaPlayerKit.play()
```

#### 2 设置播放器空间音频 [#2-设置播放器空间音频-1]

处理 Unity 场景的回调信息，解析得到物体的三维坐标位置和朝向，并将这些信息传入 [`updatePlayerPositionInfo`](/zh-CN/api-reference/rtc/ios/audio/audio-prenpost/spatial-audio#api_ibasespatialaudioengine_updateplayerpositioninfo) 方法，设置播放器的空间音频。

```swift
// 处理 Unity 场景回调信息
private func setUpSpatialForMediaPlayer(_ player: AgoraRtcMediaPlayerProtocol?, position: [NSNumber], forward:[NSNumber]) {
    // 解析 message，得到物体在场景里面的位置信息，包含 position 和 forward
    // position 是物体的三维坐标位置
    // forward 是物体朝向
    // 注意：message 协议格式需要由你们的 Unity 开发人员和 Native 开发人员协商规定
    ...

    // 设置位置和朝向信息
    let positionInfo = AgoraRemoteVoicePositionInfo()
    positionInfo.position = position  // position 为解析得到的物体位置
    positionInfo.forward = forward    // forward 为解析得到的物体朝向
    if let playerId = player?.getMediaPlayerId() {
        // 设置播放器的空间音频
        agoraLocalSpatialAudioKit?.updatePlayerPositionInfo(
            Int(playerId),
            positionInfo: positionInfo
        )
    }
}
```
<_PlatformProcessedMarker close="true" />
</_PlatformTabsGroup>
