> For the complete documentation index, see [llms.txt](https://docs.convai.com/api-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/multi-character-sessions/route-character-audio.md).

# 路由角色音频

控制共享房间麦克风，按身份绑定和静音每个角色的音频，并在 Unity 中测量角色播放情况。

控制麦克风采集，绑定一个 `AudioSource` 在多角色会话中，针对每个参与者静音或禁用某个角色的播放，并读取某个角色的播放位置，使用 `IConvaiRoomAudioService`。当你的房间已连接且其成员关系已开始报告参与者身份后，请使用此页面。

### 前提条件

* 一个已连接的多角色会话。参见 [构建你的第一个多角色会话](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/multi-character-sessions/quick-start.md).
* `IConvaiRoomAudioService`，通过以下方式获取 `ConvaiManager.TryGetRoomAudioService(out IConvaiRoomAudioService audioService)`，用于 `BindParticipantAudioOutput`, `SetParticipantAudioEnabled`以及 `TryGetCharacterAudioPlayhead`。下面的静音、远程音频和 WebGL 播放成员也可通过 `ConvaiManager.Audio` 这个便捷外观访问。
* 一个 `AudioSource` ，对应你想要单独听到的每个角色。

### 控制房间麦克风

麦克风作用于整个房间：一个采集设备为整个房间提供音频输入，并不绑定到任何单一角色。 `IsMicMuted` 报告其当前是否已静音，并且 `SetMicMuted(bool muted)` 将其静音或取消静音。订阅 `MicMuteChanged` (`Action<bool>`)，以便在状态变化时做出响应，无论是你自己的代码更改了它，还是其他内容更改了它。该外观会在其事件前加上 `开启`，因此同一事件是 `ConvaiManager.Audio.OnMicMuteChanged`.

将麦克风静音并不会决定玩家正在对哪个角色说话。该路由是由对话目标单独决定的——参见 [对话目标定位](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/conversation-targeting.md) ，以在麦克风取消静音后更改接收玩家语音的人。

```csharp
ConvaiManager manager = ConvaiManager.ActiveManager;
manager.Audio.SetMicMuted(true);
```

### 为每个角色绑定一个 AudioSource

调用 `BindParticipantAudioOutput(string participantIdentity, AudioSource audioSource)` 每个角色一次，使用 `参与者标识` 来自该角色的 `CharacterRoomMembership`。它返回 `否` 当 `participantIdentity` 为空，因此请检查返回值，不要假设绑定成功。

{% code title="Assets/Scripts/MultiCharacterAudioBinder.cs" %}

```csharp
using Convai.Runtime.Room;
using UnityEngine;

public class MultiCharacterAudioBinder : MonoBehaviour
{
    [SerializeField] private AudioSource[] _characterAudioSources;

    public void BindRoomAudio(MultiCharacterRoomSession session, IConvaiRoomAudioService audioService)
    {
        for (int i = 0; i < session.Characters.Count && i < _characterAudioSources.Length; i++)
        {
            CharacterRoomMembership membership = session.Characters[i];
            if (string.IsNullOrEmpty(membership.ParticipantIdentity))
            {
                Debug.LogWarning($"[MultiCharacter] {membership.CharacterId} has no participant identity yet.");
                continue;
            }

            audioService.BindParticipantAudioOutput(membership.ParticipantIdentity, _characterAudioSources[i]);
        }
    }
}
```

{% endcode %}

{% hint style="warning" %}
在多角色会话处于活动状态时，传入的音轨仅通过成员索引匹配到某个成员关系。若某个音轨未解析到任何成员关系，则不会附加到任何 `AudioSource` ——不会回退到按 `角色 ID`进行匹配。请按 `参与者标识`绑定，切勿按 `角色 ID`.
{% endhint %}

`ConvaiAudioOutput` 会在其 `AudioSource` 与房间音频服务在每次启用时注册，而不只是其依赖项首次解析时。角色在播放过程中被禁用后再重新启用时，会自动重新注册并保留其声音——路由并不取决于这两个组件中哪个先唤醒。

调用 `SetParticipantAudioEnabled(string participantIdentity, bool enabled)` 用于在不解除其绑定的情况下将已绑定的参与者静音或取消静音 `AudioSource`。它返回 `否` 当还没有 `AudioSource` 为该身份绑定任何内容时——请先绑定它。

```csharp
// 在不解除绑定的情况下静音次要角色
audioService.SetParticipantAudioEnabled(secondaryMembership.ParticipantIdentity, false);
```

### 为房间中的其他人类参与者路由音频

`BindParticipantAudioOutput` 和 `SetParticipantAudioEnabled` 还包括房间中存在的其他人类，例如通过 [加入现有房间](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/multi-character-sessions/join-an-existing-session.md)加入的第二位参与者。人类参与者身份使用后端格式 `human:{speaker_id}`，因此，通过空间 `AudioSource` 音频源渲染另一位学习者声音的场景，会以与绑定角色完全相同的方式绑定它。

```csharp
audioService.BindParticipantAudioOutput("human:learner-43", _otherLearnerAudioSource);
```

### 静音或禁用某个角色的播放

`SetCharacterMuted(string characterId, bool muted)` 和 `IsCharacterMuted(string characterId)` 可在不改变网络传入内容的情况下控制本地播放音量——该角色的音轨会继续流式传输，只有本地输出被静音。 `SetRemoteAudioEnabled(string characterId, bool enabled)` 和 `IsRemoteAudioEnabled(string characterId)` 更进一步：禁用会完全取消订阅该角色的音轨，因此根本不会接收其任何音频包。静音是音量决策；禁用是带宽决策。订阅 `RemoteAudioEnabledChanged` (`Action<string, bool>`，先是角色 ID，然后是新的启用状态），以便在你自己的代码或其他内容更改它时做出响应。在该外观中，同一事件是 `ConvaiManager.Audio.OnRemoteAudioEnabledChanged`.

```csharp
// 在本地静音某个角色，但继续接收其音轨
manager.Audio.SetCharacterMuted(characterId, true);

// 完全停止接收某个角色的音轨
manager.Audio.SetRemoteAudioEnabled(characterId, false);
```

{% hint style="warning" %}
`SetCharacterMuted`, `IsCharacterMuted`, `SetRemoteAudioEnabled`, `IsRemoteAudioEnabled`以及 `TryGetCharacterAudioPlayhead` 都以 `characterId`为键，而在一个包含两个共享同一 `角色 ID` 来驱动目标——参见 [为什么一个 ID 不能共享](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/multi-character-sessions/character-identity.md#why-an-id-cannot-be-shared)。它们都会解析到携带该角色 ID 的第一个成员关系，因此一旦房间中存在克隆体，它们都无法针对其中某个实例。请使用 `BindParticipantAudioOutput` 和 `SetParticipantAudioEnabled`，以 `参与者标识`为键，在房间可能包含克隆体时始终如此——对于这种情况，它们是唯一足够精确的参与者级控制。
{% endhint %}

### 在需要用户手势的平台上启用播放

某些平台（包括 WebGL）在音频播放开始前需要用户手势。检查当前平台是否需要该手势——属性是 `RequiresUserGestureForAudio` 时 `IConvaiRoomAudioService` 和 `RequiresUserGesture` 在 `ConvaiManager.Audio` 外观——并且 `IsAudioPlaybackActive` 以了解播放是否已在运行。调用 `EnableAudioPlayback()` 在完成所需手势之后，通常从绑定到点击或轻触的 UI 事件处理程序中调用。 `CanEnableAudioPlayback` 报告当前是否满足成功调用的条件，因此按钮可以在满足之前自行禁用。

```csharp
public void OnEnableAudioButtonClicked()
{
    if (manager.Audio.CanEnableAudioPlayback)
        manager.Audio.EnableAudioPlayback();
}
```

### 测量某个角色的播放位置

`TryGetCharacterAudioPlayhead(string characterId, out double playedSeconds)` 读取自当前播放信号开始以来，某个角色的音频实际上已经渲染到输出设备的秒数。与上面的静音控制一样，它以 `characterId` 为键，并解析到第一个匹配的成员关系，因此无法将一个克隆体与另一个单独测量。播放头在欠载期间会冻结，并计入任何漂移校正跳过，因此它反映的是玩家实际听到的内容，而不是按墙上时钟估算的时间。该方法在以下情况下返回 `否` 当当前平台的音频流不公开播放头时——此时请回退到墙上时钟计时器，而不要将 `playedSeconds` 视为有效。

```csharp
if (audioService.TryGetCharacterAudioPlayhead(characterId, out double playedSeconds))
    Debug.Log($"[MultiCharacter] {characterId} has played {playedSeconds:F2}s.");
```

### 故障排查

| 症状                                                       | 原因                                             | 修复方法                                                                                                                                 |
| -------------------------------------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `BindParticipantAudioOutput` 返回 `否`                      | `participantIdentity` 在调用执行时为空。                | 等待该成员关系的 `CharacterStatusChanged` 或 `CharacterAdded` 事件后再绑定，然后读取 `参与者标识` 状态。                                                         |
| `SetParticipantAudioEnabled` 返回 `否`                      | 否 `AudioSource` 是否已为该参与者身份绑定。                  | 调用 `BindParticipantAudioOutput` 先为该身份绑定。                                                                                             |
| 次要角色即使在绑定后也没有产生音频                                        | 绑定的身份与成员关系实际的 `参与者标识`不匹配，这通常是由于按 `角色 ID` 改为此项。 | 重新读取 `CharacterRoomMembership.ParticipantIdentity` 来自 `session.Characters` 并重新绑定。                                                    |
| `SetCharacterMuted` 或 `SetRemoteAudioEnabled` 似乎影响了错误的实例 | 房间中有两个成员关系共享同一个 `角色 ID`，而按角色 ID 作为键的调用无法区分它们。  | 切换到 `SetParticipantAudioEnabled`，以 `参与者标识`，用于该角色。                                                                                    |
| `EnableAudioPlayback` 没有效果                               | `CanEnableAudioPlayback` 为 `否` 在调用执行时。         | 检查 `CanEnableAudioPlayback` 在调用之前，并在以下情况下触发 `EnableAudioPlayback` 在需要用户手势的平台上，从 UI 点击或轻触处理程序中调用 `RequiresUserGestureForAudio` 是 `是`. |
| `TryGetCharacterAudioPlayhead` 返回 `否`                    | 当前平台的音频流不公开播放头。                                | 改用墙上时钟计时器来跟踪经过时间。                                                                                                                    |

### 下一步

{% content-ref url="/pages/6481618ebe16f2ac14d3eecace53ed97c7f369d1" %}
[角色身份](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/multi-character-sessions/character-identity.md)
{% endcontent-ref %}

{% content-ref url="/pages/f9d36c1e08a6a79b4209c545683def235a26a8e5" %}
[处理房间事件](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/multi-character-sessions/handle-roster-events.md)
{% endcontent-ref %}

{% content-ref url="/pages/d8451a87588c6ea22a959d1a3ecd7ecc09422e8a" %}
[对话目标选择](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/conversation-targeting.md)
{% endcontent-ref %}


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/features/multi-character-sessions/route-character-audio.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
