音视频推流完整交互协议

音视频推流完整交互协议

1. 能力边界

方向

已开放的报文

平台 → 开发者系统

SESSION_STARTSESSION_CLOSE(可选)

开发者系统 → 平台

SSEoutput 事件中的 SPEAKSTOP_OUTPUT

2. 接入方式

开发者须提供以下两个 API

接口

是否必需

接收请求

响应

会话流 API(callbackToolCode

必需

SESSION_START

200 text/event-stream,保持长连接

会话事件 API(eventToolCode

非必需

SESSION_CLOSE

200 application/json

  • 接口地址、鉴权信息等通过开放平台的API插件完成接入

3. 交互流程

image.svg

4. 会话交互

本章先说明平台请求的通用信封格式,再按一次会话的生命周期顺序说明各报文的交互细节:建立会话(SESSION_START)→ 返回结果(output)→ 播报(SPEAK)→ 停止播报(STOP_OUTPUT)→ 关闭会话(SESSION_CLOSE)。

4.1. 请求信封

平台使用 POSTapplication/json 发送请求:

{
  "specVersion": "1.0",
  "requestId": "req_001",
  "sessionId": "ses_001",
  "type": "SESSION_START | SESSION_CLOSE",
  "occurredAt": "2026-08-06T10:00:00+08:00",
  "payload": {}
}

字段

类型

是否必填

含义

specVersion

string

必填

协议版本,当前固定 1.0

requestId

string

必填

请求幂等键;同一次重试保持不变

sessionId

string

必填

本次实时会话的唯一标识

type

string

必填

SESSION_START / SESSION_CLOSE

occurredAt

string

必填

RFC 3339 时间

payload

object

必填

事件负载,结构随 type 变化

4.2. 建立会话:SESSION_START

流向:平台 -> 开发者系统

4.2.1. 请求

POST /audio-realtime/session HTTP/1.1
Content-Type: application/json
Accept: text/event-stream
X-Request-ID: req_start_001
{
  "specVersion": "1.0",
  "requestId": "req_start_001",
  "sessionId": "ses_001",
  "type": "SESSION_START",
  "occurredAt": "2026-08-06T10:00:00+08:00",
  "payload": {
    "entryContext": {
      "source": "VOICE_SKILL",
      "rawUtterance": "进入语音闲聊"
    },
    "rtc": {
      "provider": "ALIYUN_RTC",
      "channelId": "channel_001",
      "consumerUserId": "third_party_consumer_001",
      "consumerToken": "<RtcRoom.thirdToken>",
      "expiresAt": "2026-08-07T10:00:00+08:00"
    }
  }
}

payload.rtc 字段说明:

字段

string

是否必填

说明

provider

string

必填

当前固定为 ALIYUN_RTC

channelId

string

必填

RTC 房间标识

consumerUserId

string

必填

开发者服务加入 RTC 时使用的用户标识

consumerToken

string

必填

与本次房间和 consumer 匹配的临时凭证

expiresAt

string

必填

凭证过期时间,RFC 3339 格式

实现要求:

  • 必须同时使用 channelIdconsumerUserIdconsumerToken 入会,不要从 token 推断 userId;

  • RTC consumer 准备失败时,在提交 SSE 200 前返回错误;

  • 同一 START 重试不得创建第二个 RTC consumer、第二条 SSE 或第二份业务会话。

4.2.2. 响应

HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache

开发者服务应先完成 RTC consumer 的初始化和入会准备,再返回 SSE 200。平台确认 SSE 就绪后才会启动设备推流;过早返回 200 可能导致开头音频丢失。

4.2.3. 保活

SSE 空闲期间建议每 30 秒发送一次注释保活:

: keep-alive

平台默认连续 90 秒未收到完整的 SSE 帧时会关闭会话。保活帧和 output 帧都必须以空行结束。当前不支持 SSE 续连、Last-Event-ID 或消息重放;连接断开后应释放资源,由用户重新进入会话。

4.3. 返回结果:output

流向:开发者系统 -> 平台

平台与会话流 API 建立的 SSE 只接受事件名为 output 的事件:

event: output
data: {"specVersion":"1.0","sessionId":"ses_001","actions":[],"final":false}

字段

类型

是否必填

说明

specVersion

string

必填

当前固定为 1.0

sessionId

string

必填

必须与当前 SSE 会话一致

actions

array

必填

按数组顺序执行,只允许 SPEAKSTOP_OUTPUT;未开放 action 按协议错误处理,不执行也不降级。

final

boolean

true表示执行本批 Action 后结束整个会话;缺省为 false

  • 每个 Session 只能有一个 SSE writer。RTC、ASR、模型和定时任务产生的结果应先进入同一个串行队列,再写入 SSE,避免乱序、旧回复迟到或关闭后继续写入。

4.3.1. 播报Action:SPEAK

流向:开发者系统 -> 平台

{
  "type": "SPEAK",
  "payload": {
    "speechId": "speech_001",
    "index": 1,
    "hasNext": true,
    "text": "我先简单介绍一下,",
    "textDisplayMode": "APPEND_HIGHLIGHT"
  }
}

字段

类型

是否必填

说明

speechId

string

必填

一次逻辑播报的唯一标识,在 Session 内不可复用

index

integer

必填

同一 speechId1 开始连续递增

hasNext

boolean

必填

最后一片为 false

text

string

必填

非空 UTF-8 纯文本,不支持 SSML 或 Markdown

textDisplayMode

string

NONEAPPEND_HIGHLIGHT;缺省为 NONE

同一播报必须满足以下规则:

  • 单片播报固定为 index=1, hasNext=false

  • 多个 speechId 不能交错发送;

  • 同一 speechIdtextDisplayMode 必须保持一致;

  • 相同 speechId + index 的重复帧必须内容完全相同,平台只消费一次;

  • hasNext=false 只表示该播报的文本输入结束,不表示音频已经播放完成;

  • 已完成或已取消的 speechId 不能用于新的回复。

多片播报示例:

event: output
data: {"specVersion":"1.0","sessionId":"ses_001","actions":[{"type":"SPEAK","payload":{"speechId":"speech_001","index":1,"hasNext":true,"text":"李白是唐代诗人,","textDisplayMode":"APPEND_HIGHLIGHT"}}],"final":false}

event: output
data: {"specVersion":"1.0","sessionId":"ses_001","actions":[{"type":"SPEAK","payload":{"speechId":"speech_001","index":2,"hasNext":false,"text":"以浪漫奔放的诗风闻名。","textDisplayMode":"APPEND_HIGHLIGHT"}}],"final":false}

使用 APPEND_HIGHLIGHT 时,同一份 text 会在支持该能力的设备上随播报追加并高亮;不支持展示的设备仍会正常播报。NONE 或缺省表示只播报。开发者不需要判断设备是否有屏。

4.3.2. 停止当前播报Action:STOP_OUTPUT

流向:开发者系统 -> 平台

{
  "type": "STOP_OUTPUT",
  "payload": {
    "scope": "SPEAK",
    "reason": "BARGE_IN"
  }
}
  • scope 必填,当前固定为 SPEAK

  • reason 可选,仅用于说明停止原因;

  • 没有活跃播报时,STOP 按幂等空操作处理;

  • STOP 后的新回复必须使用新的 speechId,并从 index=1 开始。

需要用新回复打断旧播报时,将 STOP 和新回复首片放在同一个 output 中,并保证 STOP 在前:

event: output
data: {"specVersion":"1.0","sessionId":"ses_001","actions":[{"type":"STOP_OUTPUT","payload":{"scope":"SPEAK","reason":"BARGE_IN"}},{"type":"SPEAK","payload":{"speechId":"speech_002","index":1,"hasNext":false,"text":"三加三等于六。"}}],"final":false}

不要在同一回复的每个分片前重复发送 STOP。新播报开始后才到达的 STOP 也会停止新播报,因此必须由单 writer 保证顺序,并丢弃被新一轮对话替换的旧任务结果。

4.4. 关闭会话:SESSION_CLOSE

流向:平台 -> 开发者系统

配置了会话事件接口时,平台会发送:

{
  "specVersion": "1.0",
  "requestId": "req_close_001",
  "sessionId": "ses_001",
  "type": "SESSION_CLOSE",
  "occurredAt": "2026-08-10T10:30:00+08:00",
  "payload": {
    "reason": "USER_EXIT"
  }
}

开发者系统应返回 ACK:

{
  "specVersion": "1.0",
  "sessionId": "ses_001",
  "requestId": "req_close_001",
  "status": "RECEIVED"
}

ACK 只表示通知已接收,不用于承载结束语。

5. 错误处理

SSE 建立前,可使用标准 HTTP 状态码返回错误:

{
  "specVersion": "1.0",
  "requestId": "req_001",
  "error": {
    "code": "INVALID_REQUEST",
    "message": "sessionId is required",
    "retryable": false
  }
}

HTTP 状态码

建议场景

400

JSON、协议版本或 RTC 参数不合法

401 / 403

鉴权失败或无访问权限

404

Session 不存在

409

幂等内容冲突或 Session 状态冲突

429

服务限流

5xx

服务暂时不可用

SSE 建立后无法再修改 HTTP 状态。仍可继续写流时,可发送一条简短提示并以 final=true 结束;无法继续写流时应断开连接,并确保所有关联资源被释放。

6. 推荐实现结构

建议为每个 Session 维护独立状态对象,至少包含:RTC consumer、ASR 流、当前模型任务、当前 speechId/index、串行 SSE writer、心跳任务和关闭状态。

核心处理原则:

  1. 收到 START 后先校验和幂等占位,再准备 RTC consumer;

  2. RTC 准备成功后返回 SSE 200,启动心跳和音频处理;

  3. ASR 产生有效终句后启动对话任务;新一轮有效输入使旧任务结果失效;

  4. 模型文本按句读流式切片,所有 Action 进入单 writer;

  5. 明确退出、final=true、CLOSE、SSE 断开或异常都进入同一个幂等关闭函数。

伪代码示例:

Session open(StartEvent event) {
    Session session = sessions.createIdempotently(event.requestId(), event.sessionId());
    session.prepareRtc(event.payload().rtc());
    session.openSse();
    session.startHeartbeat();
    session.startAudioPipeline();
    return session;
}

void onAsrFinal(Session session, String text) {
    long turn = session.replaceCurrentTurn();
    model.stream(text,
        delta -> session.fragmenter(turn).accept(delta),
        () -> session.fragmenter(turn).finish(),
        error -> session.failTurn(turn, error));
}

void close(Session session, String reason) {
    if (!session.beginCloseOnce()) return;
    session.cancelModel();
    session.closeAsr();
    session.leaveRtc();
    session.stopHeartbeat();
    session.completeSse();
}

切片器应保留一片前瞻:只有看到下一片时,才能确认上一片的 hasNext=true;模型流结束时,将最后缓存片以 hasNext=false 发送。不要发送空文本结束片。

7. 安全与可观测性

建议日志至少包含:

  • requestIdsessionId 和本服务生成的 turn/speech 标识;

  • START 接收、RTC 准备完成、首个音频帧、ASR FINAL;

  • 模型开始、首字、首个 output、最后一个 output;

  • STOP、CLOSE、SSE 断开和资源释放原因;

  • 各阶段耗时和错误码。

禁止记录完整鉴权凭证、RTC token、原始 PCM、完整用户音频或包含敏感参数的 URL。用户文本应按适用的隐私规则做脱敏、采样和保留期限控制。

8. 联调验收清单

8.1. 建联与音频

  • START 参数校验完整,RTC 准备成功后才返回 SSE 200

  • 使用下发的 consumerUserId 入会,能够持续收到音频帧;

  • 空闲时持续发送保活,断流后所有资源均被释放;

  • START 重试不会创建重复 Session 或重复 RTC consumer。

8.2. 播报与打断

  • 单片和多片 SPEAK 的 speechId/index/hasNext 均符合约束;

  • 同一播报不与其他 speechId 交错;

  • 相同分片重试不会重复播报,冲突分片会被拒绝;

  • 新回复打断旧播报时,同批只发送一次 STOP_OUTPUT → SPEAK

  • 被替换的旧模型任务不会产生迟到分片。

8.3. 结束与恢复

  • final=true 前没有未结束的 speech;

  • CLOSE 重试保持幂等,未配置 CLOSE 接口时也能完成资源清理;

  • 语音退出、SSE 断开、RTC 异常和服务超时均进入统一关闭流程;

  • 退出后可立即使用新的 sessionId 重新进入;

  • 至少完成 10 次“进入 → 多轮对话 → 退出 → 重进”的连续测试。