@babblevoice/projectrtp

中文文档
3.7.0发布于 2026/08/12MITCommonJS近 30 天下载 1934

可缩放节点添加 RTP 服务器

中文使用文档
文档目录27 节
  1. 版本编号
  2. 释放过程
  3. 夹克
  4. 测试
  5. 构建测试图像
  6. 运行所有测试
  7. 运行特定测试
  8. 测试本地编辑
  9. 粗口测试
  10. 压力测试
  11. Mic 测试工具
  12. 示例脚本
  13. 运行
  14. 编解码器
  15. 使用量
  16. 环境变量
  17. 小示例
  18. 服务器示例
  19. 节点示例
  20. 状态
  21. 播放
  22. 记录
  23. 现场音频—— channel.createReadStream
  24. 现场音频—— channel.createWriteStream
  25. 用户
  26. 调音器生成
  27. 开始

项目RTP

另见: 米兰特·姆德 四、建 议

Build

一个 RTP 节点加通,提供处理 RTP 数据流并将其混合的功能. 所有表演任务都用Rust(一个sami-rs本土模块)执行,每个频道的tokio演员模型用于高通量.

ProjectRTP旨在被规模化为多个服务器,服务于其他信号服务器. RTP和信号传递应该分开,这种架构允许这样. 它得到一个代理服务器和客户端,以允许远程节点.

ProjectRTP旨在提供PBX所需的所有功能,包括通道连接,录音,回放和IVR所有必要功能.

特征

  • 2+ 通道混合
  • CODEC 转换编码( 介于以下所列支持的CODECs之间)
  • 电话录音(WAV)
    • 每个频道有多个录音机
    • 基于电源检测或命令的启动、暂停和结束
  • WAV 使用音汤描述播放从部件播放复杂句子的回放
  • 混合 play+record( playrecord) 用于0- gap 即时录入可选驳入
    • 列车模式:能量 interrupt (粗放的入门切入快取),或 concurrent (在提示持续播放时记录答案) stopplay() 一旦答案可以接受,即以内容为主,而不是以能量为主)
  • DTMF(RFC 2833) - 发送、接收和接通
  • DTLS SRTP (WebRTC) (中文(简体) )
  • 高度可缩放 - 服务器/节点解决方案以缩放媒体节点

版本编号

我们使用语义版本编号.

由于版本号为 MAJOR.MINOR.PATCH, 递增:

  1. 当您进行不兼容的 API 修改时, maJOR 版本,
  2. MINOR 版本,当您以反向兼容的方式添加功能时,以及
  3. PATCH 版本,当您向后兼容错误修正时 .

作为这项工作的一部分,我们维护jsdoc文件,以记录我们与这一方案的公共界面.

释放过程

在 GitHub 动作上管理发布. 它建立和测试任何版本.

夹克

Amd64 和 arm64 的公用嵌入器图像可用 多克枢纽。 。 。 .

测试

测试需要本地模块( libilbc + libspandsp), 因此最简单的路径是 Docker test 阶段。 当地, npm run build 生产 build/Release/projectrtp.node 从一箱起 npm test 用它来管理套房 libilbc 财务报告和审定财务报表 libspandsp 在主机上.

构建测试图像

docker build --target test -t projectrtp-test .

运行所有测试

docker run --rm projectrtp-test

图像的默认命令运行 mocha test/interface/*.js test/unit/*.js --exit。 。 。 .

注:套间用途 test/interface/*.js 财务报告和审定财务报表 test/unit/*.js -没有 test/**/*.js - 因为.. test/basictests.js 财务报告和审定财务报表 test/codectests.js 是独立的脚本(不是摩卡测试),如果被摩卡加载,会被悬挂.

运行特定测试

大部分测试文件都依赖于服务器测试( I)projectrtpserver.js)首先启用本地模块,因此在运行单个文件时包含:

docker run --rm projectrtp-test \
  ./node_modules/mocha/bin/_mocha \
  test/interface/projectrtpserver.js test/interface/projectrtpplayrecord.js \
  --exit --timeout 20000

测试本地编辑

挂载 test/ 财务报告和审定财务报表 lib/ 这样您就可以编辑和重运行而不重建本地模块 :

docker run --rm \
  -v ./test:/usr/src/projectrtp/test \
  -v ./lib:/usr/src/projectrtp/lib \
  projectrtp-test \
  ./node_modules/mocha/bin/_mocha \
  test/interface/projectrtpserver.js test/interface/projectrtpplayrecord.js \
  --exit --timeout 20000

