开源、符合HIPAA要求的 Twilio 替代方案

发布: (2026年1月7日 GMT+8 04:46)
11 min read
原文: Dev.to

Source: Dev.to
注: 如需最新的设置说明,请参阅仓库中的 open-telephony-stack/README.md

为什么我们构建了它

去年夏天,我们为医疗机构构建 AI 语音代理。我们需要:

  • 拨打和接听电话
  • 实时流式音频
  • 保持 HIPAA 合规

Twilio 看起来是显而易见的选择——直到我们遇到付费墙:每月 2,000 美元 的业务关联协议(BAA),而且在打出第一通电话之前就要付费。对于一家初创公司来说,这个价格是难以承受的。

于是我们自行搭建了技术栈:

组件功能
Asterisk开源 PBX(Docker 化)
AWS Chime SDKSIP 中继和电话号码
FastAPI shim将传统电话系统桥接到现代 WebSocket API

最终得到的是一个 完整且安全的电话系统,能够处理入站 出站电话:

  • 通过 AWS Chime Voice Connector(真实 PSTN 号码)接收来电
  • 在 Asterisk(Docker)上终止 SIP/TLS
  • 通过 RTP 将音频桥接到 WebSocket 连接
  • base64 μ‑law 音频流式传输到你的 AI 语音服务器
  • 提供一个 类似 Twilio 的 WebSocket API(参考 Twilio Media Streams)

你只需提供自己的 AI;这套栈仅负责电话基础设施。

用例示例

场景此技术栈的优势
Healthcare AI – 需要 HIPAA 合规但不想承担 Twilio 的 BAA 成本无需额外合规费用;您掌控数据
Custom call handling – Twilio 限制了您对拨号计划、媒体路由等拥有完整控制
Full stack ownership – 想拥有每一层自托管、开源、无供应商锁定
Learning/experimenting – 了解电话系统内部端到端,从 PSTN 到 WebSocket,全部代码实现

如符合以下情况,请考虑其他方案:

  • 您仅需要用于副项目的基础语音功能(Twilio 更简单)。
  • 您不想管理基础设施。
  • 您没有特殊的合规要求。

权衡: 管理此技术栈需要时间和持续的维护。

Service ports

服务端口协议描述
Asterisk SIP5061TCP/TLS使用 AWS Chime 的 SIP 信令
Asterisk ARI8088HTTPAsterisk REST 接口(仅限本地主机)
Shim server8080HTTPFastAPI 服务器,健康检查端点
RTP media10000‑10299UDP与 Asterisk 的音频流

架构概览

1. AWS Chime Voice Connector

  • PSTN 网关。您在此处配置电话号码。
  • 呼叫以 SIP/TLS 方式在端口 5061 到达。

2. Asterisk PBX (Docker)

  • 处理 SIP 信令、RTP 媒体、呼叫路由。
  • 使用 ARI(Asterisk REST 接口)而非传统的拨号计划脚本。

3. Shim server (FastAPI)

功能详情
通过 ARI WebSocket 连接到 Asterisk接收 StasisStart 事件
创建 ExternalMedia 通道将 RTP 桥接到您的 AI 语音服务器
维持 20 ms RTP 节奏隔离 WebSocket 抖动
转发音频将 Base64 μ‑law 负载发送至下游语音服务器

4. 您的 AI 语音服务器

  • 任意能够使用 Twilio 兼容的 WebSocket 媒体格式的服务器(例如 OpenAI Realtime、AWS Nova Sonic、定制 ASR/TTS)。
  • 示例实现:open-telephony-stack/src/servers/voice_agent_server.py.

DNS & TLS 设置

DNS 记录(在 TLS 之前必需)

