Files
DPM/docs/voice-integration-plan.md
T
Pine 4e2224e9dc feat(voice): 实时语音对话页(React 完全复刻原版 UI)
- /voice 页面:中间圆球 + 左右按钮 + 状态图标 + 右下角气泡(进场/淡出/阶梯字号/上限8)+ 噪声门弧线电平,结构类名与原版一致
- s2s WebSocket 客户端 + worklet 音频(mic-capture/audio-playback)+ orb 可视化
- 摄像头实时预览(默认开启)+ 视觉识别状态条(后端 YOLO 推理)
- 底部园区概览条(复用 ParkOverviewStrip),orb-wrap 视窗居中
- 页眉复用 ScreenLayout;左右键导航加入 /voice
- 文档:docs/voice-integration-plan.md
2026-08-18 01:37:44 +08:00

169 lines
8.9 KiB
Markdown
Raw 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.
# 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 |
> 注:视觉子系统是**可选增强**,不影响主语音对话方案;摄像头/检测不可用时语音链路照常工作。