ASR-demo/realtime_asr_optimization_demo/README.md

145 lines
7.6 KiB
Markdown
Raw Normal View History

2026-09-10 05:47:09 +00:00
# Realtime ASR WebSocket Optimization Demo
这是一个独立的实时 ASR WebSocket 验证项目,放在模型部署项目 `demo` 下,但不导入、不启动、也不调用仓库根目录的原始 `app/`
## 验证目标
- 浏览器麦克风或按实时速度发送的 PCM/WAV 音频通过本项目 WebSocket 发送。
- 同一 `sentence_id` 的 partial、final 和后续更新覆盖同一条 raw segment。
- raw segment 与前端 display block 分离,避免把物理切段直接等同于展示换行。
- 小于 1.6 秒且带有 `short_attach` / `embedding_attach` 策略的实名结果降级为 pending。
- 不把 embedding 字段写入 demo 状态池。
- 只有相邻且身份可信的 segment 才合并A→B→A 保持时间顺序。
- 记录 partial 首次延迟、final 延迟、partial 修订次数和服务端返回时间范围。
- 每个实时 turn 单独提交声纹特征,由辅助服务维护本 WebSocket session 的在线聚类中心。
本次修复、诊断状态和部署验收步骤见 [FIXES.md](FIXES.md)。ASR 继续使用已部署的独立 vLLM已有 vLLM 服务时无需重复启动或下载模型。
当前 VLLM 端点提供的是同步 OpenAI 音频转写接口,没有暴露原生
`create_stream/feed_stream/finish_stream`。因此本项目仍然是真实 WebSocket
音频流:麦克风 PCM 到达后立即进入 VAD按窗口调用 VLLM 生成 partial它不会
等整段音频结束。`native_partial_supported=false` 只表示模型 HTTP 接口本身
不是原生 ASR stream不伪造不存在的能力。
## 启动
需要启动两个模型服务和一个 WebSocket 页面VLLM 只负责 ASR辅助服务负责
VAD、CAM++ 声纹模型及在线聚类WebSocket 只做音频流编排,不导入原项目代码。
先在 `demo` 目录下载 ASR 和辅助模型,并启动 VLLM
```powershell
cd D:\github-project\ASR\Qwen-Asr\demo
python scripts\download_models.py
python scripts\serve.py
```
另一个终端启动辅助模型服务:
```powershell
cd D:\github-project\ASR\Qwen-Asr\demo
pip install -r requirements-auxiliary.txt
python scripts\auxiliary_server.py
```
另开一个终端启动 WebSocket 页面:
```powershell
cd D:\github-project\ASR\Qwen-Asr\demo\realtime_asr_optimization_demo
python -m venv .venv
.\.venv\Scripts\Activate.ps1
pip install -r requirements.txt
python server.py --no-browser
```
页面服务默认监听 `0.0.0.0:8082`,端口在 `server.py` 顶部的 `WEB_PORT` 内部变量中维护。
VLLM 默认地址为 `http://127.0.0.1:9950/v1`,辅助服务默认地址为
`http://127.0.0.1:8010`。可通过环境变量切换到远程服务:
```powershell
$env:MODEL_SERVICE_URL = 'http://127.0.0.1:9950/v1'
$env:AUXILIARY_SERVICE_URL = 'http://127.0.0.1:8010'
python server.py --no-browser
```
如果服务器端口已通过 VS Code Remote/端口转发映射到本机,保持上述两个
`127.0.0.1` 地址即可:本地 WebSocket 只负责编排ASR 和 VAD/CAM++ 推理仍在
服务器 GPU 服务中完成。先访问 `http://127.0.0.1:9950/v1/models`
`http://127.0.0.1:8010/health`,分别确认 VLLM 模型和辅助模型服务可达且 `ready=true`
服务器部署时使用 `--no-browser`,然后在客户端浏览器访问 `http://服务器IP:8082`。如需让启动日志显示服务器域名或 IP可设置 `WEB_DISPLAY_HOST`;它只影响提示文本,不改变监听地址。
辅助服务启动后可用 `http://服务器IP:8010/health` 检查模型状态。WebSocket
收到聚类服务错误时仍会继续输出 ASR但对应片段会显示“未知说话人”详细的
`speaker_reason` 可将鼠标悬停在标签上查看,事件日志仍会显示 `speaker_warning`
便于区分“模型未归类”和“ASR 失败”。
声纹服务的实时路径必须通过 ModelScope pipeline 的公开接口提取 embedding
`pipeline([wav_path], output_emb=True)`。不能绕过 pipeline 预处理后直接调用
`pipeline.model`,否则采样率、声道和 waveform 预处理不会执行,部分 ModelScope
版本会直接抛异常WebSocket 仍会继续输出 ASR 并把说话人保留为 pending。
更新辅助服务代码后需要重启 `python scripts/auxiliary_server.py`,仅重启页面
服务不会替换已经驻留在 GPU 中的旧辅助服务进程。
## 页面操作
1. 选择 Mic 或 File。
2. 点击开始,浏览器通过 `/ws` 建立本项目 WebSocket。
3. 页面展示腾讯 Demo 风格的气泡;麦克风或 PCM/WAV 文件按流式方式输入partial 会在讲话过程中实时刷新。
2026-09-10 06:23:18 +00:00
4. VAD 检测到静音、或活跃 turn 内声纹窗口比对检出换人时提交当前 turn先返回 pending再异步更新说话人。
2026-09-10 05:47:09 +00:00
5. 停止会发送 `stop`,服务端完成当前 turn 和 speaker 队列后再发送 `end`
6. `abort` 只取消会话,不提交当前片段。
## WebSocket 消息
客户端首条消息:
```json
{
"type": "start",
"source": "mic",
"model_service_url": "http://127.0.0.1:9950/v1",
"model": "Qwen/Qwen3-ASR-0.6B",
"speaker_diarization": 1,
"sentence_strategy": 0,
"enable_native_partial_stream": true,
"partial_interval_ms": 1200,
"max_segment_sec": 12,
2026-09-10 06:23:18 +00:00
"display_merge": true,
"speaker_window_ms": 1000,
"speaker_switch_similarity": 0.5
2026-09-10 05:47:09 +00:00
}
```
2026-09-10 06:23:18 +00:00
`speaker_window_ms``speaker_switch_similarity` 可省略,默认读取环境变量
`SPEAKER_TURN_WINDOW_MS`1000`SPEAKER_SWITCH_SIMILARITY`0.5)。
2026-09-10 05:47:09 +00:00
随后持续发送 16kHz、单声道、PCM16 二进制音频;文件模式仅支持 PCM/WAV结束发送 `{"type":"eof"}`,停止发送
`{"type":"stop"}`,取消发送 `{"type":"abort"}`。每个已完成 turn 会向辅助服务
发送一次 `/v1/speaker/resolve`,只包含当前 turn 音频和 session_id不会重复上传整段会话。
2026-09-10 06:23:18 +00:00
换人强制切段产生的边界段会附带 `speaker_verified=0`:辅助服务仍返回匹配标签,
但不更新簇质心、不建立新簇。活跃 turn 内另有窗口比对请求 `/v1/speaker/embedding`
multipart 音频,返回 `{"ok": true, "embedding": [...]}`),只出向量、零聚类副作用。
辅助服务 `speaker_protocol_version` 为 3旧版辅助服务缺少窗端点时 WebSocket 侧
探测一次即自动停用切换感知切段并发送 `speaker_warning`,不影响 ASR。
2026-09-10 05:47:09 +00:00
服务端会发送 `start`、`sentences`、`display_state`、`metrics`、`speaker_warning`、`draining`、`end` 和 `error`
页面用带 `revision``display_state` 渲染,以 `block_id` 标识展示块;`sentences` 保留原始片段及诊断状态。
停止后必须等待 `end`,其中包含完整 `sentences``display_blocks`,不能提前关闭连接。
`sentences``sentence_type=0` 是 partial`sentence_type=1` 是 final同一个
`sentence_id` 必须覆盖更新而不是追加。`sentence_strategy=0` 使用约 800ms
静音切句,`sentence_strategy=1` 使用约 1400ms 静音切段,更适合段落模式。
2026-09-10 06:23:18 +00:00
静音阈值之外还有说话人切换感知切段:活跃 turn 内每积累约 1 秒有声窗口就与
辅助服务比对一次声纹,与当前 turn 参考向量余弦低于阈值时在“停顿≥200ms 后的
首个起音”处强制切开,头段 final 的 `commit_reason``speaker_change`。这覆盖
上一位尾句与下一位间隔不足静音阈值(常见 200~600ms 换话停顿)被合成一句的问题。
2026-09-10 05:47:09 +00:00
## 目录
- `server.py`:本地 HTTP 页面和 WebSocket 会话编排。
- `model_service.py`:独立 VLLM OpenAI 音频接口适配层。
- `auxiliary_service.py`:独立辅助模型 HTTP 接口适配层。
- `speaker_assembler.py`raw segment、speaker evidence 和 display block 状态机。
- `static/`:麦克风/文件测试页面。
- `tests/`:只测试本项目状态合并和音频转换逻辑。