服务定位

sherpa-asr-online-server 是一个 C++ WebSocket 流式 ASR 服务,使用 sherpa-onnx 的流式 zipformer + silero-vad。协议沿用 mod_audio_fork 子协议约定,因此可被 FreeSWITCH mod_audio_fork 直接当作 ASR 后端,也可被任意 WebSocket 客户端调用。

特性:

  • HTTP GET /health 健康检查
  • WebSocket /audio,子协议 audio.drachtio.org
  • 客户端先发一帧 metadata 文本,再持续推 16kHz / mono / PCM16 二进制音频
  • 服务端按 partial / final 返回 type=transcription 的 JSON 文本帧
  • 客户端发送 0 长度二进制帧或关闭连接时,服务自动 flush 最后的识别结果
  • 固定数量 I/O worker 线程 + select 事件循环
  • 启动时预热 sessionPoolSize 个 ASR session

构建与运行

1
2
3
4
5
cd sherpa-asr-online-server
.\build.ps1
# 等价于:
# cmake -S . -B build -G "Visual Studio 17 2022" -A x64
# cmake --build build --config Release --target sherpa_asr_online_server

启动:

1
2
3
4
cd .\build\Release
.\sherpa_asr_online_server.exe
# 或显式指定配置:
# .\sherpa_asr_online_server.exe C:\path\to\config.json

健康检查:

1
curl http://127.0.0.1:10096/health

返回:

1
2
3
4
5
6
7
8
{
"ok": true,
"service": "sherpa-asr-online-server",
"activeSessions": 3,
"maxSessions": 16,
"wsPath": "/audio",
"ioWorkers": 4
}

WebSocket 协议

握手

1
2
3
4
5
6
7
GET /audio HTTP/1.1
Host: 127.0.0.1:10096
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: <base64-16>
Sec-WebSocket-Protocol: audio.drachtio.org

校验:

  • 必须 Sec-WebSocket-Version: 13
  • 必须 Upgrade: websocket + Connection: upgrade
  • wsSubprotocol 非空(默认 audio.drachtio.org),请求必须带对应 token
  • maxSessions 限流,超出返回 503

握手成功后服务端立即为连接绑定一个预热的 ASR session(recognizer + VAD 已就绪),日志 ws-<n> websocket session started on worker=<i>

客户端 → 服务端

1. metadata 文本帧(第一帧)

任意 JSON 文本,服务端会尝试解析 uuid | channel_uuid | call_uuid | sessionId | session_id 任一字段作为日志和录音文件名的 channelUuidmod_audio_fork 默认发:

1
{ "uuid": "fs-channel-uuid", "callerId": "10086", "direction": "inbound" }

后续 text frame 默认丢弃,debugLogTextFrames=true 时打印。

2. 二进制音频帧

16kHz / 单声道 / PCM16 little-endian 原始采样。

  • 帧大小受 maxFrameBytes 限制(默认 4MB),超过即触发 1002 协议错误关闭
  • 任意时刻发 0 长度二进制帧 = “flush”:服务端立刻 flush 当前 segment 并发出 final
  • 服务端不做重采样,上游非 16kHz 会导致 partial 全错

3. 控制帧

  • 0x8 Close:服务端先 flush final,再回 1000 bye close
  • 0x9 Ping:原样回 Pong
  • 0xA Pong:忽略
  • 其他 opcode:发 error 后回 1003 unsupported opcode 并关闭
  • 不支持分片帧(FIN=0 或 opcode=0x0),收到回 1002 protocol error

服务端 → 客户端

服务端通过文本帧下发 JSON 事件,按 type 分三类。

type=transcription

