跳到主要内容
Esc

设备端 SDK(C)

本页内容

设备端 SDK 把摄像头固件接入 Rerinku View:设备向 Rerinku View 注册,通过 MQTT 随时接收命令,有人观看或录像时把硬件编码器的输出用 WebRTC(WHIP)推上去。SDK 不负责采集和编码,固件把编码好的帧交给它即可。

  • 纯 C(C11),只需包含一个头文件 <rerinku/rerinku_device.h>。
  • 交付一个静态库 librerinku_device.a(所有依赖都在里面)和一个动态库 librerinku_device.so,两者都只导出 rerinku_* 符号,除 C 库外不依赖任何东西。
  • 支持 CMake(find_package(rerinku_device))和 pkg-config(rerinku_device.pc)。
  • 用芯片厂商的工具链交叉编译;已在瑞芯微 RV1103(Luckfox Pico Plus:Cortex-A7、uClibc、GCC 8.3、64 MB 内存)上验证。
  • 以 Apache-2.0 开源。依赖全部是 C 库,版本固定,从源码编译。

获取与编译

需要 CMake 3.21 以上和 C11 编译器,仅支持 Linux 等 POSIX 系统(pthreads、BSD socket)。第一次配置时会下载固定版本的依赖。

git clone https://github.com/akarineren/relink.git rerinku-device-sdk
cd rerinku-device-sdk
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
ctest --test-dir build --output-on-failure
cmake --install build --component sdk --strip --prefix "$PWD/sdk"

安装出来的 sdk/ 目录就是交付物,可以整个拷到别的机器上使用:

include/rerinku/rerinku_device.h       API
include/rerinku/rerinku_version.h      版本宏(编译时生成)
lib/librerinku_device.a                SDK 与全部依赖合并成的一个静态库
lib/librerinku_device.so*              同上,动态库(soname librerinku_device.so.0.5)
lib/cmake/rerinku_device/              CMake 包:find_package(rerinku_device)
lib/pkgconfig/rerinku_device.pc        pkg-config 文件(位置可移动)
share/rerinku_device/examples/         quickstart/ 和 device_example.c
share/doc/rerinku_device/              README、CHANGELOG、LICENSE、NOTICE

librerinku_device.a 是 SDK 和全部依赖合并成的一个目标文件,对外只有 rerinku_* 这些全局符号。固件只需链接这一个文件,自带的 mbedTLS、cJSON 等也不会和 SDK 内部的版本冲突。

交叉编译

传入目标平台的 CMake 工具链文件。仓库里带了瑞芯微 RV1103/RV1106 的(厂商工具链 arm-rockchip830-linux-uclibcgnueabihf):

cmake -S . -B build-rv1106 -DCMAKE_BUILD_TYPE=MinSizeRel \
      -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/rv1106-uclibc.cmake \
      -DRV1106_TOOLCHAIN_DIR=<sdk>/tools/linux/toolchain/arm-rockchip830-linux-uclibcgnueabihf
cmake --build build-rv1106 -j
cmake --install build-rv1106 --component sdk --strip --prefix "$PWD/sdk-rv1106"

其他芯片可以复制这个文件,改掉编译器前缀和 CPU 参数。SDK 必须用固件自己的工具链编译,C 库和浮点 ABI 要一致。

快速开始

完整程序在仓库的 examples/quickstart/quickstart.c。它用一个视频文件代替编码器,在电脑上就能直接运行;放到摄像头上时,把其中的 next_frame() 换成编码器的输出即可。核心部分如下:

#include <rerinku/rerinku_device.h>

rerinku_device_config_t config;
rerinku_device_config_init(&config);            /* 默认值 + struct_size */
config.endpoint = "http://192.168.1.10:8080";   /* Rerinku View 的地址 */
config.sn       = "CAM-0001";                   /* 这台设备的序列号 */

rerinku_device_callbacks_t callbacks = {
    .struct_size         = sizeof(callbacks),
    .on_status           = on_status,           /* 连接中 / 在线 / 离线 */
    .on_stream_start     = on_stream_start,     /* 有人看了:启动编码器 */
    .on_stream_stop      = on_stream_stop,      /* 没人看了:编码器可以休眠 */
    .on_keyframe_request = on_keyframe_request, /* 例如 RK_MPI_VENC_RequestIDR */
};

rerinku_device_t *device;
rerinku_err_t err = rerinku_device_create(&config, &callbacks, &device);
if (err != RERINKU_OK)
    fprintf(stderr, "%s\n", rerinku_strerror(err));

rerinku_device_channel_t video = {
    .struct_size = sizeof(video),
    .channel_id  = 0,
    .kind        = RERINKU_MEDIA_KIND_VIDEO,
    .codec       = RERINKU_CODEC_H264,
    .stream_id   = "main",
};
rerinku_device_add_channel(device, &video);
rerinku_device_start(device);                   /* 立即返回,注册和重连都在后台 */

/* 编码线程:每出一个访问单元调用一次 */
rerinku_device_write(device, 0, data, len, pts_us);

/* 关机时 */
rerinku_device_stop(device);
rerinku_device_destroy(device);

