> 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/unity-plugin-beta-overview/features/vision/advanced-topics.md).

# 高级主题

## 高级视觉配置与自定义集成

本页涵盖以下内容的完整脚本 API： `ConvaiVisionPublisher`、实现自定义帧源的约定、在 Vision 生命周期中发出的领域事件、平台特定行为以及跨平台兼容性矩阵。本文面向需要超出 Inspector 所能提供的程序化控制的开发者。

***

## ConvaiVisionPublisher 脚本 API

<table><thead><tr><th width="183.49993896484375">成员</th><th>签名</th><th>说明</th></tr></thead><tbody><tr><td><code>IsPublishing</code></td><td><code>bool IsPublishing { get; }</code></td><td><code>true</code> 当视频轨道正在发布到房间时。</td></tr><tr><td><code>FrameSource</code></td><td><code>IVisionFrameSource FrameSource { get; }</code></td><td>当前活动的帧源。 <code>null</code> 在 WebGL 上。</td></tr><tr><td><code>PublishPolicy</code></td><td><code>VisionPublishPolicy PublishPolicy { get; }</code></td><td>当前发布策略。</td></tr><tr><td><code>VideoTrackName</code></td><td><code>string VideoTrackName { get; }</code></td><td>正在使用的 WebRTC 视频轨道名称。</td></tr><tr><td><code>SetPublishPolicy</code></td><td><code>void SetPublishPolicy(VisionPublishPolicy policy)</code></td><td>更改发布策略。如果发布处于活动状态，轨道会立即以新配置文件重启。</td></tr><tr><td><code>EnablePublishing</code></td><td><code>void EnablePublishing(bool enabled)</code></td><td>在不更改策略的情况下开始或停止发布。当策略为 <code>Manual</code>；适用于所有策略。</td></tr></tbody></table>

#### 条件策略选择示例

```csharp
using Convai.Modules.Vision;
using Convai.Runtime.Vision.Publishing;

ConvaiVisionPublisher publisher = GetComponent<ConvaiVisionPublisher>();

bool isHighBandwidthNetwork = /* 你的网络质量检查 */;
VisionPublishPolicy chosen = isHighBandwidthNetwork
    ? VisionPublishPolicy.HighResponsiveness
    : VisionPublishPolicy.LowOverhead;

publisher.SetPublishPolicy(chosen);
```

***

## 实现自定义帧源

如果没有任何内置源适合你的用例——例如，你想发布由自定义后处理管线生成的渲染纹理，或者从第三方 SDK 进行流式传输——请实现 `IVisionFrameSource`.

### 接口约定

```csharp
namespace Convai.Runtime.Vision.Sources
{
    public interface IVisionFrameSource
    {
        bool IsCapturing { get; }
        long FrameCount { get; }
        (int Width, int Height) FrameDimensions { get; }
        float TargetFrameRate { get; }
        string SourceId { get; }

        // 必须是 Y 翻转（自上而下）的 RenderTexture
        RenderTexture CurrentRenderTexture { get; }

        bool IsFrameReady { get; }
        event Action FrameReady;

        void StartCapture();
        void StopCapture();
    }
}
```

### Y 翻转要求

`CurrentRenderTexture` 必须是 **自上而下** （相对于 Unity 默认自下而上的约定进行了 Y 翻转）。未能翻转纹理会在后端产生上下颠倒的视频流。请使用 `Graphics.Blit` 配合翻转后的缩放向量：

```csharp
// 将源翻转到 outputTexture 中（自上而下方向）
Graphics.Blit(sourceTexture, outputTexture, new Vector2(1f, -1f), new Vector2(0f, 1f));
```

### 最小自定义实现

