Fonoster Streams 实战指南:用 @fonoster/streams 在 Node.js 中接入 Asterisk 双向音频流(AudioSocket 协议实现) 后端音视频【免费下载链接】fonoster The open-source alternative to Twilio.项目地址https://gitcode.com/gh_mirrors/fo/fonoster点击查看免费下载本文围绕 Fonoster 仓库中的fonoster/streams模块mods/streams完整讲解 AudioSocket 协议的 Node.js 实现如何安装、如何在 Asterisk 拨号计划与 ARI 中接入 AudioSocket、如何用AudioSocket与AudioStream类建立双向音频流服务器并深入消息帧格式与底层源码帮助你掌握一套可复用的 Asterisk 音频流对接方案。AudioSocket 协议是什么AudioSocket 是一个简单的、用于从 Asterisk 访问双向音频流的协议。Fonoster 的fonoster/streams包npm 包名fonoster/streams当前仓库版本 0.26.0见 mods/streams/package.json给出了该协议的 Node.js 实现它基于 TCP 连接和 I/O 复用模型让 Asterisk 与 Node.js 应用之间可以直接交换音频数据与控制消息。在 Fonoster 项目内部该模块正是 Streams流媒体能力的基础工具。虽然它常被用作 Fonoster Streams 的工具模块但也可以独立使用——只要 Asterisk 拨号计划中配置了AudioSocket应用任何 Node.js 应用都能借助它接收/发送通话音频。从源码结构看模块由四个核心文件组成mods/streams/srcAudioSocket.ts协议服务器负责 TCP 监听、连接处理与消息分发AudioStream.ts单条双向音频流对象封装数据写入、挂断、播放等操作AudioPlayer.ts音频播放器支持播放文件与流式输入Message.tsAudioSocket 消息帧的编解码。安装在任意 Node.js 项目中安装$ npm install --save fonoster/streams包名即fonoster/streams参见 mods/streams/package.json 中的name字段依赖仅有fonoster/logger与uuid非常轻量。TypeScript 类型声明随包一并发布types: dist/index可直接获得类型提示。AudioSocket 消息帧格式先看懂协议再写代码在动手写服务端之前建议先理解协议的消息帧结构。从 Message.ts 与 types.ts 可以看到每条消息由一个3 字节头部 可选 payload组成MINIMUM_MESSAGE_LENGTH 3头部第 1 字节是消息类型MessageType第 23 字节是 payload 长度大端序readUInt16BE因此单条消息 payload 上限为 65535 字节MAXIMUM_MESSAGE_LENGTH。消息类型定义如下值MessageType含义0x00HANGUP挂断消息无 payload帧为[0x00, 0x00, 0x00]0x01ID会话标识payload 为 16 字节 UUID 二进制uuid.parse结果0x02SILENCE静音数据帧payload 为静音 PCM0x10SLIN音频数据帧signed linear PCMpayload 为原始音频字节0xffERROR错误消息payload 首字节为错误码错误码ErrorCode由 AudioSocketError.ts 映射为可读信息值错误码含义0x00NONE无错误0x01AST_HANGUPAsterisk 挂断0x02AST_FRAME_FORWARDINGAsterisk 帧转发错误0x04AST_MEMORYAsterisk 内存错误0xffUNKNOWN未知错误Message类同时提供了创建帧的静态方法Message.tscreateHangupMessage()、createIDMessage(id)、createSlinMessage(data)服务端发送音频、挂断正是通过这些工厂方法完成的。关于负载格式的限制当前向 Asterisk 发送的 payload 限定为有符号线性 16 位、8kHz、单声道 PCM小端序即 SLIN 格式而从 Asterisk 接收的 payload 格式可以通过 ARI 的format参数调整。发送端每次写入的音频块固定为 320 字节MAX_CHUNK_SIZE见 AudioPlayer.ts恰好是 8kHz 单声道 16 位 PCM 的 20ms 帧长。第一步在 Asterisk 侧接入 AudioSocketAudioSocket 是 Asterisk 的一个拨号计划应用Dialplan Application。要让 Asterisk 把通话音频推到 Node.js 服务端需先在 dialplan 中启用它。方式一AudioSocket 应用直接拨入exten 100,1,Verbose(Call to AudioSocket via Dialplan Application) same n,Answer() same n,AudioSocket(40325ec2-5efd-4bd3-805f-53576e581d13,server.example.com:9092) same n,Hangup()AudioSocket应用接收两个参数会话 UUID即上面消息帧格式中的 ID与目标服务器地址主机:端口。方式二通过 Dial 应用嵌套exten 100,1,Verbose(Call to AudioSocket via Dialplan Application) same n,Answer() same n,Dial(SIP/100,30,A(AudioSocket(40325ec2-5efd-4bd3-805f-53576e581d13,server.example.com:9092))) same n,Hangup()这里把AudioSocket(...)作为Dial的 A 选项answer 后执行的 app适合先拨通对端、再把媒体桥接给 AudioSocket 的场景。方式三通过 ARI 的 externalMedia 端点连接除拨号计划外还可以通过 ARI 的externalMedia端点连到 AudioSocket 服务器。文档明确要求transport 必须设为 TCP并把会话 UUID 填入 data 字段。在 Fonoster 的 API 服务中正是通过createExternalMediaConfig生成这类配置见 mods/apiserver/src/voice/utils/createExternalMediaConfig.ts。第二步在 Node.js 中创建 AudioSocket 服务器Asterisk 配置完成后用AudioSocket类创建监听服务器。核心 API 有三个API说明new AudioSocket()创建服务器实例底层为net.createServer见 AudioSocket.tsonConnection(handler)注册连接回调回调签名(req, stream)req.ref为会话 UUID 字符串listen(port, [bind], [callback])开始监听默认绑定0.0.0.0也支持指定绑定地址close()关闭服务器并停止监听基础示例与文档示例一致并补齐细节const { AudioSocket } require(fonoster/streams); const audioSocket new AudioSocket(); audioSocket.onConnection(async (req, res) { console.log(new connection from:, req.ref); res.on(data, (data) { // 处理收到的音频数据保存、转写、分析等 }); res.on(end, () { // 流结束时的处理 }); res.on(error, (err) { // 出错时的处理 }); // 播放音频文件的工具方法 await res.play(/path/to/audio/file); }); audioSocket.listen(9092, () { console.log(server listening on port 9092); });文档中的示例使用res.on(data/end/error)从源码看AudioStream还提供了更语义化的别名方法onData、onClose、onError见 AudioStream.ts两者指向同一批事件EventType.DATA/END/ERROR。仓库中还提供了一个可直接运行的类型脚本示例 mods/streams/src/example.ts其中展示了完整流程注册连接处理器 → 播放etc/sounds/test.sln音频 → 10 秒后调用stream.hangup()挂断。开发时可通过npm run start:devnodemon tsx启动该示例。AudioStream双向音频流与播放控制AudioStream表示一条双向的音频数据与控制消息流。其完整 API 如下方法签名说明write(data)(data: Buffer) void向 Asterisk 写媒体数据内部封装为 SLIN 消息帧后写入 socketAudioStream.tshangup()() void发送 HANGUP 消息并关闭连接同时向本端发出end事件play(filePath)async (filePath: string) Promisevoid播放音频文件playStream(inputStream)async (inputStream: Readable) Promisevoid从任意可读流播放音频stop()() void停止当前播放onData(cb)/onClose(cb)/onError(cb)(cb) AudioStream注册数据 / 结束 / 错误监听返回自身支持链式调用hangup()的实现细节值得注意它会先写入Message.createHangupMessage()再调用socket.end()最后向本端Readable发出end事件——也就是说onClose回调既会在对端主动挂断时触发也会在本端调用hangup()时触发AudioStream.ts。播放能力由内部的AudioPlayer提供AudioPlayer.tsplay(filePath)同步读取文件后交给playStreamplayStream会把输入流切成 320 字节的块逐块封装为 SLIN 帧写入 socket块间有 20ms 节流await setTimeout(20)保持 8kHz/16bit/mono 的实时节奏返回的 Promise 会在播放真正结束输入流读完且所有缓冲块写完、或被stop()/新的播放打断、或输入流出错时 settle这让调用方如 Fonoster 的 say 动词能精确感知“用户听完了音频”的时机播放具备会话机制新播放会先stop()旧播放并通过自增的currentSessionId使旧流残留的数据失效避免混音。消息解析与粘包处理底层源码如何工作AudioSocket 服务端基于裸 TCPTCP 提供的是字节流而非消息帧——一次data事件可能包含多条 AudioSocket 消息也可能只有半条。AudioSocket通过drainMessages机制解决粘包/拆包问题AudioSocket.ts每收到一块数据就追加到recvBuffer缓冲只要缓冲区剩余长度不小于 3 字节就尝试读取头部第 23 字节的 payload 长度从而算出完整消息长度若完整帧可取出则构造Message并dispatchMessage分发然后偏移指针继续循环余下不足一帧的字节保留在缓冲区等下一个data事件到来时再拼接解析。分发逻辑dispatchMessage见 AudioSocket.ts按消息类型处理ID调用连接处理器传入{ ref: uuid }SLIN/SILENCE向本端 Readable 发出data事件即触发res.on(data)HANGUP发出end事件ERROR构造AudioSocketError并发出error事件。单条消息的解析与分发被包在try/catch中确保畸形帧错误 ID、过早触发的事件、消费者回调抛错不会导致整个进程崩溃。对应地测试用例 AudioSocket.test.ts 专门验证了两类场景should survive malformed frames and keep serving other connections先发送一个错误帧再发送合法会话验证畸形帧不影响后续连接should parse messages that are coalesced or split across TCP reads把一串消息ID 5 帧音频 HANGUP从中间拆成两次 TCP 写入验证粘包与半包均能正确还原为 5 帧数据。而 Message.test.ts 则逐项验证了消息解析长度、类型、payload、UUID 提取与帧构造hangup/id/slin的正确性。生产实践Fonoster 内部如何消费 AudioSocket作为 Streams 的基础工具AudioSocket 在 Fonoster 的 API 服务mods/apiserver中被真实使用。参考 AudioSocketHandler.ts其典型用法是在指定端口创建AudioSocket实例onConnection回调里把收到的(req, res)直接交给transcribeOnConnection(...)做实时转写并注册onClose/onError监听将resAudioStream保存为实例字段供后续写入音频通过listen(port, callback)启动等待 Asterisk 通过externalMedia或拨号计划连入。这意味着把 Asterisk 通话 → AudioSocket → 实时转写/音频处理 串成一条流水线是该项目中验证过的成熟路径——你可以参照同样的模式在自己的应用里接入 STT语音转写、录音、情绪分析等能力。常见问题与注意事项端口与地址默认监听端口约定为 9092示例代码与拨号计划保持一致listen支持绑定特定网卡地址测试中常用127.0.0.1。音频格式向 Asterisk 发送的音频必须是 signed linear、16-bit、8kHz、mono PCM小端序从 Asterisk 接收的格式可用 ARIformat参数调整实际项目中音频过滤链filters通常在上游完成格式转换。挂断语义本端hangup()与对端 HANGUP 帧都会触发end事件不要在onClose中重复调用hangup()造成二次关闭。播放的阻塞性await res.play(...)会持续到播放结束或被新播放/stop 打断设计交互流程时需留意这一点。错误处理务必注册onError或res.on(error)监听避免error事件无人消费导致进程异常退出。小结本文以 mods/streams/.intro.md 为骨架完整覆盖了 AudioSocket 协议的 Node.js 实现要点消息帧格式与类型、Asterisk 三种接入方式拨号计划应用、Dial 嵌套、ARI externalMedia、AudioSocket/AudioStream/AudioPlayer的 API 与源码原理、粘包解析机制及生产级消费范式。核心代码位于 mods/streams/src测试用例位于 mods/streams/test结合这些文件即可在自有项目中复刻 Fonoster 的音频流接入方案。赞分享后端音视频【免费下载链接】fonoster The open-source alternative to Twilio.项目地址https://gitcode.com/gh_mirrors/fo/fonoster点击查看免费下载相关推荐Electric Streams 协议完全指南基于 Durable Streams 的持久化实时数据流实战Electric Streams 协议完全指南基于 Durable Streams 的持久化实时数据流实战 Electric Streams 是开源 Dura后端数据同步数据库人工智能AI AgentMCP 服务Home Assistant 配置零门槛上手第一份 YAML 和第一条自动化一次搞定Home Assistant 配置零门槛上手第一份 YAML 和第一条自动化一次搞定 Home Assistant 配置绕不开两个地方终端里的 config后端音视频Fonoster实时音频流处理5个高级应用场景的终极实践指南Fonoster实时音频流处理5个高级应用场景的终极实践指南 Fonoster作为Twilio的开源替代方案提供了强大的实时音频流处理能力让开发者能够构建后端音视频上一篇agno 工作流运行控制深度指南cancel、deep_copy、事件流与序列化run_control 实例全解析下一篇Data Science for Beginners 教学指南GitHub Classroom、Docsify 与课堂资源全解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考