> 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/scripting-reference/audio-api.md).

# 音频 API

Unity SDK 中用于从脚本控制麦克风静音、按角色输出音频以及解锁播放的参考。

`ConvaiAudio` 是音频外观层上的 `ConvaiManager`，用于通过脚本控制麦克风捕获、逐角色远程音频输出以及音频播放解锁。它将职责清晰分离：麦克风输入（你发送给 Convai 的内容）、角色音频输出（你从角色那里听到的内容）以及 WebGL 音频播放门控。

**访问：** `ConvaiManager.ActiveManager.Audio`

```csharp
var audio = ConvaiManager.ActiveManager?.Audio;
if (audio == null) return; // 管理器尚未初始化
```

***

### 麦克风控制

#### 属性

| 属性                       | 类型     | 说明                                  |
| ------------------------ | ------ | ----------------------------------- |
| `IsMicMuted`             | `bool` | 当麦克风静音且未向 Convai 发送音频时为 True        |
| `RequiresUserGesture`    | `bool` | 当音频播放因等待用户交互而被阻止时为 True（仅 WebGL）    |
| `IsAudioPlaybackActive`  | `bool` | 当音频播放当前处于活动且已解锁时为 True              |
| `CanEnableAudioPlayback` | `bool` | 当 `EnableAudioPlayback()` 可被调用以解锁音频 |

#### 方法

| 方法                                                                             | 返回                       | 说明                  |
| ------------------------------------------------------------------------------ | ------------------------ | ------------------- |
| `SetMicMuted(bool muted)`                                                      | `void`                   | 显式设置麦克风静音状态         |
| `ToggleMicMuted()`                                                             | `bool`                   | 切换麦克风静音状态。返回新的静音状态。 |
| `StartListeningAsync(int microphoneIndex = 0, CancellationToken ct = default)` | `IConvaiOperation<Unit>` | 在指定设备索引上开始麦克风捕获     |
| `StopListeningAsync(CancellationToken ct = default)`                           | `IConvaiOperation<Unit>` | 停止麦克风捕获             |

该 `microphoneIndex` 参数在 `StartListeningAsync` 中对应于返回列表中的一个索引 `IMicrophoneDeviceService.GetAvailableDevices()`。在选择索引前，请使用 `ConvaiManager.ActiveManager.TryGetMicrophoneDeviceService(out var svc)` 按名称枚举设备。索引 `0` 使用系统默认麦克风。

#### 事件

| 事件                 | 签名             | 触发于                               |
| ------------------ | -------------- | --------------------------------- |
| `OnMicMuteChanged` | `Action<bool>` | 麦克风静音状态发生变化。参数：新的静音状态（true = 静音）。 |

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

public class MicMuteIndicator : MonoBehaviour
{
    [SerializeField] private GameObject _muteIcon;

    private void OnEnable()
    {
        var audio = ConvaiManager.ActiveManager?.Audio;
        if (audio == null) return;
        audio.OnMicMuteChanged += OnMuteChanged;
        _muteIcon.SetActive(audio.IsMicMuted);
    }

    private void OnDisable()
    {
        var audio = ConvaiManager.ActiveManager?.Audio;
        如果 audio != null
            audio.OnMicMuteChanged -= OnMuteChanged;
    }

