🐘

jetson nanoでWebcamの映像をH.265で録画しつつSRTで小規模配信する

2022/05/22に公開

ayamelaboを使わせてもらって、WebRTCのmomoで自宅定点カメラを運用しています。
しかしながら、以下のような要件を追加をしてみたいので、別の方法を試してみました。

  • 送信側でローカルに録画したい。ネットワークが不調の時でも録画を継続したい。
  • ayameでは1対1に特化しているので視聴者は同時に一人だけに限定される。3人くらいで同時に視聴したい。
  • 数秒程度の遅延は許容する。遅延の小ささより画質を優先させる。

わりとうまくいったので紹介します。

全体像

中継サーバのIPアドレスを xx.yy.zz.ww と記述します。実際に使うのものに読み替えてください。
上りのポートにUDPの7001, 下りにUDPの7002を使用します。

送信側

まずjetson nanoのgstreamerでSRTを使えるようにします。
https://zenn.dev/tetsu_koba/articles/ddc3902158b94a

動かすスクリプトは以下の通り。

#!/bin/sh -xue

HOST_IP=xx.yy.zz.ww
PORT=7001
RECDIR=$PWD/rec
MAX_FILE_SIZE=$((100 * 1024 * 1024))
MAX_FILES=20

filename="$RECDIR/d%08d.m2ts"
last_index=`ls -t rec/d*.m2ts |head -1 |sed -n -e 's/\.m2ts$//' -e 's/[^0-9]//gp'`
if [ -z $last_index ] ; then
    mkdir -p $RECDIR
    next_index=0
else
    next_index=`echo "$last_index + 1" |bc`
fi    

touch $RECDIR/start_${next_index}_$(date +%Y%m%d%H%M%S)
gst-launch-1.0 -e v4l2src device="/dev/video0" ! \
    'image/jpeg, width=1280, height=720, framerate=30/1' ! \
    nvv4l2decoder mjpeg=1 ! 'video/x-raw(memory:NVMM)' ! \
    nvvidconv ! 'video/x-raw(memory:NVMM), format=(string)I420' ! \
    nvv4l2h265enc insert-sps-pps=1 iframeinterval=60 bitrate=1000000 ! \
    queue ! \
    h265parse ! mpegtsmux alignment=7 ! tee name=t \
    t. ! queue leaky=2 ! srtclientsink uri="srt://$HOST_IP:$PORT" \
    t. ! queue ! multifilesink location=$filename index=$next_index \
        next-file=4 max-file-size=$MAX_FILE_SIZE max-files=$MAX_FILES

映像は1280x720 30fps H.265で1Mbpsにしています。jetson nanoの能力的にはもっと高解像度にもできますが、視聴するのが主にiPhoneなので、このくらいで十分です。
video codecはH.265にしてみました。特にこだわりはありません。H.264でも同じようにできます。
srtclientsinkで指定するURIの中のhostの部分はIPアドレスで指定する必要があります。ネームサーバでホスト名を解決してくれません。(これで半日ハマりました。)

録画ファイルは100MBのものを最大20個まで保存して、古いものを自動で削除するようにしています。
開始時に録画ディレクトリにstart_${next_index}_$(date +%Y%m%d%H%M%S)の空ファイルを作ることで再起動などで録画が継続していない時間帯があった場合にそれがわかるようになっています。

録画ファイルのフォーマットはmpegtsです。これはmp4と違って不意の電源断などでファイルが尻切れになったとしても書き込まれた部分までは再生することができます。mp4では尻切れのファイルは再生不能です。必要ならば後でmp4に変換すればよいです。

中継サーバ

Orecle Cloudの無料枠のarm64インスタンスで動かしています。ubuntu server 20.04です。
UDPの7001と7002を外部からアクセスできるように、ファイアーウォールの設定を変更します。
同じくUDPを使うアプリのmoshを使う時の設定が参考になると思います。

gstreamerのインストール

$ sudo apt-get install gstreamer1.0-tools gstreamer1.0-alsa \
gstreamer1.0-plugins-base gstreamer1.0-plugins-good \
gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly \
gstreamer1.0-libav
$ gst-inspect-1.0 --version
gst-inspect-1.0 version 1.16.2
GStreamer 1.16.2
https://launchpad.net/distros/ubuntu/+source/gstreamer1.0

動かすスクリプトは以下の通り。

#!/bin/sh

RECVLATENCY=200

while :; do
  sleep 2
  gst-launch-1.0 -v \
    srtserversrc uri="srt://:7001" latency=$RECVLATENCY ! \
    queue leaky=2 ! srtserversink uri="srt://:7002"
done

上りにモバイル回線を使用する時にはRECVLATENCYを300くらいにしてみてください。
サーバ側でも録画したい場合は、送信側のスクリプトのteeで分岐するところを真似ればできると思います。

視聴側

PCではVLCを使います。
URLにsrt://xx.yy.zz.ww:7002 を指定します。

スマフォではHaivison Player Proのアプリをインストールして視聴します。
アドレスに xx.yy.zz.ww ポートに 7002を指定します。

感想など

視聴側は同時に3つまで動作確認しています。不特定多数に向けた配信は想定していません。
MacBookProのVLC, iPhone, iPadでは問題なく視聴できます。ただしMacではCPU負荷が高めでファンが回り始めてしまいます。
Androidは端末に楽天モバイルで実質1円で購入したOPPO製のものを使用したのですが、残念ながらアプリとの相性が合わないのか、ときどき映像が崩れたり切れたりして実用には耐えませんでした。
iPadが一番適しています。

でも視聴に専用アプリが必要なのは面倒くさいです。自分だけで使うならいいですが、他の人に使ってもらうのにその方法を教えるのが面倒です。やはりブラウザで試聴したい。
SRTは上りに使うのはよいと思います。次は中継サーバでHLSに変換するのを試してみようと思います。

たまに送信側を止めたときに、中継サーバ側の状態が変になることがあります。中継サーバで動いているgstreamerのプロセスを一度強制終了させると復帰します。
このあたりは最新版のlibsrtでは改善されているかもしれません。
** 2022/05/23 追記
ubuntu 22.04のgstreamer, libsrtを使う方法については以下の記事を見てください。
https://zenn.dev/tetsu_koba/articles/763d517484b07f
https://zenn.dev/tetsu_koba/articles/26b4fc43387322
** 追記ここまで

新しいビデオコーデックのAV1も試してみたいのですが、AV1をmpegtsに乗せる実装はまだありませんでした。

参考

https://qiita.com/tomopyonsama/items/00bfd5352821c420ccf6

https://qiita.com/tetsu_koba/items/3af1fcc976e2ef45bb29

https://zenn.dev/tetsu_koba/articles/5143135dca6f65

https://zenn.dev/tetsu_koba/articles/3a36fdd43ca0ca

Discussion