Skip to content

设备端 C

这页只覆盖设备端单端视角,目标是尽快跑通“引用 Nano SDK -> 启动设备 -> 连上后送流”。 下文以 Linux 为例。其他平台需要提供下面的信息以获取对应的 SDK:

  • 操作系统名称、版本与类型,比如:freeRTOS 或者 Ubuntu 24.04.3 LTS
  • 芯片的具体型号
  • 供电类型(常电或者低功耗)
  • 完整的工具链文件压缩包

前置条件

1. 获取二进制库

从 TiRTC Nano 的发布页下载对应 Linux 平台的二进制包。解压后至少会有头文件和库文件:

text
TIRTC_NANO/
|-- include/
|   |-- tiRTC.h
|   |-- basedef.h
|   `-- ...
`-- lib/
    `-- libTiRTC.a

2. 在工程中引用

把发布包放到 third_party/TIRTC_NANO/ 后,最小 CMakeLists.txt 可以这样写:

cmake
cmake_minimum_required(VERSION 3.16)
project(tirtc_device_demo C)

set(CMAKE_C_STANDARD 11)
set(TIRTC_NANO_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/third_party/TIRTC_NANO)

find_library(TIRTC_LIB
  NAMES TiRTC
  PATHS ${TIRTC_NANO_ROOT}/lib
  NO_DEFAULT_PATH
  REQUIRED
)

add_executable(device_demo src/main.c)

target_include_directories(device_demo PRIVATE
  ${TIRTC_NANO_ROOT}/include
)

target_link_libraries(device_demo PRIVATE
  ${TIRTC_LIB}
  pthread
)

如果你的发布包把头文件放在 include/tirtc/,把 target_include_directories() 改成那个目录即可。

3. 启动设备并送流

下面这份示例只保留首条链路:初始化、注册回调、启动、连上后送流。

这份示例选择的是“连上就发”策略:设备一旦在 on_conn_accepted(hconn) 里拿到连接,就开始往这条连接送音视频。如果你的产品需要按需推流,再把发送开关放到 on_subscribe_video / on_subscribe_audio / on_request_key_frame 这组回调里。

c
#include <pthread.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "tiRTC.h"

static const char *kTag = "TiRtcQuickStart";

enum {
    kVideoStreamId = 10, /* 视频流 stream_id */
    kAudioStreamId = 11, /* 音频流 stream_id */
};

typedef struct {
    tirtc_conn_t hconn;
    volatile int running;
    pthread_t media_stream_thread;
} ConnCtx;

static volatile int g_running = 1;

static int send_h264_frame(tirtc_conn_t hconn,
                           const void *data,
                           uint32_t len,
                           uint32_t ts_ms,
                           int is_key_frame)
{
    TIRTCFRAMEINFO fi;
    memset(&fi, 0, sizeof(fi));
    fi.stream_id = kVideoStreamId;
    fi.media = TIRTC_VIDEO_H264; /* 如果你送 H.265,这里改成 TIRTC_VIDEO_H265 */

    /* flags 是位字段。关键帧标志按位设置,不直接整体覆盖。 */
    if (is_key_frame) {
        fi.flags |= TIRTC_FRAME_FLAG_KEY_FRAME;
    }

    fi.ts = ts_ms;
    fi.length = len;
    return TiRtcSendVideoStream(hconn, &fi, data);
}

static int send_g711a_frame(tirtc_conn_t hconn,
                            const void *data,
                            uint32_t len,
                            uint32_t ts_ms)
{
    TIRTCFRAMEINFO fi;
    memset(&fi, 0, sizeof(fi));
    fi.stream_id = kAudioStreamId;
    fi.media = TIRTC_AUDIO_ALAW;
    fi.flags = TIRTC_AUDIOSAMPLE_8K16B1C; /* 8 kHz、16 bit、单声道 */
    fi.ts = ts_ms;
    fi.length = len;
    return TiRtcSendAudioStream(hconn, &fi, data);
}

static void *media_stream_thread(void *opaque)
{
    ConnCtx *ctx = (ConnCtx *)opaque;

    while (ctx->running) {
        /* 用你的编码器产出替换这两行。 */
        /* send_h264_frame(ctx->hconn, video_data, video_len, video_ts_ms, is_key_frame); */
        /* send_g711a_frame(ctx->hconn, audio_data, audio_len, audio_ts_ms); */

        usleep(10 * 1000);
    }

    return NULL;
}

static void *connection_cleanup_thread(void *opaque)
{
    ConnCtx *ctx = (ConnCtx *)opaque;

    pthread_join(ctx->media_stream_thread, NULL);
    TiRtcDisconnect(ctx->hconn);
    free(ctx);
    return NULL;
}

static void on_event(int event, const void *data, int len)
{
    (void)data;
    (void)len;

    if (event == TiEVENT_SYS_STARTED) {
        printf("[%s] sdk started\n", kTag);
    } else if (event == TiEVENT_SYS_STOPPED) {
        printf("[%s] sdk stopped\n", kTag);
    }
}

static void on_conn_accepted(tirtc_conn_t hconn)
{
    ConnCtx *ctx = (ConnCtx *)calloc(1, sizeof(*ctx));
    ctx->hconn = hconn;
    ctx->running = 1;

    TiRtcConnSetUserData(hconn, ctx);
    pthread_create(&ctx->media_stream_thread, NULL, media_stream_thread, ctx);

    printf("[%s] conn accepted, start streaming\n", kTag);
}

static void on_conn_error(tirtc_conn_t hconn, int error)
{
    ConnCtx *ctx = (ConnCtx *)TiRtcConnGetUserData(hconn);

    printf("[%s] conn error=%d (%s)\n", kTag, error, TiRtcGetErrorStr(error));

    if (ctx != NULL) {
        ctx->running = 0;
        TiRtcConnSetUserData(hconn, NULL);

        /* on_conn_error() 之后,由独立线程调用 TiRtcDisconnect()。 */
        pthread_t tid;
        pthread_create(&tid, NULL, connection_cleanup_thread, ctx);
        pthread_detach(tid);
    }
}

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

static void on_signal(int signo)
{
    (void)signo;
    g_running = 0;
}

int main(void)
{
    const char *device_id = "your-device-id";
    const char *device_secret_key = "your-device-secret-key";

    signal(SIGINT, on_signal);
    signal(SIGTERM, on_signal);

    int code = TiRtcInit();
    if (code != 0) {
        fprintf(stderr, "[%s] TiRtcInit failed: %s\n", kTag, TiRtcGetErrorStr(code));
        return 1;
    }

    TiRtcLogConfig(1, NULL, 0);
    TiRtcLogSetLevel(4);

    code = TiRtcSetOption(
        TIRTC_OPT_SECRET_KEY,
        device_secret_key,
        (uint32_t)strlen(device_secret_key)
    );
    if (code != 0) {
        fprintf(stderr, "[%s] TiRtcSetOption failed: %s\n", kTag, TiRtcGetErrorStr(code));
        TiRtcUninit();
        return 1;
    }

    code = TiRtcStart(device_id, &kCallbacks);
    if (code != 0) {
        fprintf(stderr, "[%s] TiRtcStart failed: %s\n", kTag, TiRtcGetErrorStr(code));
        TiRtcUninit();
        return 1;
    }

    while (g_running) {
        sleep(1);
    }

    TiRtcStop();
    TiRtcUninit();
    return 0;
}

这页示例背后的约束

  • TiRtcStart() 返回 0 只表示参数通过初步检查;真正启动完成以 TiEVENT_SYS_STARTED 为准。
  • kCallbacks 这类 TIRTCCALLBACKS 对象不能是局部临时变量;它的生命周期必须覆盖整个 SDK 运行期。
  • 所有回调都在 Nano 的内部线程执行。回调里只改状态、唤醒线程或投递事件,不要直接做阻塞 I/O、长时间计算或编码工作。
  • on_conn_error() 之后,这条连接上的收发已经不再可靠;不要在回调里直接调用 TiRtcDisconnect(),要像示例这样切到独立线程或外部事件循环里处理。
  • 这份 quick start 采用的是“连上就发”策略;如果你的产品要按需推流,就实现 on_subscribe_video / on_subscribe_audio / on_request_key_frame 来控制发送时机。
  • TiRtcSendVideoStream() 返回 TIRTC_E_BUSY 时,不要继续堆非关键帧;非关键帧可直接丢弃,关键帧可短暂重试。
  • 如果你需要改 TIRTC_OPT_MAX_SEND_BUFFER,必须在 TiRtcInit() 之前设置;如果把 TIRTC_OPT_NETWORK_TYPE 设成 TIRTC_NETCONN_4G,就必须同时提供 TIRTC_OPT_ICCID

4. 和客户端对齐

  • kVideoStreamIdkAudioStreamId 要和客户端的 stream_id 一致
  • 客户端连接这台设备时,remote_id 就填这台设备的 device_id
  • 客户端建连用 token,不要传 device_secret_key

Ti RTC 开发文档