如果您更改了 Rust 源文件, 请重建图像 .

粗口测试

箱子本身的单元测试是通过货物进行的:

npm run rust:test          # or: cd rust && cargo test --lib

压力测试

在荷载下的随机时间打开并关闭频道. 设计用于货币和赛跑条件测试——没有通关/故障,但会在不良行为上崩溃或产生出意料的输出.

docker run --rm projectrtp-test sh -c \
  "cd /usr/src/projectrtp && npm run stress"

Mic 测试工具

test/tools/mictest.js 是一个CLI工具,用于手动测试真麦克风和扬声器的播放记录。 它需要当地土建(npm run build和 sox 在主机上 – 完整的构建依赖列表请参见 Dockerfile.

node test/tools/mictest.js [--prompt <file>] [--output <file>] [--duration <ms>] [--interrupt] [--bargeinpower <n>]

播放一个提示(或生成的测试语气),记录您的声音,实时打印事件,并保存到 /tmp/mictest_recording.wav。 。 。 .

示例脚本

示例文件夹包含使用此库的两个脚本 。 简单的节点可以在不需要特殊功能的地方被使用. 标准 projectp docker 图像指向此 - 因此启动一个 docker 实例会作为节点运行并尝试连接到一个中央控制服务器.

高级节点.js是一个包含前处理和后处理的范例——可以使用,例如下载录音,执行文字到演讲或完成后上传录音.

运行

环境变量HOST应包含控制服务器的主机地址-其默认为127.0.0.1. 可变 PORT 有端口号码可以通信 - 默认为9002.

环境变量 PA 采取以下形式——地址被传递回控制服务器,从而可以在SDP中发布——即允许在不同IP地址上设置多个RTP节点. 如果您不通过, 默认脚本将会通过调用来确定您的公共 IP 地址 https://checkip.amazonaws.com (中文(简体) ) 获取你的IP.

docker run -- restart= always --net=host -d -it --env PA=127.0.0.1 --name=prtp projectrtp:latest 互联网档案馆的存檔,存档日期2013-12-02

编解码器

ProjectRTP支持以下 CODEC 的转码

  • 澳门
  • PCMU 电脑
  • G722集团
  • iLBC (英语)

使用量

2个示例脚本可见于示例文件夹. simplenode.js作为主条目被多克容器使用.

环境变量

这些在简单节点.js的例子中解析.

  • PORT - 要连接的端口
  • HOST - 要连接到的主机
  • PA - 这个主机的公开地址 - 它被传回到控制套接字上, 以便发布给客户端 - 如果没有脚本民调 https://checkip.amazonaws.com (中文(简体) ) 我们的公开演讲.

小示例

这个工程包含所有功能,可以用作本地资源或多个节点处理媒体. 在两个模式之间切换是无缝的. 在节点模式下使用时,它有一个简单的协议,可以在服务器和节点之间进行通信.

每个活动节点连接到主服务器,向主服务器提供服务. 然后主服务器打开任何可用的 RTP 频道(WebRTC 或普通 RTP). 所使用的协议可以在 /lib 文件中查看.

服务器示例

服务器通常在打开媒体端口之前运行应用程序逻辑.


const prtp = require( "@babblevoice/projectrtp" ).projectrtp

/* This switches this server to a central
server and requires nodes to connect to us
to provide worker nodes */
await prtp.server.listen()

const codec = 9 /* G722 */

/*
When you have a node configured and connected to this server...

Now open our channel:
The remote comes from your client (web browser?)
including options for WebRTC if it is a web browser.
*/
let channela = await prtp.openchannel( {
      "remote": { address, port, codec }
      } )


let channelb = await prtp.openchannel( {
      "remote": { "address": otheraddress, "port": otherport, codec }
      } )

/*
Offer both of these channels to the remote clients (convert to SDP?)
*/

/* Ask projectrtp to mix the audio of both channels */
channela.mix( channelb )

/* Keep calling the mix function with other channels to create a conference */

我们可以换个方式 即指示控制服务器连接节点 我们可以增加多个节点,也可以使用一个道克星团作为一个发布多个节点.


const prtp = require( "@babblevoice/projectrtp" ).projectrtp

const port = 9002
const host = "192.168.0.100"

prtp.server.addnode( { host, port } )

/*
When we need a channel now, the library will
make that request to one of our nodes.
*/
let channela = await prtp.openchannel( {
      "remote": { address, port, codec }
      } )

节点示例

节点是网络的工作马. 连接到服务器并等待指令 。 更多实例请参见示例文件夹,例如如何连接到事件中来履行诸如从存储中下载这样的S3等功能.


const prtp = require( "@babblevoice/projectrtp" ).projectrtp

async function go() {

  prtp.run()

  prtp.node.listen( "0.0.0.0", 9002 )
  const pa = await wgets( "https://checkip.amazonaws.com" )

  prtp.setaddress( pa )

}

listen()

状态

当我们从我们的节点(或者如果站立龙只是我们自己)接收到一个对象时,该对象包含关于: s州. 它包括开放频道数量,MOS质量分数等项目.

MOS只包括我们收到的测量数据.

滴答时间是处理RTP数据在发送前需要的时间. 我们做一些工作 当我们发送和接收 RTP数据,但大多数工作都是用打勾完成的,这表明了负荷和能力.

测量是在US(即我们在命名公约中)。 在以上的例子中,平均时间为84uS。 在溪流的ptime(我们只用20mS的ptime)上发生一滴.

如果有多个频道被混合,它们都会得到与同一频道被混合的一样的打勾时间,这代表了所有频道的总时间.

播放

播放一个声音"soup"。 这是一个要播放的文件列表,可能包括要播放的文件的起止时间. 项目RTP 支持 wav 文件 。 一个例子汤:


channel.play( {
  "soup": {
    "loop": true,
    "files": [
      { "wav": "filename.wav", "start": 1000, "stop": 4000 }
    ]
  }
} )

  • 循环可以是真实的(连续的- well- INT MAX) 也可以是数字
  • 循环可以是主汤(即通过所有文件循环),也可以是在一个文件(即通过这个文件循环了3次)上
  • 文件名可以在param"wav","pcma","pcmu","l168k","l1616k","ilbc","g722"中出现. 见下文关于它们用途的说明,所有文件都载有该格式的文件名称(尚未完成).
  • 启动 - 从( 可选的) 开始播放的 mS 数量
  • stop - 停止播放的 mS 数( 可选)

起止参数允许很多片段包含在同一文件中.

文件名可以包含在不同的参数中 。 如果您提供了参数, RTP 服务器将假设文件存在, 并且格式正确 。 使用此参数的目标是在播放一个文件时减少任何转码的CPU间接费用.

其他汤的例子

队列通知

channel.play( {
  "loop": true,
  "files": [
    { "wav": "ringing.wav", "loop": 6 },
    { "wav": "youare.wav" },
    { "wav": "first.wav" },
    { "wav": "inline.wav" }
  ]
} )

我们也许还合并了一些疯狂的文件:

channel.play( {
  "loop": true,
  "files": [
    { "wav": "ringing.wav", "loop": 6 },
    { "wav": "youare.wav" },
    { "wav": "ordinals.wav", "start": 3000, "stop": 4500 },
    { "wav": "inline.wav" }
  ]
} )

记录

记录到一个文件。 ProjectRTP目前支持2个频道的PCM.

可以根据 projectrtp 选择: 文件 = - 要保存为 uuid= - 频道 uuuid 要记录

以秒为多至 MA 最大尺寸( 5 秒? ) , 默认为 1 秒 RMS 功率由每包计算,然后使用移动平均滤波器进行平均计算. 平均电源包=将平均电源窗口移到平均电源上

一定已经开始了 如果被忽略了 才刚刚开始 启动功率 =

何时完成 - 如果低于这一数额 完成以下功率 =

与完后功率结合使用 - 用于有功率阈值的相接 即在这个 mS 数通过之前, 功率低于完成的下限 心电图 = < int > mseconds

一定超乎意料 并设定了录音的尺寸限制 最大长度 = < int > mseconds

必须是一或二 数字通道 = < int > 计数

channel.record( {
  "file": "/voicemail/greeting_1.wav",
  "maxduration": 10 * 1000 /* mS */,
  "numchannels": 1
} )

现场音频—— channel.createReadStream

返回标准节点 Readable 当频道接收/发送时, 发送解码音频缓冲器 。 使用此功能将音频输入到任何需要流出的东西中——STT,翻译,标题,WebSocket转发器,上磁盘抓取——而无需通过记录器/文件路径.

每个被排放块的字节形状固定在阅读器的寿命,并作为可读器的属性被曝光:

const reader = channel.createReadStream( {
  direction:   "in",    // "in" (default) | "out" | "both"
  format:      "l16",   // "l16" (default) | "pcma" | "pcmu" | "g722" | "ilbc"
  samplerate:  8000,    // 8000 (default) | 16000  — only meaningful for l16
  numchannels: 1        // 1 (default) | 2  — stereo interleaves L=in R=out
} )

reader.format        // "l16"
reader.samplerate    // 8000
reader.numchannels   // 1
reader.direction     // "in"
reader.readerId      // monotonic id (handy for log correlation)

reader.on( "data", ( buf ) => { /* Buffer, one 20 ms frame */ } )
reader.on( "end",  () => {} )   // fires on channel close or reader.destroy()

reader.pipe( wsStream )         // standard Node stream composition
reader.destroy()                // explicit teardown

方向

  • "in" - 音频对等端发送我们,后解码。 你想要的STT/语音生物鉴别仪 在呼叫器.
  • "out" ——音频此频道正在发送(玩家,回声,或混合输出). 同行所闻.
  • "both" ——立体声,相接出,L=入,R=出. 要求数 numchannels: 2。 。 。 .

用于独立的每语源流( 如 diarized 标题), 创建 两个读取器在同一频道 一个 direction: "in",一个 direction: "out"两道单流,两道独立的STT管线,它们之间没有接合.

格式

"l16" 按所选样本率给出线性PCM-16 LE——每个STT/翻译API接受的通用格式. 线条格式( E)"pcma", (中文(简体) ), "pcmu", (中文(简体) ), "g722", (中文(简体) ), "ilbc")提供原始的编码字节,当你想在别处转发一条腿而不重新编码时有用.

