# 开发参考

## 1. 系统边界

这个项目当前采用单进程后端 + 浏览器本地渲染 3D 数字人的模式。

明确边界如下：

- 服务端不再输出视频流，只输出音频流和动画控制帧。
- 浏览器负责 Three.js/VRM 渲染、模型加载、动画缓冲和最终口型展示。
- WebRTC 只承载音频。
- 字幕、状态和动画控制通过 HTTP/SSE/WebSocket 传输。

这意味着后续如果你要替换数字人渲染方案，优先改的是浏览器侧；如果你要替换识别、大模型或语音合成，优先改的是 `services/` 和 `core/`。

## 2. 运行时主链路

### 2.1 语音链路

1. 浏览器通过 `getUserMedia` 获取麦克风。
2. 浏览器调用 `POST /webrtc/offer`，建立音频上行和下行连接。
3. 服务端在 [main.py](../main.py) 中接收音轨并进入 `_consume_user_audio()`。
4. 音频被重采样为 16k 单声道后送入 VAD。
5. VAD 检测到用户语音结束后，触发 `ChatPipeline.process_turn()`。
6. `ASRService` 输出用户文本。
7. `LLMService` 调用线上 DeepSeek 兼容接口生成回答。
8. `TTSService` 按句子分段合成。
9. 每段 TTS 音频一方面入 `AudioBus`，通过 WebRTC 下发给浏览器播放；另一方面经 `AvatarService` 转成动画帧，通过 `/ws/animation` 发给浏览器。
10. 浏览器在播放语音的同时，从动画缓冲队列中取帧，驱动 VRM 表情、GLTF morph target 和头部骨骼。

### 2.2 文本链路

1. 浏览器调用 `POST /chat/text`。
2. 后端直接执行 `LLM -> TTS -> 动画控制`。
3. 字幕通过 `/ws/subtitles` 下发，语音仍通过 WebRTC 下发。

文本链路主要用于调试 LLM/TTS/动画，不依赖麦克风、VAD 和 ASR。

## 3. 后端模块职责

### 3.1 入口层

[main.py](../main.py) 负责：

- 应用初始化。
- 管理全局单例服务。
- 维护 WebRTC peer 集合、字幕客户端集合、动画客户端集合。
- 维护 `RuntimeState`，给 `/health` 和 `/events` 提供实时指标。

当前实现假设一次只保留一个主 WebRTC 连接。新连接到来时会主动关闭旧连接。这是刻意简化，不是 bug。

### 3.2 会话状态机

[core/state_machine.py](../core/state_machine.py) 的状态很轻：

- `idle`
- `user_speaking`
- `thinking`
- `avatar_speaking`

它目前主要服务于两个目标：

- 打断时取消数字人播报。
- 给前端和监控面板提供可观测状态。

如果后面要做更复杂的并发控制，比如排队、多会话或多路媒体编排，这里需要升级成更明确的 session controller，而不只是一个枚举状态机。

### 3.3 流水线层

[core/pipeline.py](../core/pipeline.py) 是主编排器。

当前设计特点：

- `process_turn()` 处理语音输入。
- `process_text_turn()` 处理文本输入。
- TTS 会按句或短片段切分，以降低首包时延。
- 每段合成结果都会回调到上层，用于同步推送音频和动画。

后续如果要接入流式 LLM 或更细粒度的流式 TTS，这个文件是首选改造点。

### 3.4 服务层

`services/` 下的实现都偏适配器风格：

- [services/asr.py](../services/asr.py): 本地 ASR 模型封装。
- [services/llm.py](../services/llm.py): DeepSeek/OpenAI 兼容接口封装，并维护短历史上下文。
- [services/tts.py](../services/tts.py): 本地 TTS 模型封装。
- [services/vad.py](../services/vad.py): 语音起止检测。
- [services/avatar.py](../services/avatar.py): 从音频提取能量和频谱亮度，再映射到嘴型和头部姿态。

这里最大的工程价值是“可替换性”。后续更换供应商或模型时，尽量保持 `core/pipeline.py` 的接口不变，只替换 `services/` 内部实现。

## 4. 浏览器侧实现

[web/app.js](../web/app.js) 同时承担了以下职责：

- 创建 WebRTC 连接。
- 订阅 `/events`、`/ws/subtitles`、`/ws/animation`。
- 维护聊天消息面板和诊断面板。
- 用 Three.js + VRM 加载和渲染模型。
- 把动画控制帧缓存约 `120ms` 后再播放，尽量和音频保持一致。

