ASR-demo/realtime_asr_optimization_demo
Bifang c76e70d10e omp修复版本 2026-09-10 14:23:18 +08:00
..
static Initial cmomit: replace with new code 2026-09-10 13:47:09 +08:00
tests omp修复版本 2026-09-10 14:23:18 +08:00
.gitignore Initial cmomit: replace with new code 2026-09-10 13:47:09 +08:00
README.md omp修复版本 2026-09-10 14:23:18 +08:00
auxiliary_service.py omp修复版本 2026-09-10 14:23:18 +08:00
model_service.py Initial cmomit: replace with new code 2026-09-10 13:47:09 +08:00
requirements.txt Initial cmomit: replace with new code 2026-09-10 13:47:09 +08:00
server.py omp修复版本 2026-09-10 14:23:18 +08:00
speaker_assembler.py Initial cmomit: replace with new code 2026-09-10 13:47:09 +08:00

README.md

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。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

cd D:\github-project\ASR\Qwen-Asr\demo
python scripts\download_models.py
python scripts\serve.py

另一个终端启动辅助模型服务:

cd D:\github-project\ASR\Qwen-Asr\demo
pip install -r requirements-auxiliary.txt
python scripts\auxiliary_server.py

另开一个终端启动 WebSocket 页面:

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。可通过环境变量切换到远程服务:

$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/modelshttp://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 内声纹窗口比对检出换人时提交当前 turn先返回 pending再异步更新说话人。
  5. 停止会发送 stop,服务端完成当前 turn 和 speaker 队列后再发送 end
  6. abort 只取消会话,不提交当前片段。

WebSocket 消息

客户端首条消息:

{
  "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,
  "speaker_window_ms": 1000,
  "speaker_switch_similarity": 0.5
}

speaker_window_msspeaker_switch_similarity 可省略,默认读取环境变量 SPEAKER_TURN_WINDOW_MS1000SPEAKER_SWITCH_SIMILARITY0.5)。

随后持续发送 16kHz、单声道、PCM16 二进制音频;文件模式仅支持 PCM/WAV结束发送 {"type":"eof"},停止发送 {"type":"stop"},取消发送 {"type":"abort"}。每个已完成 turn 会向辅助服务 发送一次 /v1/speaker/resolve,只包含当前 turn 音频和 session_id不会重复上传整段会话。 换人强制切段产生的边界段会附带 speaker_verified=0:辅助服务仍返回匹配标签, 但不更新簇质心、不建立新簇。活跃 turn 内另有窗口比对请求 /v1/speaker/embedding multipart 音频,返回 {"ok": true, "embedding": [...]}),只出向量、零聚类副作用。 辅助服务 speaker_protocol_version 为 3旧版辅助服务缺少窗端点时 WebSocket 侧 探测一次即自动停用切换感知切段并发送 speaker_warning,不影响 ASR。

服务端会发送 startsentencesdisplay_statemetricsspeaker_warningdrainingenderror。 页面用带 revisiondisplay_state 渲染,以 block_id 标识展示块;sentences 保留原始片段及诊断状态。 停止后必须等待 end,其中包含完整 sentencesdisplay_blocks,不能提前关闭连接。 sentencessentence_type=0 是 partialsentence_type=1 是 final同一个 sentence_id 必须覆盖更新而不是追加。sentence_strategy=0 使用约 800ms 静音切句,sentence_strategy=1 使用约 1400ms 静音切段,更适合段落模式。 静音阈值之外还有说话人切换感知切段:活跃 turn 内每积累约 1 秒有声窗口就与 辅助服务比对一次声纹,与当前 turn 参考向量余弦低于阈值时在“停顿≥200ms 后的 首个起音”处强制切开,头段 final 的 commit_reasonspeaker_change。这覆盖 上一位尾句与下一位间隔不足静音阈值(常见 200~600ms 换话停顿)被合成一句的问题。

目录

  • server.py:本地 HTTP 页面和 WebSocket 会话编排。
  • model_service.py:独立 VLLM OpenAI 音频接口适配层。
  • auxiliary_service.py:独立辅助模型 HTTP 接口适配层。
  • speaker_assembler.pyraw segment、speaker evidence 和 display block 状态机。
  • static/:麦克风/文件测试页面。
  • tests/:只测试本项目状态合并和音频转换逻辑。