后压和后降

输油管从不会堵住20毫秒的RTP滴答. 如果消费者(或节点事件回路,或凝胶队列)落后,框被丢弃. 在实践中,只有在消费者真的被卡住的情况下才咬伤——典型的STT/WebSocket消费者舒适地跟上了20ms帧.

示例——将一条腿插入WebSocket STT服务:

const reader = channel.createReadStream( { direction: "in", format: "l16", samplerate: 16000 } )
const ws = new WebSocket( "wss://stt.example.com/stream?rate=16000&format=l16" )

reader.on( "data", ( buf ) => ws.send( buf ) )
ws.on( "message", ( transcript ) => console.log( "→", transcript.toString() ) )

/* When the call ends the reader emits `end` and the ws.send loop quiets. */
reader.on( "end", () => ws.close() )

示例 – 双声道呼叫录制分出每个频道 :

const caller = channel.createReadStream( { direction: "in" } )
const agent  = channel.createReadStream( { direction: "out" } )

caller.pipe( fs.createWriteStream( "/tmp/caller.pcm" ) )
agent.pipe(  fs.createWriteStream( "/tmp/agent.pcm" ) )

现场音频—— channel.createWriteStream

对称对应到 createReadStream返回节点 Writable 将活音注入频道外出行走的腿上——管子放入TTS流中,活口翻译入音,一瓶子合成出的声音,任何产生PCM的东西.

