RTC TCP Client SDK 参考文档

1. 概述​

RTC TCP Client(API 前缀 tai_*)是 tRTC(tuya自研RTC协议) 的 TCP 实现。以源码形式提供,通过 PAL(Platform Abstraction Layer)实现跨平台移植,适用于 POSIX、FreeRTOS(ESP-IDF)等环境。

头文件:tuya_ai.h(单一 include)

特点​

  • 简洁 API:类型化发送函数(tai_send_text、tai_send_audio_*、tai_send_image),无需手动组装数据结构体
  • 后台接收线程:tai_connect 后自动启动后台线程处理接收和 keepalive
  • 回调驱动:通过 on_audio、on_text、on_image、on_event、on_disconnect 接收数据
  • 无外部依赖:用户 PAL 只需提供原始 TCP 和平台原语;TLS 和加密由 SDK 内部通过 bundled mbedTLS 处理

2. 常量定义​

2.1 数据包类型​

宏值说明
TAI_PKT_CLIENT_HELLO1客户端握手
TAI_PKT_AUTHENTICATE_RESPONSE3服务端对 ClientHello 的鉴权结果
TAI_PKT_PING4心跳请求
TAI_PKT_PONG5心跳响应
TAI_PKT_CONNECTION_CLOSE6连接关闭
TAI_PKT_SESSION_NEW7新建会话
TAI_PKT_SESSION_CLOSE8关闭会话
TAI_PKT_CONNECTION_REFRESH_REQ9连接刷新请求
TAI_PKT_CONNECTION_REFRESH_RESP10连接刷新响应
TAI_PKT_VIDEO30视频数据
TAI_PKT_AUDIO31音频数据
TAI_PKT_IMAGE32图像数据
TAI_PKT_FILE33文件数据
TAI_PKT_TEXT34文本数据
TAI_PKT_EVENT35事件数据

2.2 流标志(Stream Flags)​

宏值说明
TAI_STREAM_ONE_SHOT0x00单包(完整数据)
TAI_STREAM_START0x01流开始
TAI_STREAM_MIDDLE0x02流中间
TAI_STREAM_END0x03流结束

2.3 事件类型​

宏值说明
TAI_EVT_START0会话开始
TAI_EVT_PAYLOADS_END1负载结束
TAI_EVT_END2会话结束
TAI_EVT_ONE_SHOT3单次事件
TAI_EVT_CHAT_BREAK4聊天打断/云端 VAD 回合结束(用户停止说话或中途插话);当前云端以此作为回合边界信号;只需清除下行 TTS 缓存/播放队列,不要结束上行 Event
TAI_EVT_SERVER_VAD5云端 VAD 检测到用户停止说话(旧服务端信号,当前云端不再下发,常量仅为协议兼容保留)
TAI_EVT_MCP_CMD1000MCP 命令(设备侧执行)
TAI_EVT_SERVER_TIMEOVER1001服务端超时
TAI_EVT_UPDATE_CONTEXT1002上下文更新

2.4 客户端类型​

宏值说明
TAI_CLIENT_DEVICE1设备端
TAI_CLIENT_APP2应用端

2.5 音频编码​

宏值说明
TAI_AUDIO_PCM101原始 PCM
TAI_AUDIO_OPUS111Opus 编码

2.6 图像格式​

宏值说明
TAI_IMG_JPEG1JPEG
TAI_IMG_PNG2PNG

2.7 图像负载类型​

宏值说明
TAI_IMG_PAYLOAD_RAW0原始二进制
TAI_IMG_PAYLOAD_BASE641Base64 编码
TAI_IMG_PAYLOAD_URL2URL 字符串

2.8 签名级别​

宏值说明
TAI_SIGN_NONE0不签名
TAI_SIGN_HMAC_SHA11HMAC-SHA1
TAI_SIGN_HMAC_SHA2562HMAC-SHA256(推荐)

2.9 数据 ID​

宏值说明
TAI_DATA_ID_AUDIO_UP1上行音频
TAI_DATA_ID_AUDIO_DOWN2下行音频
TAI_DATA_ID_TEXT_UP3上行文本
TAI_DATA_ID_TEXT_DOWN4下行文本
TAI_DATA_ID_IMAGE_UP5上行图像
TAI_DATA_ID_AUDIO_AUX7辅助音频

