本文へスキップ
Esc

デバイス SDK(C)

このページの内容

デバイス SDK は、カメラのファームウェアを Rerinku View に接続します。デバイスは Rerinku View に登録し、MQTT でいつでもコマンドを受け取り、誰かが視聴しているときや録画中にはハードウェアエンコーダーの出力を WebRTC(WHIP)で送信します。SDK はキャプチャとエンコードを担当しません。ファームウェアはエンコード済みのフレームを 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)に対応しています。
  • チップメーカーのツールチェーンでクロスコンパイルします。Rockchip RV1103(Luckfox Pico Plus:Cortex-A7、uClibc、GCC 8.3、メモリ 64 MB)で動作を確認しています。
  • Apache-2.0 のオープンソースです。依存ライブラリはすべて C のライブラリで、バージョンを固定してソースからビルドします。

入手とビルド

CMake 3.21 以上と C11 コンパイラーが必要です。対応するのは Linux などの POSIX システム(pthreads、BSD ソケット)だけです。初回の configure 時に、バージョンを固定した依存ライブラリがダウンロードされます。

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 とすべての依存ライブラリをまとめた 1 つの静的ライブラリ
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 とすべての依存ライブラリを 1 つのオブジェクトファイルにまとめたもので、外部に見えるグローバルシンボルは rerinku_* だけです。ファームウェアはこのファイル 1 つをリンクするだけで済み、ファームウェアが独自に持っている mbedTLS や cJSON などが、SDK 内部のバージョンと衝突することもありません。

クロスコンパイル

ターゲットプラットフォームの CMake ツールチェーンファイルを渡します。リポジトリには Rockchip 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 にあります。エンコーダーの代わりに動画ファイルを使うので、PC 上でそのまま動かせます。カメラに載せるときは、その中の 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);                   /* すぐに戻る。登録と再接続はバックグラウンドで行う */

/* エンコードスレッド:アクセスユニットが 1 つ出るたびに 1 回呼ぶ */
rerinku_device_write(device, 0, data, len, pts_us);

/* シャットダウン時 */
rerinku_device_stop(device);
rerinku_device_destroy(device);