在电脑上试运行:先用 ffmpeg 生成一段测试视频(每帧前带访问单元分隔符),再运行快速开始程序:

ffmpeg -f lavfi -i testsrc2=size=1280x720:rate=25 -t 10 -c:v libx264 \
       -profile:v baseline -g 50 -bf 0 -x264-params aud=1 clip.h264
./quickstart http://192.168.1.10:8080 QUICKSTART-001 clip.h264

设备注册成功后会自动出现在 Rerinku View 的设备列表里,打开就能看到画面。功能更全的示例是 examples/device_example.c:H.264/H.265 文件加 G.711 音频,并处理命令和抓图请求。

链接到你的固件

CMake:

find_package(rerinku_device 0.5 CONFIG REQUIRED)   # -DCMAKE_PREFIX_PATH=<sdk 目录>
target_link_libraries(camera PRIVATE rerinku::device)          # 静态库
# 或 rerinku::device_shared                                     # 动态库

交叉编译的工具链文件通常把 find_package 限制在 sysroot 内,这时要显式指定 SDK 目录:

find_package(rerinku_device 0.5 CONFIG REQUIRED
             PATHS "${RERINKU_SDK_DIR}" NO_DEFAULT_PATH NO_CMAKE_FIND_ROOT_PATH)

pkg-config(Makefile、Buildroot、芯片厂商的 SDK):

export PKG_CONFIG_PATH=<sdk 目录>/lib/pkgconfig
# 静态链接:静态库里已包含全部依赖,只需要再加 pthread
cc -std=c11 $(pkg-config --cflags rerinku_device) camera.c \
   "$(pkg-config --variable=libdir rerinku_device)/librerinku_device.a" -pthread
# 动态链接
cc -std=c11 $(pkg-config --cflags rerinku_device) camera.c $(pkg-config --libs rerinku_device)

examples/quickstart/ 里两种写法都有现成的 CMakeLists.txt 和 Makefile。

在 CMake 工程里直接用源码: add_subdirectory(rerinku-device-sdk) 后链接 rerinku::device。Android 就是这样用的:NDK 下直接编译 SDK 源码,外面包一层 JNI。

API 约定

API 的写法与它所基于的 C 库(mbedTLS、libjuice、libsrtp)保持一致:

  • 命名:函数和类型以 rerinku_ 开头,宏和枚举值以 RERINKU_ 开头;类型以 _t 结尾,回调类型以 _cb 结尾;枚举值带上类型名(rerinku_stream_state_t → RERINKU_STREAM_STATE_*,唯一的例外是 RERINKU_OK)。整个库通用的函数是 rerinku_*,针对设备的是 rerinku_device_*,第一个参数是设备。
  • 错误:可能失败的函数返回 rerinku_err_t:RERINKU_OK(0)或负数的 RERINKU_ERR_*,用 rerinku_strerror() 得到说明。rerinku_device_write() 例外,它返回 rerinku_write_result_t,因为它的大多数结果(没人在看、等待关键帧)都是正常情况。
  • 对象:rerinku_device_t 是不透明句柄,create / destroy 和 start / stop 成对使用。
  • 带版本的结构体:配置、通道、回调三个结构体的第一个字段是 struct_size。用旧头文件编译的程序传入的结构体更小,缺少的字段保持默认值;新版本加的字段,旧版本的库会忽略。
  • 版本号:<rerinku/rerinku_version.h> 提供头文件的版本(RERINKU_VERSION_MAJOR、_MINOR、_PATCH、_STRING,以及十六进制的 RERINKU_VERSION_NUMBER);rerinku_version() 和 rerinku_version_number() 返回实际链接到的库的版本。1.0 之前,次版本号变化时 soname 也会变(librerinku_device.so.0.5)。
  • 导出:对外的函数都标了 RERINKU_API,其余符号一律隐藏。

线程:所有函数都可以在任意线程调用。回调在 SDK 自己的线程里执行,请尽快返回;不要在回调里调用 rerinku_device_stop() 或 rerinku_device_destroy()。

内存:传进来的字符串都会被复制。调用方传入的所有数据(包括帧数据)所有权都不变,只需在调用期间有效。

设备的生命周期

rerinku_device_start() 立即返回。注册、MQTT 连接和故障恢复都在后台进行,失败会一直按退避重试(1 秒起,翻倍到 30 秒),所以设备即使比网络或 Rerinku View 先启动,也会在之后自动上线。on_status 报告连接中、在线、离线三种状态。

  • 推流:有人观看或录像时,平台发来推流命令,SDK 调用 on_stream_start,建立 WHIP 会话并请求一个关键帧;没人需要时调用 on_stream_stop。服务端断开的会话(重启、断网)由 SDK 自动重建。
  • 令牌:注册时平台会签发设备令牌(Rerinku View 默认 24 小时有效)。SDK 在有效期过去四分之三时重新注册;计时以服务端时钟为准,设备时钟还停在 1970 年也没关系。HTTP 请求被拒(401)、或 MQTT 登录被拒时,会立即重新注册。
  • 设备密钥:Rerinku View 设置了 DEVICE_KEY 时,只有带正确 secret 的设备才能注册,secret = base64(HMAC-SHA256(设备密钥, sn))。量产时请在出厂时为每台设备写入它的 secret,不要把设备密钥本身放进固件。

