人与AI串联语音
约 3414 字大约 11 分钟
2026-10-16
本页包含已接线核心流程及可选扩展;具体子能力必须按设备、平台与用户授权求交集。参见支持范围、场景索引和命令手册。图中S编号只用于本文定位,不放入实际JSON。
前置条件
平台选定串联链路,能力交集为 halfDuplex。每轮启动/停止输入;串联与实时并存,不是被废弃的模式。
本页示例声明并选择可选udpReplyStartReceipt;不具备该能力时从上报features去除该项,平台selected省略该字段,跳过安装回执子流程。新能力仅软件链路已接线,最终端云状态见支持范围。串联和实时AI UDP复用此子流程,回执字段以命令契约为准。
图中P代表经鉴权的平台业务控制器和媒体节点,不要求同一进程;A是用户应用,D是设备。控制图描述一种合法顺序,跨通道不承诺严格同序。
完整时序
报文索引
编号按图阅读;编号保留历史引用,不要求数值连续。字段定义以命令链接为准。
| 编号 | 发送→接收 | Topic/接口 | 方法及契约 | 应答/等待 | 状态或作用 |
|---|---|---|---|---|---|
| S02-M01 | D→P | SIG↑ | capabilityReport | requestAck;10秒 | 全量替换本设备当前能力;断线重连重新上报,不授予权限。 |
| S02-M02 | P→D | SIG↓ | requestAck | —;不等待 | 同 revision 同内容返回原结果;同 revision 不同内容返回 409。 |
| S02-M03 | D→P | SIG↑ | sessionCreate | sessionCreated;10秒 | 设备发起会话;平台验证设备能力、业务配置、资源与授权交集,不指定供应商。 |
| S02-M04 | P→D | SIG↓ | sessionCreated | —;不等待 | 仅代表创建并分配媒体资源成功,不代表媒体连通;成功必须带顶层 sessionId。 |
| S02-M05 | D→P | SIG↑ | mediaState | requestAck;10秒 | 报告设备实际传输状态。WebRTC 需 ICE/DTLS 成功;UDP 需有效会话媒体探测且平台也收到有效包。 |
| S02-M06 | P→D | SIG↓ | requestAck | —;不等待 | 仅确认状态上报;平台须独立验证自身连通状态。 |
| S02-M07 | P→D | SIG↓ | sessionEstablished | —;不等待 | 平台确认所需参与端媒体已连通后通知;不等于已收到首包或扬声器起播。 |
| S02-M08 | D→P | SIG↑ | audioStart | requestAck;10秒 | 串联每轮新 inputId;实时一次启动持续多轮。恢复暂停用新 inputId,不新建会话。 |
| S02-M09 | P→D | SIG↓ | requestAck | —;不等待 | 输入通道已可用,设备才上行;并不表示用户已经讲话。 |
| S02-M10 | D→P | SIG↑ | audioStop | requestAck;10秒 | 串联提交该轮输入;实时只暂停,不把结束判断强加给供应商。重复请求不重复提交。 |
| S02-M11 | P→D | SIG↓ | requestAck | —;不等待 | 停止输入已接受;回复可以继续,串联下一轮需等待自身策略或取消旧回复。 |
| S02-M12 | P→D | MSG↓ | respCreated | —;不等待 | 为内容与音频事件建立关联,不能据此推断UDP已到达。 |
| S02-M13 | P→D | MSG↓ | respSttDelta | —;不等待 | 可选增量识别,按 messageSeq 排序。 |
| S02-M14 | P→D | MSG↓ | respSttDone | —;不等待 | 最终识别替换此前增量;不等于回复文本。 |
| S02-M15 | P→D | MSG↓ | respTextDone | —;不等待 | 最终完整文本取代增量;不是播放完成。 |
| S02-M16 | P→D | SIG↓ | respAudioStart | 已选安装确认时等原回执及实际准入;至多首次2秒 | 建立回复边界;不得把信令投递当首帧已发 |
| S02-M27 | D→P | SIG↑ | respAudioStart | 不等待二次ACK;不刷新原安装截止 | 确认原边界安装及PCM Ready,不证明已解码或播放完成 |
| S02-M18 | P→D | MSG↓ | respTextDelta | —;不等待 | 按 messageSeq 排序,不拼接重复增量。 |
| S02-M19 | P→D | MSG↓ | respTextDone | —;不等待 | 最终完整文本取代增量;不是播放完成。 |
| S02-M20 | P→D | SIG↓ | respAudioDone | —;不等待 | 平台已结束该回复网络音频发送,不是设备播放完成 |
| S02-M25 | P→D | MSG↓ | respDone | 应答或事件,无ACK | 核对补齐,不以受理替代完成 |
| S02-M28 | P→D | SIG↓ | sessionError | —;不等待 | 原安装门实际到期,只关闭该回复,不关闭有效主会话 |
| S02-M29 | P→D | MSG↓ | respDone | —;不等待 | 原回复failed终态;迟到回执不复活,不发送成功音频Done |
| S02-M26 | P→D | MSG↓ | respDone | 应答或事件,无ACK | 核对补齐,不以受理替代完成 |
| S02-M22 | P→D | SIG↓ | sessionError | —;不等待 | 带 code 的异步错误;不可恢复时随后 sessionEnded,可恢复错误不自动结束主会话。 |
| S02-M23 | D→P | SIG↑ | sessionEnd | sessionEnded;10秒 | 设备主动结束整个会话,立即本地停采集/播放;平台撤销轨、处理器、密钥/令牌及资源占用。 |
| S02-M24 | P→D | SIG↓ | sessionEnded | —;不等待 | 主动结束用原 token 和 code;其他结束用独立事件 token 与 eventSeq。ended 是唯一会话终态。 |
逐条JSON
S02-M01 · capabilityReport
{
"msgToken": "cap-S02",
"method": "capabilityReport",
"params": {
"revision": 1,
"purposes": [
"aiConversation"
],
"transports": [
"udp"
],
"supportedInteractionModes": [
"halfDuplex"
],
"aecMode": "none",
"audio": {
"format": "opus",
"sampleRate": 16000,
"channels": 1,
"frameDuration": 20
},
"cameras": [],
"features": [
"audio",
"udpReplyStartReceipt"
]
}
}S02-M02 · requestAck
{
"msgToken": "cap-S02",
"method": "requestAck",
"code": 200,
"data": {
"requestMethod": "capabilityReport",
"result": {
"acceptedRevision": 1
}
}
}S02-M03 · sessionCreate
{
"msgToken": "s02-19",
"method": "sessionCreate",
"params": {
"purpose": "aiConversation",
"transport": "udp",
"tracks": [
{
"trackId": "mic-1",
"kind": "audio",
"sourceId": "mic",
"direction": "sendonly",
"codec": "opus",
"state": "requested"
},
{
"trackId": "speaker-1",
"kind": "audio",
"sourceId": "speaker",
"direction": "recvonly",
"codec": "opus",
"state": "requested"
}
],
"supportedInteractionModes": [
"halfDuplex"
],
"audioParams": {
"format": "opus",
"sampleRate": 16000,
"channels": 1,
"frameDuration": 20
}
}
}S02-M04 · sessionCreated
{
"msgToken": "s02-19",
"method": "sessionCreated",
"sessionId": "s02-session",
"code": 200,
"data": {
"state": "negotiating",
"selected": {
"transport": "udp",
"interactionMode": "halfDuplex",
"udpReplyStartReceipt": true,
"audioParams": {
"format": "opus",
"sampleRate": 16000,
"channels": 1,
"frameDuration": 20
}
},
"tracks": [
{
"trackId": "mic-1",
"kind": "audio",
"sourceId": "mic",
"direction": "sendonly",
"codec": "opus",
"state": "requested"
},
{
"trackId": "speaker-1",
"kind": "audio",
"sourceId": "speaker",
"direction": "recvonly",
"codec": "opus",
"state": "requested"
}
],
"media": {
"udp": {
"server": "<media-host>",
"port": 0,
"key": "<redacted-key>",
"nonce": "<redacted-nonce>"
}
}
}
}S02-M05 · mediaState
{
"msgToken": "s02-20",
"method": "mediaState",
"sessionId": "s02-session",
"params": {
"mediaRevision": 1,
"state": "connected"
}
}S02-M06 · requestAck
{
"msgToken": "s02-20",
"method": "requestAck",
"sessionId": "s02-session",
"code": 200,
"data": {
"requestMethod": "mediaState",
"result": {
"mediaRevision": 1,
"state": "connected"
}
}
}S02-M07 · sessionEstablished
{
"msgToken": "s02-21",
"method": "sessionEstablished",
"sessionId": "s02-session",
"eventSeq": 1,
"data": {
"state": "established",
"mediaRevision": 1
}
}S02-M08 · audioStart
{
"msgToken": "s02-22",
"method": "audioStart",
"sessionId": "s02-session",
"params": {
"inputId": "input-1",
"firstPacketSeq": 256
}
}S02-M09 · requestAck
{
"msgToken": "s02-22",
"method": "requestAck",
"sessionId": "s02-session",
"code": 200,
"data": {
"requestMethod": "audioStart",
"result": {
"inputId": "input-1"
}
}
}S02-M10 · audioStop
{
"msgToken": "s02-23",
"method": "audioStop",
"sessionId": "s02-session",
"params": {
"inputId": "input-1",
"lastPacketSeq": 12800
}
}S02-M11 · requestAck
{
"msgToken": "s02-23",
"method": "requestAck",
"sessionId": "s02-session",
"code": 200,
"data": {
"requestMethod": "audioStop",
"result": {
"inputId": "input-1"
}
}
}S02-M12 · respCreated
{
"msgToken": "s02-25",
"method": "respCreated",
"sessionId": "s02-session",
"messageSeq": 1,
"data": {
"respId": "reply-1",
"inputId": "input-1"
}
}S02-M13 · respSttDelta
{
"msgToken": "s02-24",
"method": "respSttDelta",
"sessionId": "s02-session",
"messageSeq": 2,
"data": {
"respId": "reply-1",
"inputId": "input-1",
"delta": "你好"
}
}S02-M14 · respSttDone
{
"msgToken": "s02-26",
"method": "respSttDone",
"sessionId": "s02-session",
"messageSeq": 3,
"data": {
"respId": "reply-1",
"inputId": "input-1",
"text": "你好"
}
}S02-M15 · respTextDone
{
"msgToken": "s02-27",
"method": "respTextDone",
"sessionId": "s02-session",
"messageSeq": 4,
"data": {
"respId": "reply-1",
"contentIndex": 0,
"text": "你好,有什么可以帮你?"
}
}S02-M16 · respAudioStart
{
"msgToken": "s02-28",
"method": "respAudioStart",
"sessionId": "s02-session",
"eventSeq": 2,
"data": {
"respId": "reply-1",
"trackId": "speaker-1",
"mediaRevision": 1,
"udpNonce": "0000000000000001",
"firstPacketSeq": 128
}
}S02-M27 · respAudioStart
{
"msgToken": "s02-28",
"method": "respAudioStart",
"sessionId": "s02-session",
"code": 200,
"data": {
"respId": "reply-1",
"trackId": "speaker-1",
"mediaRevision": 1
}
}S02-M18 · respTextDelta
{
"msgToken": "s02-30",
"method": "respTextDelta",
"sessionId": "s02-session",
"messageSeq": 5,
"data": {
"respId": "reply-1",
"contentIndex": 1,
"delta": "请继续"
}
}S02-M19 · respTextDone
{
"msgToken": "s02-31",
"method": "respTextDone",
"sessionId": "s02-session",
"messageSeq": 6,
"data": {
"respId": "reply-1",
"contentIndex": 1,
"text": "请继续。"
}
}S02-M20 · respAudioDone
{
"msgToken": "s02-32",
"method": "respAudioDone",
"sessionId": "s02-session",
"eventSeq": 4,
"data": {
"respId": "reply-1",
"trackId": "speaker-1",
"mediaRevision": 1,
"lastPacketSeq": 12800
}
}S02-M25 · respDone
{
"method": "respDone",
"msgToken": "s02-audit-0",
"sessionId": "s02-session",
"messageSeq": 7,
"data": {
"respId": "reply-1",
"inputId": "input-1",
"status": "completed",
"contentCount": 2,
"audioState": "sent"
}
}S02-M28 · sessionError
{
"msgToken": "s02-install-expired",
"method": "sessionError",
"sessionId": "s02-session",
"code": 504,
"eventSeq": 3,
"data": {
"scope": "response",
"respId": "reply-1",
"mediaRevision": 1,
"reason": "mediaFailed",
"retryable": true
}
}S02-M29 · respDone
{
"msgToken": "s02-install-failed",
"method": "respDone",
"sessionId": "s02-session",
"messageSeq": 5,
"data": {
"respId": "reply-1",
"inputId": "input-1",
"status": "failed",
"reason": "mediaFailed",
"audioState": "failed",
"contentCount": 1
}
}S02-M26 · respDone
{
"msgToken": "s02-final-58",
"method": "respDone",
"sessionId": "s02-session",
"messageSeq": 8,
"data": {
"respId": "reply-1",
"inputId": "input-1",
"status": "failed",
"reason": "upstreamUnavailable",
"contentCount": 0,
"audioState": "failed"
}
}S02-M22 · sessionError
{
"msgToken": "s02-34",
"method": "sessionError",
"sessionId": "s02-session",
"code": 502,
"eventSeq": 6,
"data": {
"reason": "modelTimeout",
"retryable": true,
"scope": "response",
"respId": "reply-1"
}
}S02-M23 · sessionEnd
{
"msgToken": "s02-35",
"method": "sessionEnd",
"sessionId": "s02-session",
"params": {
"reason": "userHangup"
}
}S02-M24 · sessionEnded
{
"msgToken": "s02-35",
"method": "sessionEnded",
"sessionId": "s02-session",
"code": 200,
"data": {
"state": "ended",
"reason": "userHangup"
}
}媒体与本地动作
| 编号 | 路径 | 格式与约束 |
|---|---|---|
| S02-R01 | D→P | UDP:16字节头 + AES-CTR 加密的 Opus 静音探测帧;复用已分配通道;探测不送模型、不作为用户有效语音。端口和密钥为不可连接占位符。 |
| S02-R02 | P→D | UDP 同会话静音探测应答帧;平台和设备双向验证通道,不播放也不送入模型;统一信令适配待实现,不改变包头。 |
| S02-R03 | D→P | UDP Opus:16 kHz / 20 ms / mono;仅当前输入段采集;帧号按本会话方向递增。 |
| S02-R04 | P→D | UDP:16字节头 + AES-CTR Opus;以会话密钥解密;没有 respId,不能将控制序号解释为逐包回复ID。 |
异常、状态与资源释放
以下JSON仅为报文正文;编号、方向、说明不发送。loop下一轮需分配新token/inputId;同一次请求重试必须沿用原token。字段以命令手册为准。
路由缩写均展开于公共约定。ACT/PROP超时以产品配置为准;内部工具以工具超时为准,不套用10秒设备统一信令超时。
图表示一种合法顺序,不要求先收齐文本才发送音频;内容与信令跨 Topic、媒体跨通道无全序。首文本、首网络音频、设备解码出连续两帧有声和扬声器实际起播分别采集。失败轮可再次 audioStart,不能隐式换 Provider。
能力不支持、无权限、设备拒绝、协商失败、断网、重复请求、过期会话及迟到媒体按S11共通失败流程和公共错误表处理。业务专属分支以本页为准,不因错误静默打开额外能力。
验证入口
更新日志
2026/10/16 16:04
查看所有更新日志
fe3a7-docs(AI与音视频): 按场景整理完整接入流程与命令契约于
