# 火山引擎 RTC AIGC 前端接入逻辑梳理 ## 一、整体架构概览 ``` ┌──────────────────────────────────────────────────────────────┐ │ 前端 (React) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌───────────┐ │ │ │ MainPage │──>│ useJoin │──>│ RtcClient │ │ │ │(入口页面) │ │(加房hook)│ │(SDK 封装) │ │ │ └──────────┘ └──────────┘ └─────┬─────┘ │ │ │ │ │ │ ┌────▼─────┐ ┌─────────▼──────────┐ │ │ │ Redux │ │ @volcengine/rtc │ │ │ │ Store │ │ (火山引擎 RTC SDK) │ │ │ └──────────┘ └─────────┬──────────┘ │ │ │ │ └──────────────────────────────────────┼───────────────────────┘ │ ┌──────────────────▼──────────────────┐ │ Backend (FastAPI) │ │ /getScenes /proxy /api/chat_cb │ └─────────────────────────────────────┘ ``` ## 二、核心模块说明 ### 2.1 配置层 (`config/index.ts`) ```typescript // 后端 API 代理地址 export const AIGC_PROXY_HOST = 'http://localhost:3001'; ``` ### 2.2 API 层 (`app/api.ts` + `app/base.ts`) 定义了 3 个 API: | API | 路径 | 说明 | |-----|------|------| | `getScenes` | `POST /getScenes` | 获取场景配置(含 RTC 凭证 AppId/RoomId/UserId/Token) | | `StartVoiceChat` | `POST /proxy?Action=StartVoiceChat` | 启动 AI 语音对话(传 `{ SceneID }` ) | | `StopVoiceChat` | `POST /proxy?Action=StopVoiceChat` | 停止 AI 语音对话(传 `{ SceneID }` ) | **请求封装逻辑** (`app/base.ts`): - `generateAPIs()` 根据 API 配置自动生成请求函数 - POST 请求格式:`fetch(AIGC_PROXY_HOST + apiPath + ?Action=xxx, { method: 'post', body: JSON })` - 响应统一结构:`{ ResponseMetadata: { Action, RequestId, Error? }, Result }` - `resultHandler()` 检查 Error 字段,成功返回 `Result`,失败抛异常 ### 2.3 RTC 客户端封装 (`lib/RtcClient.ts`) 单例模式,核心属性/方法: ``` RTCClient (单例) ├── basicInfo: { app_id, room_id, user_id, token } // 由 getScenes 返回后注入 ├── engine: IRTCEngine // RTC 引擎实例 ├── audioBotEnabled: boolean // AI Bot 是否启用 │ ├── createEngine() // 创建引擎 + 注册 AI 降噪扩展 ├── addEventListeners() // 绑定 15 个 RTC 事件回调 ├── joinRoom() // 加入房间(自动发布/订阅音频, RoomProfile=chat) ├── leaveRoom() // 离房并销毁引擎 ├── startAudioCapture() // 开始麦克风采集 ├── stopAudioCapture() // 停止麦克风采集 ├── startAgent(sceneId) // 调用 StartVoiceChat API → 启动 AI Bot ├── stopAgent(sceneId) // 调用 StopVoiceChat API → 停止 AI Bot ├── commandAgent() // 通过 Binary Message 向 Bot 发送控制指令(打断等) └── getDevices() // 枚举音频/视频设备 ``` ### 2.4 状态管理 (`store/slices/`) #### room slice — 核心状态 ```typescript { scene: string; // 当前场景 ID sceneConfigMap: Record; // 场景配置(botName, isVision 等) rtcConfigMap: Record; // RTC 凭证(AppId, RoomId, UserId, Token) isJoined: boolean; // 是否已加房 isAIGCEnable: boolean; // AI Bot 是否启用 isAITalking: boolean; // AI 正在说话 isAIThinking: boolean; // AI 正在思考 isUserTalking: boolean; // 用户正在说话 msgHistory: Msg[]; // 对话历史 localUser: LocalUser; // 本地用户(含 publishAudio 状态) remoteUsers: IUser[]; // 远端用户列表 networkQuality: NetworkQuality; // 网络质量 } ``` #### device slice — 设备状态 ```typescript { audioInputs: MediaDeviceInfo[]; // 麦克风列表 videoInputs: MediaDeviceInfo[]; // 摄像头列表 selectedMicrophone?: string; // 选中的麦克风 ID selectedCamera?: string; // 选中的摄像头 ID devicePermissions: { audio, video }; // 设备权限 } ``` ### 2.5 消息处理 (`utils/handler.ts`) AI Bot 通过 RTC 的 **Binary Message** 机制推送消息,采用 **TLV 编码格式**: ``` | type (4字节) | length (4字节, big-endian) | value (JSON字符串) | ``` 消息类型: | Type 标识 | 含义 | 数据结构 | |-----------|------|----------| | `conv` | 状态简报 | `{ Stage: { Code, Description } }` — Code: 1=LISTENING, 2=THINKING, 3=SPEAKING, 4=INTERRUPTED, 5=FINISHED | | `subv` | 字幕 | `{ data: [{ text, definite, userId, paragraph }] }` | | `tool` | Function Call | `{ tool_calls: [{ id, function: { name } }] }` | | `ctrl` | 控制指令(发送) | `{ Command, InterruptMode, Message }` | | `func` | Function Call 响应(发送) | `{ ToolCallID, Content }` | ### 2.6 事件监听 (`lib/listenerHooks.ts`) 监听 15 个 RTC 事件: | 事件 | 处理逻辑 | |------|----------| | `onError` | 处理 DUPLICATE_LOGIN 等错误 | | `onUserJoined` | AI Bot 加入房间,更新远端用户列表 | | `onUserLeave` | 远端用户离开 | | `onUserPublishStream` | 远端用户发布流(AI Bot 开始说话) | | `onUserUnpublishStream` | 远端用户取消发布 | | `onLocalAudioPropertiesReport` | 本地音量检测(用于 UI 动画) | | `onRemoteAudioPropertiesReport` | 远端音量检测 | | `onRoomBinaryMessageReceived` | **核心!** 接收 AI Bot 的 TLV 消息 → 交给 MessageHandler 解析 | | `onNetworkQuality` | 网络质量监测 | | `onAudioDeviceStateChanged` | 设备热插拔 | | `onAutoplayFailed` / `onPlayerEvent` | 自动播放失败处理 | | `onTrackEnded` | 屏幕共享关闭 | | `onLocalStreamStats` / `onRemoteStreamStats` | 流统计信息 | ## 三、完整调用流程 ### 3.1 页面初始化 ``` 1. MainPage 挂载 2. 调用 getScenes API → 获取场景列表 3. 存入 Redux:sceneConfigMap、rtcConfigMap 4. 同时将 rtcConfig 注入 RtcClient.basicInfo(含 AppId/RoomId/UserId/Token) 5. 展示 Antechamber(通话前)界面 ``` ### 3.2 用户点击"通话"按钮 → `useJoin.disPatchJoin()` ``` 1. 检测浏览器兼容性 → VERTC.isSupported() 2. 创建 RTC 引擎 → RtcClient.createEngine() └─ 内部:VERTC.createEngine(appId) + 注册 AI 降噪扩展 3. 绑定事件监听 → RtcClient.addEventListeners(listeners) 4. 加入房间 → RtcClient.joinRoom() └─ engine.joinRoom(token, roomId, userInfo, { isAutoPublish: true, isAutoSubscribeAudio: true, roomProfileType: 'chat' }) 5. 枚举音频设备 → RtcClient.getDevices({ audio: true }) 6. 更新 Redux 状态 → localJoinRoom, updateSelectedDevice, updateMediaInputs 7. 开启麦克风 → switchMic() └─ publishStream(AUDIO) + startAudioCapture() 8. 启动 AI Bot → handleAIGCModeStart() └─ RtcClient.startAgent(sceneId) └─ 内部调用 POST /proxy?Action=StartVoiceChat { SceneID } 9. 更新状态 → isAIGCEnable = true, isJoined = true 10. UI 切换:Antechamber → Room 界面 ``` ### 3.3 AI 对话过程 ``` 用户说话 → 麦克风采集 → RTC SDK 自动推流到远端 ↓ 火山引擎 RTC 服务端 ↓ ASR(语音识别)→ LLM(大模型)→ TTS(语音合成) ↓ Bot 将音频流推回房间 + 发送 Binary Message(字幕/状态) ↓ 前端接收: - 音频流 → RTC SDK 自动播放(isAutoSubscribeAudio: true) - Binary Message → onRoomBinaryMessageReceived → TLV 解码 → MessageHandler ├─ conv(状态): THINKING → 更新 isAIThinking │ SPEAKING → 更新 isAITalking │ FINISHED → isAITalking = false │ INTERRUPTED → 标记消息为已打断 └─ subv(字幕): text/definite/paragraph → 更新 msgHistory ``` ### 3.4 打断 AI ``` 方式一:语音打断(isInterruptMode = true 时自动生效,服务端检测) 方式二:点击"打断"按钮 → RtcClient.commandAgent({ agentName: botName, command: 'interrupt', }) → engine.sendUserBinaryMessage(botName, TLV 编码({ Command: 'interrupt', InterruptMode: 0, Message: '' })) ``` ### 3.5 用户挂断 → `useLeave()` ``` 1. 停止音频/视频/屏幕采集 2. 停止 AI Bot → RtcClient.stopAgent(sceneId) └─ POST /proxy?Action=StopVoiceChat { SceneID } 3. 离开房间 → RtcClient.leaveRoom() └─ engine.leaveRoom() + VERTC.destroyEngine(engine) 4. 清除 Redux 状态 → clearHistoryMsg, clearCurrentMsg, localLeaveRoom, isAIGCEnable=false 5. UI 切换:Room → Antechamber ``` ## 四、TLV 协议编解码 ### 编码 `string2tlv(str, type)` ``` 输入: str = '{"Command":"interrupt"}', type = 'ctrl' 输出 ArrayBuffer: [0-3] type 字符编码: 'c','t','r','l' → [99, 116, 114, 108] [4-7] length (big-endian): JSON 字节长度 [8-...) value: UTF-8 编码的 JSON 字符串 ``` ### 解码 `tlv2String(buffer)` ``` 输入: ArrayBuffer 输出: { type: 'subv', value: '{"data":[...]}' } ``` ## 五、关键依赖 | 包名 | 用途 | |------|------| | `@volcengine/rtc` | 火山引擎 RTC Web SDK | | `@volcengine/rtc/extension-ainr` | AI 降噪扩展(可选,不影响核心功能) | ## 六、接入要点总结 1. **后端需提供 3 个接口**:`getScenes`(返回 RTC 凭证和场景配置)、`StartVoiceChat`、`StopVoiceChat` 2. **RTC 加房参数**:`isAutoPublish: true, isAutoSubscribeAudio: true, roomProfileType: chat` 3. **AI 消息通道**:通过 RTC Binary Message (`onRoomBinaryMessageReceived`) 收发,TLV 编码 4. **AI Bot 是一个"远端用户"**:它加入同一房间,发布音频流,通过 Binary Message 推送字幕和状态 5. **打断机制**:通过 `sendUserBinaryMessage` 向 Bot 发送 `ctrl` 类型的 TLV 指令