送帧

每次调用 rerinku_device_write() 传入一个完整的访问单元(一帧画面的全部 NAL 单元),或一帧音频,并附上采集时间(微秒,单调递增,起点任意,例如瑞芯微 MPI 的 u64PTS 可以直接使用)。

  • H.264/H.265 通道默认使用 RERINKU_BITSTREAM_FORMAT_AUTO,逐帧识别格式:3 字节或 4 字节起始码的 Annex-B、1/2/4 字节长度前缀,或单个 NAL。数据会先校验,再规整成 4 字节起始码。
  • SPS/PPS(H.265 还有 VPS)会被缓存,关键帧缺少它们时自动补上,新加入的观看端总能拿到可解码的画面。
  • SDK 缓存当前的 GOP(默认每路视频最多 1 MiB),新观看端立刻从关键帧开始播放,不用等下一个 IDR。
  • 写入永远不会被网络阻塞。没人观看时返回 RERINKU_WRITE_RESULT_NOT_STREAMING,几乎没有开销,因此一直送帧也可以;在 on_stream_stop 里让编码器休眠可以省电。
  • 网络跟不上时返回 RERINKU_WRITE_RESULT_DROPPED,丢掉这一帧,并请求一个新关键帧,画面从那里继续。

支持的编码:视频 H.264、H.265(浏览器不支持 H.265 时无法播放,面向网页观看请推 H.264);音频 Opus、G.711 µ-law/A-law。

平台发来的请求

回调 / 函数 用途
on_command → rerinku_device_reply_command() / rerinku_device_fail_command() SDK 自己不处理的命令(重启、云台……)。params_json 是命令参数,可以稍后在任意线程回复
on_snapshot → rerinku_device_reply_snapshot() 平台要一张缩略图:抓一张 JPEG 交给 SDK,硬件 JPEG 编码器什么时候好什么时候回
on_runtime_info 有人打开设备详情时,平台每隔几秒询问一次:填一个 JSON 对象,如温度、CPU、内存
rerinku_device_send_event() 设备主动上报事件(移动侦测、告警……)。离线时返回 RERINKU_ERR_NETWORK,SDK 不会排队补发,重要的事件请自己保存,上线后再发
rerinku_device_upload_recording() 把设备断网期间自己录的 MP4 交给平台,平台按录制时间归入录像。会阻塞到上传完成,请在自己的线程里调用

设备的静态信息(SoC、CPU、内存、传感器……)通过配置里的 ext_json 在每次连接时上报,平台会保存并显示在设备信息里;实时数据(温度、CPU 占用……)由 on_runtime_info 按需回答。平台认得的键会显示成对应的名称,其他键按原名列出,设备不用等平台更新就能加自己的字段。

协议与网络

步骤 协议 说明
注册 HTTP(S) POST <endpoint>/ 提交 sn 与设备信息,换取 WHIP 地址、MQTT 地址、ICE 服务器和设备令牌
命令 MQTT 3.1.1 用户名是 SN,密码是设备令牌,客户端 ID 是 rerinku-dev-<sn>;Rerinku View 内置的 broker 只允许设备使用自己的主题
画面 WebRTC(WHIP) 一次 POST 完成 SDP 交换,ICE 由 libjuice 负责,DTLS-SRTP 加密
抓图、录像上传 HTTP(S) 带设备令牌

设备需要能访问 Rerinku View 的 HTTP 端口和 MQTT 端口,并允许 UDP 出站(WebRTC 媒体)。https:// 和 mqtts:// 会校验服务端证书,证书校验依赖正确的时钟(NTP);可以用 ca_file 指定只包含所需根证书的 PEM 文件,节省内存。

资源占用

在 RV1103(Cortex-A7 1.2 GHz)上测得,MinSizeRel,640×360 H.264 约 0.8 Mbit/s 加 G.711A:

librerinku_device.so(strip 后) 0.39 MB,只依赖 C 库
空闲时内存(已注册、MQTT 在线) 约 0.8 MB
推流时内存 约 1.8 MB
推流时 CPU 约单核的 6%
线程 空闲 3 个,推流 5 个

rerinku_device_config_t 里可以调节的内存:

  • gop_cache_max_bytes:每路视频的 GOP 缓存,默认 1 MiB。小内存设备可设为 0,并实现 on_keyframe_request,由编码器的 IDR 开始画面。
  • nack_history_packets:为重传保留的已发包(每个约 1.2 KB),默认 512;64 MB 内存的设备建议 128–256。

已知限制

  • 只推流(WHIP),不接收;不支持 trickle ICE 和 DataChannel。
  • 只支持 POSIX 系统,暂无 Windows 版。
  • 事件不排队:离线期间的事件需要自己保存后补发。

完整的变更记录和新旧名称对照见仓库里的 CHANGELOG.md。