Record typeNameValueTTL
Asip.yourdomain.com您的弹性 IP(例如 54.123.45.67300(或默认)

在以下操作之前创建此 A 记录:

  • 请求 Let’s Encrypt 证书(Certbot 验证域所有权)
  • 配置 AWS Chime Voice Connector 终止(Chime 必须解析主机名)
  • pjsip.conf 中设置 external_signaling_address(必须与 DNS 名称匹配)

添加记录后,等待传播(几分钟到 48 小时)。使用以下方式验证:

dig sip.yourdomain.com
# or
nslookup sip.yourdomain.com

使用 Let’s Encrypt 的 TLS

  • Certbot 在 EC2 实例上运行,绑定到 80 端口。
  • 证书颁发给 sip.yourdomain.com
  • Asterisk 通过 Docker 卷挂载从 /etc/letsencrypt/live/... 读取证书。
  • 续订钩子在证书轮换时重新加载 Asterisk。
  • Chime 使用 Let’s Encrypt 的 CA 根证书进行验证——无需自签名证书、手动续订,也不会出现意外过期。

呼叫流程(当有人拨打您的号码时)

  1. 呼叫方 拨打您的 AWS Chime 电话号码。

  2. Chime 向您的 Asterisk 服务器发送 SIP INVITE(TLS:5061)。

  3. Asteriskextensions.conf 中匹配呼叫

    Answer()
    Stasis(voice-agent)
  4. ARI 通过 WebSocket 向 shim 服务器发送 StasisStart 事件。

  5. Shim 服务器 执行以下步骤:

    a. 打开到您的语音服务器的 WebSocket。
    b. 创建一个 ARI 混音桥。
    c. 将 PSTN 通道添加到桥中。
    d. 为 RTP 分配一个 UDP 端口(10000‑10299;每个实时通话使用独立端口)。
    e. 创建指向该端口的 ExternalMedia 通道。
    f. 将 ExternalMedia 通道添加到桥中。

  6. 音频流向:

    PSTN ↔ Bridge ↔ ExternalMedia ↔ Shim (RTP) ↔ Voice Server (WSS)
  7. 呼叫结束(呼叫方挂断 AI 通过 ARI 工具调用结束通话):

    • ARI 发送 ChannelHangupRequest / ChannelDestroyed
    • Shim 清理:关闭 WebSocket,删除桥,释放 RTP 端口。

配置文件

所有配置文件位于 deployment/asterisk-server/asterisk-config/ 目录下。Docker 容器会挂载此目录。

pjsip.conf – SIP 中继配置

这是最重要的文件。 它定义了指向 AWS Chime 的 SIP 中继,包括传输设置、TLS 证书、入站/出站端点,以及必须与您创建的 DNS 名称匹配的 external_signing_address

(仓库的其余部分包含其他配置文件、Docker Compose 文件和示例脚本。)

AWS Chime SDK + Asterisk Shim – 快速入门指南

以下是原始 markdown 的清理版。所有标题、代码块、表格和项目符号均已重新排版以提升可读性,同时保留了原始内容。


概览

文件用途
pjsip.confSIP 传输、TLS 设置以及 Chime Voice Connector 主机。
extensions.conf最小化拨号计划 – 将来电路由到 ARI Stasis 应用 (voice‑agent)。
ari.confAsterisk REST 接口 (ARI) 的凭证。
http.conf内置 HTTP 服务器用于 ARI(绑定到 localhost 以提升安全性)。
rtp.confUDP 端口范围,用于 RTP 媒体流(默认 10000‑10299)。
modules.conf仅加载所需模块:PJSIP、ARI 与 μ‑law 编码器。

注意

  • external_signaling_address 必须与您的 DNS 名称 以及 TLS 证书保持一致。
  • local_net 用于告诉 Asterisk 哪些是 “内部” 与 “外部”,以便进行 NAT 处理。
  • verify_server=no 因为 Chime 提供客户端证书。
  • cert/key 文件是 Asterisk 在 TLS 握手期间向 Chime 提供的证书。

前置条件

  • AWS 账户
  • EC2 实例(推荐 t3.medium 或更高,Amazon Linux 2023)
  • 弹性 IP(绑定到 EC2 虚拟机,Chime Voice Connector 需要静态 IP)
  • 域名,其 A 记录指向弹性 IP
  • DockerDocker Compose 已在实例上安装

配置 Chime Voice Connector

  1. 打开 AWS Chime SDK 控制台。
  2. 创建一个 Voice Connector(或编辑已有的)。
设置
Hostsip.yourdomain.com
Port5061
ProtocolTLS
  1. 记录 Voice Connector 主机名——稍后在 pjsip.conf 中需要使用它。

获取 TLS 证书(Let’s Encrypt)

# 安装 certbot
sudo yum install -y certbot

# 申请证书(需要开放 80 端口)
sudo certbot certonly --standalone \
  --preferred-challenges http \
  -d sip.yourdomain.com \
  --agree-tos -m your@email.com

# 启用自动续期
sudo systemctl enable --now certbot-renew.timer

# 创建一个续期钩子,在 Docker 中重新加载 Asterisk
sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-asterisk.sh > /dev/null

该仓库还提供了一个 Lambda 函数,可在 AWS 为服务 AMAZONEC2CHIME_VOICECONNECTOR 发布新 IP 范围时自动更新安全组。

部署 Asterisk 服务器(Docker)

# 切换到 Docker 部署目录
cd deployment/asterisk-server

# -------------------------------------------------
# 根据需要编辑配置文件:
#   - pjsip.conf : 域名、证书路径、voice‑connector 主机
#   - ari.conf   : 安全的 ARI 用户名/密码
#   - rtp.conf   : 如有需要,调整端口范围
# -------------------------------------------------

# 启动 Asterisk 容器
docker-compose up -d

# 查看日志
docker logs -f asterisk-server

# 打开交互式 Asterisk CLI
docker exec -it asterisk-server asterisk -rvvvvv

准备 Shim 服务器

创建 .env 文件

cat > .env <<'EOF'
ARI_BASE=http://127.0.0.1:8088/ari
ARI_USER=ariuser
ARI_PASS=your-secure-password-here
ARI_APP=voice-agent
EXTERNAL_MEDIA_HOST=127.0.0.1
ECS_MEDIA_WSS_URL=wss://your-voice-server.internal/voice/voice
RTP_PORT_START=10000
RTP_PORT_END=10299
EOF

构建并运行 Shim

# 构建 shim Docker 镜像
docker build -t asterisk-shim -f deployment/shim-server/Dockerfile .

# 运行 shim(使用 host 网络,以便绑定 RTP 端口)
docker run -d --env-file .env --network host --name asterisk-shim asterisk-shim

# 验证 shim 健康检查端点
curl http://localhost:8080/health

测试端到端流程

  1. 拨打您的 AWS Chime 电话号码(即分配给该 Voice Connector 的号码),
  2. 确认来电能够通过 SIP/TLS 进入 Asterisk,并在 ARI voice-agent 应用中被接收,
  3. 检查媒体是否通过 RTP 端口成功传输,
  4. 如有需要,使用 docker exec -it asterisk-server asterisk -rvvvvv 查看详细日志并进行故障排除。

Source:

e Voice Connector).
2. 监视日志:

