服务定位
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 | cd sherpa-asr-online-server |
启动:
1 | cd .\build\Release |
健康检查:
1 | curl http://127.0.0.1:10096/health |
返回:
1 | { |
WebSocket 协议
握手
1 | GET /audio HTTP/1.1 |
校验:
- 必须
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 任一字段作为日志和录音文件名的 channelUuid。mod_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. 控制帧
0x8Close:服务端先 flush final,再回1000 byeclose0x9Ping:原样回 Pong0xAPong:忽略- 其他 opcode:发
error后回1003 unsupported opcode并关闭 - 不支持分片帧(FIN=0 或 opcode=0x0),收到回
1002 protocol error
服务端 → 客户端
服务端通过文本帧下发 JSON 事件,按 type 分三类。
type=transcription
每次 partial 推送 / final 段完成 / 客户端 flush 都可能发一条:
1 | { |
| 字段 | 说明 |
|---|---|
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 | client server |
关闭路径:
| 触发 | 行为 |
|---|---|
| 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 / VADnumThreads: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 | { |
业务通过 ctx.hear({...}) 或 ctx.callHear(...) 触发,runtime 内部负责 audio_fork 启停与 transcription 事件订阅。
排查识别问题
开启上行音频录制
1 | { |
每个 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