4e2224e9dc
- /voice 页面:中间圆球 + 左右按钮 + 状态图标 + 右下角气泡(进场/淡出/阶梯字号/上限8)+ 噪声门弧线电平,结构类名与原版一致 - s2s WebSocket 客户端 + worklet 音频(mic-capture/audio-playback)+ orb 可视化 - 摄像头实时预览(默认开启)+ 视觉识别状态条(后端 YOLO 推理) - 底部园区概览条(复用 ParkOverviewStrip),orb-wrap 视窗居中 - 页眉复用 ScreenLayout;左右键导航加入 /voice - 文档:docs/voice-integration-plan.md
169 lines
8.9 KiB
Markdown
169 lines
8.9 KiB
Markdown
# 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 demo(index.html / main.js / s2s-ws-client.js / 两个 worklet) | React 19 + Vite + Tailwind;`/ai` 页已有 `AiChatPanel`(文字 + **录音上传式**语音) |
|
||
| 后端 | s2s 独立进程 :8765(VAD 本地 + 云 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)
|
||
|
||
- **推荐 v1:iframe 内嵌**。把 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` 派生。
|
||
- v2:React 组件移植。把 `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 本地必需);若部署机无 GPU,CPU 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 + worklets,Tailwind 重绘 UI) | 1–2 天 |
|
||
|
||
---
|
||
|
||
## 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`(麦克风语音对话也需要)。
|
||
- **Windows(NSIS 打包目标)**: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 |
|
||
|
||
> 注:视觉子系统是**可选增强**,不影响主语音对话方案;摄像头/检测不可用时语音链路照常工作。
|