服务定位

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
6
cd onnx-platform\sherpa-asr-online-server
.\build.ps1
# 等价于:
# cmake -S . -B build/win_x64 -G "Visual Studio 17 2022" -A x64
# cmake --build build/win_x64 --config Release --target sherpa_asr_online_server
# cmake --install build/win_x64 --config Release --prefix target/win_x64

构建目录保留在 build\win_x64\,**可运行的发布产物安装到 target\win_x64\**(仅含可执行文件、
config.json 与运行时 DLL)。bun run dev / bun run dev:asr 即从这里启动它。

启动:

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

target\win_x64\ 启动时优先改该目录下的 config.json;重新构建不会自动覆盖已有的 target\win_x64\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 默认丢弃,logging.debugTextFrames=true 时打印。

2. 二进制音频帧

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

  • 帧大小受 socket.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

关键配置

config.json 按域分组:server / concurrency / socket / logging /
recording / asr / vad(与仓库 onnx-platform/sherpa-asr-online-server/README.md 一致)。

server — 监听

字段 默认 说明
server.host 127.0.0.1 实际监听地址
server.port 10096 实际监听端口
server.healthPath /health HTTP 健康检查路径
server.wsPath /audio WebSocket 升级路径
server.wsSubprotocol audio.drachtio.org 强制子协议;空则不强制

concurrency — 并发

字段 默认 说明
concurrency.maxSessions 16 并发会话上限;超出返回 503
concurrency.ioWorkers CPU 核心数 I/O worker 线程数(受 FD_SETSIZE 约束)
concurrency.sessionPoolSize 16 预热的 ASR session 数;超过 maxSessions 会被裁剪
concurrency.numThreads 1 ONNX 推理线程数
concurrency.acceptBacklog SOMAXCONN listen(backlog)
concurrency.workerPollTimeoutMs 10 工作线程单轮 select 超时;夹紧到 [1, 1000]

socket — 套接字缓冲

字段 默认 说明
socket.tcpNoDelay true 启用 TCP_NODELAY
socket.maxFrameBytes 4194304 单个 WebSocket 帧最大字节数;超过即 1002 协议错误
socket.readBufferBytes 65536 每连接 recv 缓冲
socket.writeBufferBytes 65536 每连接 send 缓冲(预留)
socket.recvBufferBytes 262144 SO_RCVBUF(0 表示不调整)
socket.sendBufferBytes 262144 SO_SNDBUF(0 表示不调整)

logging — 日志

字段 默认 说明
logging.dir logs 日志目录;相对路径按运行目录解析,按天滚动
logging.debugTextFrames false 打印每条 metadata 文本帧
logging.debugAudioFrames false 每个二进制帧附识别状态摘要(高吞吐慎开)
logging.debugRecognitionState false 每次识别状态变化落日志(定位 final 缺失)

recording — 录音

字段 默认 说明
recording.enabled false 启用上行音频录制
recording.dir recordings 录制目录(相对路径按配置文件所在目录解析)

asr — 识别

ASR 模型采用 asr.activeModel + asr.models profile 表结构:在 asr.models
声明多个模型 profile,用 asr.activeModel 指定当前加载哪一个。切换模型只改
asr.activeModel,无需改动各 profile 的文件路径。默认配置声明两个 profile:中文
int8 流式 zipformer(默认)与中英双语。

字段 默认 说明
asr.activeModel sherpa-onnx-streaming-zipformer-zh-int8-2025-06-30 当前加载的模型 profile id,必须存在于 asr.models
asr.models (profile 表) 模型 profile 表,每项含 modelDir / encoder / decoder / joiner / tokens / bpeVocab? / modelType / modelingUnit
asr.provider cpu ONNX 后端
asr.sampleRate 16000 仅支持 16kHz
asr.debug false sherpa-onnx 内部 debug
asr.sendPartialResults true 是否下发 partial
asr.hotwordsScore 1.5 热词权重;对所有 hotword profile 生效
asr.defaultHotwordProfile metadata 未指定 hotwordProfile 时使用的 profile ID;为空则不启用 hotwords
asr.hotwordProfiles {} 静态 hotword profile 集合;连接建立后按 metadata 选择
asr.partialMinIntervalMs 300 同一句 partial 下发节流间隔
asr.decodeBatchMs 80 批量 decode 节奏

asr.models 每个 profile 的 modelDir 相对仓库 onnx-platform/models/ 解析,绝对路径
原样使用;profile 内的模型文件路径相对 modelDir 解析。

vad — 语音活动检测(强制启用,无开关)

字段 默认 说明
vad.model silero_vad.onnx silero-vad onnx;相对 onnx-platform/models/ 解析,绝对路径原样使用
vad.bufferSizeSeconds 30.0 silero-vad ring buffer
vad.threshold 0.5 VAD 置信度阈值
vad.minSilenceDuration 0.5 触发段尾的最小静音时长
vad.minSpeechDuration 0.25 视为有效语音的最小时长
vad.windowSize 512 VAD 窗大小(采样点)
vad.maxSpeechDuration 20.0 单段最大语音时长,超过强制切段

VAD 强制启用,没有 enableVad 开关;endpoint 检测走 sherpa 默认,不在配置中暴露
enableEndpoint / endpointRule* 字段
。旧版扁平字段 asrModelName / asrEncoder /
asrDecoder / asrJoiner / asrTokens / asrBpeVocab / asrModelType / asrModelingUnit /
vadModel / listenHost / listenPort / recordAudioEnabled / recordAudioDir /
enableVad / enableEndpoint / endpointRule* 已移除,迁移时改写为上述分组结构。完整字段见仓库
onnx-platform/sherpa-asr-online-server/README.md

吞吐调优

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

与 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
5
6
{
"recording": {
"enabled": true,
"dir": "recordings"
}
}

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

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

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

调试日志

开关 用途
logging.debugTextFrames 每条 metadata / 文本帧落日志
logging.debugAudioFrames 每个二进制帧附识别状态摘要(高吞吐慎开)
logging.debugRecognitionState 每次识别状态变化落日志(定位 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

参考

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