This commit is contained in:
xsl
2026-03-27 10:49:34 +08:00
commit 22a5f31847
56 changed files with 5145 additions and 0 deletions
+724
View File
@@ -0,0 +1,724 @@
# 智能可视化语音聊天系统 — 详细任务清单(本机实时版)
> **目标**:在本机(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 | 流式推理未完全实现,首包延迟不稳定 |