2.10 返回码​

宏值说明
TAI_OK0成功
TAI_ERR_ARGS-1参数错误
TAI_ERR_MEM-2内存不足
TAI_ERR_NET-3网络错误
TAI_ERR_TLS-4TLS 错误
TAI_ERR_PROTO-5协议错误
TAI_ERR_HMAC-6HMAC 校验失败
TAI_ERR_AGAIN-7需要重试
TAI_ERR_CRYPTO-8加解密错误

3. 配置(tai_config_t)​

调用 tai_ctx_init 前填充此结构体。所有指针字段在 context 生命周期内必须保持有效。

3.1 服务器配置​

字段类型说明
hostconst char *服务器地址(通常从 iot_client_get_session_token() 返回的 token 中解析得到)
portuint16_t服务器端口
tls_sniconst char *TLS SNI 主机名(通常与 host 相同;若 token 中提供域名,优先使用域名)

3.2 身份配置​

字段类型说明
device_idconst char *设备 ID(配网后获得的 devid)
local_keyconst char *本地密钥(配网后获得的 local_key)
client_typeuint8_t客户端类型:TAI_CLIENT_DEVICE 或 TAI_CLIENT_APP(0 = 默认 TAI_CLIENT_DEVICE)
protocol_versionuint8_t协议版本:使用 TAI_VER_21(0 = 默认 TAI_VER_21)

3.3 会话选项​

字段类型说明
session_attrs_jsonconst char *会话属性 JSON(NULL 使用默认值)。可配置云端 VAD、音频格式等
event_user_data_jsonconst char *事件用户数据 JSON(NULL 使用默认值)
agent_tokenconst char *Agent Token(指定特定 Agent,NULL 使用产品默认 Agent)

3.4 业务标识​

字段类型说明
biz_codeuint32_t业务码(0 = 默认 65537)
biz_taguint64_t业务标签(0 = 默认 119)

3.5 安全配置​

字段类型说明
sign_leveluint8_t签名级别:TAI_SIGN_HMAC_SHA256(推荐)或 TAI_SIGN_HMAC_SHA1。注意 TAI_SIGN_NONE(0)会被视为"未设置"而强制回退到 TAI_SIGN_HMAC_SHA256,无法通过该字段关闭签名

3.6 保活配置​

字段类型说明
ping_interval_msuint32_tPing 间隔(0 = 默认 60000ms)
ping_timeout_msuint32_t接收存活超时,任意入站数据均刷新(0 = 默认 90000ms);主动背压暂停期间不计超时,恢复时获得完整的新预算
connect_timeout_msuint32_t连接超时(0 = 默认 5000ms)。分别约束 tai_connect 的两个串行等待阶段:先是连接建立(TCP 建连 + TLS 握手,共用一份预算),再是服务端 SessionNew 应答。任一阶段超时即判定连接失败,因此 tai_connect 最坏耗时约为该值的 2 倍。

3.7 测试配置​

字段类型说明
disable_tlsuint8_t非零时跳过 TLS,仅用于集成测试

3.8 平台适配​

字段类型说明
palconst pal_t *平台适配层实现指针
cert_bundle_attachtls_cert_bundle_attach_fn平台证书包回调(ESP-IDF 上设为 esp_crt_bundle_attach,否则 TLS 不做证书校验);NULL 表示不使用。详见 TLS 证书验证

3.9 回调函数​

所有回调均在后台接收线程中调用。

字段类型说明
on_audiofunction pointer音频数据回调
on_textfunction pointer文本数据回调
on_imagefunction pointer图像数据回调(云端生成的图片)
on_eventfunction pointer事件回调(MCP、打断、VAD 等)
on_disconnectfunction pointer断连回调
on_flow_controlint (*)(tai_ctx_t *, void *)可选接收背压钩子;非零允许接收,0 暂停读取与解析,NULL 不启用背压
user_datavoid *透传到所有回调

回调签名:

每个回调接收单个 const 消息结构体指针 + user_data(不再是旧的多参数形式)。

