模块定位

mod_audio_fork 是一个 FreeSWITCH 模块,它在通话上附加一个 media bug(媒体监听器),把通话的实时音频以 L16 编码通过 WebSocket 推送到远端服务器,同时支持远端推回音频做实时播放。是本项目把 FreeSWITCH 与 AI 语音服务串起来的唯一桥梁

  • 仓库来源:Wangijun/mod_audio_fork,fork 自 W1ck3dZA/mod_audio_fork
  • 语言:C++(~78%)+ C
  • 协议:标准 RFC6455 WebSocket,子协议 audio.drachtio.org

为什么不用 mod_unimrcp

维度 mod_unimrcp mod_audio_fork
协议 MRCPv2 + SIP + RTP 一根 WebSocket
部署 要 unimrcpserver + 插件 + XML 配置 业务侧实现一个 WS server 即可
流式 ASR 协议层支持但开源插件少 天然流式,partial 秒到
与现代 AI 栈对接 难(要写 MRCP 插件) 直接喂 PCM16 给 sherpa/whisper/其他
双向音频 较复杂 服务端推 binary 帧即可

本项目当前版本完全不依赖 mod_unimrcp,识别只走 mod_audio_fork

核心能力

  • 双向音频流:上行通过 WS 上传 PCM16;下行可接收服务端推回的 base64 JSON 或 binary 音频帧
  • 音频标记mark / clearMarks 同步播放节奏
  • 混流模式mono(仅主叫)、mixed(主被叫混音)、stereo(双声道分离)
  • 采样率:8000 / 16000 / 24000 / 32000 / 48000 / 64000 Hz,内置 Speex 重采样
  • SIMD 优化:AVX2 / SSE2 编译加速
  • TLS 支持wss:// 直连
  • 平滑停止graceful-shutdown 排空缓冲后再关闭
  • HTTP Basic Auth:通过 channel variable 注入认证头

构建与安装

模块仓库提供 build.sh 一键脚本:

1
2
3
4
git clone https://github.com/Wangijun/mod_audio_fork.git
cd mod_audio_fork
chmod +x build.sh
sudo ./build.sh all

或分步:

1
2
3
sudo ./build.sh deps      # 装编译依赖
./build.sh build # 编译
sudo ./build.sh install # 安装到 FreeSWITCH 的 mod 目录

详细构建说明见仓库 BUILD.md

在 FreeSWITCH 中加载:

1
2
<!-- conf/autoload_configs/modules.conf.xml -->
<load module="mod_audio_fork"/>

或运行时:

1
fs_cli -x "load mod_audio_fork"

uuid_audio_fork 命令集

通用语法:uuid_audio_fork <uuid> <command> [args...]

命令 用途
start <wss-url> <mix-type> <sampling-rate> [bugname] [metadata] [bidi_enabled] [bidi_stream_enabled] [bidi_stream_samplerate] 启动 media bug,建立 WebSocket,开始推流
stop [bugname] [metadata] 关闭连接、移除 bug;可附最后一段文本元数据
send_text [bugname] <text> 向服务端发一条 text frame(DTMF / 自定义控制等)
pause [bugname] 暂停推流(音频帧丢弃)
resume [bugname] 恢复推流
graceful-shutdown [bugname] 不再发新音频,排空缓冲后关闭
stop_play [bugname] 清空当前下行播放缓冲

示例

1
2
3
4
fs_cli -x "uuid_audio_fork <uuid> start ws://127.0.0.1:10096/audio mono 16k callflow_asr {}"
fs_cli -x "uuid_audio_fork <uuid> pause callflow_asr"
fs_cli -x "uuid_audio_fork <uuid> resume callflow_asr"
fs_cli -x "uuid_audio_fork <uuid> stop callflow_asr {\"reason\":\"complete\"}"

bugname 在同一通通话里需保持唯一;callflow-esl 默认用 callflow_asr

关键环境变量

变量 默认 说明
MOD_AUDIO_FORK_SUBPROTOCOL_NAME audio.drachtio.org WebSocket 子协议名;服务端必须接受同名
MOD_AUDIO_FORK_SERVICE_THREADS 1 服务线程数(1-5)
MOD_AUDIO_FORK_BUFFER_SECS 2 缓冲秒数(1-5)