```csharp
using System;
using UnityEngine;
using Convai.Runtime.Vision.Sources;

public class MyCustomFrameSource : MonoBehaviour, IVisionFrameSource
{
    [SerializeField] RenderTexture _sourceTexture;

    RenderTexture _outputTexture;
    bool _isCapturing;
    long _frameCount;

    public bool IsCapturing => _isCapturing;
    public long FrameCount => _frameCount;
    public (int Width, int Height) FrameDimensions =>
        _outputTexture != null ? (_outputTexture.width, _outputTexture.height) : (0, 0);
    public float TargetFrameRate => 15f;
    public string SourceId => "custom";
    public RenderTexture CurrentRenderTexture => _outputTexture;
    public bool IsFrameReady => _outputTexture != null && _frameCount > 0;

    public event Action FrameReady;

    public void StartCapture()
    {
        _outputTexture = new RenderTexture(
            _sourceTexture.width, _sourceTexture.height, 24, RenderTextureFormat.ARGB32);
        _isCapturing = true;
    }

    public void StopCapture()
    {
        _isCapturing = false;
        if (_outputTexture != null) { _outputTexture.Release(); _outputTexture = null; }
    }

    void Update()
    {
        if (!_isCapturing || _sourceTexture == null || _outputTexture == null) return;

        // 将源纹理进行 Y 翻转并写入输出
        Graphics.Blit(_sourceTexture, _outputTexture, new Vector2(1f, -1f), new Vector2(0f, 1f));
        _frameCount++;
        FrameReady?.Invoke();
    }
}
```

### 可选：IVisionFrameSourceStatusProvider

为了公开详细的状态信息（由 `VisionDebugPreview` 以及协调器的健康检查使用），还要实现 `IVisionFrameSourceStatusProvider`:

```csharp
using Convai.Runtime.Vision.Sources;

public class MyCustomFrameSource : MonoBehaviour, IVisionFrameSource, IVisionFrameSourceStatusProvider
{
    public VisionSourceState State { get; private set; } = VisionSourceState.Idle;
    public VisionSourceErrorKind ErrorKind { get; private set; } = VisionSourceErrorKind.None;
    public string StatusMessage { get; private set; }
    public bool HasUsableFrame { get; private set; }
    public event Action StatusChanged;

    // ...
}
```

将你的自定义组件赋给 `ConvaiVisionPublisher`的 **帧源组件** 字段。发布器接受任何 `MonoBehaviour` ，实现了 `IVisionFrameSource`.

***

## 领域事件

Vision 通过 SDK 的 `IEventHub`发布所有重要状态变化。可从任何持有 `IEventHub` 引用的组件订阅这些事件，或通过 ConvaiManager 的事件基础设施订阅。

### 事件参考

<table><thead><tr><th width="225">事件类型</th><th>命名空间</th><th>关键字段</th></tr></thead><tbody><tr><td><code>VisionCaptureStarted</code></td><td><code>Convai.Domain.DomainEvents.Vision</code></td><td><code>宽度</code>, <code>高度</code>, <code>FramesPerSecond</code>, <code>SourceId</code>, <code>时间戳</code>, <code>宽高比</code>, <code>总像素数</code></td></tr><tr><td><code>VisionFrameCaptured</code></td><td><code>Convai.Domain.DomainEvents.Vision</code></td><td><code>宽度</code>, <code>高度</code>, <code>FrameIndex</code>, <code>SizeBytes</code>, <code>SourceId</code>, <code>时间戳</code></td></tr><tr><td><code>VisionCaptureStopped</code></td><td><code>Convai.Domain.DomainEvents.Vision</code></td><td><code>TotalFramesCaptured</code>, <code>原因</code>, <code>SourceId</code>, <code>错误消息</code>, <code>错误代码</code>, <code>IsError</code>, <code>IsNormalStop</code></td></tr><tr><td><code>VideoTrackPublished</code></td><td><code>Convai.Domain.DomainEvents.Vision</code></td><td><code>TrackSid</code>, <code>TrackName</code>, <code>RoomSessionId</code>, <code>时间戳</code>, <code>IsVisionTrack</code></td></tr><tr><td><code>VideoTrackUnpublished</code></td><td><code>Convai.Domain.DomainEvents.Vision</code></td><td><code>TrackSid</code>, <code>TrackName</code>, <code>原因</code>, <code>时间戳</code>, <code>IsNormalUnpublish</code></td></tr></tbody></table>

{% hint style="info" %}
`VisionFrameCaptured` 不 **不是** 携带帧像素数据。它是一个轻量级的统计事件，用于遥测和监控。实际帧数据会直接通过视频管线传输。
{% endhint %}

### VisionCaptureStopReason 值