void (*on_audio) (tai_ctx_t *ctx, const tai_audio_msg_t *msg, void *user_data);
void (*on_text) (tai_ctx_t *ctx, const tai_text_msg_t *msg, void *user_data);
void (*on_image) (tai_ctx_t *ctx, const tai_image_msg_t *msg, void *user_data);
void (*on_event) (tai_ctx_t *ctx, const tai_event_msg_t *msg, void *user_data);
void (*on_disconnect)(tai_ctx_t *ctx, const tai_disconnect_msg_t *msg, void *user_data);
int (*on_flow_control)(tai_ctx_t *ctx, void *user_data);

接收背压(on_flow_control)​

  • worker 在每次读取前及完整 Frame 之间调用钩子。符合规范的服务端不会在握手阶段触发它(应答先于媒体下发);仅当服务端把媒体排在应答之前才可能在握手期被调用。钩子必须非阻塞;应用负责同步共享的队列状态。
  • 返回 0 会暂停读取和解析,所有入站流量都会停滞,包括 ChatBreak、ASR 文本、Pong 和 EOF 检测。恢复后先处理已缓冲的完整 Frame,再读取;不完整输入回到有上限的阻塞接收。
  • 主动暂停挂起接收存活超时;恢复时获得新的完整 ping_timeout_ms 预算。Ping 和停止请求仍执行,Ping 发送失败仍断连。
  • 背压也在 Audio Packet 内的每个编解码帧回调前检查。中途暂停会以零拷贝方式保留该 Packet 的剩余字节,恢复时先交付剩余帧、再处理后续 Packet。应用应在 on_audio 中按服务端时间(msg->timestamp_ms)丢弃过期音频,而不是改写 SDK 接收状态。

默认每 AGENTIC_KIT_TAI_FLOW_CONTROL_POLL_MS(50 ms)重查背压,并以 AGENTIC_KIT_TAI_WORKER_YIELD_MS(10 ms)在持续流量下让出 CPU。

pal_t 新增必填的 sleep_ms 回调。它必须非忙等地休眠至少请求的毫秒数,0 为 no-op,且不能依赖 socket 可读性。所有自定义 PAL 和使用方必须用新头文件重新编译。

接收消息结构体​

tai_audio_msg_t(音频回调):

字段类型说明
dataconst uint8_t *Opus 帧 / PCM 字节
lensize_t数据字节数
codecuint8_tTAI_AUDIO_OPUS / TAI_AUDIO_PCM / 0=未知
sample_rateuint32_t采样率(Hz),0=未知
frame_durationuint16_t每 Opus 帧时长(ms)
stream_flaguint8_tTAI_STREAM_*(取自媒体头)
data_iduint16_t数据 ID:AUDIO_DOWN(2) / AUDIO_AUX(7)
event_idconst char *turn id(借用);无则为 ""
timestamp_msuint64_t服务端媒体头时间戳,非本地时间;过滤流时以 START 的值锁定,与打断时间比较

tai_text_msg_t(文本回调):

字段类型说明
textconst char *UTF-8 文本,非 NUL 结尾
lensize_t文本字节数
stream_flaguint8_tTAI_STREAM_*
data_iduint16_t数据 ID:TAI_DATA_ID_TEXT_DOWN(4)
sequint32_t每事件内文本序号(varint)
event_idconst char *turn id(借用);无则为 ""

tai_image_msg_t(图像回调):

字段类型说明
dataconst uint8_t *编码图像字节(JPEG/PNG);回调生命周期内有效
lensize_t数据字节数
formatuint8_tTAI_IMG_JPEG / TAI_IMG_PNG / 0=未知
widthuint16_t宽(px),未知或非 START/ONE_SHOT 包时为 0
heightuint16_t高(px),未知或非 START/ONE_SHOT 包时为 0
stream_flaguint8_tTAI_STREAM_*
data_iduint16_t数据 ID(取自媒体头)
event_idconst char *turn id(借用);无则为 ""
timestamp_msuint64_t流起始时间戳(媒体头)
流式接收

接收到的图片以分片流形式到达:START(或 ONE_SHOT)携带首字节与 image-params,MIDDLE 继续,END 结束(len 可能为 0)。调用方按 stream_flag 累积分片,在流结束后(END 或 ONE_SHOT)解码整图。format/width/height 仅在 START/ONE_SHOT 从 image-params 解析得到,MIDDLE/END 时为 0。

