跳到正文

全双工语音通信 ​

ctx.duplex 让同一通电话在播放语音时持续接收客户音频。客户说话可以打断当前播报,识别结果随后驱动下一句回复。它适合需要多轮对话和插话的业务;已注册的 duplex-ai-chat-demo-business 展示了完整流程。

链路如何工作 ​

ctx.duplex.start() 在当前 FreeSWITCH 通道上启动独立的 media bug,并让 mod_audio_fork 连接全双工网关。网关把客户音频转发给 ASR,再把识别事件送回 FreeSWITCH;callflow-esl 从 ESL 事件收到 onTranscription 和 onFinal。

业务调用 ctx.duplex.speak() 时,控制器先通过 uuid_broadcast 打开 audio_fork:// 播放目标,再用 uuid_audio_fork send_text 把话术交给网关。网关向 TTS 请求 PCM 流,经原有 WebSocket 回送下行音频;FreeSWITCH 播放它时仍继续推送客户上行音频。下行结束时网关发送零长音频帧,控制器以匹配当前播放目标的 PLAYBACK_STOP 确认自然播完。

这里的 audio_fork:// 是全双工会话的播放目标。普通单呼 TTS 的流式 profile 也可以使用同名播放前缀,但它通过 callflow-esl 直接创建 TTS 流链接;两条调用链见 TTS 对接。

插话与识别回调 ​

全双工网关只在当前句允许打断、且正在播放时判定插话。bargeInTrigger 有三种选择:

值打断时机
speech-start收到 VAD 开嗓事件或识别事件中的语音活动信号
partial收到达到网关 bargeIn.minTextLength 的识别文本;当前配置选用此值
final收到达到最小文本长度的句末结果

命中后,网关取消当前 TTS 流并发送 kill_audio;控制器停止当前播放句柄、触发 onBargeIn,使正在等待的 ctx.duplex.speak() 返回 interrupted: true。interruptible: false 可用于不希望客户插话打断的单句,例如告别语。业务也能用 ctx.duplex.interrupt(reason) 主动停止当前播报。

onTranscription 接收 partial 和 final,onFinal 只接收 final。业务通常在 onFinal 中决定回复,但回调本身不等待异步播报;连续 final 应放进串行任务队列,避免多句回复并发抢占。ctx.duplex.speak() 返回 completed、interrupted 和 durationMs;超时或下发失败时也可能返回 completed: false, interrupted: false,业务应按结果决定是否继续。精确签名和事件字段见 组件 API。

业务接入 ​

  1. 按 服务目录 启动并检查 audio_fork_server,确认 FreeSWITCH 能访问网关的 WebSocket 地址。网关自身需要能访问 ASR 与 TTS;它们的地址由 speech-gateway/audio-fork-server/config.toml 配置。
  2. 在 apps/callflow-esl/config.toml 启用 [duplex],把 wsUrl 指向网关的 /audio。字段和当前示例值见 配置参考。[audioFork] 是普通 hear / callHear 链路,不作为全双工网关地址。
  3. 在业务接通后调用 ctx.duplex.start(options, handlers),用 ctx.duplex.speak() 播报,用 onFinal 处理识别结果,并在业务退出时调用 ctx.duplex.stop()。start() 返回的 DuplexSession 包含通道 UUID、media bug 名称和启动时的状态快照;播放与停止仍通过 ctx.duplex 调用。

全双工激活期间,普通 ctx.speak()、ctx.hear() 和 ctx.callHear() 不可混用。启动全双工前也不能有正在运行的 hear 或 callHear;如需切回普通组件,先停止全双工会话。通话结束时 SessionRunner 还会兜底停止全双工链路。

可直接从 src/business/demo/duplex-ai-chat-demo-business.ts 查看接通、录音、final 串行处理、告别挂机和 finally 清理的完整实现。启动网关和 callflow-esl 进行联调后,请在任务结束前停止本次启动的服务。

排查入口 ​

现象优先核对
start() 报未启用或缺少地址[duplex].enabled 和 [duplex].wsUrl;网关 GET /health
日志出现 duplex_ws_url_unidirectional_asr_warningwsUrl 是否误指向单向 ASR 的 10096 或 10099;该检查只告警,不阻止启动
有识别结果但播报未结束网关到 TTS 的连接、PCM 流和 FreeSWITCH 的 PLAYBACK_STOP;关联 duplex_speak_dispatched 与 duplex_speak_completed 日志
播报未被插话打断interruptible、bargeInTrigger、网关 bargeIn.enabled 与 minTextLength,以及 ASR 是否输出相应的 VAD 或文本事件
普通播报或识别组件报互斥错误先 await ctx.duplex.stop(),再启动普通组件

网关健康检查地址在 [duplex].healthUrl 中提供给部署和联调使用;ctx.duplex.start() 直接发起 FreeSWITCH 命令,不会先调用该 HTTP 健康检查。

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