Files
DPM/docs/voice-integration-plan.md
T

169 lines
8.9 KiB
Markdown
Raw Normal View History

# DPM 实时语音对话整合方案(复用 live-avatar 云化栈)
> 目标:砍掉数字人/口型/wav2lip 等全部视觉部分,只保留「语音识别 + 对话 + 实时交互」链路;
> 前端在 DPM React 应用中新增对话页(复用 live-avatar demo 前端的样式与逻辑);
> 后端并入 DPM `backend/pyproject.toml`。
> 基线:live-avatar 云化栈已在 `/Volumes/Pine/mycode/opc/live` 跑通(s2s v0.2.11 + cloud-migration 补丁 + demo 前端)。
---
## 0. 现状对比
| | live-avatar(已跑通) | DPM(现状) |
|---|---|---|
| 前端 | 原生 JS demoindex.html / main.js / s2s-ws-client.js / 两个 worklet | React 19 + Vite + Tailwind`/ai` 页已有 `AiChatPanel`(文字 + **录音上传式**语音) |
| 后端 | s2s 独立进程 :8765VAD 本地 + 云 ASR/LLM/TTS | FastAPI :8000;已有 `app/asr.py`dashscope paraformer)、`app/llm.py`qwen + 工具)、`SSL_CERT_FILE=certifi` 同款处理 |
| 语音体验 | **实时全双工**:本地 VAD → 流式云 ASR → 流式 LLM → 流式 TTS → 音频回放 | 录音 → 上传 → 转写 → 文字回复(无流式、无 TTS 回音) |
**DPM 缺的正是 live-avatar 栈的实时交互能力**;两边的云凭据/CA 处理已一致(dashscope + certifi),合并成本低。
---
## 1. 两个关键决策点
### 1.1 后端:内嵌进 DPM FastAPI(推荐) vs 独立 s2s 进程
- **推荐:内嵌**。s2s v0.2.11 的 `api/openai_realtime/websocket_router.create_app(pool, stop_event)` 本身就返回一个 FastAPI 应用,
可在 DPM `app.main` 里构建管线池后 `app.mount("/v1", s2s_app)`,与现有 `/api/*` 共存、单进程单端口。
Tauri 桌面打包只需一个后端进程,无子进程管理。
- 备选:s2s 独立跑 :8765,DPM 负责拉起/守护。改动最小,但两进程两端口,打包与部署更重。
### 1.2 前端:iframe 内嵌(推荐 v1 vs React 组件移植(v2
- **推荐 v1iframe 内嵌**。把 live-avatar `demo/` 目录拷入 DPM `public/voice-demo/`(去掉数字人 iframe/视觉元素),
React 新增 `VoiceAssistant.jsx`(路由 `/voice`)里 `<iframe src="/voice-demo/">` 全屏。
**100% 复用现有样式与逻辑、零 React 改写、风险最低**WS 地址由 `voice-demo/config.js``VITE_API_BASE` 派生。
- v2React 组件移植。把 `s2s-ws-client.js` + 两个 worklet 拷入 `src/voice/`,用 hooks 驱动、JSX 重绘圆球/气泡,
与 DPM 布局/Tailwind 完全融合;工作量较大,建议先 v1 跑通再决定。
---
## 2. 后端合并清单
### 2.1 `backend/pyproject.toml`
```toml
dependencies = [
# ...现有依赖(fastapi/uvicorn/dashscope/certifi 已覆盖)...
# s2s 云化栈(path 依赖,指向补丁后的 s2s-cloud;或 vendored 到 backend/vendor/
"speech-to-speech @ file:///Volumes/Pine/mycode/opc/live/s2s-cloud",
# s2s 运行所需(base 依赖里 Darwin 专用的 mlx/transformers 等不要全装)
"torch>=2.4", # VAD(silero) 本地必需;CPU 亦可
"numpy>=1.26",
"scipy>=1.10",
"httpx>=0.28",
"websockets>=12",
"nltk==3.9.4",
"openai==2.28.0",
"soundfile>=0.13",
]
```
> 安装用 `pip install -e "backend[voice]" --no-deps` + 手动补依赖,避免 s2s base 依赖把 mlx/transformers 等大件拖进来。
> `torch` 是唯一的大依赖(VAD 本地必需);若部署机无 GPUCPU torch 跑 silero 足够。
### 2.2 配置(`app/config.py` + `backend/.env`
```env
DASHSCOPE_API_KEY=sk-... # 已有(与 app/asr.py、app/llm.py 共用)
S2S_ENABLED=1
S2S_NUM_PIPELINES=4 # 并发会话数
S2S_WS_PATH=/v1/realtime
S2S_STT_MODEL=qwen3-asr-flash-realtime
S2S_LLM_MODEL=qwen-plus
S2S_TTS_MODEL=qwen3-tts-flash-realtime
S2S_TTS_VOICE=Cherry
```
### 2.3 代码(新 `app/s2s_bridge.py` + 挂载)
1. `s2s_bridge.py`:读取 Settings → 用与 `start_backend.sh` 等价的参数构建管线池
`--stt dashscope-asr --llm_backend chat-completions --tts qwen3-cloud --enable_live_transcription false`),
返回 `websocket_router.create_app(pool, stop_event)`
2. `app/main.py` lifespan`ENABLED` 时启动池 → `app.mount(settings.S2S_WS_PATH, s2s_app)`;关闭时 stop。
3. 现有 `/api/ai/chat``/api/ai/asr` 保留(旧 AiChatPanel 流程不破坏)。
---
## 3. 前端合并清单
1. `src/pages/VoiceAssistant.jsx`(新):
- 全屏 `<iframe src="/voice-demo/" />`(v1),或未来 React 移植版。
- 加入 `App.jsx` 路由:`<Route path="/voice" element={<VoiceAssistant />} />`(建议放在 ScreenLayout 内或独立全屏)。
2. `public/voice-demo/`:拷贝 live-avatar `demo/`index.html / main.js / style.css / ws/ / worklets/ / textchat.html),
并删除数字人 iframe`index.html` 里的 `#avatar-frame` → 背景改纯色/园区图)。
3. `public/voice-demo/config.js`:暴露 `window.VOICE_WS_URL`(从 `VITE_API_BASE` 派生 `ws://<host>:<port>/v1/realtime`)。
4. 麦克风权限:
- 浏览器开发模式:`localhost` 直接可用。
- **Tauri v2 打包**`src-tauri/tauri.conf.json``app.macOSPrivateApi: true`(macOS 麦克风)+ 系统隐私授权说明;
Windows 需在 manifest/权限描述中声明 microphone。
---
## 4. 实施步骤
| 步骤 | 内容 | 工作量 |
|---|---|---|
| P1 | 后端内嵌:pyproject 加依赖 → s2s_bridge.py → mount → 冒烟(WS 连得上、说话→回复→出声) | 0.5–1 天 |
| P2 | 前端:public/voice-demo 拷贝去视觉化 → VoiceAssistant.jsx + 路由 → WS 指向 DPM 后端 | 0.5 天 |
| P3 | 联调:DPM 页面语音对话全链路 + 并发验证 | 0.5 天 |
| P4(可选) | React 原生移植(hooks 驱动 client + workletsTailwind 重绘 UI | 12 天 |
---
## 5. 风险与注意
- **torch 体积**VAD 本地必需(s2s 管线结构固定 VAD 本地),CPU 版可接受;若部署机极简可评估 VAD 上云(不推荐,延迟差)。
- **单管线槽**`S2S_NUM_PIPELINES` 默认 1 → 并发对话会拒连;按需调到 4。
- **WS 鉴权**:内网/桌面场景可无鉴权;如需,在 session.update 里带 token 校验。
- **iframe 限制**v1 iframe 内样式隔离、无法用 DPM 主题;v2 移植可解决。
- **Tauri 麦克风**:打包前务必配置 macOSPrivateApi + 权限描述,否则桌面版无声/无权限。
---
## 附录 A · 视觉触发子系统(USB 摄像头 + 桌面端检测)
> 目标:大屏前 USB 摄像头拍观众;检测有人**正对且停留 ≥10 秒** → 主动问候 + 全屏联动;问候后**静默 60 秒**。
### A.1 架构与数据通路
```
大屏机(Tauri 应用,fullscreen
└─ USB 摄像头(大屏前,拍观众)
└─ webview getUserMedia(一路视频流)
├─ 持续检测:MediaPipe FaceMesh 1~2fps(本机、不出设备)
│ ├─ 头姿 yaw/pitch 各 ±15° = 正对
│ └─ 连续 10s 正对 → 触发(窗口内 ≥80% 帧正对,防抖)
└─ 按需抓帧:LLM 决定时 canvas 取当前帧 → input_image → 云 qwen3-vl(人数/人物)
触发动作:
① 主动问候:client.sendUserText("检测到有人正对摄像头…请主动问候") + requestResponse()
→ s2s 走 LLM 生成问候语 → TTS 播报(零后端改动,复用现有 WS 协议)
② 全屏联动:调后端 → event_bus / mqtt.publish → 各屏响应
静默窗口:问候触发后 60s 冷却,期内检测照跑但不触发
```
### A.2 Tauri 摄像头权限(必须配置)
- **macOS**`tauri.conf.json``app.macOSPrivateApi: true`Info.plist 加
`NSCameraUsageDescription` / `NSMicrophoneUsageDescription`(麦克风语音对话也需要)。
- **WindowsNSIS 打包目标)**WebView2 的 getUserMedia 跟随系统隐私设置
(设置 → 隐私和安全性 → 相机/麦克风 → 允许该应用);安装包建议在应用清单声明设备能力。
- 大屏机摄像头常开:确认系统隐私里该应用允许相机,否则检测与抓帧同时失效。
### A.3 隐私与合规
- 持续检测帧**仅在本机内存处理、即用即弃**,不上云;只有 LLM 决定抓的那一帧发往百炼 VLM。
- 大屏前采集观众人脸属个人信息处理 → 大屏旁需公示"影像识别用于互动服务"。
### A.4 落地清单(并入 P2/P3
| 项 | 说明 |
|---|---|
| 前端新增 `useCameraDetect` hook | getUserMedia + MediaPipe FaceMesh + 状态机(IDLE→FACING→TRIGGER→SILENT |
| 依赖 | `@mediapipe/tasks-vision`face_detector + face_landmarker |
| 参数化 | 停留 10s / 静默 60s / 角度 ±15° / 频率 1~2fps → 配置文件或设置页 |
| 触发动作 | 主动问候(sendUserText+create+ MQTT 联动(后端 API |
| 首对话抓帧 | 与检测共用同一路视频流,canvas 取帧 → input_image → qwen3-vl |
> 注:视觉子系统是**可选增强**,不影响主语音对话方案;摄像头/检测不可用时语音链路照常工作。