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"
                    }
                }
            ]
        }
    }
});
参数类型必填默认值说明
idString是—同一 SDK 实例内唯一
sourceString否locallocal 或 remote
modeString否sendonly工作模式
transportObject是—连接配置
transport.protocolString否websocket目前仅 websocket
transport.urlString是—仅 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);
事件时机字段
processorReadySession 创建成功,可以处理音频processorId、sessionId、uniqueId
processorError运行异常,含无效的 remote + sendrecv上列三个字段,加 code、message
processorClosed通话结束、SDK 关闭或 Processor 结束 SessionprocessorId、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,不做模型推理。

生效

  1. 已开通 WebRTC 座席,页面完成 JS SDK 集成,HTTPS,麦克风已授权。
  2. CTILink.setup() 写入 processors,webrtc: true。
  3. 座席登录并发起 WebRTC 通话。
  4. SDK 建立 WebSocket,发送 Control(SessionStart)。
  5. Processor 返回 Session(Ready) 后,SDK 按 source 持续发送 Uplink。
  6. 仅 local + sendrecv 接收 Downlink,并替换座席麦克风输入。
  7. 通话结束发送 Control(SessionEnd) 并关闭 Session。

WebSocket 异常断开时,SDK 重新建连并再次发送 SessionStart。Processor 新建 Session,不沿用断开前的状态。

Processor 与 WebRTC 媒体流相互独立。启用 Processor 不改变浏览器与媒体服务器之间的 WebRTC 音频编码码率。

协议与数据模型

每个 Processor 一条 WebSocket、一个 Processor Session。

消息顺序:

  1. WebSocket Connected(SDK → Processor)
  2. Control(SessionStart)(SDK → Processor)
  3. Session(Ready)(Processor → SDK)
  4. Audio(Uplink),source 为 local 或 remote(SDK → Processor)
  5. Audio(Downlink),仅 local + sendrecv(Processor → SDK)
  6. 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

字段类型必填说明
sessionIdString是Session 唯一标识
processorIdString是Processor ID
sourceString是local 或 remote
modeString是sendonly 或 sendrecv
uniqueIdString是通话唯一标识
enterpriseIdString是企业 ID
cnoString是座席工号
customerNumberString否客户号码
callTypeInteger是通话类型
protocolVersionString是协议版本
uplinkFormatAudioFormat是上行格式
downlinkFormatAudioFormat否下行格式。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:

字段类型必填当前取值
sampleRateInteger是16000
channelsInteger是1
sampleBitsInteger是16
encodingString是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名称说明
1000Success成功
1100InvalidRequest请求格式错误
1101InvalidSessionSession 无效
1102UnsupportedFormat不支持的音频格式
1103UnsupportedMode不支持的处理模式
1104AuthenticationFailed鉴权失败
1105InternalErrorProcessor 内部异常

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 ms640 bytes
40 ms1280 bytes
60 ms(默认)1920 bytes
100 ms3200 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。

验收

序号检查通过标准
1WebSocket浏览器能看到到 wss:// 的长连接
2SessionStartProcessor 日志收到,且已配置 media.audio.processors
3Session(Ready)result 为 1000,浏览器收到 processorReady
4UplinkReady 之后持续收到 source 对应的 PCM
5Downlink仅 sendrecv 需要。对端听到处理后的声音
6SessionEnd通话结束后收到,并释放 Session

失败时按现象看:

现象先查
无法建连地址、是否 wss://、HTTPS 页面、服务是否启动、防火墙或代理
没有 SessionStart是否已有 WebRTC 通话、是否配置了 processors、WebSocket 是否已连通
没有 Audio麦克风权限、座席是否在说话、Session 是否 Ready、AudioFormat 是否协商成功
sendrecv 对端无声是否持续回 Audio、格式是否一致、是否带了 WAV 头、是否整段一次性发送
异常关闭通话结束、浏览器关闭、连接断开或 SDK 主动关闭。收到 SessionEnd 后释放资源
自动重连使用新的 SessionStart,丢弃旧 Session

日志至少留下:WebSocket 建立与关闭、SessionStart / SessionEnd、Session Error、Audio 收发统计、异常和平均处理耗时。


Did this page help you?