Skip to content

TiRTC Nano Integration Guide

Example repository: https://github.com/tangeai/tirtc-example-nano

Prerequisites

Prepare the following information before you start:

ItemPurpose
Target platform informationTells us the target platform, chip architecture, and runtime environment
ToolchainRequired when your target platform is uncommon and needs a custom build
service_entryThe TiRTC service entry URL
licenseThe device identity credential, formatted as "<device_id>,<key>"
Logging strategyLinux can log to files; small systems can take over logging via callback

For common platforms, we can compile and deliver the Nano SDK directly.

Get the SDK

TiRTC Nano is usually built and delivered by us.

You need to:

  1. Tell us your target platform details
  2. Obtain the Nano SDK package we deliver for that target

For common platforms, we deliver the corresponding library directly. If your platform has special toolchain or sysroot requirements, you provide the required build environment and we handle the build.

Integrate the SDK

Integrate the delivered headers and libraries into your device project.

For sample projects, build scripts, and follow-up integration examples, use the tirtc-example-nano repository as the primary reference.

Here is a minimal Linux build example:

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

Use the delivered package and its build instructions as the source of truth for real integration.

Prepare the Callbacks and Device-Side Startup Parameters

Before the device starts, prepare at least the following:

  • The TIRTCCALLBACKS passed to TiRtcStart()
  • service_entry: the TiRTC service entry the device connects to after startup
  • license: the device identity credential, formatted as "<device_id>,<key>"

If your device also needs resource control or network-specific behavior, prepare these optional settings as needed:

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

There are two timing constraints worth keeping in mind:

  • If you use TIRTC_OPT_NETWORK_TYPE and set it to TIRTC_NETCONN_4G, TIRTC_OPT_ICCID becomes mandatory
  • If you need to change TIRTC_OPT_MAX_SEND_BUFFER, you must set it before TiRtcInit()

Notes:

  • The device does not need a token when it calls TiRtcStart()
  • A token is needed only if this process also actively calls TiRtcConnect() to connect to another device
  • The TIRTCCALLBACKS passed to TiRtcStart() must not point to a temporary variable

Initialize and Start the Device Service

Start the device in this order:

  1. Prepare the TIRTCCALLBACKS that will be passed to TiRtcStart()
  2. Call TiRtcInit(), then call TiRtcLogConfig() / TiRtcLogSetLevel() and TiRtcSetOpt() to configure logging and startup parameters such as service_entry
  3. Call TiRtcStart(license, &callbacks) to start the device service, then wait for on_event(TiEVENT_SYS_STARTED, ...)

The following minimal device-side skeleton can be used as a direct reference:

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

Notes:

  • TIRTC_OPT_MAX_SEND_BUFFER is a special case. If you need to change it, call TiRtcSetOpt() before TiRtcInit()
  • license must use the format "<device_id>,<key>"
  • The TIRTCCALLBACKS passed to TiRtcStart() must not point to a temporary variable
  • A return value of 0 from TiRtcStart() only means the parameters passed basic validation. Actual startup success is confirmed after TiEVENT_SYS_STARTED

Initialize Connection Context in on_conn_accepted()

After the device is online, a remote viewer connects to it actively. When the device receives a new connection, the entry callback is on_conn_accepted().

From this callback onward, that connection enters your application's business flow. In practice, you usually do three things here:

  1. Create your own per-connection context when needed
  2. Attach that context to hconn through TiRtcConnSetUserData()
  3. Associate the connection with the business state it needs, such as sender threads, stream-enable state, or pending-command state

Stream-control behavior is up to your business flow: you can wait for on_request_video() / on_request_audio() before sending, or you can start sending by default right after the connection is accepted.

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

Send Audio and Video

The device sends media data to the SDK through the Nano frame-based interfaces.

Agree on stream_id First

stream_id ranges from 0 to 15. It must be globally unique within the same connection. Audio and video cannot reuse the same value. Before sending and receiving media, agree with the viewer on the stream_id used by each video stream and audio stream.

Choose the Send Strategy

There are two send strategies. Choose the one that matches your product behavior:

  • Fixed sending: the device starts sending the agreed streams immediately after the connection is established
  • On-demand sending: the viewer uses on_request_video(), on_release_video(), on_request_audio(), and on_release_audio() to tell the device when to start or stop a stream, and uses on_request_iframe() to request the next keyframe

For example, you can use this stream_id layout: main video 0, sub video 1, main audio 2, intercom audio 3. You can also define a different layout as long as the IDs remain unique within one connection.

Send Media Frames

Here is a minimal H.264 send example:

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

Do not violate these media-send rules:

  • Once a video stream starts sending, it is recommended to send an I-frame immediately; if you receive on_request_iframe(), you should also provide an I-frame as soon as possible
  • The first frame of each video stream must be a keyframe
  • fi.length must contain only the payload length, not the size of TIRTCFRAMEINFO
  • For audio frames, flags must carry the sample format rather than a keyframe flag
  • If the function returns TIRTC_E_BUSY, the send buffer is full; apply rate limiting, reduce frames, or wait instead of continuing to push more data

Send and Receive Commands and In-Stream Messages

In addition to audio and video, Nano provides two common data paths:

  • Command channel: suitable for state queries, control instructions, and request-response exchanges
  • In-stream messages: suitable for lightweight messages aligned with the media timeline of a specific stream

Command Channel

Command-channel packets are received through on_data(). After a packet arrives, first decide whether it is a request or a response, then dispatch accordingly.

The lower 15 bits of the command ID are defined by your application, but values below 0x1000 are reserved for internal SDK use. In request-response scenarios, the correct order is: obtain the SN, register the pending item, and then send the command.

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

In-Stream Messages

In-stream messages are sent through TiRtcSendMedia() with media set to 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);
}

Disconnect and Release Resources

Handle device-side teardown in this order:

  1. When a connection errors out or the business flow ends, stop the send thread or any other worker thread that may still use hconn
  2. Call TiRtcDisconnect(hconn) for local cleanup; this call is asynchronous, and once it returns you must stop using that hconn
  3. Release the connection context in on_disconnected() or in your own teardown path
  4. When the device exits as a whole, call TiRtcStop() and wait for TiEVENT_SYS_STOPPED
  5. Finally call TiRtcUninit()

The key rule here is: stop per-connection worker threads first, then call TiRtcDisconnect(). Once TiRtcDisconnect() returns, do not continue operating on that hconn.

Logs and Troubleshooting

Decide how logs are stored, exported, and uploaded before integration starts.

Two common logging patterns are:

  • Linux devices: call TiRtcLogConfig() to write logs to a file
  • Small systems or RTOS: call TiRtcLogSetCallback() to take over log output; once the callback is set, the SDK stops writing logs by itself
c
TiRtcLogConfig(0, "/tmp/tirtc.log", 512 * 1024);
TiRtcLogSetLevel(5);

When troubleshooting, start with these checks:

  • Did you receive TiEVENT_SYS_STARTED
  • Did on_conn_accepted() fire
  • Did you receive on_request_video() or on_request_audio() but fail to start sending the corresponding stream
  • Is the sender frequently returning TIRTC_E_BUSY
  • After a connection error, is your code still trying to use an invalid hconn

Next

TiRTC