
intdashからデータをリアルタイムで取得したいみなさん、
こんにちは。ソリューションアーキテクトの伊勢です。
intdashの魅力のひとつは低遅延でリアルタイム性の高いデータ伝送です。
今回は収集データをストリームで取得する方法をご紹介します。
はじめに
今回の対象はリアルタイムAPIからのデータ取得です。

リアルタイムAPI向けSDK
リアルタイムAPI向けのSDKを利用します。
RESTと異なる専用のAPIでクライアントライブラリが提供されています。
前回と変わって導入方法はシンプルです。
今回は収集した映像データをリアルタイムに取得してRTSP配信します。1
前提
サンプルプログラムで映像関連の利用技術が多いので説明です。
RTSP(Real Time Streaming Protocol)とは
主に監視やライブストリーミングで利用される通信プロトコルです。
数秒程度の遅延で映像を配信することができます。
今回はRTSPを入力とするシステムとの連携を想定して利用します。
映像はH.264で圧縮します。
H.264とは
動画圧縮規格です。
前のフレームから変化した部分だけを抽出してデータ量を低減できる仕組みです。
- IDRフレーム/キーフレーム:フレーム画素の全量を持つ、定期的に送信
- Non IDRフレーム/デルタフレーム:変化点の画素のみを持つ
intdashでもエッジから伝送する映像の圧縮に利用しています。
今回H.264データをRTSP配信するためにFFmpegを利用します。
FFmpegとは
FFmpeg(エフエフエムペグ)は動画や音声を変換・編集するためのオープンソースのマルチメディアツールです。
シンプルに利用でき、RTSPサーバーへの映像配信や可視化をしてくれます。
MediaMTXとは
オープンソースのマルチメディアサーバーです。
RTSPサーバーとして利用します。
インストール
クライアントライブラリインストール
各言語向けに提供されているクライアントライブラリiscpをインストールします。
. ./venv/bin/activate
pip install iscp

動作確認
Pythonを起動してパッケージをimportします。
エラーが出なければ、正常に読み込めています。
python
>>> import iscp

FFmpegインストール
FFmpegをインストールしてバージョンを確認します。
brew install ffmpeg
ffmpeg -version

インストールすると可視化ビューア ffplay も使えるようになります。
ffplay -f lavfi -i testsrc=duration=10:size=1280x720:rate=30

MediaMTXインストール
MediaMTXのバイナリ(.tar.gz)をダウンロードして展開します。
設定ファイルmediamtx.ymlを編集して、送信するデータに合わせてFPS設定を変えておきます。
rpiCameraFPS: 15

起動するとRTSPを8554ポートでリスニングしているのがわかります。
OSのセキュリティ警告が出たら許可します。
./mediamtx

やってみた
全体構成
intdash Motion V2から収集した映像をダウンストリームで取得します。

- 撮影映像をH.264エンコードしてintdash ServerにiSCPで送信
- サンプルプログラムがH.264をダウンストリームしてRTSPサーバーへ配信
- ffplayでRTSPサーバーからストリームを受信して可視化
RTSP配信自体にかかる遅延との比較のため、ダウンストリーム直後もffplayで可視化しています。
実行結果
遅延を測りやすい被写体を撮影しました。

機材をセットします。

先ほどの通り、MediaMTXを起動します。
サンプルプログラムを起動します。
python lesson3/src/rtsp_stream.py --api_url https://example.intdash.jp --api_token <YOUR_API_TOKEN> --project_uuid <YOUR_PROJECT_UUID> --edge_uuid <YOUR_EDGE_UUID>

intdash Motion V2でデータ収集を開始します。
- Data Type:
h264_frame - Data Name:
1/h264

ffplayでRTSPストリームを受信します。
ffplay -window_title "After RTSP" rtsp://localhost:8554/stream

映像比較
撮影した時刻を比較してみます。



全体構成図と見比べて説明します。
- ①リアル世界です。
- ②intdashアップストリーム直前のMotionのプレビューです。
- ③intdashダウンストリーム直後のffplayの可視化です。
- ④RTSP受信後のffplayの可視化です。
①〜④は0.954秒ほどずれています。2
①〜③は0.370秒なのでRTSP配信の遅延が0.584秒なのがわかります。
データ伝送の低遅延性
①〜③にはMotionのH.264エンコード、ffplayのH.264デコード時間が含まれます。
データ伝送のみの遅延を見るため、送信前〜受信後の時間をログ出力してみます。


ブレがありますが、90.4ミリ秒〜370.6ミリ秒で推移しています。3
サンプルプログラム
APIアクセス部分を中心に説明します。
サーバー接続
REST APIと同じくAPIトークンで接続しています。
製品開発以外ではサーバー構成はhttps://〜で提供されるため、固定でHTTPS(443ポート)を指定しています。
api_url_parsed = urllib.parse.urlparse(api_url)
conn = await iscp.Conn.connect(
address=f"{api_url_parsed.hostname}:{api_port}",
connector=iscp.WebSocketConnector(enable_tls=True),
token_source=lambda: api_token,
project_uuid=project_uuid,
)
データチャンク受信
- H.264のIDRとNon IDRフレームに限定
- 空チャンク(H.264が含まれないチャンク)を省略
self.down = await self.conn.open_downstream( filters=[ iscp.DownstreamFilter( source_node_id=self.edge_uuid, data_filters=[ iscp.DataFilter(name="1/h264", type="h264_frame/idr_frame"), iscp.DataFilter(name="1/h264", type="h264_frame/non_idr_frame"), ], ) ], omit_empty_chunk=True, )
- 遅延ログ出力のための経過時間、可視化のためのペイロードを返却
async for msg in self.down.chunks(): for group in msg.data_point_groups: for data_point in group.data_points: yield data_point.elapsed_time, data_point.payload
遅延ロガー
受信時刻 - 送信時刻で遅延時間を算出します。
- 計測の基準時刻とデータポイントの経過時間から送信時の絶対時刻を算出
- ローカルPCで受信時刻との差分を算出
current_time = iscp.DateTime.utcnow()
absolute_time_unix_nano = self.basetime.unix_nano() + elapsed_time
absolute_time = iscp.DateTime.from_unix_nano(absolute_time_unix_nano)
delay = (current_time.unix_nano() - absolute_time.unix_nano()) / 1_000_000
ffmpeg/ffplay起動
subprocessで起動します。
intdashからダウンストリームしたH.264を再エンコードせずに渡しています。
subprocess.Popen(
[
"ffmpeg",
"-f",
"h264",
"-i",
"-",
"-fflags",
"nobuffer",
"-preset",
"ultrafast",
"-c:v",
"copy", # 再エンコードなし
"-f",
"rtsp",
RTSP_URL,
],
stdin=subprocess.PIPE,
stderr=sys.stderr if STDERR_FLG else subprocess.DEVNULL,
),
subprocess.Popen(
[
"ffplay",
"-f",
"h264",
"-fflags",
"nobuffer",
"-flags",
"low_delay",
"-",
"-window_title",
"Before RTSP",
],
stdin=subprocess.PIPE,
stderr=sys.stderr if STDERR_FLG else subprocess.DEVNULL,
),
おわりに
今回はリアルタイムAPIのデータ取得をご紹介しました。
映像を扱うのは難易度が高く、今回は手軽なFFmpegを利用しました。
次回はもっとリアルタイム映像データに適した取り扱い方に挑戦してみます。