tai_event_msg_t(事件回调):

字段类型说明
event_typeuint16_tTAI_EVT_*
dataconst uint8_t *事件负载(通常为 JSON)
lensize_t负载字节数
event_idconst char *attr 61(借用);无则为 ""
user_dataconst uint8_t *attr 111(借用),非 NUL 结尾;无则为 NULL
user_data_lensize_tuser_data 字节数;与事件负载分离,SDK 不解析其 JSON
ChatBreak 打断时间

TAI_EVT_CHAT_BREAK 的服务端时间不在事件负载里,而在 attr 111 中:{"breakAttributes":{"time":"<server-time>"}},值为服务端 epoch 毫秒,与 on_audio 的 timestamp_ms 同钟同单位。读取 msg->user_data、解析 breakAttributes.time,再与 on_audio 中锁定的 timestamp_ms 比较,即可判定该流是否已过期。MQTT 侧的 asrInterrupt 走另一条路径:时间在其自身负载的 time 字段,且是同一个服务端时间值。

tai_disconnect_msg_t(断连回调):

字段类型说明
reasonuint8_tTAI_DISCONNECT_*(见下表)
close_codeuint16_t服务端关闭码(SESSION/CONNECTION);其他情况为 0
detailuint8_tTAI_TRANSPORT_* / TAI_PROTO_ERR_*(见下表);其他情况为 0
connection_aliveuint8_t仅当 reason==SESSION_CLOSE 时为 1
session_idchar[64]值拷贝;无则为 ""
生命周期

msg 以及其内部所有指针(data / text / event_id 等)仅在回调执行期间有效——SDK 从不堆分配它们。如需在回调返回后保留,请自行拷贝。

断连原因(reason)​

宏值说明
TAI_DISCONNECT_SESSION_CLOSE0服务端 SessionClose;链路可能仍存活(connection_alive==1)
TAI_DISCONNECT_CONNECTION_CLOSE1服务端 ConnectionClose;worker 停止
TAI_DISCONNECT_TRANSPORT2worker 检测到传输层故障
TAI_DISCONNECT_PROTOCOL3fail-fast:解析/行为错误

detail(当 reason==TRANSPORT)​

宏值说明
TAI_TRANSPORT_PING_TIMEOUT1Ping 超时
TAI_TRANSPORT_EOF2对端关闭(EOF)
TAI_TRANSPORT_NET_ERROR3网络错误

detail(当 reason==PROTOCOL)​

宏值说明
TAI_PROTO_ERR_BAD_VERSION1未知帧首字节(错位)
TAI_PROTO_ERR_HMAC2帧 HMAC 校验失败
TAI_PROTO_ERR_FRAME_DECODE3帧头解码失败
TAI_PROTO_ERR_FRAG4孤立 MIDDLE/LAST、溢出、超大
TAI_PROTO_ERR_PKT_DECODE5应用包 / 属性块格式错误
TAI_PROTO_ERR_UNKNOWN_PKT6未知包类型(严格模式)
TAI_PROTO_ERR_EVENT7事件解包失败 / 未知事件类型
TAI_PROTO_ERR_MEDIA_HDR8媒体/文本头截断
TAI_PROTO_ERR_UNEXPECTED9包合法但状态错误(行为错误)
TAI_PROTO_ERR_OVERSIZED10入站帧超出 rx_buf

4. 生命周期 API​

tai_ctx_size​

size_t tai_ctx_size(void);

返回 context 所需的内存大小。调用方需分配至少此大小的内存,传给 tai_ctx_init。


tai_ctx_init​

tai_ctx_t *tai_ctx_init(void *mem, const tai_config_t *cfg);

初始化 context。mem 必须至少为 tai_ctx_size() 字节,且在 context 生命周期内保持有效。

参数:

  • mem — 预分配的内存块
  • cfg — 配置结构体

返回值: 成功返回 tai_ctx_t*(即 mem 强转),失败返回 NULL。


tai_ctx_deinit​

void tai_ctx_deinit(tai_ctx_t *ctx);

反初始化 context,释放内部资源。调用后 mem 可以 free。必须在 tai_disconnect 之后调用。


tai_connect​

int tai_connect(tai_ctx_t *ctx);