# Asterisk logs (SIP/RTP activity)
docker logs -f asterisk-server

# Shim server logs (session lifecycle)
docker logs -f asterisk-shim

您应该会看到类似以下的条目:

INVITE received
CallSession created
ExternalMedia channel established

WebSocket API(Shim ↔ 语音服务器)

该 API 镜像 Twilio Media Streams —— 具有相同的事件结构和 μ‑law 音频格式。

音频格式

属性
编解码器μ‑law (PCMU)
采样率8000 Hz
帧大小160 bytes (20 ms)
编码Base64

事件负载

start(shim → 语音服务器)

{
  "event": "start",
  "start": {
    "streamSid": "unique-stream-id",
    "callSid": "asterisk-channel-id",
    "customParameters": {
      "source": "asterisk-shim",
      "format": "ulaw"
    }
  }
}

media(双向)

{
  "event": "media",
  "streamSid": "unique-stream-id",
  "media": {
    "payload": "base64-encoded-ulaw-audio",
    "timestamp": 1234
  }
}

clear(语音服务器 → shim)

{ "event": "clear" }

mark(双向)

{
  "event": "mark",
  "streamSid": "unique-stream-id",
  "mark": { "name": "responsePart" }
}

stop(任意方向)

{
  "event": "stop",
  "streamSid": "unique-stream-id"
}

示例实现

一个最小示例 voice_agent_server.py 已包含在仓库中。它演示了:

  • 处理上述 WebSocket 事件
  • 实时音频处理
Back to Blog

相关文章

阅读更多 »

Rapg:基于 TUI 的密钥管理器

我们都有这种经历。你加入一个新项目,首先听到的就是:“在 Slack 的置顶消息里查找 .env 文件”。或者你有多个 .env …

技术是赋能者,而非救世主

为什么思考的清晰度比你使用的工具更重要。Technology 常被视为一种魔法开关——只要打开,它就能让一切改善。新的 software,...

踏入 agentic coding

使用 Copilot Agent 的经验 我主要使用 GitHub Copilot 进行 inline edits 和 PR reviews,让我的大脑完成大部分思考。最近我决定 t...