aliyun-speech-gateway
aliyun-speech-gateway 是一个 Bun + TypeScript 的阿里云 NLS 语音网关。它把 阿里云实时语音识别和语音合成包装成与当前本地语音链路接近的接口,便于 callflow-esl、FreeSWITCH mod_audio_fork 或管理台测试工具在不改调用方式的 情况下切换到云端 ASR/TTS。
服务启动后会同时监听两个端口:
- ASR WebSocket 服务:默认
0.0.0.0:10097 - TTS HTTP 服务:默认
0.0.0.0:9081
运行
在仓库根目录运行:
bash
bun install
bun run dev:aliyun-speech也可以进入子项目单独运行:
bash
cd speech-gateway/aliyun-speech-gateway
bun run dev # 监听模式
bun run start # 启动一次
bun run typecheck # TypeScript 类型检查构建可执行文件
bash
bun run build # typecheck 后构建 Windows x64
bun run build:win # 构建 dist/win_x64/aliyun-speech-gateway.exe
bun run build:linux # 从当前环境交叉构建 dist/linux_x64/aliyun-speech-gateway
bun run build:all # typecheck 后构建 Windows + Linux x64 与单文件 bundle构建产物是可直接部署的目录,包含独立可执行文件与外置 config.toml(dist/win_x64/、dist/linux_x64/), 以及单文件分发目录 dist/bundle/(index.js + config.toml)。
配置
配置文件位于 speech-gateway/aliyun-speech-gateway/config.toml。阿里云凭据建议优先通过 环境变量传入,避免把机器或账号专属值写死到代码中:
powershell
$env:ALIYUN_NLS_APP_KEY="your-app-key"
$env:ALIYUN_NLS_TOKEN="your-token"
$env:ALIYUN_NLS_URL="wss://nls-gateway-cn-shanghai.aliyuncs.com/ws/v1"
bun run --cwd speech-gateway/aliyun-speech-gateway start下表中的四个 VAD / 断句参数也支持环境变量覆盖(优先级高于 config.toml),便于在不改配置文件的 前提下按现场噪声环境调参:
powershell
$env:ALIYUN_ASR_MAX_SENTENCE_SILENCE="800"
$env:ALIYUN_ASR_SPEECH_NOISE_THRESHOLD="-0.1"
$env:ALIYUN_ASR_ENABLE_SEMANTIC_SENTENCE_DETECTION="false"
$env:ALIYUN_ASR_ENABLE_MULTI_THRESH_MOD="true"| 配置段 | 字段 | 用途 |
|---|---|---|
aliyun | appKey / token / url | 阿里云 NLS 应用、临时 token 与网关地址;appKey、token 可被环境变量覆盖 |
asr | host / port | ASR HTTP/WebSocket 监听地址 |
asr | healthPath / wsPath | 健康检查与音频 WebSocket 路径,默认 /health、/audio |
asr | wsSubprotocol | WebSocket 子协议,默认 audio.drachtio.org,用于兼容 mod_audio_fork |
asr | maxSessions | 最大并发 ASR 会话数 |
asr | startTimeoutMs / maxQueuedBytes / maxQueuedChunks | 阿里云 ASR 启动超时,以及启动完成前允许暂存的音频队列上限 |
asr | sampleRate / format | 发送给阿里云识别 SDK 的音频格式,默认 16000 / pcm |
asr | maxSentenceSilence | 语音静音断句检测阈值(ms,范围 200~6000,默认 800) |
asr | speechNoiseThreshold | 噪音参数阈值(可选浮点数,范围 [-1, 1]) |
asr | enableSemanticSentenceDetection | 是否开启语义断句(布尔值,默认 false 纯 VAD 断句) |
asr | enableMultiThreshMod | 防止 VAD 断句切割过长(布尔值,可选) |
asr | profiles | 热词/词表映射表(Record<string, string>),将 hotwordProfile 映射至阿里云 vocabulary_id |
tts | host / port | TTS HTTP 监听地址 |
tts | publicBaseUrl | 返回给调用方的公开访问基址,必须是 FreeSWITCH 可访问的地址 |
tts | sampleRate | 合成音频采样率 |
tts | requestTimeoutMs / streamStartTimeoutMs | 普通 TTS 总请求超时,以及流式 TTS 首包超时 |
tts | defaultVoice / voices | 默认音色与 speakerId 到阿里云 voice 的映射 |
tts | volume / pitchRate / maxTextLength | 合成音量、音调和单次文本长度上限 |
logging | dir / maxSize / maxFiles / level | 控制台与按天轮转文件日志配置 |
ASR 接口
健康检查
http
GET http://127.0.0.1:10097/health响应示例:
json
{
"ok": true,
"service": "aliyun-speech-gateway-asr",
"activeSessions": 0,
"maxSessions": 64,
"wsPath": "/audio",
"ioWorkers": 1
}实时识别 WebSocket
默认地址:
text
ws://127.0.0.1:10097/audio连接与参数协议(全面对齐 sherpa-asr-server 流式 WS):
- URL Query 参数:支持在建连 URL 中携带上下文参数,例如:
ws://127.0.0.1:10097/audio?turnId=1&channelUuid=call-uuid&language=zh&hotwordProfile=custom_vocab支持字段:turnId、channelUuid(或channel_uuid、callUuid)、language、hotwordProfile、hotwords。 - 子协议头:
Sec-WebSocket-Protocol需要包含配置中的asr.wsSubprotocol(默认audio.drachtio.org)。 - 首包 Metadata 帧:第一个文本帧可携带 JSON 元数据覆盖或补充参数:json若配置了
{ "channelUuid": "call-uuid", "turnId": 1, "language": "zh", "hotwordProfile": "ecommerce", "hotwords": "退款,换货,七天无理由" }asr.profiles,hotwordProfile会自动映射为阿里云定制vocabulary_id注入识别会话。 - 音频二进制帧:后续二进制帧为 16kHz 单声道 PCM16LE 音频。
- 主动定稿(零长帧):发送 0 字节二进制帧表示主动促成当前句子定稿(Active Flush)。网关会促使阿里云会话下发
isFinal: true结算帧(如期间无有效语音,则安全下发空文本结算帧并置speechActive: false解除下游打断状态);同时保持下游 WebSocket 连接活跃不断开,并在同连接上自动重启云端识别会话,无缝准备下一轮识别。
识别结果消息包含完整的 VAD 语音生命周期事件,且字段严格对齐 sherpa-asr-server:
开嗓即时帧(
SentenceBegin): 当服务端 VAD 首次检测到说话人开头发音时立即推送,无需等待中间识别文本,下游系统据此可在bargeInTrigger: "speech-start"策略下触发毫秒级打断:json{ "type": "transcription", "data": { "text": "", "isFinal": false, "utteranceIndex": 1, "turnId": 1, "language": "zh", "speechStarted": true, "speechActive": true, "speechSegmentDetected": true, "speechSegmentCompleted": false, "serverSessionId": "ws-1", "channelUuid": "call-uuid", "metadataText": "{\"channelUuid\":\"call-uuid\",\"turnId\":1}" } }中间识别文本(
TranscriptionResultChanged):json{ "type": "transcription", "data": { "text": "您好,请问", "isFinal": false, "utteranceIndex": 1, "turnId": 1, "language": "zh", "speechStarted": true, "speechActive": true, "speechSegmentDetected": true, "speechSegmentCompleted": false, "serverSessionId": "ws-1", "channelUuid": "call-uuid" } }整句结算帧(
SentenceEnd/final): 当说话人停顿静音超过asr.maxSentenceSilence阈值、发生语义断句或收到零长帧时下发:json{ "type": "transcription", "data": { "text": "您好,请问有什么可以帮您。", "isFinal": true, "utteranceIndex": 1, "turnId": 1, "language": "zh", "speechStarted": true, "speechActive": false, "speechSegmentDetected": true, "speechSegmentCompleted": true, "serverSessionId": "ws-1", "channelUuid": "call-uuid", "finalReason": "endpoint" } }
三种帧在日志里用不同事件名区分:asr_transcription_begin(开嗓瞬时帧)/ asr_transcription_partial(中间结果)/ asr_transcription_final(整句结算)。判定规则:text === "" && !isFinal && speechStarted 为开嗓帧,isFinal 为整句帧(此时 speechActive 强制置 false),其余为中间结果。
错误消息示例:
json
{
"type": "error",
"data": {
"message": "Aliyun NLS request failed"
}
}TTS 接口
健康检查
http
GET http://127.0.0.1:9081/health响应会返回服务监听信息、当前采样率和可用 speakers。该结构兼容管理台对 TTS 服务的健康检查。
普通 WAV 合成
http
POST http://127.0.0.1:9081/tts
Content-Type: application/json
{
"text": "您好,欢迎致电。",
"speakerId": 0,
"speed": 1
}响应示例:
json
{
"wavUrl": "http://127.0.0.1:9081/wav/tts-0123456789abcdef.wav",
"fileName": "tts-0123456789abcdef.wav",
"sampleRate": 16000,
"cached": false
}相同 text + speakerId + speed + voice 的请求会复用 public/wav/ 下的缓存文件。 生成后的 WAV 可以通过以下接口读取:
http
GET http://127.0.0.1:9081/wav/tts-0123456789abcdef.wav
HEAD http://127.0.0.1:9081/wav/tts-0123456789abcdef.wav流式音频合成(支持 PCM 与 MP3)
创建短期流式播放链接(全面对齐 sherpa-tts-server 契约):
http
POST http://127.0.0.1:9081/tts-stream
Content-Type: application/json
{
"text": "这是一段需要尽快开始播放的文本。",
"speakerId": 0,
"speed": 1,
"format": "pcm"
}字段说明:
format:可选"pcm"或"mp3",默认"mp3"。生产推荐使用"pcm"配套audio_fork://协议,规避 FreeSWITCHmod_shout阻塞混音线程的问题。
响应示例:
json
{
"streamUrl": "http://127.0.0.1:9081/tts-stream/tts-0123456789abcdef.pcm",
"streamPath": "/tts-stream/tts-0123456789abcdef.pcm",
"sampleRate": 16000,
"estimatedDurationMs": 3520,
"cached": false
}随后客户端(如 FreeSWITCH 或 audio-fork-server)访问 streamUrl:
format: "pcm":输出Content-Type: audio/pcm;rate=16000与x-audio-channels: 1响应头,直接推流 16kHz 16-bit 单声道线性 PCM 裸流,同时落盘写入public/pcm/下的缓存文件。format: "mp3":输出Content-Type: audio/mpeg响应头,流式推送 MP3 音频,同时落盘写入public/mp3/下的缓存文件。
相同 text + speakerId + speed + voice + format 的请求会复用已落盘的缓存文件,命中时 POST /tts-stream 会返回 cached: true,后续 GET /tts-stream/<file>.[pcm|mp3] 直接读取缓存文件并带上 Content-Length:
http
GET http://127.0.0.1:9081/tts-stream/tts-0123456789abcdef.pcm
HEAD http://127.0.0.1:9081/tts-stream/tts-0123456789abcdef.pcm首次 GET 如果合成失败、客户端断开或写文件失败,服务会删除临时文件,不留下半截正式缓存。5 分钟 TTL 只限制未生成缓存前的内存播放票据;已经落盘的缓存文件不受该 TTL 限制。
对接注意事项
- 如果要让
callflow-esl使用阿里云 ASR:- 把
apps/callflow-esl/config.toml的audioFork.wsUrl改为ws://<gateway-host>:10097/audio。 bugName、mixType=mono、sampleRate=16k保持与本地 ASR 一致即可。- 支持在 URL Query 中带上
?turnId=1&channelUuid=<uuid>,或者在首包 metadata JSON 中携带。
- 把
- 如果要让
callflow-esl使用阿里云 TTS:- 把
tts.profiles.wav.endpoint改为http://<gateway-host>:9081/tts。 - 把
tts.profiles.stream.endpoint改为http://<gateway-host>:9081/tts-stream。 - 推荐设置
tts.profiles.stream.playbackPrefix: "audio_fork://",此时callflow-esl会请求format: "pcm",网关直出audio/pcm;rate=16000流,与本地sherpa-tts-server体验完全一致且规避mod_shout阻塞。
- 把
- 管理台的 TTS 试听页可以直接填
http://<gateway-host>:9081/tts;ASR 测试页可把主机和端口改成<gateway-host>:10097,上传 16kHz mono PCM16 WAV 或用浏览器录音测试。 tts.publicBaseUrl必须配置成 FreeSWITCH 或调用方能够访问到的地址;如果服务运行在另一台机器上,不要保留默认的127.0.0.1。- ASR 输入默认按 16kHz 单声道 PCM 处理,调用方需要保证实际音频格式与
asr.sampleRate、asr.format一致。 /wav/:fileName、/tts-stream/:fileName受严格正则防护,只允许读取本服务生成的tts-<16位hash>.[wav|mp3|pcm]文件名,防止路径穿越。- 本服务没有内置鉴权,建议只暴露在可信网络内,或放在带鉴权的反向代理之后。
- 任务结束后如果手动启动过服务,请停止对应 Bun 进程,避免端口被后续联调占用。