# 智能可视化语音聊天系统 — 详细任务清单（本机实时版）

> **目标**：在本机（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) | 流式 VADIterator，32ms 帧级延迟 | 无 |
| **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.0076（130x 实时） |
| 模型大小 | ~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 流 | 累积式 chunk（1A, 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.0076，3s × 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 nightly（RTX 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-TTS（M4 阶段再装）
# 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
- 下行音频可保持 24kHz（WebRTC 支持）

### 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`）

### M2：WebRTC + 数字人常驻（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 | 流式推理未完全实现，首包延迟不稳定 |
