131 lines
6.4 KiB
Markdown
131 lines
6.4 KiB
Markdown
|
|
# 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 会在讲话过程中实时刷新。
|
|||
|
|
4. VAD 检测到静音后提交当前 turn,先返回 pending,再异步更新说话人。
|
|||
|
|
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,
|
|||
|
|
"display_merge": true
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
随后持续发送 16kHz、单声道、PCM16 二进制音频;文件模式仅支持 PCM/WAV,结束发送 `{"type":"eof"}`,停止发送
|
|||
|
|
`{"type":"stop"}`,取消发送 `{"type":"abort"}`。每个已完成 turn 会向辅助服务
|
|||
|
|
发送一次 `/v1/speaker/resolve`,只包含当前 turn 音频和 session_id,不会重复上传整段会话。
|
|||
|
|
|
|||
|
|
服务端会发送 `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 静音切段,更适合段落模式。
|
|||
|
|
|
|||
|
|
## 目录
|
|||
|
|
|
|||
|
|
- `server.py`:本地 HTTP 页面和 WebSocket 会话编排。
|
|||
|
|
- `model_service.py`:独立 VLLM OpenAI 音频接口适配层。
|
|||
|
|
- `auxiliary_service.py`:独立辅助模型 HTTP 接口适配层。
|
|||
|
|
- `speaker_assembler.py`:raw segment、speaker evidence 和 display block 状态机。
|
|||
|
|
- `static/`:麦克风/文件测试页面。
|
|||
|
|
- `tests/`:只测试本项目状态合并和音频转换逻辑。
|