执行 TLS 握手、发送 ClientHello、建立会话、启动后台接收线程。

返回值: TAI_OK 成功,TAI_ERR_* 失败。

行为:

  • 阻塞直到连接建立或失败
  • 成功后,后台线程开始处理 Ping/Pong 和接收数据
  • 连接成功后即可调用 tai_send_* 系列函数

tai_disconnect​

void tai_disconnect(tai_ctx_t *ctx);

先停止并 join 后台接收线程,再发送一个 best-effort 的 SessionClose,最后关闭传输(TLS / TCP)。

行为:

  • 阻塞直到后台线程退出(join)
  • 调用后不可再发送数据
  • 之后应调用 tai_ctx_deinit
  • 不可在回调中调用(会 join 自身所在的 worker 线程,导致自死锁);如需从回调中触发断连,请用 tai_request_disconnect

tai_request_disconnect​

void tai_request_disconnect(tai_ctx_t *ctx);

请求后台 worker 停止,但不 join、不拆除连接。可从任意线程调用,包括从接收回调内部调用(与 tai_disconnect 不同)。worker 会在下一轮循环退出。

用途: 在回调中安全地触发断连。调用后,拥有 context 的线程仍需调用 tai_disconnect() 来 join worker、发送 SessionClose 并释放资源。


5. 发送 API​

tai_send_text​

int tai_send_text(tai_ctx_t *ctx, const char *text, size_t len);

发送文本消息。一次调用内部依次发出 4 个应用包:EventStart → 文本(ONE_SHOT 标志)→ EventPayloadsEnd → EventEnd,调用方无需再手动收尾。

参数:

  • text — UTF-8 文本
  • len — 文本字节数

返回值: TAI_OK 或 TAI_ERR_*。


tai_send_audio_start​

int tai_send_audio_start(tai_ctx_t *ctx,
uint8_t codec, uint8_t channels,
uint8_t bit_depth, uint32_t sample_rate);

开始一段音频流。必须在 tai_send_audio_chunk 之前调用。

云端 VAD 模式

启用云端 VAD(Server VAD,连续对话)时,整个聊天会话只调用一次本函数,不要每个回合重复调用;上行音频流保持打开直到会话结束。详见 VAD 与打断 中的"云端 VAD vs 设备端 VAD"对比表。

参数:

  • codec — 编码格式:TAI_AUDIO_PCM(101)或 TAI_AUDIO_OPUS(111)
  • channels — 声道数(通常为 1)
  • bit_depth — 位深(通常为 16)
  • sample_rate — 采样率(通常为 16000)

tai_send_audio_chunk​

int tai_send_audio_chunk(tai_ctx_t *ctx, const uint8_t *pcm, size_t len);

发送一帧音频数据。可多次调用(流中间包)。

参数:

  • pcm — 音频数据(PCM 或 Opus 帧,取决于 audio_start 时的 codec)
  • len — 数据字节数

tai_send_audio_end​

int tai_send_audio_end(tai_ctx_t *ctx);

结束音频流。通知服务端本次音频输入完毕,开始处理。

云端 VAD 模式下不要调用

启用云端 VAD(asr.enableVad)时不应调用本函数。当前云端的回合结束信号是 TAI_EVT_CHAT_BREAK(TAI_EVT_SERVER_VAD 已不再下发),收到它也不要调用本函数。它仅用于手动按键对讲或设备端本地 VAD 判停。错误调用会主动结束当前上行 Event,导致云端截断用户语音。详见 VAD 与打断。


tai_send_image​

int tai_send_image(tai_ctx_t *ctx,
const uint8_t *data, size_t len,
uint8_t format, uint16_t width, uint16_t height);

发送单张图片。

参数:

  • data — 图片二进制数据
  • len — 数据长度
  • format — 图片格式:TAI_IMG_JPEG(1)或 TAI_IMG_PNG(2)
  • width / height — 图片尺寸

tai_send_image_with_text​

int tai_send_image_with_text(tai_ctx_t *ctx,
const char *text, size_t text_len,
const uint8_t *img_data, size_t img_len,
uint8_t format,
uint16_t width, uint16_t height);

同时发送文本和图片(如图片理解场景)。内部会先发文本包再发图片包,组成一次完整请求。


