Skip to content

Nano 接入指南

示例仓库见 tirtc-example-nano

前置条件

开始前先准备好下面这些信息:

项目用途
目标平台信息告诉我们你的目标平台、芯片架构和运行环境
工具链非常见平台时,用于由我们编译对应平台库
license设备身份凭证,格式为 "<device_id>,<key>"

获取 SDK

TiRTC Nano 默认由我们负责编译和交付。

你需要:

  1. 告诉我们你的目标平台信息
  2. 拿到我们交付给你的 Nano SDK

如果你的平台比较常见,我们会直接交付对应平台库;如果你的平台有特殊工具链或 sysroot 约束,再由你提供必要编译环境,我们负责编译。

集成 SDK

把我们交付的头文件和库接入你的设备工程。

示例工程、构建脚本和后续接入样例,优先参考 tirtc-example-nano 仓库。

下面给出一个最小 Linux 编译示意:

bash
gcc -o device_app device_app.c \
  -I/path/to/TIRTC_NANO/include/tirtc \
  -L/path/to/TIRTC_NANO/lib \
  -lTiRTC -lTGTRP -lpthread

实际集成以交付包和配套构建说明为准。

准备回调和设备侧启动参数

设备侧启动前,至少先准备好下面这些内容:

  • 传给 TiRtcStart()TIRTCCALLBACKS
  • service_entry:设备启动后要访问的 TiRTC 服务入口
  • license:设备身份凭证,格式为 "<device_id>,<key>"

如果你的设备还需要控制资源或联网方式,再按需准备这些可选参数:

  • TIRTC_OPT_MAX_CONNECTIONS
  • TIRTC_OPT_NETWORK_TYPE
  • TIRTC_OPT_ICCID
  • TIRTC_OPT_WAKEUP
  • TIRTC_OPT_RESTRICTED_NETWORK
  • TIRTC_OPT_MAX_SEND_BUFFER

其中有两个时序约束要单独记住:

  • 如果使用 TIRTC_OPT_NETWORK_TYPE 且设为 TIRTC_NETCONN_4GTIRTC_OPT_ICCID 就是必填项
  • 如果要修改 TIRTC_OPT_MAX_SEND_BUFFER,必须在 TiRtcInit() 之前设置

注意:

  • 设备侧调用 TiRtcStart() 启动服务时不需要 token
  • 只有当当前进程还要主动调用 TiRtcConnect() 去连接别的设备时,才需要 token
  • 传给 TiRtcStart()TIRTCCALLBACKS 不能指向临时变量

初始化并启动设备服务

设备端按这个顺序启动即可:

  1. 先准备好传给 TiRtcStart()TIRTCCALLBACKS
  2. 调用 TiRtcInit(),再调用 TiRtcLogConfig() / TiRtcLogSetLevel()TiRtcSetOpt(),设置日志、service_entry 等启动参数
  3. 调用 TiRtcStart(license, &callbacks) 启动设备服务,并等待 on_event(TiEVENT_SYS_STARTED, ...)

下面是一段可直接对照接入的最小设备端骨架:

c
#include <stdio.h>
#include <string.h>

#include "tiRTC.h"

static void on_event(int event, const void *data, int len)
{
    (void)data;
    (void)len;
    if (event == TiEVENT_SYS_STARTED) {
        printf("TiRTC started\n");
    } else if (event == TiEVENT_SYS_STOPPED) {
        printf("TiRTC stopped\n");
    }
}

static void on_conn_accepted(tirtc_conn_t hconn)
{
    printf("accepted conn=%p\n", (void *)hconn);
}

static void on_conn_error(tirtc_conn_t hconn, int error)
{
    printf("conn error conn=%p error=%d\n", (void *)hconn, error);
}

static const TIRTCCALLBACKS k_callbacks = {
    .on_event = on_event,
    .on_conn_accepted = on_conn_accepted,
    .on_conn_error = on_conn_error,
};

int start_device(const char *service_entry, const char *license)
{
    int ret = TiRtcInit();
    if (ret != 0) {
        return ret;
    }

    TiRtcLogConfig(1, NULL, 0);
    TiRtcLogSetLevel(5);
    TiRtcSetOpt(TIRTC_OPT_SERVICE_ENTRY, service_entry, (uint32_t)strlen(service_entry));

    ret = TiRtcStart(license, &k_callbacks);
    if (ret != 0) {
        TiRtcUninit();
        return ret;
    }

    return 0;
}

注意:

  • TIRTC_OPT_MAX_SEND_BUFFER 是特例,如果你要改它,要在 TiRtcInit() 之前调用 TiRtcSetOpt()
  • license 的格式是 "<device_id>,<key>"
  • 传给 TiRtcStart()TIRTCCALLBACKS 不能指向临时变量
  • TiRtcStart() 返回 0 只表示参数通过初步检查,真正启动成功要等 TiEVENT_SYS_STARTED

on_conn_accepted() 中建立连接上下文

设备上线后,远端查看端会主动连接这台设备。设备侧收到新连接时,入口回调是 on_conn_accepted()

从这个回调开始,这条连接进入应用自己的业务处理流程。你通常要在这里完成三件事:

  1. 按需为这条连接创建自己的上下文
  2. TiRtcConnSetUserData() 把上下文挂到 hconn
  3. 关联这条连接需要的业务状态,例如发送线程、流开关状态、待应答命令队列

流控方式由业务自己决定:要么等对端发出 on_request_video() / on_request_audio() 之后再送流,要么连接建立后默认开始送流。

c
typedef struct {
    tirtc_conn_t hconn;
    int video_enabled[4];
    int audio_enabled[4];
} DeviceConnCtx;

