文档目录
设备端 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。