tai_chat_break​

int tai_chat_break(tai_ctx_t *ctx);

发送聊天打断事件。用于用户主动打断 AI 回复(如按键中断)。服务端收到后会停止当前响应生成。


tai_send_mcp_response​

int tai_send_mcp_response(tai_ctx_t *ctx, const char *json_rpc_response);

发送 MCP 命令的响应。当 on_event 收到 TAI_EVT_MCP_CMD 时,设备执行完命令后通过此函数返回结果。

参数:

  • json_rpc_response — JSON-RPC 格式的响应字符串

6. 日志​

SDK 日志的全局开关是编译期的 AGENTIC_KIT_LOG_LEVEL(在 common/log.h 统一设限,作用于整个 SDK,不限于本模块);本模块可再用 AGENTIC_KIT_TAI_LOG_LEVEL 单独压低(原 TAI_LOG_LEVEL 的改名,只降不升,默认等于全局值)。有效值:

值含义
0全关
1TAI_LOG_ERROR
2TAI_LOG_WARN
3TAI_LOG_INFO
4TAI_LOG_DEBUG(默认)

高于上限的日志在编译期消除;上限以下无条件输出——没有运行时级别(tai_set_log_level() 已随运行时层一起移除)。量产用 -DAGENTIC_KIT_LOG_LEVEL=2 压到 error + warn,或用 -DAGENTIC_KIT_TAI_LOG_LEVEL=N 只压本模块(低于 3 时 tai_log_packet 的 JSON 格式化器连同调用一并消失);改变日志去向(或按级别丢弃)同样是构建决策:定义 AGENTIC_KIT_LOG 把分发接管进你自己的宏。

媒体中间帧默认按 1/N 采样打 INFO(AGENTIC_KIT_TAI_LOG_MEDIA_SAMPLE_N,默认 50;设 0 = 关采样进 flood 模式)。flood 模式下中间帧改打 DEBUG,仅当有效上限为 4 时才输出——上限为 3 时把采样设为 0,中间帧会完全没有日志(比默认采样更少,别误判成丢包)。


7. 典型使用流程​

#include "tuya_ai.h"

// 1. 分配内存
void *mem = malloc(tai_ctx_size());

// 2. 配置
tai_config_t cfg = {
.host = "ai-gw.example.com",
.port = 8883,
.device_id = devid,
.local_key = local_key,
.client_type = TAI_CLIENT_DEVICE,
.protocol_version = TAI_VER_21,
.sign_level = TAI_SIGN_HMAC_SHA256,
.pal = &my_pal,
.on_audio = my_on_audio,
.on_text = my_on_text,
.on_image = my_on_image,
.on_event = my_on_event,
.on_disconnect = my_on_disconnect,
};

// 3. 初始化 + 连接
tai_ctx_t *ctx = tai_ctx_init(mem, &cfg);
if (tai_connect(ctx) != TAI_OK) { /* handle error */ }

// 4. 发送文本
tai_send_text(ctx, "hello", 5);

// 5. 或发送音频流
tai_send_audio_start(ctx, TAI_AUDIO_PCM, 1, 16, 16000);
while (has_audio) {
tai_send_audio_chunk(ctx, pcm_frame, frame_len);
}
tai_send_audio_end(ctx); // 手动模式收尾;云端 VAD 模式下不要调用 audio_end

// 6. 响应通过回调异步接收...

// 7. 清理
tai_disconnect(ctx);
tai_ctx_deinit(ctx);
free(mem);

音频流的调用模式取决于 VAD 模式(云端 VAD 连续对话 vs 设备端 VAD/手动按键),详见 VAD 与打断 中的对比表。


8. 线程安全​

  • tai_send_* 函数是线程安全的,可以从任意线程调用
  • 所有回调在同一个后台接收线程中按序调用
  • 回调中可以调用 tai_send_*()(不持有锁)
  • 回调中不可调用 tai_connect、tai_disconnect 或 tai_ctx_deinit(它们会 join worker 线程,导致自死锁)
  • 如需在回调中触发断连,应调用 tai_request_disconnect(),再由拥有 context 的线程调用 tai_disconnect()
© 2026 涂鸦智能Tuya Physical AI · Agentic-kit