跳到主要内容

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_texttai_send_audio_*tai_send_image),无需手动组装数据结构体
  • 后台接收线程tai_connect 后自动启动后台线程处理接收和 keepalive
  • 回调驱动:通过 on_audioon_texton_imageon_eventon_disconnect 接收数据
  • 无外部依赖:用户 PAL 只需提供原始 TCP 和平台原语;TLS 和加密由 SDK 内部通过 bundled mbedTLS 处理

2. 常量定义

2.1 数据包类型

说明
TAI_PKT_CLIENT_HELLO1客户端握手
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聊天打断(用户中途说话)
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_DEVICETAI_CLIENT_APP
protocol_versionuint8_t协议版本:使用 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业务码
biz_taguint64_t业务标签

3.5 安全配置

字段类型说明
sign_leveluint8_t签名级别:TAI_SIGN_NONE / TAI_SIGN_HMAC_SHA256(推荐)

3.6 保活配置

字段类型说明
ping_interval_msuint32_tPing 间隔(0 = 默认 60000ms)
ping_timeout_msuint32_tPing 超时(0 = 默认 90000ms)
connect_timeout_msuint32_t连接超时(0 = 默认 5000ms)。分别约束 tai_connect 的两个串行等待阶段:先是 TLS 握手,再是服务端 SessionNew 应答。任一阶段超时即判定连接失败,因此 tai_connect 最坏耗时约为该值的 2 倍(握手 + 应答)。

3.7 测试配置

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

3.8 平台适配

字段类型说明
palconst pal_t *平台适配层实现指针

3.9 回调函数

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

字段类型说明
on_audiofunction pointer音频数据回调
on_textfunction pointer文本数据回调
on_imagefunction pointer图像数据回调(云端生成的图片)
on_eventfunction pointer事件回调(MCP、打断、VAD 等)
on_disconnectfunction pointer断连回调
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);

接收消息结构体

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流起始时间戳(媒体头)

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(借用);无则为 ""

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);

发送文本消息。单包完成(内部设置 ONE_SHOT 标志)。

参数:

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

返回值: TAI_OKTAI_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 之前调用。

参数:

  • 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);

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


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. 日志

static inline void tai_set_log_level(int level);
static inline int tai_get_log_level(void);

设置/获取运行时日志级别。有效值:

含义
0禁用所有日志
1TAI_LOG_ERROR
2TAI_LOG_WARN
3TAI_LOG_INFO
4TAI_LOG_DEBUG(默认)

编译时可通过定义 TAI_LOG_LEVEL 宏设置最大编译级别(超过的日志在编译期消除)。


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);

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

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

8. 线程安全

  • tai_send_* 函数是线程安全的,可以从任意线程调用
  • 所有回调在同一个后台接收线程中按序调用
  • 回调中可以调用 tai_send_*()(不持有锁)
  • 回调中不可调用 tai_connecttai_disconnecttai_ctx_deinit(它们会 join worker 线程,导致自死锁)
  • 如需在回调中触发断连,应调用 tai_request_disconnect(),再由拥有 context 的线程调用 tai_disconnect()