每次 partial 推送 / final 段完成 / 客户端 flush 都可能发一条:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
"type": "transcription",
"data": {
"text": "你好世界",
"isFinal": false,
"utteranceIndex": 3,
"speechStarted": true,
"speechActive": true,
"speechSegmentDetected": true,
"speechSegmentCompleted": false,
"endpointDetected": false,
"serverSessionId": "ws-12",
"channelUuid": "fs-channel-uuid",
"metadataText": "{\"uuid\":\"fs-channel-uuid\",...}",
"finalReason": "endpoint",
"flushReason": "client-close"
}
}
字段 说明
text 当前识别文本(partial 或 final)
isFinal true 表示本句已结算,下一段从 utteranceIndex+1 开始
utteranceIndex 同一连接内的语句序号,从 1 起
speechStarted/Active/SegmentDetected/SegmentCompleted VAD 状态
endpointDetected sherpa 三规则触发 endpoint
serverSessionId 服务端会话 ID(ws-<n>
channelUuid 从 metadata 解析到的通道 UUID
finalReason isFinal=true 时填:endpoint / segment-completed / final / flush
flushReason flush 触发时填:client-close / zero-length-binary / socket-read-end

partial 节流:同一句话内,partial 最快每 partialMinIntervalMs ms 下发一次;文本未变化的事件不重发。

type=error

1
{ "type": "error", "data": { "message": "ASR accept failed: <reason>" } }

error 通常紧随 close:

  • ASR AcceptPcm16 失败 → 1011 内部错误
  • 协议错误(帧过大 / 不支持的 opcode)→ 1002 / 1003

type=disconnect

服务端主动断开前,可能先发:

1
{ "type": "disconnect", "data": { "reason": "<message>" } }

连接生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
client                       server
|--- TCP connect --------->|
|--- HTTP GET /audio ----->|
|<-- 101 Switching Proto --|
|--- TEXT metadata ------->| (acquire session)
|--- BIN PCM16 frame ----->|
|<-- transcription partial-|
|--- BIN PCM16 frame ----->|
|<-- transcription final --| (segment_completed / endpoint)
|--- BIN zero-length ----->| (flush)
|<-- transcription final --| (flushReason=zero-length-binary)
|--- CLOSE frame --------->|
|<-- CLOSE 1000 bye -------|

关闭路径:

触发 行为
client 发 close flush final → 回 1000 bye → 关闭
client TCP 断开 flush final(flushReason=socket-read-end)→ 释放 session
服务端流式异常 error → 关闭码 1011
协议错误 error + disconnect → 关闭码 1002 / 1003

关键配置

服务与 IO

字段 默认 说明
listenHost / listenPort 127.0.0.1 / 10096 监听地址
healthPath /health HTTP 健康检查路径
wsPath /audio WS 升级路径
wsSubprotocol audio.drachtio.org 强制子协议;空则不强制
maxSessions 16 并发会话上限;超出返回 503
ioWorkers CPU 核心数 I/O worker 线程数(受 FD_SETSIZE 约束)
sessionPoolSize 16 预热的 ASR session 数
maxFrameBytes 4194304 单帧最大字节数
recordAudioEnabled false 是否落盘上行音频排查
recordAudioDir recordings 录音目录

Sherpa ASR / VAD

字段 默认 说明
asrEncoder/Decoder/Joiner/Tokens bilingual-zh-en ONNX 模型路径
vadModel silero_vad.onnx VAD 模型
provider cpu ONNX 后端
sampleRate 16000 仅支持 16kHz
numThreads 1 ONNX 线程
enableVad true 启用 VAD 分段
enableEndpoint true 启用 endpoint 检测
sendPartialResults true 是否下发 partial
partialMinIntervalMs 300 同一句 partial 下发节流间隔
decodeBatchMs 80 批量 decode 节奏
endpointRule1MinTrailingSilence 2.4 sherpa endpoint 规则 1
endpointRule2MinTrailingSilence 1.2 sherpa endpoint 规则 2
endpointRule3MinUtteranceLength 20.0 sherpa endpoint 规则 3
vadThreshold 0.5 VAD 置信度阈值
vadMinSilenceDuration 0.5 段尾最小静音时长
vadMaxSpeechDuration 20.0 单段最大语音时长

吞吐调优

  • ioWorkers:每个 worker 受 FD_SETSIZE 约束(Windows 默认 1024,本进程编译期固定 1024,单 worker ~992 连接)
  • sessionPoolSize:建议接近或等于 maxSessions,避免连接抖动反复冷启动 recognizer / VAD
  • numThreads:ONNX 推理线程
  • partialMinIntervalMs / decodeBatchMs:影响 partial 实时性 vs CPU 占用
  • recordAudioEnabled:默认关闭,只在排查问题时打开

与 FreeSWITCH 集成

通过 uuid_audio_fork

1
fs_cli -x "uuid_audio_fork <uuid> start ws://127.0.0.1:10096/audio mono 16k callflow_asr {}"

callflow-esl 默认配置:

1
2
3
4
5
6
7
8
{
"audioFork": {
"wsUrl": "ws://192.168.2.246:10096/audio",
"bugName": "callflow_asr",
"mixType": "mono",
"sampleRate": "16k"
}
}

业务通过 ctx.hear({...})ctx.callHear(...) 触发,runtime 内部负责 audio_fork 启停与 transcription 事件订阅。

排查识别问题

开启上行音频录制

1
2
3
4
{
"recordAudioEnabled": true,
"recordAudioDir": "recordings"
}

每个 WebSocket 会话会把 PCM16 二进制保存为 .wav

1
asr-<YYYYMMDD-HHMMSS-mmm>-<serverSessionId>[-<channelUuid>].wav

放回 sherpa-demo/demo_online_asr.exe 复现,确认是模型问题还是网络问题。

调试日志

开关 用途
debugLogTextFrames 每条 metadata / 文本帧落日志
debugLogAudioFrames 每个二进制帧附识别状态摘要(高吞吐慎开)
debugLogRecognitionState 每次识别状态变化落日志(定位 final 缺失)

测试客户端

仓库内 request-asr.js 用原生 net + crypto 实现最小 WebSocket 客户端,按本协议发送 models/sherpa-onnx-sense-voice/test_wavs/zh_vad.wav 的 PCM16:

1
node .\request-asr.js

打印每条 JSON 事件。

局限

注意

  • 仅支持 16kHz / mono / PCM16 上行,不做自动重采样
  • 不支持 WebSocket 帧分片
  • 模型加载耗时 1~3 秒/个,预热池规模影响启动时间
  • 无鉴权、无限流(除 maxSessions),建议仅在内网 / 可信网络运行
  • 服务无 graceful shutdown:Ctrl+C 直接终止 accept 与 worker

参考

  • 协议详细描述:仓库 sherpa-asr-online-server/README.md
  • mod_audio_fork 子协议:见 mod_audio_fork