| 值           | 含义                            |
| ----------- | ----------------------------- |
| `用户请求` (0)  | `StopCapture()` 已被显式调用        |
| `会话结束` (1)  | Convai 房间会话已结束                |
| `相机丢失` (2)  | 摄像头已断开连接或变得不可用                |
| `错误` (3)    | 发生了内部错误（参见 `错误消息` 是位于 `错误代码`) |
| `组件已禁用` (4) | 帧源组件已被禁用                      |

### VideoTrackUnpublishReason 值

| 值           | 含义                              |
| ----------- | ------------------------------- |
| `用户请求` (0)  | `EnablePublishing(false)` 或策略更改 |
| `会话结束` (1)  | 房间会话已结束                         |
| `源丢失` (2)   | 帧源被移除或意外停止                      |
| `错误` (3)    | 由于传输错误，轨道取消发布                   |
| `组件已禁用` (4) | 发布器组件已被禁用                       |

### 从 MonoBehaviour 访问 Vision 状态

`IEventHub` 是 SDK 内部模块依赖注入系统的一部分。它不能直接从常规 `MonoBehaviour` 脚本中访问——它会提供给实现 `IConvaiModule` 的类，通过模块生命周期注入。

对于用户脚本，推荐的方法是通过发布器和帧源直接监视状态：

```csharp
using UnityEngine;
using Convai.Modules.Vision;
using Convai.Runtime.Vision.Sources;

public class VisionStateMonitor : MonoBehaviour
{
    [SerializeField] ConvaiVisionPublisher _publisher;
    [SerializeField] MonoBehaviour _frameSourceComponent;

    IVisionFrameSourceStatusProvider _statusProvider;
    bool _wasPublishing;

    void OnEnable()
    {
        _statusProvider = _frameSourceComponent as IVisionFrameSourceStatusProvider;
        if (_statusProvider != null)
            _statusProvider.StatusChanged += OnFrameSourceStatusChanged;
    }

    void OnDisable()
    {
        if (_statusProvider != null)
            _statusProvider.StatusChanged -= OnFrameSourceStatusChanged;
    }

    void Update()
    {
        // 检测 IsPublishing 变化
        bool isPublishing = _publisher != null && _publisher.IsPublishing;
        if (isPublishing != _wasPublishing)
        {
            _wasPublishing = isPublishing;
            Debug.Log($"Vision 发布状态已更改：{isPublishing}");
        }
    }

    void OnFrameSourceStatusChanged()
    {
        if (_statusProvider == null) return;
        Debug.Log($"帧源状态：{_statusProvider.State} | 错误：{_statusProvider.ErrorKind}");
        if (!string.IsNullOrEmpty(_statusProvider.StatusMessage))
            Debug.Log($"状态消息：{_statusProvider.StatusMessage}");
    }
}
```

### 领域事件订阅（SDK 模块上下文）

如果你正在构建一个实现 `IConvaiModule` 并通过模块生命周期接收 `IEventHub` 的类，领域事件订阅遵循以下模式：

```csharp
using Convai.Domain.DomainEvents.Vision;
using Convai.SDK.Infrastructure.Events; // IEventHub

// 在通过模块注入接收 IEventHub 的类中：
void Subscribe(IEventHub eventHub)
{
    eventHub.Subscribe<VisionCaptureStarted>(OnCaptureStarted);
    eventHub.Subscribe<VisionCaptureStopped>(OnCaptureStopped);
    eventHub.Subscribe<VideoTrackPublished>(OnTrackPublished);
}

void Unsubscribe(IEventHub eventHub)
{
    eventHub.Unsubscribe<VisionCaptureStarted>(OnCaptureStarted);
    eventHub.Unsubscribe<VisionCaptureStopped>(OnCaptureStopped);
    eventHub.Unsubscribe<VideoTrackPublished>(OnTrackPublished);
}

void OnCaptureStarted(VisionCaptureStarted e)
    => Debug.Log($"Vision 捕获已开始：{e.Width}x{e.Height} @ {e.FramesPerSecond} fps");

void OnCaptureStopped(VisionCaptureStopped e)
{
    if (e.IsError)
        Debug.LogError($"Vision 捕获已停止，错误：{e.ErrorMessage}");
}

void OnTrackPublished(VideoTrackPublished e)
    => Debug.Log($"视频轨道已发布：{e.TrackName}（SID：{e.TrackSid}）");
```

***

## WebGL 平台深度解析

