725 lines
26 KiB
Markdown
725 lines
26 KiB
Markdown
# 智能可视化语音聊天系统 — 详细任务清单(本机实时版)
|
||
|
||
> **目标**:在本机(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 | 流式推理未完全实现,首包延迟不稳定 |
|