const writer = channel.createWriteStream( {
  format:      "l16",   // v1: l16 only
  samplerate:  8000,    // v1: 8000 only
  numchannels: 1        // v1: mono only
} )

writer.format        // "l16"
writer.samplerate    // 8000
writer.numchannels   // 1
writer.writerId      // monotonic id

writer.write( pcmBuffer )   // accepts any chunk size — framed internally
writer.end()                 // flushes the tail then reverts to silence
writer.destroy()             // immediate teardown, discards buffered samples

ttsStream.pipe( writer )     // standard Node stream composition

与 play

一个作家和 channel.play 共享频道上的单个出源槽。 开始一个写入器取代任何活动播放器( emits) play/end reason=new页:1 channel.play 取代活动作者。 最多有一个外出源;当两者都不在场时,信道发出沉默.

结构

你们可以 .write 任意块大小 —— Rust 侧缓冲器会用字节来框起它们,为勾选设置了20ms的插槽. 8 kHz L16 流需要 ~ 16,000 字节/秒( 每 20 ms 帧 有 320 字节) , 因此是典型的 .pipe 从一个WebSocket或HTTP身体保持舒适.

后压

Rust-side缓冲器以1s(50×20ms)为界. 如果消费者写得比滴水管快, _write 将其召回延后到空格空出——标准节点 Writable 后压踢入并 .write 返回时 false。在下行中(没有写入一个勾选符),该频道为该槽发出静音.