在 WebGL 构建中，Vision 管线会绕过 `IVisionFrameSource` 约定，完全不使用。相反， `ConvaiVisionPublisher` 使用浏览器的 `canvas.captureStream()` API 捕获可见的 Unity WebGL 画布，并发布生成的 `MediaStream` 。

与原生路径的主要差异：

| 方面                   | 原生                | WebGL                    |
| -------------------- | ----------------- | ------------------------ |
| 帧源组件                 | 所需                | 不使用                      |
| 捕获分辨率                | 由 `CapturePreset` | 与浏览器画布大小一致               |
| 最大帧率                 | 最高 30 fps         | 上限为 15 fps               |
| `VisionDebugPreview` | 显示捕获              | 显示为空白（无 `RenderTexture`) |
| `IsPublishing`       | `true` 当轨道处于直播状态时 | `true` 当轨道处于直播状态时        |
| 发布策略                 | 全部值均受支持           | 帧率限制为 15 fps             |

{% hint style="warning" %}
WebGL 浏览器策略要求在允许音频播放之前必须有用户手势（点击、按键），即使视频已经在发布中也是如此。如果你的会话同时使用 Vision 和语音，请确保用户在房间连接之前已经与页面交互。
{% endhint %}

***

## 平台兼容性矩阵

<table><thead><tr><th width="235.00006103515625">功能</th><th>PC / Mac</th><th>Android / iOS</th><th>WebGL</th><th>Meta Quest</th></tr></thead><tbody><tr><td><code>CameraVisionFrameSource</code></td><td>✅</td><td>✅</td><td>❌（无需帧源）</td><td>✅</td></tr><tr><td><code>WebcamVisionFrameSource</code></td><td>✅</td><td>✅（权限流程）</td><td>❌</td><td>❌</td></tr><tr><td><code>QuestVisionFrameSource</code></td><td>❌</td><td>❌</td><td>❌</td><td>✅（需要 Meta XR SDK）</td></tr><tr><td>WebGL 画布捕获</td><td>❌</td><td>❌</td><td>✅（自动）</td><td>❌</td></tr><tr><td><code>VisionDebugPreview</code></td><td>✅（仅编辑器）</td><td>✅（仅编辑器）</td><td>⚠️ 空白</td><td>✅（仅编辑器）</td></tr><tr><td>最大发布 FPS</td><td>30</td><td>30</td><td>15</td><td>30</td></tr><tr><td><code>高响应</code> 策略</td><td>✅</td><td>✅</td><td>✅（FPS 受限）</td><td>✅</td></tr></tbody></table>

***

## 常见陷阱

{% hint style="warning" %}
**同一层级中的多个帧源。** 如果存在多个 `IVisionFrameSource` 组件，请始终将正确的一个分配给 `ConvaiVisionPublisher`的 **帧源组件** 字段。自动发现会选择它找到的第一个源，这可能不是你想要的那个。
{% endhint %}

{% hint style="warning" %}
**`VisionPublishPolicy` 仅在客户端侧生效。** 该策略控制传输（FPS 和比特率）。它不控制 Convai 后端使用哪个 AI 模型或视觉提供方，也不能保证角色的特定响应延迟。
{% endhint %}

{% hint style="warning" %}
**SRP/URP + `CameraCaptureMode`.** 在 SRP/URP 项目中， `Auto` 会正确选择显式渲染路径。在 SRP 项目中手动设置 `CameraCaptureMode` 到 `BuiltInHooks` 会产生黑屏，因为 SRP 摄像机不会触发渲染回调。
{% endhint %}

***

## 结论

本页涵盖了 Vision 可编程接口的全部深度——发布器 API、自定义帧源约定、领域事件、WebGL 管线以及平台矩阵。若要快速回到基于 Inspector 的基础内容，请重新查看 [快速入门](/api-docs/zh/cha-jian-yu-ji-cheng/unity-plugin-beta-overview/features/vision/quick-start.md)。如需诊断上述任何区域的问题，请参见 [故障排查](/api-docs/zh/cha-jian-yu-ji-cheng/unity-plugin-beta-overview/features/vision/troubleshooting-and-diagnostics.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 dynamically by asking a question.

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

```
GET https://docs.convai.com/api-docs/zh/cha-jian-yu-ji-cheng/unity-plugin-beta-overview/features/vision/advanced-topics.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