前端当前兼容三种形态：

- VRM expression manager。
- 通用 GLTF morph target。
- 没有表情 rig 时回退到调试头像。

如果以后想接入真实业务数字人模型，先确认模型是否提供：

- 标准 VRM 表情。
- 或稳定可映射的 morph target 名称。
- 或至少可控制的头骨/颈骨。

## 5. 动画协议

服务端通过 `/ws/animation` 发送 JSON，关键字段包括：

- `type`: `animation_ready` / `animation_state` / `animation_reset` / `animation_chunk`
- `driver`: 当前驱动器名称。
- `mode`: 驱动模式。
- `schema`: 控制 schema，当前默认是 ARKit 风格 blendshape。
- `fps`: 帧率。
- `frame_count`: 当前消息内帧数。
- `frames`: 每帧控制数据。

`frames[*]` 典型结构：

```json
{
  "seq": 101,
  "time_ms": 80,
  "speaking": true,
  "controls": {
    "jawOpen": 0.42,
    "mouthClose": 0.66,
    "mouthFunnel": 0.21,
    "mouthPucker": 0.18,
    "viseme_aa": 0.42,
    "viseme_ee": 0.27,
    "viseme_oh": 0.31,
    "headYaw": 0.01,
    "headPitch": -0.02,
    "headRoll": 0.01
  }
}
```

注意：当前口型驱动是“基于音频特征估算”，不是基于音素或强制对齐的精确 viseme，所以它更适合演示验证，不适合直接当作高精度唇形同步方案。

## 6. 字幕协议

`/ws/subtitles` 发送的消息结构比较简单：

- `role`: `user` 或 `ai`
- `text`: 文本内容
- `source`: `voice` 或 `text`
- `partial`: 是否是分段中间结果
- `final`: 是否已经结束
- `ts_ms`: 时间戳

前端对 AI 文本采用“局部追加”策略，所以如果以后改成 token streaming，要继续保证消息顺序和终止标记一致。

## 7. 状态与观测

后端通过 `/health` 和 `/events` 暴露运行状态。推荐排查问题时按这个顺序看：

1. `state`: 当前状态机是否符合预期。
2. `peers`: WebRTC 是否真正建连。
3. `vad_start_count` / `vad_end_count`: 是否检测到语音边界。
4. `last_asr_latency_ms` / `last_llm_latency_ms` / `last_tts_latency_ms`: 性能瓶颈在哪一段。
5. `llm.ready` / `asr.ready` / `tts.ready` / `vad.ready`: 组件有没有降级。
6. `last_animation_frame_count`: 是否真正产生了控制帧。

## 8. 当前约束

这些约束需要后续开发明确知晓：

- 当前是进程内单例服务，不是多租户、多 session 设计。
- 浏览器侧只允许一个主 WebRTC 会话接管服务端音频输出。
- 模型推理基本都直接跑在应用事件循环附近，适合研发验证，不适合高并发。
- 当前动画驱动依赖音频能量和谱质心，不具备严格唇音级别精度。
- 语音链路高度依赖 VAD；如果要做电话式长连接交互，建议补更稳的 turn management。

## 9. 推荐扩展方向

按收益排序，后续建议优先做这些：

1. 把 `RuntimeState`、音频缓冲和历史上下文下沉到真正的 session 对象，摆脱全局单例。
2. 把 ASR、TTS、动画生成从主事件循环中隔离出去，避免阻塞 FastAPI/WebSocket。
3. 为 `/ws/subtitles` 和 `/ws/animation` 增加明确版本号和协议文档，避免前后端演进时互相踩踏。
4. 如果追求更真实口型，改成音素/viseme 驱动而不是纯音频特征驱动。
5. 为关键链路补自动化回归，至少覆盖文本链路、语音链路和打断流程。

## 10. 常用调试入口

- `GET /health`: 看整体运行状态。
- `GET /events`: 看持续状态流。
- `POST /chat/text`: 快速验证 LLM/TTS/动画，不依赖麦克风。
- `GET /avatar/schema`: 看控制字段列表。
- [scripts/run_all_checks.sh](../scripts/run_all_checks.sh): 统一检查入口。

如果你刚接手这个项目，建议先按下面顺序理解：

1. [README.md](../README.md)
2. [main.py](../main.py)
3. [core/pipeline.py](../core/pipeline.py)
4. [services/avatar.py](../services/avatar.py)
5. [web/app.js](../web/app.js)
