Files
product/visual-chat-task-list.md
2026-03-27 10:49:34 +08:00

725 lines
26 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 智能可视化语音聊天系统 — 详细任务清单(本机实时版)
> **目标**:在本机(RTX 5090 / WSL2)实现"浏览器访问 → WebRTC 传输 → 单工语音对话 → 数字人口型同步"的**实时**可用系统。
> **基础**`environments.md` 与 `research-notes.md` 中已验证的环境、模型和踩坑修复全部复用。
> **最后更新**2026-03-25 — 经过逐组件可行性硬核验证后修订(第三版)。
---
## 0. 逐组件可行性验证报告(执行前必读)
### 0.1 组件全景审计表
| 组件 | 选型 | 许可证 | 纯本地 | RTX 5090 验证 | 实时性证据 | 风险等级 |
|------|------|--------|--------|---------------|-----------|---------|
| **VAD** | Silero VAD 6.x | MIT | 是(CPU) | N/A(纯CPU) | 流式 VADIterator32ms 帧级延迟 | 无 |
| **ASR** | SenseVoice-Small (FunASR) | Apache 2.0 | 是(GPU) | 已有用户确认 cu128 可跑 | 中文 CER 4.2%CPU 14x实时,GPU 更快 | 低 |
| **LLM** | 在线 API (OpenAI 兼容) | N/A | 否(设计如此) | N/A | 流式,首 token ~0.5-1.5s | 中(网络) |
| **TTS** | Kokoro-82M-v1.1-zh | Apache 2.0 | 是(GPU) | 确认支持 CUDA 12.8 RTX 50 系 | 210x实时(4090)<2GB VRAM | 低 |
| **TTS 升级** | Qwen3-TTS 0.6B | Apache 2.0 | 是(GPU) | 确认可跑,需 torch.compile 优化 | 97ms 首音频,RTF<0.25(优化后) | 中 |
| **口型** | MuseTalk v1.5 | MIT | 是(GPU) | 已在你机器验证通过 | 30fps+(V100)RTX 5090 更快 | 中(需改流式) |
| **头动底片** | LivePortrait | MIT | 是(离线预处理) | 已在你机器验证通过 | 离线,无实时要求 | 无 |
| **WebRTC** | aiortc | BSD-3-Clause | 是 | N/A(纯Python) | asyncio 原生,H.264 已优化 | 低 |
**结论:全部组件均为开源许可(MIT / Apache 2.0 / BSD-3),全部可本地运行,全部有 RTX 5090 兼容路径。**
### 0.2 两个致命问题及修正
#### 问题一:faster-whisper 中文识别率灾难
| | faster-whisper large-v3-turbo | SenseVoice-Small |
|---|---|---|
| 中文 WER/CER | **WER 77%**(几乎不可用) | **CER 4.2%**(优秀) |
| 中文方言 | 不支持 | 7 大方言 + 26 种口音 |
| 推理速度(GPU) | ~50x 实时 | RTF 0.0076130x 实时) |
| 模型大小 | ~1.5GB | ~234MB |
| 许可证 | MIT | Apache 2.0 |
**决定:ASR 从 faster-whisper 改为 SenseVoice-Small。** faster-whisper 的 turbo 模型砍掉了 28/32 层 decoder,导致中文(需要更复杂解码的语言)准确率暴跌,完全不可接受。
#### 问题二:CosyVoice2 实际延迟远超宣传
| | 宣传值 | 开源版实测值(社区) |
|---|---|---|
| 流式首块延迟 | 150ms | **1 - 4.5 秒** |
| 原因 | 阿里云预训练音色,无特征提取 | 开源 zero-shot 需提取 token/embedding |
| 流式实现 | 优化的 chunk 流 | 累积式 chunk1A, 2A, 3A...),无 KV cache |
**决定:TTS 从 CosyVoice2 改为 Kokoro-82M(快速启动) + Qwen3-TTS 0.6B(质量升级路径)。**
### 0.3 修正后的端到端延迟预算
| 阶段 | 选型 | 预估延迟 | 数据来源 |
|------|------|----------|---------|
| VAD 尾部检测 | Silero VAD | ~300ms | 默认 silence_duration_ms=300 |
| ASR 转写(3s) | SenseVoice-Small GPU | ~50-150ms | RTF 0.00763s × 0.0076 ≈ 23ms + 开销 |
| LLM 首 token | 在线流式 | ~500-1500ms | 取决于模型/网络 |
| TTS 首音频块 | Kokoro-82M | ~20-50ms | 210x 实时 on 4090 |
| MuseTalk 首帧批 | MuseTalk batch=4 | ~100-200ms | 30fps+ → 4帧 ≈ 130ms |
| WebRTC 编码传输 | aiortc H.264 | ~50-100ms | 软编码 |
| **合计** | | **~1.0 - 2.3s** | |
> 比上版预算更优,因为 SenseVoice 比 faster-whisper 更快,Kokoro 比 CosyVoice2 快一个数量级。
### 0.4 GPU 显存预算(RTX 5090 = 32GB
| 模型 | 精度 | VRAM | 常驻 |
|------|------|------|------|
| MuseTalk (UNet + VAE + Whisper) | fp16 | ~3-4GB | 是 |
| SenseVoice-Small | fp16 | ~0.8GB | 是 |
| Kokoro-82M | fp16 | ~1.5GB | 是 |
| PyTorch CUDA 上下文 | — | ~1GB | 固定 |
| **合计** | | **~6-7GB** | |
> 32GB VRAM 用不到 1/4,若后续升级 Qwen3-TTS 0.6B+3GB)仍有充裕余量。
### 0.5 "开源可控"确认清单
| 组件 | 许可证 | 可商用 | 代码仓库 | 离线可用 |
|------|--------|--------|----------|---------|
| Silero VAD | MIT | 是 | github.com/snakers4/silero-vad | 是 |
| SenseVoice-Small | Apache 2.0 | 是 | github.com/FunAudioLLM/SenseVoice | 是 |
| Kokoro-82M | Apache 2.0 | 是 | github.com/hexgrad/kokoro | 是 |
| Qwen3-TTS | Apache 2.0 | 是 | github.com/QwenLM/Qwen3-TTS | 是 |
| MuseTalk v1.5 | MIT | 是 | github.com/TMElyralab/MuseTalk | 是 |
| LivePortrait | MIT | 是 | github.com/KlingTeam/LivePortrait | 是 |
| aiortc | BSD-3 | 是 | github.com/aiortc/aiortc | 是 |
**零云端依赖(LLM 除外,且 LLM 有本地兜底),零非开源组件。**
### 0.6 "稳健落地"保障策略
| 风险 | 发生时 | 保底方案 | 切换成本 |
|------|--------|----------|---------|
| SenseVoice 在 nightly PyTorch 编译失败 | M0 | 用 MuseTalk 自带 whisper-tiny 做 ASR(精度低但能跑) | 改一行 import |
| Kokoro 中文质量不达标 | M1 | 切 Qwen3-TTS 0.6B(已验证 RTX 5090 可跑) | 换一个 TTS 服务封装 |
| Qwen3-TTS RTX 5090 推理慢 | M4 | torch.compile + CUDA graphs 优化(社区已有方案,RTF<0.25 | 加 2 行代码 |
| MuseTalk 流式改造困难 | M1 | 先用"分段文件处理"TTS 输出完整 WAV → MuseTalk 批量合成 → 整段播放 | 延迟增加 2-3s 但可用 |
| 在线 LLM 不可用 | 运行时 | 本地预设话术兜底,8s 超时自动触发 | 已内建 |
| aiortc 软编码瓶颈 | M2 | 改用 ffmpeg + NVENC 硬编码管道 | 约 1 天工作量 |
---
## 1. 总体架构
### 1.1 系统拓扑
```
浏览器 (Chrome)
│ WebRTC (ICE/DTLS/SRTP)
┌─────────────────────────────────────────────────────┐
│ webrtc-gateway (aiortc + FastAPI) │
│ ┌──────────┐ ┌──────────────────────┐ │
│ │ 上行音频 │ │ 下行视频 + 音频轨 │ │
│ └────┬─────┘ └──────────▲───────────┘ │
└───────┼───────────────────────────────┼─────────────┘
│ │
▼ │
┌──────────────┐ ┌──────┴──────┐
│ Silero VAD │ │ mixer │
│ (CPU,MIT) │ │ (音视频合流) │
└──────┬───────┘ └──▲───────▲──┘
│ speech.end │ │
▼ │ │
┌──────────────┐ ┌──────┴──┐ ┌──┴──────────┐
│ SenseVoice │ │MuseTalk │ │ TTS 音频 │
│ ASR (GPU) │ │ 口型渲染│ │ (Kokoro) │
│ Apache 2.0 │ │ (GPU) │ │ Apache 2.0 │
└──────┬───────┘ │ MIT │ └──────▲──────┘
│ text └────▲────┘ │
▼ │ wav │ text
┌──────────────┐ ┌─────┴──────┐ │
│ LLM Client │──text──▶│ Kokoro TTS ├───────┘
│ (在线 API) │ │ (GPU) │
└──────────────┘ └────────────┘
```
### 1.2 单工状态机
```
speech.start speech.end
┌────┐ ──────────▶ ┌──────────────┐ ────────▶ ┌──────────┐
│IDLE│ │USER_SPEAKING │ │THINKING │
└──▲─┘ ◀────────── └──────────────┘ └────┬─────┘
│ barge-in │ tts.ready
│ (用户抢话) ▼
│ ┌─────────────────┐ ┌────────────────┐
└───────────────│ │◀────────│AVATAR_SPEAKING │
playback.done │ (回到 IDLE) │ └────────────────┘
└─────────────────┘
```
**状态规则**
- `IDLE`:数字人播放闭嘴循环视频,下行静音
- `USER_SPEAKING`:数字人立即切闭嘴循环(如正在说话则中断),录音开始
- `THINKING`:录音结束,ASR → LLM → TTS 管线启动,数字人继续闭嘴循环
- `AVATAR_SPEAKING`MuseTalk 输出帧 + TTS 音频同步下发
- 任何状态下检测到 `speech.start` → 立即跳转 `USER_SPEAKING`barge-in
### 1.3 进程内通信(不用消息中间件)
单机单进程,所有模块通过 `asyncio.Queue``asyncio.Event` 通信,延迟最低:
```python
audio_chunk_queue: asyncio.Queue[bytes] # VAD → ASR
text_queue: asyncio.Queue[str] # ASR → LLM
tts_chunk_queue: asyncio.Queue[bytes] # LLM→TTS → mixer
avatar_frame_queue: asyncio.Queue[np.ndarray] # TTS→MuseTalk → mixer
state_event: asyncio.Event # 状态变更通知
```
---
## 2. 环境与依赖
### 2.1 创建统一 conda 环境
```bash
conda create -n digital-human python=3.10 -y
conda activate digital-human
conda install -y -c conda-forge ffmpeg
# PyTorch nightlyRTX 5090 必须,已验证版本)
pip install --pre torch torchvision torchaudio \
--index-url https://download.pytorch.org/whl/nightly/cu128
```
### 2.2 安装各模块依赖
```bash
# MuseTalk 依赖链(按 research-notes.md 已验证)
cd /home/xsl/work/MuseTalk
pip install -r requirements.txt
pip install --no-build-isolation chumpy
pip install mmengine
MMCV_WITH_OPS=1 pip install mmcv==2.1.0 --no-build-isolation
pip install "mmdet>=3.3.0" "mmpose>=1.3.0"
pip install xtcocotools json-tricks munkres
pip install "transformers>=4.45.0"
# ASR - SenseVoice
pip install funasr
# TTS - Kokoro
pip install kokoro soundfile
# TTS 升级备选 - Qwen3-TTSM4 阶段再装)
# pip install qwen3-tts
# VAD
pip install silero-vad
# WebRTC + Web 服务
pip install aiortc aiohttp uvicorn fastapi
# 工具库
pip install pydantic python-dotenv tenacity numpy opencv-python-headless
```
### 2.3 模型权重
| 模型 | 路径 | 状态 | 大小 |
|------|------|------|------|
| MuseTalk v1.5 全套 | `/home/xsl/work/MuseTalk/models/` | 已有 | 5.5GB |
| LivePortrait 权重 | `/home/xsl/work/LivePortrait/pretrained_weights/` | 已有 | 1.2GB |
| SenseVoice-Small | 首次调用自动下载 | **新增** | ~234MB |
| Kokoro-82M-v1.1-zh | 首次调用自动下载 | **新增** | ~180MB |
| Silero VAD | 首次调用自动下载 | **自动** | ~2MB |
```bash
# SenseVoice 模型会在首次 AutoModel() 调用时自动下载
# Kokoro 模型会在首次 import kokoro 时自动下载
# 也可以手动预下载:
python -c "from funasr import AutoModel; AutoModel(model='FunAudioLLM/SenseVoiceSmall', device='cuda:0')"
python -c "import kokoro; kokoro.KPipeline(lang_code='z')"
```
### 2.4 MuseTalk 补丁(必须,已验证)
`torch.load(weights_only=False)` monkey patch — 保持 research-notes.md 中已验证的方案。
### 2.5 验收清单
- [x] `python -c "import torch; print(torch.cuda.is_available(), torch.cuda.get_device_name())"` → True, NVIDIA GeForce RTX 5090
- [x] SenseVoice 加载模型 + 转写一段中文音频成功
- [x] Kokoro 合成一句中文语音成功
- [x] MuseTalk 单样例推理输出视频成功
---
## 3. VAD 语音活动检测(Silero VAD, MIT
### 3.1 实现
```python
from silero_vad import load_silero_vad, VADIterator
model = load_silero_vad()
vad_iterator = VADIterator(
model,
threshold=0.5,
sampling_rate=16000,
min_silence_duration_ms=300, # 尾部静音判定
speech_pad_ms=30,
)
# 流式处理 WebRTC 上行音频帧
for audio_chunk in webrtc_audio_stream:
speech_dict = vad_iterator(audio_chunk)
if speech_dict:
if 'start' in speech_dict:
arbitrator.on_speech_start()
if 'end' in speech_dict:
arbitrator.on_speech_end(recorded_audio)
```
### 3.2 验收
- [x] 静音环境不误触发
- [x] 正常说话稳定触发
- [x] 说话结束后 ~300ms 内检测到 speech.end
---
## 4. ASR 语音识别(SenseVoice-Small, Apache 2.0, 本地 GPU
### 4.1 模型加载
```python
from funasr import AutoModel
from funasr.utils.postprocess_utils import rich_transcription_postprocess
asr_model = AutoModel(
model="FunAudioLLM/SenseVoiceSmall",
device="cuda:0",
hub="hf",
)
```
### 4.2 推理接口
```python
async def transcribe(audio_array: np.ndarray) -> str:
"""16kHz mono float32 numpy array → 文本"""
res = asr_model.generate(
input=audio_array,
cache={},
language="zh",
use_itn=True,
)
return rich_transcription_postprocess(res[0]["text"])
```
### 4.3 性能预期
- AISHELL-1 中文 CER: 4.2%(比 whisper-turbo 的 77% WER 好一个量级)
- 3 秒语音推理延迟: ~50-150ms (GPU)
- VRAM: ~0.8GB
### 4.4 验收
- [ ] 10 条中文测试语音,转写准确率主观 > 90%
- [ ] 单段 3 秒语音延迟 < 300ms
---
## 5. LLM 对话(在线 API,流式输出)
### 5.1 客户端实现
```python
import openai
client = openai.AsyncOpenAI(
api_key=os.getenv("LLM_API_KEY"),
base_url=os.getenv("LLM_BASE_URL"),
)
async def chat_stream(messages: list[dict]) -> AsyncIterator[str]:
response = await client.chat.completions.create(
model=os.getenv("LLM_MODEL", "gpt-4o-mini"),
messages=messages,
stream=True,
max_tokens=150,
temperature=0.7,
)
async for chunk in response:
if chunk.choices[0].delta.content:
yield chunk.choices[0].delta.content
```
### 5.2 流式分句送 TTS
- LLM 流式输出 token → 累积到句子边界(。!?,\n)→ 立即送 TTS
- 系统提示词要求"用简短口语回复,每句不超过 30 字"
### 5.3 降级策略
- 超时 8s 或网络错误 → 返回预设兜底话术
-`tenacity` 做 1 次快速重试(2s 超时)
### 5.4 验收
- [x] 正常首 token < 2 秒
- [x] 断网 3 秒内返回兜底话术
- [x] 20 轮对话上下文连贯
---
## 6. TTS 语音合成(Kokoro-82M, Apache 2.0, 本地 GPU
### 6.1 模型加载
```python
import kokoro
pipeline = kokoro.KPipeline(lang_code='z') # z = 中文
```
### 6.2 合成接口
```python
async def synthesize(text: str) -> tuple[np.ndarray, int]:
"""文本 → (audio_array, sample_rate)"""
generator = pipeline(text, voice='zf_xiaoxiao') # 中文女声
audio_chunks = []
for _, _, chunk in generator:
audio_chunks.append(chunk)
audio = np.concatenate(audio_chunks)
return audio, 24000 # Kokoro 输出 24kHz
```
### 6.3 音频格式对齐
- Kokoro 输出 24kHz → 需重采样到 16kHz 供 MuseTalk whisper
- 下行音频可保持 24kHzWebRTC 支持)
### 6.4 升级路径(Qwen3-TTS
M4 阶段如果 Kokoro 中文质量不满意,切换到 Qwen3-TTS 0.6B
- 97ms 首音频延迟
- 更好的中文自然度
- 需额外 ~3GB VRAM(总计仍 <10GB
- RTX 5090 需 `torch.compile` + CUDA graphs 优化(社区已验证方案)
### 6.5 验收
- [ ] 40 字中文文本合成延迟 < 500ms
- [ ] 音质清晰,无明显机器感
- [x] 连续合成 20 句无崩溃
---
## 7. 数字人渲染(MuseTalk v1.5, MIT, 本地 GPU
### 7.1 离线预处理:底片库(一次性)
**Step 1**: 用 LivePortrait 生成闭嘴自然动作循环视频
```bash
conda activate digital-human
cd /home/xsl/work/LivePortrait
for drv in d3.mp4 d6.mp4 d10.mp4; do
python inference.py \
-s /path/to/avatar_photo.jpg \
-d assets/examples/driving/$drv \
--flag_crop_driving_video \
-o /home/xsl/product/assets/avatar_loops/
done
```
**Step 2**: 用 MuseTalk 预处理(提取 latents/coords/masks → 缓存)
```bash
cd /home/xsl/work/MuseTalk
python -m scripts.realtime_inference \
--inference_config configs/inference/realtime.yaml \
--version v15 \
--unet_model_path models/musetalkV15/unet.pth \
--unet_config models/musetalkV15/musetalk.json
```
预处理产物缓存后,运行时直接加载,不再需要人脸检测和解析。
### 7.2 实时口型合成:改造 MuseTalk
**核心**:将 `Avatar.inference()` 从完整音频文件改为流式音频 chunk。
```python
class RealtimeAvatar:
def __init__(self, prepared_data_path):
# 加载预处理缓存
self.latents = torch.load(f"{prepared_data_path}/latents.pt")
self.coords = pickle.load(open(f"{prepared_data_path}/coords.pkl", 'rb'))
self.frames = [...] # 预加载原始帧
self.masks = [...] # 预加载 mask
self.frame_idx = 0
async def process_audio_chunk(self, pcm_16k: np.ndarray) -> list[np.ndarray]:
"""接收 TTS PCM 块 → 返回口型帧列表"""
whisper_features = audio_processor.get_audio_feature(pcm_16k)
whisper_chunks = audio_processor.get_whisper_chunk(whisper_features, ...)
output_frames = []
for batch in datagen(whisper_chunks, self.latents, batch_size=4):
pred = unet.model(batch_latent, timesteps, encoder_hidden_states=audio_feat)
recon = vae.decode_latents(pred)
for frame in recon:
blended = blend_frame(frame, self.frame_idx)
output_frames.append(blended)
self.frame_idx = (self.frame_idx + 1) % len(self.frames)
return output_frames
def get_idle_frame(self) -> np.ndarray:
"""闭嘴循环帧(不过 UNet"""
frame = self.frames[self.frame_idx % len(self.frames)]
self.frame_idx = (self.frame_idx + 1) % len(self.frames)
return frame
```
### 7.3 帧率与同步
- 25fps(与底片一致),每帧 40ms
- TTS 音频时长 → 计算帧数 → MuseTalk 生成对应数量帧
- asyncio 定时器控制帧发送节奏
### 7.4 验收
- [ ] 闭嘴循环 60 秒无卡顿
- [ ] 5 秒 TTS 音频口型帧 200ms 内开始输出
- [ ] 口型同步主观无 >200ms 错位
---
## 8. WebRTC 网关与前端
### 8.1 后端(aiortc + FastAPI, BSD-3
```python
@app.post("/webrtc/offer")
async def webrtc_offer(request: Request):
params = await request.json()
offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"])
pc = RTCPeerConnection()
video_track = AvatarVideoTrack() # 从 MuseTalk 帧队列读取
audio_track = AvatarAudioTrack() # 从 TTS 音频队列读取
pc.addTrack(video_track)
pc.addTrack(audio_track)
@pc.on("track")
def on_track(track):
if track.kind == "audio":
asyncio.ensure_future(process_incoming_audio(track))
await pc.setRemoteDescription(offer)
answer = await pc.createAnswer()
await pc.setLocalDescription(answer)
return {"sdp": pc.localDescription.sdp, "type": pc.localDescription.type}
```
### 8.2 前端页面(纯 HTML/JS,无框架依赖)
```
┌──────────────────────────────┐
│ 数字人视频 │
│ (WebRTC video 标签) │
│ │
├──────────────────────────────┤
│ 🎤 正在倾听... │ ← 状态指示
├──────────────────────────────┤
│ 你: "今天天气怎么样?" │ ← ASR 字幕
│ AI: "今天天气不错呢..." │ ← LLM 回复字幕
└──────────────────────────────┘
```
- 页面加载 → 自动建 WebRTC 连接 → 请求麦克风权限
- 下行视频+音频 → `<video>` 标签
- WebSocket 接收字幕和状态更新
- 断连 3 秒自动重连
### 8.3 验收
- [ ] Chrome 打开 3 秒内看到数字人视频
- [x] 说话后字幕区显示 ASR 文本
- [x] 刷新页面自动重连
---
## 9. 单工仲裁器
```python
class SessionState(Enum):
IDLE = "idle"
USER_SPEAKING = "user_speaking"
THINKING = "thinking"
AVATAR_SPEAKING = "avatar_speaking"
class Arbitrator:
def __init__(self):
self.state = SessionState.IDLE
self._lock = asyncio.Lock()
self._cancel = asyncio.Event()
async def on_speech_start(self):
async with self._lock:
if self.state == SessionState.AVATAR_SPEAKING:
self._cancel.set()
self.state = SessionState.USER_SPEAKING
async def on_speech_end(self, audio: bytes):
async with self._lock:
self.state = SessionState.THINKING
asyncio.create_task(self._run_pipeline(audio))
async def on_playback_done(self):
async with self._lock:
if not self._cancel.is_set():
self.state = SessionState.IDLE
```
### 验收
- [ ] 数字人说话中用户插嘴 → 200ms 内停止
- [ ] 连续抢话 20 次无死锁
---
## 10. 配置
### `.env`
```env
# LLM
LLM_API_KEY=sk-xxx
LLM_BASE_URL=https://api.openai.com/v1
LLM_MODEL=gpt-4o-mini
LLM_MAX_TOKENS=150
LLM_TIMEOUT=8
# 模型路径(复用现有)
MUSETALK_DIR=/home/xsl/work/MuseTalk
MUSETALK_UNET=/home/xsl/work/MuseTalk/models/musetalkV15/unet.pth
MUSETALK_CONFIG=/home/xsl/work/MuseTalk/models/musetalkV15/musetalk.json
# 服务
WEBRTC_PORT=8080
AVATAR_FPS=25
```
---
## 11. 目录结构
```
/home/xsl/product/
├── main.py # 入口
├── config.py # 配置
├── .env
├── requirements.txt
├── core/
│ ├── state_machine.py # 单工仲裁器
│ ├── session.py # 会话管理
│ └── pipeline.py # ASR→LLM→TTS→Avatar 编排
├── services/
│ ├── vad.py # Silero VAD
│ ├── asr.py # SenseVoice-Small
│ ├── llm.py # LLM 客户端
│ ├── tts.py # Kokoro TTS
│ └── avatar.py # MuseTalk 实时推理
├── webrtc/
│ ├── gateway.py # aiortc 信令
│ ├── video_track.py # 数字人视频轨
│ └── audio_track.py # 数字人音频轨
├── web/
│ ├── index.html
│ ├── app.js
│ └── style.css
├── assets/
│ ├── avatar_loops/ # 预生成闭嘴循环视频
│ └── avatar_prepared/ # MuseTalk 预处理缓存
├── scripts/
│ ├── prepare_avatar.sh # 预处理底片
│ ├── start.sh # 一键启动
│ └── smoke_test.py # 冒烟测试
└── docs/
├── visual-chat-task-list.md
├── environments.md
└── research-notes.md
```
---
## 12. 里程碑
### M0:环境就绪(0.5 天)
- [x] 创建 `digital-human` conda 环境
- [x] 安装全部依赖(PyTorch nightly + MuseTalk + SenseVoice + Kokoro + aiortc
- [x] 验证四个 GPU 模型可分别加载并推理
- [ ] 验证四个模型可同时常驻 GPU(显存 < 10GB
### M1:离线管线串通(2 天)
- [x] `services/asr.py`SenseVoice 输入 WAV → 输出文本
- [x] `services/llm.py`:输入文本 → 流式输出文本
- [x] `services/tts.py`:Kokoro 输入文本 → 输出 WAV
- [x] `services/avatar.py`MuseTalk 输入 WAV → 输出帧序列(失败自动回退简易口型)
- [x] `scripts/smoke_test.py`:一段录音走完全链路输出视频文件(当前为文本输入链路,产出 `wav + mp4`
### M2WebRTC + 数字人常驻(2 天)
- [x] `webrtc/gateway.py`aiortc 信令接口
- [x] `webrtc/video_track.py`:发闭嘴循环帧
- [x] `webrtc/audio_track.py`:发静音
- [x] `web/`:前端页面
- [x] 浏览器可看到数字人循环视频
### M3:全链路联调 + 单工(2 天)
- [x] `services/vad.py`Silero VAD 处理上行音频
- [x] `core/state_machine.py`:仲裁器
- [x] `core/pipeline.py`:完整 VAD→ASR→LLM→TTS→MuseTalk→下行
- [x] barge-in 实现
- [x] WebSocket 字幕下发
### M4:优化与稳定(2 天)
- [ ] 端到端延迟调优(目标 < 3s)
- [ ] 1 小时长时间运行测试
- [x] 显存监控(nvidia-smi 周期采样)
- [x] LLM 降级测试
- [ ] 评估是否升级 Qwen3-TTS(中文质量对比)
- [x] 20 轮连续对话压测
补充自动化脚本:
- `scripts/qa_check.py`:文本链路压测(支持 20 轮)
- `scripts/llm_fallback_check.py`LLM 断网/超时兜底验证
- `scripts/gpu_monitor.sh`GPU 利用率与显存采样(CSV
- `scripts/model_probe.py`ASR/TTS/VAD 快速加载与推理探测
---
## 13. 被排除的方案及原因(决策日志)
| 方案 | 排除原因 |
|------|---------|
| faster-whisper (大模型中文) | 中文 WER 77%,完全不可接受 |
| CosyVoice2 (TTS) | 开源版流式首包 1-4.5s,与宣传 150ms 严重不符 |
| edge-tts | 依赖微软云服务器,非本地,离线不可用 |
| Fish-Speech S2 | 许可证限制商用,需单独授权 |
| LatentSync | 仅 4fps,无法实时 |
| Redis/ZeroMQ 消息总线 | 单机单进程无需中间件,asyncio Queue 延迟更低 |
| ChatTTS | 流式推理未完全实现,首包延迟不稳定 |