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を利用しました。
次回はもっとリアルタイム映像データに適した取り扱い方に挑戦してみます。