CTICloud JS SDK 实时语音流开发指南
JS SDK 实时语音流开发指南
本文说明座席浏览器里的 JS SDK 如何把实时音频交给企业自建 Processor。页名见文首标题。能力位置见 BYO-VP。
初始化方法是 CTILink.setup()。事件注册是 CTILink.event()。
快速阅读
适用环境:WebRTC 座席,Chrome、Edge、Electron 等支持 WebRTC 的浏览器。页面使用 HTTPS,并已获得麦克风权限。Processor 是企业自己的 WebSocket 服务,地址只用 wss://,并能处理二进制消息。
local 是当前座席麦克风。remote 是 WebRTC 远端轨道,通常是客户。Uplink 是 SDK 发给 Processor 的 PCM。Downlink 只存在于 source: "local" 且 mode: "sendrecv",用于替换送给通话对端的座席声音。
这条链路不是呼入 / 外呼机器人的 RASR,也不是挂机后的 ASR。情绪和关键词是 Processor 的辅助结果。座席工作台实时转写事件名是 transcript,不要改叫 RASR。
未配置 media.audio.processors 时,SDK 不建立语音流连接。
阅读地图
| 要做的事 | 章节 |
|---|---|
| 填写 Processor | 配置项 |
| 通话建立后何时开始推流 | 生效 |
| WebSocket 帧、采样率、错误码 | 协议与数据模型 |
| 联调顺序和失败现象 | 验收 |
配置项
CTILink.setup({
webrtc: true,
media: {
audio: {
processors: [
{
id: "voice-service",
source: "local",
mode: "sendrecv",
transport: {
protocol: "websocket",
url: "wss://voice.example.com/ws"
}
}
]
}
}
});| 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| id | String | 是 | — | 同一 SDK 实例内唯一 |
| source | String | 否 | local | local 或 remote |
| mode | String | 否 | sendonly | 工作模式 |
| transport | Object | 是 | — | 连接配置 |
| transport.protocol | String | 否 | websocket | 目前仅 websocket |
| transport.url | String | 是 | — | 仅 wss:// |
| source | 含义 |
|---|---|
local | 座席浏览器麦克风 |
remote | 远端媒体轨道,通常为客户 |
| mode | 行为 |
|---|---|
sendonly | 只把 source 指定的音频送给 Processor |
sendrecv | 发送本地音频,并接收处理后的 PCM,替换座席麦克风输入。仅 source: "local" |
规则:
- 可以配置多个 Processor,每个 Processor 一条 WebSocket、一个 Session。
- 可以配置多个
sendonly。同一来源的多个sendonly收到同一份音频。 - 最多一个
sendrecv,且source必须为local。它返回的音频是送给通话对端的本地音频。 remote加sendrecv无效,触发processorError。- 没有
mixed。两侧都要时,配两个sendonly,用同一个uniqueId在服务端关联。 - 建议单座席 Processor 不超过 5 个。多个业务消费同一路音频时,优先在服务端分发。
两侧同时分析的示例:
processors: [
{
id: "asr",
source: "remote",
mode: "sendonly",
transport: {
protocol: "websocket",
url: "wss://asr.example.com/ws"
}
},
{
id: "voice",
source: "local",
mode: "sendrecv",
transport: {
protocol: "websocket",
url: "wss://voice.example.com/ws"
}
}
]示例里的 id: "asr" 只是 Processor 标识,表示把音频交给自有服务。它不是 RASR,也不是离线 ASR。
鉴权由 Processor 服务实现。SDK 不限定算法。可用 URL 短期 Token、带时间戳和随机数的 URL 签名、API 网关或 IP 白名单,并在 SessionStart 中再核对会话信息。
回调
CTILink.event("processorReady", handleProcessorReady);
CTILink.event("processorError", handleProcessorError);
CTILink.event("processorClosed", handleProcessorClosed);| 事件 | 时机 | 字段 |
|---|---|---|
| processorReady | Session 创建成功,可以处理音频 | processorId、sessionId、uniqueId |
| processorError | 运行异常,含无效的 remote + sendrecv | 上列三个字段,加 code、message |
| processorClosed | 通话结束、SDK 关闭或 Processor 结束 Session | processorId、sessionId、uniqueId |
SDK 在通话建立时创建 Processor,通话结束后关闭。开发者不另行调用创建或关闭。
带宽
默认格式:Sample Rate 16000 Hz,Channels 1,Sample Bits 16 bit,Encoding pcm_s16le。
单路原始 PCM 约 256 kbps(约 32 KB/s)。加上 WebSocket 和 TLS 后,单路约 280~320 kbps(35~40 KB/s)。
| 配置 | 浏览器上行 | 浏览器下行 | 合计 |
|---|---|---|---|
local + sendonly | 约 280~320 kbps | 极低 | 约 300 kbps |
remote + sendonly | 约 280~320 kbps | 极低 | 约 300 kbps |
local + sendrecv | 约 280~320 kbps | 约 280~320 kbps | 约 600 kbps |
同时一个 local sendonly 和一个 remote sendonly 时,上行合计约 600 kbps。
按每个座席一个 Processor 估算,只含浏览器到 Processor 的 WebSocket,不含 WebRTC RTP:
| 在线座席 | 单路 sendonly | 单路 sendrecv |
|---|---|---|
| 100 | 约 30 Mbps | 约 60 Mbps |
| 500 | 约 150 Mbps | 约 300 Mbps |
| 1000 | 约 300 Mbps | 约 600 Mbps |
每个座席同时推 local 和 remote 两路 sendonly 时,上行约为单路 sendonly 的两倍。浏览器只复制 PCM 和收发 WebSocket,不做模型推理。
生效
- 已开通 WebRTC 座席,页面完成 JS SDK 集成,HTTPS,麦克风已授权。
CTILink.setup()写入processors,webrtc: true。- 座席登录并发起 WebRTC 通话。
- SDK 建立 WebSocket,发送
Control(SessionStart)。 - Processor 返回
Session(Ready)后,SDK 按source持续发送 Uplink。 - 仅
local+sendrecv接收 Downlink,并替换座席麦克风输入。 - 通话结束发送
Control(SessionEnd)并关闭 Session。
WebSocket 异常断开时,SDK 重新建连并再次发送 SessionStart。Processor 新建 Session,不沿用断开前的状态。
Processor 与 WebRTC 媒体流相互独立。启用 Processor 不改变浏览器与媒体服务器之间的 WebRTC 音频编码码率。
协议与数据模型
每个 Processor 一条 WebSocket、一个 Processor Session。
消息顺序:
- WebSocket Connected(SDK → Processor)
Control(SessionStart)(SDK → Processor)Session(Ready)(Processor → SDK)Audio(Uplink),source为local或remote(SDK → Processor)Audio(Downlink),仅local+sendrecv(Processor → SDK)Control(SessionEnd)(SDK → Processor)
Session 状态:Disconnected、Connected、Opening、Ready、Streaming、Closing、Closed。
帧格式
+------------------------+
| Header Length (4 Bytes)|
+------------------------+
| Header (JSON UTF-8) |
+------------------------+
| Payload (Binary/JSON) |
+------------------------+Header Length 是 4 字节无符号整数,Big Endian。Control 与 Session 的 Payload 是 JSON。Audio 的 Payload 是 PCM 二进制。
| Header 字段 | 必填 | 说明 |
|---|---|---|
| type | 是 | Control、Session、Audio |
| action | 否 | Control 动作 |
| event | 否 | Session 事件 |
| sessionId | 否 | Processor Session ID |
| direction | 否 | Audio 方向:Uplink 或 Downlink |
| source | 否 | Audio 使用,local 或 remote |
direction 只表示 SDK 与 Processor 之间的方向,与 WebRTC 媒体方向无关。
ProcessorSession
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| sessionId | String | 是 | Session 唯一标识 |
| processorId | String | 是 | Processor ID |
| source | String | 是 | local 或 remote |
| mode | String | 是 | sendonly 或 sendrecv |
| uniqueId | String | 是 | 通话唯一标识 |
| enterpriseId | String | 是 | 企业 ID |
| cno | String | 是 | 座席工号 |
| customerNumber | String | 否 | 客户号码 |
| callType | Integer | 是 | 通话类型 |
| protocolVersion | String | 是 | 协议版本 |
| uplinkFormat | AudioFormat | 是 | 上行格式 |
| downlinkFormat | AudioFormat | 否 | 下行格式。sendrecv 时携带 |
{
"sessionId": "7db32cf6-17a6-4f58-8c09-d5d0db0ef871",
"processorId": "voice",
"source": "local",
"mode": "sendrecv",
"uniqueId": "sip-1-166514362901.12",
"enterpriseId": "60000001",
"cno": "8001",
"customerNumber": "13800138000",
"callType": 1,
"protocolVersion": "1.0",
"uplinkFormat": {
"sampleRate": 16000,
"channels": 1,
"sampleBits": 16,
"encoding": "pcm_s16le"
},
"downlinkFormat": {
"sampleRate": 16000,
"channels": 1,
"sampleBits": 16,
"encoding": "pcm_s16le"
}
}AudioFormat:
| 字段 | 类型 | 必填 | 当前取值 |
|---|---|---|---|
| sampleRate | Integer | 是 | 16000 |
| channels | Integer | 是 | 1 |
| sampleBits | Integer | 是 | 16 |
| encoding | String | 是 | pcm_s16le |
不支持该格式时,Processor 返回 Session(Error),错误码 1102 UnsupportedFormat。
Control
SessionStart 的 Header:
{
"type": "Control",
"action": "SessionStart"
}Payload 为 { "session": { } },session 即 ProcessorSession。
SessionEnd 的 Header:
{
"type": "Control",
"action": "SessionEnd"
}Payload:
{
"sessionId": "7db32cf6-17a6-4f58-8c09-d5d0db0ef871"
}Session
Ready:
{
"type": "Session",
"event": "Ready"
}{
"sessionId": "7db32cf6-17a6-4f58-8c09-d5d0db0ef871",
"result": 1000,
"description": "Success"
}Error:
{
"type": "Session",
"event": "Error"
}{
"sessionId": "7db32cf6-17a6-4f58-8c09-d5d0db0ef871",
"result": 1105,
"description": "Internal Error"
}| Code | 名称 | 说明 |
|---|---|---|
| 1000 | Success | 成功 |
| 1100 | InvalidRequest | 请求格式错误 |
| 1101 | InvalidSession | Session 无效 |
| 1102 | UnsupportedFormat | 不支持的音频格式 |
| 1103 | UnsupportedMode | 不支持的处理模式 |
| 1104 | AuthenticationFailed | 鉴权失败 |
| 1105 | InternalError | Processor 内部异常 |
Audio
Uplink Header 示例:
{
"type": "Audio",
"direction": "Uplink",
"source": "local",
"sessionId": "7db32cf6-17a6-4f58-8c09-d5d0db0ef871"
}Downlink 仅 local + sendrecv。source 为 local,direction 为 Downlink。Payload 都是裸 PCM。
要求:不含 WAV 头;格式与协商的 AudioFormat 一致;连续发送;不要缓存整段再一次返回。不支持 WAV、MP3、AAC、Opus。
SDK 默认约 60 ms 一帧。
| 帧时长 | PCM 大小 |
|---|---|
| 20 ms | 640 bytes |
| 40 ms | 1280 bytes |
| 60 ms(默认) | 1920 bytes |
| 100 ms | 3200 bytes |
回包建议周期 20~60 ms,大小 640~1920 bytes,不超过 100 ms / 3200 bytes。
sendonly 不要回 Audio。sendrecv 保持连续 PCM。Echo Processor 把收到的 Uplink 原样作为 Downlink 返回,用于确认协议和格式,不代表业务处理。
服务端实现建议把 WebSocket 收发和推理分开。每个 Session 独立,不把多个通话放进同一个 Session。实现侧可以自行记录 createTime、lastAudioTime,这两个名字不是协议字段。
示例工程 echo-processor-python 尚未开放。
联调建议值,不是可用性承诺:单帧处理不超过 20 ms;Processor 平均处理延迟不超过 100 ms;最大处理延迟不超过 300 ms;WebSocket RTT 小于 100 ms。
验收
| 序号 | 检查 | 通过标准 |
|---|---|---|
| 1 | WebSocket | 浏览器能看到到 wss:// 的长连接 |
| 2 | SessionStart | Processor 日志收到,且已配置 media.audio.processors |
| 3 | Session(Ready) | result 为 1000,浏览器收到 processorReady |
| 4 | Uplink | Ready 之后持续收到 source 对应的 PCM |
| 5 | Downlink | 仅 sendrecv 需要。对端听到处理后的声音 |
| 6 | SessionEnd | 通话结束后收到,并释放 Session |
失败时按现象看:
| 现象 | 先查 |
|---|---|
| 无法建连 | 地址、是否 wss://、HTTPS 页面、服务是否启动、防火墙或代理 |
| 没有 SessionStart | 是否已有 WebRTC 通话、是否配置了 processors、WebSocket 是否已连通 |
| 没有 Audio | 麦克风权限、座席是否在说话、Session 是否 Ready、AudioFormat 是否协商成功 |
sendrecv 对端无声 | 是否持续回 Audio、格式是否一致、是否带了 WAV 头、是否整段一次性发送 |
| 异常关闭 | 通话结束、浏览器关闭、连接断开或 SDK 主动关闭。收到 SessionEnd 后释放资源 |
| 自动重连 | 使用新的 SessionStart,丢弃旧 Session |
日志至少留下:WebSocket 建立与关闭、SessionStart / SessionEnd、Session Error、Audio 收发统计、异常和平均处理耗时。
Updated about 2 months ago