设备端 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.a2. 在工程中引用
把发布包放到 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。