aptpod Tech Blog

株式会社アプトポッドのテクノロジーブログです

SDK入門③〜RTSPで映像配信するぞ〜

intdashからデータをリアルタイムで取得したいみなさん、

こんにちは。ソリューションアーキテクトの伊勢です。

intdashの魅力のひとつは低遅延でリアルタイム性の高いデータ伝送です。

今回は収集データをストリームで取得する方法をご紹介します。

はじめに

今回の対象はリアルタイムAPIからのデータ取得です。

intdash SDK

リアルタイムAPI向けSDK

リアルタイムAPI向けのSDKを利用します。

RESTと異なる専用のAPIでクライアントライブラリが提供されています。

前回と変わって導入方法はシンプルです。

今回は収集した映像データをリアルタイムに取得してRTSP配信します。1

前提

サンプルプログラムで映像関連の利用技術が多いので説明です。

RTSP(Real Time Streaming Protocol)とは

主に監視やライブストリーミングで利用される通信プロトコルです。

数秒程度の遅延で映像を配信することができます。

今回はRTSPを入力とするシステムとの連携を想定して利用します。

映像はH.264で圧縮します。

H.264とは

動画圧縮規格です。

前のフレームから変化した部分だけを抽出してデータ量を低減できる仕組みです。

  • IDRフレーム/キーフレーム:フレーム画素の全量を持つ、定期的に送信
  • Non IDRフレーム/デルタフレーム:変化点の画素のみを持つ

www.idknet.co.jp

intdashでもエッジから伝送する映像の圧縮に利用しています。

今回H.264データをRTSP配信するためにFFmpegを利用します。

FFmpegとは

FFmpeg(エフエフエムペグ)は動画や音声を変換・編集するためのオープンソースのマルチメディアツールです。

シンプルに利用でき、RTSPサーバーへの映像配信や可視化をしてくれます。

www.innoqos.com

MediaMTXとは

オープンソースのマルチメディアサーバーです。

RTSPサーバーとして利用します。

qiita.com

インストール

クライアントライブラリインストール

各言語向けに提供されているクライアントライブラリiscpをインストールします。

. ./venv/bin/activate
pip install iscp

クライアントライブラリのインストール

動作確認

Pythonを起動してパッケージをimportします。

エラーが出なければ、正常に読み込めています。

python
>>> import iscp

import確認

FFmpegインストール

FFmpegをインストールしてバージョンを確認します。

brew install ffmpeg
ffmpeg -version

FFmpegインストール

インストールすると可視化ビューア ffplay も使えるようになります。

ffplay -f lavfi -i testsrc=duration=10:size=1280x720:rate=30

カラーパターン再生

MediaMTXインストール

MediaMTXのバイナリ(.tar.gz)をダウンロードして展開します。

設定ファイルmediamtx.ymlを編集して、送信するデータに合わせてFPS設定を変えておきます。

  rpiCameraFPS: 15

FPS設定変更

起動するとRTSPを8554ポートでリスニングしているのがわかります。

OSのセキュリティ警告が出たら許可します。

./mediamtx 

MediaMTX動作確認

やってみた

全体構成

intdash Motion V2から収集した映像をダウンストリームで取得します。

RTSPストリーム配信

  • 撮影映像をH.264エンコードしてintdash ServerにiSCPで送信
  • サンプルプログラムがH.264をダウンストリームしてRTSPサーバーへ配信
  • ffplayでRTSPサーバーからストリームを受信して可視化

RTSP配信自体にかかる遅延との比較のため、ダウンストリーム直後もffplayで可視化しています。

実行結果

遅延を測りやすい被写体を撮影しました。

東京駅ヤエチカ(八重洲地下街) DROPCLOCK

機材をセットします。

通勤客の横で

先ほどの通り、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

MotionからH.264を送信

ffplayでRTSPストリームを受信します。

ffplay -window_title "After RTSP" rtsp://localhost:8554/stream

ビューアーでRTSPを受信

映像比較

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

撮影時刻比較

ミリ秒まで比較

全体構成図

全体構成図と見比べて説明します。

  • ①リアル世界です。
  • ②intdashアップストリーム直前のMotionのプレビューです。
  • ③intdashダウンストリーム直後のffplayの可視化です。
  • ④RTSP受信後のffplayの可視化です。

①〜④は0.954秒ほどずれています。2

①〜③は0.370秒なのでRTSP配信の遅延が0.584秒なのがわかります。

データ伝送の低遅延性

①〜③にはMotionのH.264エンコード、ffplayのH.264デコード時間が含まれます。

データ伝送のみの遅延を見るため、送信前〜受信後の時間をログ出力してみます。

intdashの遅延時間

遅延ログ

ブレがありますが、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を利用しました。

次回はもっとリアルタイム映像データに適した取り扱い方に挑戦してみます。


  1. サンプルプログラムをGitHubで公開しています。
  2. Motion/PCともに同じLTE SIMで通信しています。フレームは640x480・15FPSです。
  3. ネットワークの遅延 + intdashの遅延です。正確に測定するなら、潤沢な通信帯域・PCリソース・適切な試行回数が必要ですが、今回は簡単に測定しています。また、正確にはiPhoneとPCの時刻誤差(NTP精度)が含まれます。