跳到正文

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"
配置段字段用途
aliyunappKey / token / url阿里云 NLS 应用、临时 token 与网关地址;appKey、token 可被环境变量覆盖
asrhost / portASR HTTP/WebSocket 监听地址
asrhealthPath / wsPath健康检查与音频 WebSocket 路径,默认 /health、/audio
asrwsSubprotocolWebSocket 子协议,默认 audio.drachtio.org,用于兼容 mod_audio_fork
asrmaxSessions最大并发 ASR 会话数
asrstartTimeoutMs / maxQueuedBytes / maxQueuedChunks阿里云 ASR 启动超时,以及启动完成前允许暂存的音频队列上限
asrsampleRate / format发送给阿里云识别 SDK 的音频格式,默认 16000 / pcm
asrmaxSentenceSilence语音静音断句检测阈值(ms,范围 200~6000,默认 800)
asrspeechNoiseThreshold噪音参数阈值(可选浮点数,范围 [-1, 1])
asrenableSemanticSentenceDetection是否开启语义断句(布尔值,默认 false 纯 VAD 断句)
asrenableMultiThreshMod防止 VAD 断句切割过长(布尔值,可选)
asrprofiles热词/词表映射表(Record<string, string>),将 hotwordProfile 映射至阿里云 vocabulary_id
ttshost / portTTS HTTP 监听地址
ttspublicBaseUrl返回给调用方的公开访问基址,必须是 FreeSWITCH 可访问的地址
ttssampleRate合成音频采样率
ttsrequestTimeoutMs / streamStartTimeoutMs普通 TTS 总请求超时,以及流式 TTS 首包超时
ttsdefaultVoice / voices默认音色与 speakerId 到阿里云 voice 的映射
ttsvolume / pitchRate / maxTextLength合成音量、音调和单次文本长度上限
loggingdir / 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:

  1. 开嗓即时帧(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}"
      }
    }
  2. 中间识别文本(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"
      }
    }
  3. 整句结算帧(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:// 协议,规避 FreeSWITCH mod_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 进程,避免端口被后续联调占用。

文档与代码在同一仓库维护,现有 Markdown 是唯一内容源。