设备端集成
设备端集成的目标是使用业务服务端下发的 peer_id 和 token 建立 TiRTC 连接,发送 start_session 启动会话,然后完成音频上下行、事件处理和会话结束。
本页假定设备端已完成 TiRTC SDK 集成。如果尚未集成,请先参阅 TiRTC 设备端集成文档。
建立 TiRTC 连接
设备收到 peer_id 与 token 后,调用 TiRTC SDK 建立连接。
#include "tiRTC.h"
#include <stdbool.h>
#include <string.h>
static tirtc_conn_t g_ai_conn = NULL;
static bool g_tirtc_ready = false;
static int ai_tirtc_runtime_init(const char *device_id)
{
if (g_tirtc_ready) {
return 0;
}
int ret = TiRtcInit();
if (ret != 0) {
return ret;
}
TIRTCCALLBACKS cbs;
memset(&cbs, 0, sizeof(cbs));
ret = TiRtcStart(device_id, &cbs);
if (ret != 0) {
TiRtcUninit();
return ret;
}
g_tirtc_ready = true;
return 0;
}
static void on_ai_whip(int err, tirtc_conn_t hconn, void *user_data)
{
(void)user_data;
if (err != 0) {
/* 建连失败:记录错误码,释放本地会话状态 */
return;
}
g_ai_conn = hconn;
/* 建连成功后,下一步发送 start_session 命令 */
}
int start_ai_tirtc_connect(const char *device_id, const char *peer_id, const char *token)
{
int ret = 0;
if (device_id == NULL || device_id[0] == '\0' ||
peer_id == NULL || peer_id[0] == '\0' ||
token == NULL || token[0] == '\0') {
return -1;
}
ret = ai_tirtc_runtime_init(device_id);
if (ret != 0) {
return ret;
}
/* 返回 0 表示请求已提交,最终是否成功以 on_ai_whip 回调为准 */
return TiRtcWhipConnect(peer_id, token, on_ai_whip, NULL);
}
void stop_ai_tirtc_connect(void)
{
if (g_ai_conn != NULL) {
TiRtcClose(g_ai_conn);
g_ai_conn = NULL;
}
if (g_tirtc_ready) {
TiRtcStop();
TiRtcUninit();
g_tirtc_ready = false;
}
}TiRtcWhipConnect 返回 0 只表示建连请求已提交;真正成功以回调中的 err == 0 为准。
发送 start_session
TiRTC 连接建立后,设备端需要通过命令通道发送 start_session。关于详细字段格式和默认值说明,请参考 事件协议。
role_id 为必传字段,必须使用业务服务端下发的本次会话角色 ID。
{
"jsonrpc": "2.0",
"id": "start-session-001",
"method": "start_session",
"params": {
"device_id": "DEMO_DEVICE_01",
"role_id": "role_xxx",
"input_audio": {
"codec": "opus",
"sample_rate": 16000,
"channels": 1
},
"output_audio": {
"codec": "opus",
"sample_rate": 16000,
"channels": 1
}
}
}收到成功响应后,设备应以响应中的 input_audio 和 output_audio 作为本次会话实际音频格式,然后开始持续上行音频。
支持的音频格式与采样率
| codec | 采样率(Hz) | 声道 |
|---|---|---|
opus | 16000 | 1 |
pcm | 16000、8000 | 1 |
g711a | 16000、8000 | 1 |
amr | 8000(NB)、16000(WB) | 1 |
会话实际使用的格式以 start_session 成功响应中的 input_audio 和 output_audio 为准。完整说明见 事件协议。
上行设备语音
设备端在采集线程中按固定周期发送音频帧,建议单帧时长控制在 100 ms 以内。
#include <stdint.h>
#include <string.h>
#include "tiRTC.h"
static const uint8_t kAiAudioStreamId = 1;
int ai_send_audio_opus(tirtc_conn_t hconn,
const void *opus_data,
uint32_t len,
uint32_t ts_ms)
{
TIRTCFRAMEINFO fi;
memset(&fi, 0, sizeof(fi));
fi.stream_id = kAiAudioStreamId;
fi.media = TIRTC_AUDIO_OPUS;
// 重要:必需设置与 input_audio 一致的音频格式 flags
fi.flags = TIRTC_AUDIOSAMPLE_16K16B1C;
fi.ts = ts_ms;
fi.length = len;
return TiRtcSendAudioStream(hconn, &fi, opus_data);
}必须确保 start_session.input_audio、fi.media、fi.flags 三者完全对齐,否则可能导致平台侧识别失败或解码异常。
上行设备视频
需要让智能体理解摄像头画面时,在 start_session 成功后继续发送视频帧。平台会把抽取到的画面作为智能体理解当前环境的视觉上下文,回复仍通过原有的下行音频、字幕和事件返回设备。
接入视频前需要确认:
- 设备摄像头和编码器能够输出完整的 H.264 或 H.265 视频帧。
- 当前角色使用支持视觉理解的模型,并且已经启用图像输入能力。
- 设备已经实现音频采集、下行音频播放和会话清理。
媒体流约定
AI 对讲默认使用下面的 stream_id:
| 媒体 | 方向 | stream_id |
|---|---|---|
| 麦克风音频 | 设备 -> 平台 | 1 |
| 回复音频 | 平台 -> 设备 | 1 |
| 摄像头视频 | 设备 -> 平台 | 0 |
视频必须使用 stream_id = 0,与平台当前的视频接收配置保持一致。同一条 TiRTC 连接内,音频和视频不能共用同一个 stream_id。
start_session 只协商音频格式,不返回视频格式。视频编码和 stream_id 按本节约定设置;如果探鸽平台或接入包提供了其他约定,以实际约定为准。
发送视频帧
在 start_session 成功后启动摄像头和编码器,将每个完整的编码帧填入 TIRTCFRAMEINFO,再调用 TiRtcSendVideoStream()。
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "tiRTC.h"
static const uint8_t kAiVideoStreamId = 0;
/**
* 向 AI 对讲平台发送一帧 H.264 视频。
*
* 返回值大于 0 表示帧已进入发送队列,负数表示 TiRTC 错误码。
*/
int ai_send_video_h264(tirtc_conn_t hconn,
const void *frame_data,
uint32_t frame_len,
uint32_t ts_ms,
bool is_key_frame)
{
TIRTCFRAMEINFO fi;
if (hconn == NULL || frame_data == NULL || frame_len == 0) {
return TIRTC_E_INVALID_PARAMETER;
}
memset(&fi, 0, sizeof(fi));
fi.stream_id = kAiVideoStreamId;
fi.media = TIRTC_VIDEO_H264;
fi.flags = is_key_frame ? TIRTC_FRAME_FLAG_KEY_FRAME : 0;
fi.ts = ts_ms;
fi.length = frame_len;
return TiRtcSendVideoStream(hconn, &fi, frame_data);
}如果设备输出 H.265,把 fi.media 改为 TIRTC_VIDEO_H265。不要把 MP4 等容器文件直接作为单帧 payload 发送;应先由编码器或解复用模块输出完整的编码视频帧。
关键帧与发送节奏
- 每路视频流的第一帧必须是关键帧,并设置
TIRTC_FRAME_FLAG_KEY_FRAME。 - 后续每个关键帧都要设置关键帧标志,普通帧的
flags设为0。 fi.ts使用单调递增的毫秒时间戳,不要在同一会话中回退。- 如果
TiRtcSendVideoStream()返回TIRTC_E_BUSY,表示发送缓冲区已满。此时丢弃后续非关键帧,尽快让编码器输出新的关键帧后再恢复发送。 - 结束会话、连接断开或发生错误时,应同时停止摄像头采集和视频编码,不能继续向旧的
hconn发送。
视频接入成功信号
TiRtcSendVideoStream()持续返回正数,没有连续出现TIRTC_E_BUSY或参数错误。- 设备日志能够记录视频
stream_id = 0、编码类型、关键帧标志、时间戳和帧长度。 - 向智能体询问当前画面内容时,回复能够体现设备摄像头中的可见信息。
视频已发送,但智能体无法理解画面
优先检查:
- 视频是否使用
stream_id = 0;其他stream_id的视频帧会被平台忽略。 fi.media是否为TIRTC_VIDEO_H264或TIRTC_VIDEO_H265。- 第一帧以及后续关键帧是否设置了
TIRTC_FRAME_FLAG_KEY_FRAME。 - 当前角色是否使用支持视觉理解的模型,并已启用图像输入。
- payload 是否为完整的编码视频帧,而不是 MP4 文件、RTP 包或不完整的分片。
如果出现 TIRTC_E_BUSY 后视频不再恢复,不要继续堆积普通帧。丢弃普通帧并请求编码器尽快生成关键帧,收到新的关键帧后再恢复发送。
播放下行语音
回复音频会通过 TiRTC 音频流下发。设备端应在音频回调中按 start_session 响应的 output_audio 解码并送入播放链路。
AI 对讲的下行音频默认使用 stream_id = 1。处理建议:
- 按
stream_id == 1过滤 AI 对讲音频流。 - 按
media和协商的codec解码。 - 播放链路应支持连续流式播放,不要等待整句 TTS 结束后再播放。
- 如果收到打断或会话结束事件,应及时停止当前播放缓冲。
处理事件
AI 对讲的控制消息、字幕和设备能力调用都通过命令通道发送。详见 事件协议。
设备端至少需要实现 start_session、caption、round_start、round_end、interrupt、submit_speech、update_config、device_action 和 end_session。
最小跑通路径
- 服务端使用 AppId/AK/SK,通过 TGV1 签名调用
/v1/token/aichat,确保返回peer_id与token。 - 设备端用
peer_id和token调用TiRtcWhipConnect,确认回调err == 0。 - 设备端通过命令通道发送
start_session,确认收到包含session_id的成功响应。 - 设备端发送一小段音频,确认能收到下行
caption事件。 - 如需视频对讲,发送
stream_id = 0的视频关键帧和后续视频帧,并确认智能体能够理解当前画面。 - 测试
interrupt与end_session,确认音频、摄像头、编码器和连接资源能被正确清理。