关键 channel variable

可在 dialplan 或 uuid_setvar 中设置:

变量 用途
MOD_AUDIO_BASIC_AUTH_USERNAME / MOD_AUDIO_BASIC_AUTH_PASSWORD WSS 端的 HTTP Basic Auth
MOD_AUDIO_FORK_ALLOW_SELFSIGNED TLS 允许自签证书
MOD_AUDIO_FORK_SKIP_SERVER_CERT_HOSTNAME_CHECK 跳过 TLS hostname 校验
MOD_AUDIO_FORK_ALLOW_EXPIRED 允许过期证书

自定义事件

模块在 FreeSWITCH event bus 上发布以下 CUSTOM event(subclass):

Event Subclass 含义
mod_audio_fork::connect WebSocket 已连接
mod_audio_fork::connect_failed 握手失败
mod_audio_fork::disconnect 连接断开
mod_audio_fork::buffer_overrun 缓冲溢出(来不及发)
mod_audio_fork::transcription 服务端推回的识别文本
mod_audio_fork::transfer 服务端请求转接
mod_audio_fork::play_audio 服务端推回播放音频
mod_audio_fork::kill_audio 服务端请求中断播放
mod_audio_fork::error 错误事件
mod_audio_fork::json 服务端自定义 JSON 帧

callflow-esl 主要消费 connect / disconnect / transcription 三种。

上下行消息协议(与服务端约定)

客户端 → 服务端

  • 第一帧:text frame,JSON metadata。callflow-esl 默认下发:

    1
    2
    3
    4
    5
    {
    "uuid": "<fs-channel-uuid>",
    "callerId": "...",
    "direction": "inbound"
    }
  • 后续帧:binary frame,原始 16kHz / mono / PCM16 little-endian 音频

服务端 → 客户端

服务端可发 text frame,JSON 体按 type 分发:

1
2
3
4
5
6
7
8
{ "type": "transcription", "text": "...", "isFinal": true }
{ "type": "playAudio", "audioContent": "<base64>", "sampleRate": 16000 }
{ "type": "killAudio" }
{ "type": "mark", "name": "..." }
{ "type": "clearMarks" }
{ "type": "transfer", "to": "..." }
{ "type": "disconnect" }
{ "type": "error", "message": "..." }

当通话需要服务端推 TTS 给主叫播放时(双向音频模式),playAudio 携带 base64 音频或 binary 帧。

mark 队列最大 30 项。

在本项目中的使用

callflow-esl 通过配置项 audioFork 调用本模块:

1
2
3
4
5
6
7
8
9
10
11
12
{
"audioFork": {
"wsUrl": "ws://192.168.2.246:10096/audio",
"bugName": "callflow_asr",
"mixType": "mono",
"sampleRate": "16k",
"bidirectionalAudioEnabled": false,
"bidirectionalAudioStreamEnabled": false,
"connectTimeoutMs": 5000,
"eventSubscriptionMode": "all"
}
}

runtime 在 ctx.hear() 启动时下发 uuid_audio_fork start,并在结束 / 挂机时下发 stopeventSubscriptionMode 控制 ESL 事件订阅策略,默认 "all" 是当前唯一稳定下发 mod_audio_fork::transcription 的策略。详见 配置参考

已知坑

注意

  • 采样率必须匹配:上游推 16kHz 但服务端按 8kHz 解码,partial 全乱。本项目默认 mono / 16k
  • bugname 唯一性:同一通通话多次 start 用同一个 bugname 会失败;如果业务里需要先停后启,要么传不同 bugname,要么先 stopstart
  • CUSTOM 事件订阅:FreeSWITCH 默认 event plain CUSTOM mod_audio_fork::transcription 在某些版本下不稳定,本项目改用 event plain ALL 再在应用侧按 UUID 过滤。
  • TLS 自签:内网部署常用自签证书,记得配 MOD_AUDIO_FORK_ALLOW_SELFSIGNED=true

参考链接