    private void OnMuteChanged(bool isMuted) => _muteIcon.SetActive(isMuted);
}
```

***

### 按角色音频控制

这些方法控制特定角色的音频输出是否在本地播放。请使用来自以下来源的角色 ID： `ConvaiCharacter.CharacterId` 或 `ConvaiManager.ActiveManager.Characters`.

| 方法                                                        | 返回     | 说明                              |
| --------------------------------------------------------- | ------ | ------------------------------- |
| `SetCharacterMuted(string characterId, bool muted)`       | `bool` | 设置特定角色的静音状态。返回 `true` 如果更改已应用。  |
| `MuteCharacter(string characterId)`                       | `bool` | 使特定角色的音频输出静音。返回 `true` 如果已应用。   |
| `UnmuteCharacter(string characterId)`                     | `bool` | 取消特定角色音频输出的静音。返回 `true` 如果已应用。  |
| `IsCharacterMuted(string characterId)`                    | `bool` | 返回 `true` 如果该角色的音频输出当前已静音。      |
| `SetRemoteAudioEnabled(string characterId, bool enabled)` | `bool` | 为角色启用或禁用远程音频输出。返回 `true` 如果已应用。 |
| `IsRemoteAudioEnabled(string characterId)`                | `bool` | 返回 `true` 如果该角色的远程音频输出已启用。      |

{% hint style="info" %}
**静音与远程音频启用：** 这两者是独立的控制项。远程音频启用/禁用控制 SDK 是否为该角色流式传输音频输出。静音控制的是本地接收到的音频是否通过你的音频输出设备播放。禁用远程音频可节省带宽；静音只是本地音量控制。
{% endhint %}

#### 事件

| 事件                            | 签名                     | 触发于                                       |
| ----------------------------- | ---------------------- | ----------------------------------------- |
| `OnRemoteAudioEnabledChanged` | `Action<string, bool>` | 角色的远程音频启用状态发生变化。参数：characterId，isEnabled。 |

***

### 音频播放——WebGL

在 WebGL 中，浏览器会阻止音频播放，直到用户与页面发生交互。SDK 会根据这一要求对音频输出进行门控。

| 成员                       | 类型     | 说明                                  |
| ------------------------ | ------ | ----------------------------------- |
| `RequiresUserGesture`    | `bool` | 当音频因等待用户交互而被阻止时为 True               |
| `CanEnableAudioPlayback` | `bool` | 当 `EnableAudioPlayback()` 可调用       |
| `IsAudioPlaybackActive`  | `bool` | 当音频播放已解锁并处于活动状态时为 True              |
| `EnableAudioPlayback()`  | `void` | 解锁音频播放。 **必须在用户手势处理程序内调用** （按钮点击等）。 |

{% hint style="danger" %}
在 WebGL 中，调用 `EnableAudioPlayback()` 位于用户手势处理程序之外（例如在 `Start()` 或 `OnEnable()`）不会生效——浏览器会阻止解锁。将其绑定到 UI 按钮的 `onClick` 事件，或者在 `OnPointerClick`.
{% endhint %}

```csharp
using Convai.Runtime.Facades;
using UnityEngine;
using UnityEngine.UI;

public class WebGLStartButton : MonoBehaviour
{
    [SerializeField] private Button     _startButton;
    [SerializeField] private GameObject _startPanel;

    private void OnEnable()  => _startButton.onClick.AddListener(OnStartClicked);
    private void OnDisable() => _startButton.onClick.RemoveListener(OnStartClicked);

    private void OnStartClicked()
    {
        var audio = ConvaiManager.ActiveManager?.Audio;
        if (audio == null) return;

        // 在用户手势内解锁音频
        if (audio.CanEnableAudioPlayback)
            audio.EnableAudioPlayback();

        // 连接并开始监听
        ConvaiManager.ActiveManager.EnableAudioAndStartListening();

        _startPanel.SetActive(false);
    }
}
```

***

### 使用示例

#### 示例 1——训练模拟 HUD 中的静音按钮

一个军事训练模拟在 HUD 中提供了一个按键式麦克风静音功能，这样受训者可以在向观察员发言前先将自己静音，而无需断开连接。

```csharp
using Convai.Runtime.Facades;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

public class MuteButton : MonoBehaviour
{
    [SerializeField] private Button   _button;
    [SerializeField] private TMP_Text _label;

    private void OnEnable()
    {
        _button.onClick.AddListener(OnClicked);
        var audio = ConvaiManager.ActiveManager?.Audio;
        如果 audio != null
        {
            audio.OnMicMuteChanged += RefreshLabel;
            RefreshLabel(audio.IsMicMuted);
        }
    }

    private void OnDisable()
    {
        _button.onClick.RemoveListener(OnClicked);
        var audio = ConvaiManager.ActiveManager?.Audio;
        如果 audio != null
            audio.OnMicMuteChanged -= RefreshLabel;
    }

    private void OnClicked() => ConvaiManager.ActiveManager?.Audio?.ToggleMicMuted();

    private void RefreshLabel(bool isMuted) =>
        _label.text = isMuted ? "取消静音" : "静音";
}
```

#### 示例 2——多 NPC 评估场景中的按角色音频切换

一个企业入职培训模拟有两位 AI 教员。评估管理器脚本会在主教员的评估环节将副教员静音，然后再恢复两者。

```csharp
using Convai.Runtime.Components;
using Convai.Runtime.Facades;
using System.Collections;
using UnityEngine;