static void on_conn_accepted(tirtc_conn_t hconn)
{
    DeviceConnCtx *ctx = calloc(1, sizeof(*ctx));
    ctx->hconn = hconn;
    TiRtcConnSetUserData(hconn, ctx);
}

static void on_disconnected(tirtc_conn_t hconn)
{
    DeviceConnCtx *ctx = (DeviceConnCtx *)TiRtcConnGetUserData(hconn);
    free(ctx);
}

发送音视频

设备把媒体数据按 Nano 的帧接口发送给 SDK。

先约定好 stream_id

stream_id 的取值范围是 0~15。它在同一条连接内是全局唯一的,音频和视频不能重号。你需要先和查看端约定每一路视频流、音频流分别使用哪个 stream_id,再按这个约定收发媒体数据。

选择发送方式

发送方式有两种,按你的业务形态选一种:

  • 固定发送:连接建立后,设备直接开始发送约定好的音视频流
  • 按需发送:查看端通过 on_request_video()on_release_video()on_request_audio()on_release_audio() 通知设备开始或停止某一路流,on_request_iframe() 用来请求下一帧关键帧

例如,你可以约定主视频 0、子视频 1、主音频 2、对讲音频 3。也可以按自己的业务约定其他编号,只要同一条连接里不重号即可。

发送媒体帧

下面是一个最小 H.264 发送示例:

c
static int send_h264_frame(tirtc_conn_t hconn,
                           uint8_t stream_id,
                           const void *data,
                           uint32_t len,
                           uint32_t timestamp_ms,
                           int is_key_frame)
{
    TIRTCFRAMEINFO fi;
    memset(&fi, 0, sizeof(fi));
    fi.stream_id = stream_id;
    fi.media = TIRTC_VIDEO_H264;
    fi.flags = is_key_frame ? TIRTC_FRAME_FLAG_KEY_FRAME : 0;
    fi.ts = timestamp_ms;
    fi.length = len;
    return TiRtcSendVideo(hconn, &fi, data);
}

媒体发送时有几个规则不能踩:

  • 某一路视频一旦开始发送,建议先立刻送出一个 I 帧;如果收到了 on_request_iframe(),也应尽快补一个 I 帧
  • 每路视频流的第一帧必须是关键帧
  • fi.length 只填写 payload 长度,不包含 TIRTCFRAMEINFO 自身
  • 音频帧的 flags 填采样规格,而不是关键帧标记
  • 如果返回 TIRTC_E_BUSY,说明发送缓冲区已满,需要限流、降帧或等待,而不是继续堆送

收发命令和流内消息

除了音视频,Nano 还提供两类常用数据通路:

  • 命令通道:适合状态查询、控制指令、请求-应答
  • 流内消息:适合跟随某一路媒体时间线的轻量消息

命令通道

命令通道的数据通过 on_data() 收进来。收到命令后,先区分“请求”还是“应答”,再分别处理。命令 ID 的低 15 位由应用自己定义,但 0x1000 以下保留给 SDK 内部使用。请求-应答场景下,正确顺序是:先拿 SN,先登记等待项,再发送命令。

c
#define CMD_QUERY_STREAM 0x1004

static void request_stream_info(tirtc_conn_t hconn, uint8_t stream_id)
{
    uint32_t sn = atomic_get_cmd_sn();
    register_pending(sn, CMD_QUERY_STREAM);
    TiRtcSendReqWithSn(hconn, sn, CMD_QUERY_STREAM, &stream_id, sizeof(stream_id));
}

流内消息

流内消息通过 TiRtcSendMedia() 发送,只是把 media 设成 TIRTC_MEDIA_MESSAGE

c
static void send_stream_message(tirtc_conn_t hconn,
                                uint8_t stream_id,
                                const char *text,
                                uint32_t timestamp_ms)
{
    TIRTCFRAMEINFO fi;
    memset(&fi, 0, sizeof(fi));
    fi.stream_id = stream_id;
    fi.media = TIRTC_MEDIA_MESSAGE;
    fi.ts = timestamp_ms;
    fi.length = (uint32_t)strlen(text);
    TiRtcSendMedia(hconn, &fi, text);
}

断开连接并释放资源

设备侧的收尾动作按这个顺序处理:

  1. 某条连接出错或业务结束时,先停掉这条连接上的发送线程或其他会继续使用 hconn 的工作线程
  2. 再调用 TiRtcDisconnect(hconn) 做本地收尾;这是异步调用,返回后就不要再继续使用这个 hconn
  3. on_disconnected() 或你自己的收尾路径里释放连接上下文
  4. 设备整体退出时,调用 TiRtcStop(),并等待 TiEVENT_SYS_STOPPED
  5. 最后调用 TiRtcUninit()

这里的关键约束是:先让连接级业务线程退出,再调用 TiRtcDisconnect();并且在 TiRtcDisconnect() 返回后,就不要再在这个 hconn 上继续做操作。

日志与排查

接入前先确定日志的落盘、导出和回传方式。

日志输出有两种常见方式:

  • Linux 设备:调用 TiRtcLogConfig() 输出到文件
  • 小系统或 RTOS:调用 TiRtcLogSetCallback() 接管日志;设置回调后 SDK 不再自己输出日志
c
TiRtcLogConfig(0, "/tmp/tirtc.log", 512 * 1024);
TiRtcLogSetLevel(5);

排查时先看:

  • 是否收到 TiEVENT_SYS_STARTED
  • 是否触发 on_conn_accepted()
  • 是否收到了 on_request_video() / on_request_audio(),却没有开始发送对应流
  • 发送侧是否频繁返回 TIRTC_E_BUSY
  • 连接出错后是否继续误用已经失效的 hconn

下一步

Ti RTC 开发文档