Files
product/docs/development-guide.md
2026-03-29 23:33:22 +08:00

9.1 KiB

开发参考

1. 系统边界

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

明确边界如下:

  • 服务端不再输出视频流,只输出音频流和动画控制帧。
  • 浏览器负责 Three.js/VRM 渲染、模型加载、动画缓冲和最终口型展示。
  • 浏览器和服务端之间的语音收发都通过 WS /ws/audio 完成。
  • 字幕、状态和动画控制通过 HTTP/SSE/WebSocket 传输。

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

2. 运行时主链路

2.1 语音链路

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

2.2 文本链路

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

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

3. 后端模块职责

3.1 入口层

main.py 负责:

  • 应用初始化。
  • 管理全局单例服务。
  • 维护音频、字幕、动画客户端集合。
  • 维护 RuntimeState,给 /health/events 提供实时指标。

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

现在这个约束已经扩展到三个浏览器通道:

  • WS /ws/audio
  • WS /ws/subtitles
  • WS /ws/animation

也就是说,服务端当前只允许一个浏览器页面完整接管数字人会话。新页面连上后,旧页面会被主动断开,关闭码为 1012 replaced

3.2 会话状态机

core/state_machine.py 的状态很轻:

  • idle
  • user_speaking
  • thinking
  • avatar_speaking

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

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

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

3.3 流水线层

core/pipeline.py 是主编排器。

当前设计特点:

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

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

3.4 服务层

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

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

4. 浏览器侧实现

web/app.js 同时承担了以下职责:

  • 建立音频 WebSocket。
  • 订阅 /events/ws/subtitles/ws/animation
  • 维护聊天消息面板和诊断面板。
  • 用 Three.js + VRM 加载和渲染模型。
  • 把动画控制帧缓存约 120ms 后再播放,尽量和音频保持一致。
  • 处理刷新后的语音恢复:如果浏览器拦截自动恢复,会等待下一次用户手势再恢复麦克风和播放上下文。

前端当前兼容三种形态:

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

当前前端待机动画状态:

  • 已启用随机眨眼。
  • 更激进的身体 idle motion 已回退,因为部分 VRM 模型会出现 T pose 或姿态异常。

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

  • 标准 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[*] 典型结构:

{
  "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: userai
  • text: 文本内容
  • source: voicetext
  • partial: 是否是分段中间结果
  • final: 是否已经结束
  • ts_ms: 时间戳

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

7. 状态与观测

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

  1. state: 当前状态机是否符合预期。
  2. audio_clients: 语音通道是否真正建连。
  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 设计。
  • 浏览器侧只允许一个页面完整接管服务端语音输入输出与字幕/动画订阅。
  • 模型推理基本都直接跑在应用事件循环附近,适合研发验证,不适合高并发。
  • 当前动画驱动依赖音频能量和谱质心,不具备严格唇音级别精度。
  • 语音链路高度依赖 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/动画,不依赖麦克风。
  • WS /ws/audio: 看语音上传和语音回放主通道。
  • GET /avatar/schema: 看控制字段列表。
  • scripts/run_all_checks.sh: 统一检查入口。

脚本使用建议:

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

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