public class AssessmentAudioManager : MonoBehaviour
{
    [SerializeField] private ConvaiCharacter _primaryInstructor;
    [SerializeField] private ConvaiCharacter _secondaryInstructor;

    public void BeginPrimaryEvaluation()
    {
        var audio = ConvaiManager.ActiveManager?.Audio;
        if (audio == null) return;

        // 将副教员静音，这样只会听到主教员
        audio.MuteCharacter(_secondaryInstructor.CharacterId);
        audio.UnmuteCharacter(_primaryInstructor.CharacterId);
    }

    public void RestoreBothInstructors()
    {
        var audio = ConvaiManager.ActiveManager?.Audio;
        if (audio == null) return;

        audio.UnmuteCharacter(_primaryInstructor.CharacterId);
        audio.UnmuteCharacter(_secondaryInstructor.CharacterId);
    }
}
```

#### 示例 3——连接前选择麦克风设备

一个交互式体验允许用户在会话开始前从下拉列表中选择自己偏好的麦克风，然后使用所选设备开始监听。

```csharp
using Convai.Runtime.Facades;
using System.Collections.Generic;
using TMPro;
using UnityEngine;

public class MicrophoneSelector : MonoBehaviour
{
    [SerializeField] private TMP_Dropdown _dropdown;

    private List<string> _deviceNames = new();

    private void Start()
    {
        var manager = ConvaiManager.ActiveManager;
        if (manager == null || !manager.TryGetMicrophoneDeviceService(out var svc)) return;

        _deviceNames.Clear();
        _dropdown.ClearOptions();

        foreach (var device in svc.GetAvailableDevices())
        {
            _deviceNames.Add(device.Name);
            _dropdown.options.Add(new TMP_Dropdown.OptionData(device.Name));
        }

        _dropdown.RefreshShownValue();
    }

    public async void OnConfirmDevice()
    {
        int index = _dropdown.value;
        var audio = ConvaiManager.ActiveManager?.Audio;
        if (audio == null) return;

        await audio.StartListeningAsync(index, destroyCancellationToken);
        Debug.Log($"Listening on: {_deviceNames[index]}");
    }
}
```

***

### 故障排除

| 症状                                     | 可能原因                                          | 修复方法                                                                                                          |
| -------------------------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| WebGL 上没有音频输出                          | `EnableAudioPlayback()` 未在用户手势中调用             | 将 `EnableAudioPlayback()` 绑定到按钮的 `onClick`；不要在 `Start()` 或 `Awake()`                                          |
| `ToggleMicMuted()` 返回 `true` 但角色仍能听到玩家 | `IsMicMuted` 控制 SDK 静音 —— 请确认角色会话已连接且音频管线正在运行 | 检查 `IsSessionConnected` 在当前活动角色上生效；静音只会在会话进行中生效                                                               |
| `MuteCharacter()` 返回 `false`           | 角色 ID 无法识别——角色可能尚未注册                          | 验证该 ID 是否匹配 `ConvaiCharacter.CharacterId`；该角色必须位于 `ConvaiManager.Characters`                                  |
| `StartListeningAsync` 在移动设备上失败         | 平台麦克风权限未授予                                    | 使用 `TryGetPermissionService` 在调用前请求麦克风权限 `StartListeningAsync`                                                |
| `IsRemoteAudioEnabled` 返回 `false` 连接后  | `EnableRemoteAudioOnStart` 在角色上是 `false`      | 设置 `ConvaiCharacter.EnableRemoteAudioOnStart = true` 在 Inspector 中，或者调用 `character.EnableRemoteAudio()` 会话开始后 |

***

### 下一步

有关按角色音频事件和会话控制，请参阅 [角色与玩家 API](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/scripting-reference/character-and-player-api.md)。有关 WebGL 特定的音频要求和限制，请参阅 [WebGL 平台指南](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/platform-guides/webgl.md)。有关连接和会话控制，请参阅 [ConvaiManager API](/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/scripting-reference/convaimanager-api.md).


---

# 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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/convai-unity-sdk/scripting-reference/audio-api.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