期末

writer.end() 放下JS的发件人 鲁斯特 AudioWriter 看到关闭,冲出任何部分框架 加上沉默,然后从频道退休。 A 类 play/end reason=completed 事件起火——与基于文件的对称 play 自然结束.

例子——将 TTS 响应输入活调:

const writer = channel.createWriteStream()

const res = await fetch( "https://tts.example.com/say", {
  method: "POST",
  body:   JSON.stringify( { text: "Your call is important to us." } ),
} )
// res.body is a Readable web stream of raw PCM-16 @ 8 kHz mono.
// Pipe straight through; backpressure is preserved end-to-end.
res.body.pipe( writer )

用户

调音器生成

为了让RTP尽可能可扩展,我们不会支持飞音生成. 目前磁盘空间比CPU资源便宜得多. 在8K取样的1S瓦夫数据为16K. 使用声音,我们可以为每个支持的可解码器提供wav文件,并方便地循环,这需要极少的CPU上浮.

我们需要提供的是生成 wav 文件的有用工具, 生成用于电信(即振铃、 DTMF等) 的音量.


const prtp = require( "@babblevoice/projectrtp" ).projectrtp

let filename = "/some/file.wav"
prtp.tone.generate( "350+440*0.5:1000", filename )

格式试图密切遵循格式 https://www.itu.int/dms pub/itu-t/opb/sp/T-SP-E.180-2010-PDF-E.pdf 互联网档案馆的存檔,存档日期2014-12-21 - 虽然该标准在某些方面不是最明确的.

我们目前支持

  • + 共增加1个或更多频率
  • '*' 减小振幅( 即能力乘以此值, 0. 75 是 0. 75 x 最大值)
  • ':"结肠后值为cadences,即调子移动到下个调子前的持续时间
  • '~' start~ end, 例如, 这可以用于频率或振幅, 350+440* 0.5~0: 100 将产生从 anp 0. 5 到 0 到 1000 mS 期间的音调

实例

英国拨号:

prtp.tone.generate( "350+440*0.5:1000" "dialtone.wav" )

联合王国

prtp.tone.generate( "400+450*0.5/0/400+450*0.5/0:400/200/400/2000" "ringing.wav" )

自动取款机

1209赫兹1336赫兹1477赫兹1633Hz (英语)
697赫兹1个2个3个A 类
770赫兹页:1页:16个页:1
852赫兹第7条第8条第9条C级
941赫兹* 联合国0 个{\fn方正粗倩简体\fs12\an8\1cHFFFF00\b0}D级

例1将混合1209Hz和697Hz

prtp.tone.generate( "697+1209*0.5:400" "dtmf1.wav" )
prtp.tone.generate( "697+1209*0.5/0/697+1336*0.5/0/697+1477*0.5/0:400/100", "dtmf1-3.wav" )

开始

  • Wav文件类型(l16, rate pcmu, pcma等)之间的格式转换.
  • 犯罪时保持平整(在CI中强制):
cd rust && cargo clippy --all-targets -- -D warnings

参考文献

版本与发布记录

@babblevoice/projectrtp 共发布 88 个版本,首次发布于 2022/06/17;过去 12 个月发布了 35 个版本。

版本发布日期距今
3.7.0latest2026/08/122 个月前
3.6.02026/08/122 个月前
3.5.02026/08/122 个月前
3.4.02026/07/233 个月前
3.3.02026/07/233 个月前
3.2.62026/07/183 个月前

关于 @babblevoice/projectrtp 的常见问题

@babblevoice/projectrtp 是什么?

可缩放节点添加 RTP 服务器

如何安装 @babblevoice/projectrtp?

在项目目录运行 npm install @babblevoice/projectrtp,或使用 pnpm add / yarn add / bun add。

@babblevoice/projectrtp 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/babblevoice__projectrtp 是否存在。

@babblevoice/projectrtp 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@babblevoice/projectrtp 可以商用吗?

@babblevoice/projectrtp 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@babblevoice/projectrtp 的下载量有多少?

根据 npm 官方下载统计,@babblevoice/projectrtp 在 2026/10/08 – 2026/11/06 这 30 天内被下载 1,934 次,日均约 64 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@babblevoice/projectrtp 还在维护吗?

最新版本 3.7.0 发布于 2026/08/12(2 个月前);至今共发布 88 个版本,其中过去 12 个月发布了 35 个;首次发布于 2022/06/17。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。