PC で試すには、まず 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 の書き方は、SDK が土台にしている C ライブラリ(mbedTLS、libjuice、libsrtp)に合わせています。

  • 命名:関数と型は rerinku_ で、マクロと列挙値は RERINKU_ で始まります。型は _t で、コールバック型は _cb で終わります。列挙値には型名が付きます(rerinku_stream_state_t → RERINKU_STREAM_STATE_*。唯一の例外は RERINKU_OK)。ライブラリ全体に共通の関数は rerinku_*、デバイスに対する関数は rerinku_device_* で、その第 1 引数はデバイスです。
  • エラー:失敗する可能性のある関数は rerinku_err_t を返します。値は RERINKU_OK(0)か負の RERINKU_ERR_* で、rerinku_strerror() で説明文を取得できます。rerinku_device_write() は例外で、rerinku_write_result_t を返します。その結果の大半(視聴者がいない、キーフレーム待ち)は正常な状態だからです。
  • オブジェクト:rerinku_device_t は不透明なハンドルで、create / destroy と start / stop を対にして使います。
  • バージョン付きの構造体:設定、チャンネル、コールバックの 3 つの構造体は、最初のフィールドが struct_size です。古いヘッダーでビルドしたプログラムが渡す構造体は小さく、足りないフィールドは既定値のままになります。新しいバージョンで追加されたフィールドは、古いバージョンのライブラリでは無視されます。
  • バージョン番号:<rerinku/rerinku_version.h> はヘッダーのバージョン(RERINKU_VERSION_MAJOR、_MINOR、_PATCH、_STRING、および 16 進数の 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 は、接続中、オンライン、オフラインの 3 つの状態を通知します。

  • ストリーム送信:誰かが視聴しているときや録画中には、プラットフォームからストリーム送信のコマンドが届きます。SDK は on_stream_start を呼び、WHIP セッションを確立してキーフレームを 1 つ要求します。不要になると on_stream_stop を呼びます。サーバー側から切断されたセッション(再起動、ネットワーク切断)は、SDK が自動で作り直します。
  • トークン:登録時に、プラットフォームがデバイストークンを発行します(Rerinku View の既定では 24 時間有効)。SDK は有効期間の 4 分の 3 が過ぎた時点で登録し直します。時間の計測はサーバーの時計を基準にするので、デバイスの時計が 1970 年のままでも問題ありません。HTTP リクエストが拒否された(401)場合や、MQTT のログインが拒否された場合は、すぐに登録し直します。
  • デバイスキー:Rerinku View で DEVICE_KEY を設定している場合、正しい secret を持つデバイスしか登録できません。secret = base64(HMAC-SHA256(デバイスキー, sn)) です。量産時は、出荷時に各デバイスへそれぞれの secret を書き込み、デバイスキーそのものはファームウェアに入れないでください。

フレームの送信

rerinku_device_write() には、1 回の呼び出しごとに完全なアクセスユニット(映像 1 フレーム分のすべての NAL ユニット)か音声 1 フレームを渡し、キャプチャ時刻を添えます(マイクロ秒、単調増加、起点は任意。たとえば Rockchip 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 本あたり最大 1 MiB)ので、新しい視聴者はすぐにキーフレームから再生を始められ、次の IDR を待つ必要がありません。
  • 書き込みがネットワークによってブロックされることはありません。視聴者がいないときは RERINKU_WRITE_RESULT_NOT_STREAMING を返し、オーバーヘッドはほとんどないので、フレームをずっと送り続けても構いません。on_stream_stop でエンコーダーを休止させれば、電力を節約できます。
  • ネットワークが追いつかないときは RERINKU_WRITE_RESULT_DROPPED を返してそのフレームを捨て、新しいキーフレームを要求します。映像はそこから再開します。

対応コーデック:映像は H.264、H.265(ブラウザーが H.265 に対応していないと再生できないため、Web ページでの視聴が前提なら H.264 を送ってください)、音声は Opus、G.711 µ-law/A-law です。

プラットフォームからの要求

コールバック / 関数 用途
on_command → rerinku_device_reply_command() / rerinku_device_fail_command() SDK 自身が処理しないコマンド(再起動、PTZ……)。params_json はコマンドのパラメーターで、あとから任意のスレッドで応答できます
on_snapshot → rerinku_device_reply_snapshot() プラットフォームがサムネイルを求めています。JPEG を 1 枚撮って SDK に渡します。ハードウェア JPEG エンコーダーの準備ができたときに応答すれば構いません
on_runtime_info 誰かがデバイス詳細を開くと、プラットフォームが数秒ごとに問い合わせます。温度、CPU、メモリなどを入れた JSON オブジェクトを返します
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 内蔵のブローカーは、デバイスに自分のトピックしか使わせません
映像 WebRTC(WHIP) 1 回の 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 1 コアの約 6%
スレッド アイドル時 3 本、ストリーム送信中 5 本

rerinku_device_config_t で調整できるメモリ:

  • gop_cache_max_bytes:映像 1 本あたりの GOP キャッシュ。既定は 1 MiB。メモリの少ないデバイスでは 0 にして on_keyframe_request を実装し、エンコーダーの IDR から映像を始めることもできます。
  • nack_history_packets:再送のために保持しておく送信済みパケット(1 個あたり約 1.2 KB)。既定は 512。メモリ 64 MB のデバイスでは 128〜256 をおすすめします。

既知の制限

  • ストリームの送信(WHIP)のみで、受信はしません。trickle ICE と DataChannel には対応していません。
  • 対応するのは POSIX システムだけで、Windows 版はまだありません。
  • イベントはキューに入りません。オフライン中のイベントは自分で保存して、後から送る必要があります。

変更履歴の全体と新旧の名前の対応表は、リポジトリの CHANGELOG.md を参照してください。