jetson nanoのgstreamerでNVIDIAのハードウェアアクセラレーションと新しいlibsrtをdockerを使って共生(2)

2022/05/28に公開約6,300字

この記事の続きです。

https://zenn.dev/tetsu_koba/articles/26b4fc43387322

これまでの経緯

jetson nanoでWebcamの映像をH.265で録画しつつSRTで小規模配信する」でSRTをつかって配信することができたのですが、libsrtに古いものを使用していました。そのためかサーバ側の状態が不安定になることがありました。
dockerを使ってgstreamerで新しいlibsrtを利用する」でサーバ側で使用するlibsrtを新しいものにすることができました。
送信側については「jetson nanoのgstreamerでNVIDIAのハードウェアアクセラレーションと新しいlibsrtをdockerを使って共生させる」で、ハードウェアアクセラレーションをつかう部分とSRTで送信する部分を分離してfifoでつなぎ、後半部分をdockerを使って新しいlibsrtを使うようにしました。しかし、コマ落ちが発生してしまって配信品質に問題がありました。
jetson nanoでgstreamerとffmpegの合わせ技でwebcamの映像をH.265でSRTで送信する」でSRTで送信する部分をffmpegを使うことで新しいlibsrtを使うことができました。しかもこの方法ではコマ落ちは発生しませんでした。そうするとコマ落ちが発生する原因はfifoではないので、gstreamerでもコマ落ちせずに配信できる方法があるのではないかということで調べたのが今回の記事です。

まず結論

jetson nanoで動かすスクリプトを以下のようにすることで、コマ落ちがない状態で送信することができました。
つまり、NVIDIAのハードウェアアクセラレーションをするgstreamerのエレメントと新しいlibsrtを使うubuntu 22.04のgstreamerを共存して動かすことに成功しました。

#!/bin/sh -xue

HOST_IP=xx.yy.zzz.www
PORT=7001

FIFO=$PWD/.fifo
rm -f $FIFO
mkfifo $FIFO

docker run --rm -v $PWD:$PWD -p $PORT:$PORT/udp koba/srt \
    /bin/sh -c "gst-launch-1.0 -eq fdsrc fd=0 blocksize=65536 ! \
        h265parse ! mpegtsmux alignment=7 ! \
        srtclientsink uri=\"srt://$HOST_IP:$PORT\" < $FIFO" &

gst-launch-1.0 -eq 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 ! \
    filesink location=$FIFO

結論に至る過程

ffmpegを実行するときにstrace -f -o st.log を頭につけることで、straceのログをファイルに書き出します。

$ grep '/\.fifo' st_ffmpeg.log 
12619 execve("/usr/local/bin/ffmpeg-small-srt", ["ffmpeg-small-srt", "-probesize", "1024", "-f", "hevc", "-r", "30", "-i", "/home/koba/work/srt/.fifo", "-c", "copy", "-f", "mpegts", "srt://140.83.51.23:7001"], 0x7ff2ea51f0 /* 34 vars */) = 0
12619 openat(AT_FDCWD, "/home/koba/work/srt/.fifo", O_RDONLY) = 4

fifoをオープンしたのは 12619のスレッドで、ファイルディスクリプタは4だとわかりました。

12619 read(4, "\0\0\0\1@\1\f\1\377\377\1@\0\0\3\0\0\3\0\0\3\0\0\3\0]\254\t\0\0\0\1"..., 65536) = 64403

readのときのバッファサイズは64KBにしていることがわかりました。

同じようにgstreamerの方もstraceのログをとり、fifoをオープンしたファイルディスクリプタに注目します。
readのときのバッファサイズは4KBでした。
filesrcにblocksize=65536 をつけて実行しなおすと、映像のコマ落ちはさらにひどいものになりました。
straceのログではバッファサイズは64KBに変わっていました。

さらに見ていくと、このようにreadだけを繰り返し、用意した64KBのバッファがいっぱいになるまでループしているように見える箇所がありました。

14    read(9,  <unfinished ...>
14    <... read resumed>"L\200p\372q\f?]R}\244\334`5\0277\311\332m\335\261\345\216\316\t\261\357\303\250\17H\272"..., 65536) = 3251
14    read(9,  <unfinished ...>
14    <... read resumed>"\0\0\0\1\2\1\320\225\t`\351\7\355O\353\252\263V\206\307p?\302\374\206\237I\177\233\22WO"..., 62285) = 3560
14    read(9,  <unfinished ...>
14    <... read resumed>"\0\0\0\1\2\1\320\235\10`9\rM\326\207T%9\216\256\2224\267\312`=\"\374\35\21}\270"..., 58725) = 6716
14    read(9,  <unfinished ...>
14    <... read resumed>"\0\0\0\1\2\1\320\245\10`\37\306\332)N/\322\244\3555KH\205\tP\316\244\10\\\17\2457"..., 52009) = 4582
14    read(9,  <unfinished ...>
14    <... read resumed>"\0\0\0\1\2\1\320\255\10`\347Z\300\3600\t}\216\33W\374\33\02165\335\2403\202\10\f'"..., 47427) = 3869
14    read(9,  <unfinished ...>
14    <... read resumed>"\0\0\0\1\2\1\320\265\t`\24\26H\356\331!k\31\330]\312xM\316\226\200Z~3\334\211R"..., 43558) = 1952
14    read(9,  <unfinished ...>
14    <... read resumed>"\0\0\0\1\2\1\320\275\10`\37\306\347\276\345y\2\204\230>[z|\362'\356/>\267D}-"..., 41606) = 3602
14    read(9,  <unfinished ...>
14    <... read resumed>"\0\0\0\1\2\1\320\305\10`\37\306\347\276\352\251\20O\22\23\366\351\237\215\356\362\0276\210\376\4\326"..., 38004) = 3211
14    read(9,  <unfinished ...>
14    <... read resumed>"\0\0\0\1\2\1\320\315\10`\347\26\255\345\355\357\33\304\224e\330=\224\260\364z\272Ocy\215|"..., 34793) = 3059
14    read(9,  <unfinished ...>
14    <... read resumed>"\0\0\0\1\2\1\320\325\10`\37\306\360,E\261J\252\35\314)Z\242\3\304\356\327\230`\v\2!"..., 31734) = 2906
14    read(9,  <unfinished ...>
14    <... read resumed>"\0\0\0\1\2\1\320\335\35\200:\177]\6\262\253\255\234\te\1Y\336l\0013]\341\16\307ri"..., 28828) = 6766
14    read(9,  <unfinished ...>
14    <... read resumed>"\0\0\0\1\2\1\320\345\35\200\360\22\265\7\314dm\325\255cH\2669lgF\26\"\206A?\362"..., 22062) = 5374
14    read(9,  <unfinished ...>
14    <... read resumed>"\0\0\0\1\2\1\320\355\35\200\347.\262\3666\6\3\240o\257\202\342_(l'$\260\204\277\367\342"..., 16688) = 4896
14    read(9,  <unfinished ...>
14    <... read resumed>"\0\0\0\1\2\1\320\365\35\200\352\324\20\2222\301\232=A\325\276\247\316>\254l\320\347}G~p"..., 11792) = 4505
14    read(9,  <unfinished ...>
14    <... read resumed>"\0\0\0\1\2\1\320\375\10`\37\306\363\236\372z\347\321\334j\251\265\3512\2\316NB\36\371\320M"..., 7287) = 2506
14    read(9,  <unfinished ...>
14    <... read resumed>"\0\0\0\1\2\1\321\5\10`_\305\201\300h\32\252\33\21\373UE`\7\325!)\270B4S\235"..., 4781) = 2535
14    read(9,  <unfinished ...>
14    <... read resumed>"\0\0\0\1\2\1\321\r\10`:\265M\344J\372\324\23\325|\335\362\304\334\275l\253\v|\326\353I"..., 2246) = 2246

該当するソースコードはここです。

https://gitlab.freedesktop.org/gstreamer/gstreamer/-/blob/0339363aabcec23d5a949317843cae1ff59b9221/subprojects/gstreamer/plugins/elements/gstfilesrc.c#L337-360
readできるものがある限り、なるべくバッファを満たそうとループしています。ブロックデバイス上に置かれた普通のファイルならばこれでよいのですが、fifoの場合ではこれは流れをせきとめることになってしまいます。バッファサイズを64KBに増やしたらコマ落ちが悪化したのはこれが原因です。

一方、fdsrcのエレメントのソースコードを見てみると、こちらはパイプを意識しているので、filesrcのようにまとめて読もうとせずに1回のreadで読めた分だけ次に渡すようになっていました。filesrcの代わりにfdsrcを使うとコマ落ちが解消しそうです。

cat $FIFO | gst-launch-1.0 -eq fdsrc fd=0 blocksize=65536 ! \
    h265parse ! mpegtsmux alignment=7 ! \
    srtclientsink uri="srt://$HOST:$PORT" &

このようにすることでfilesrcの代わりにfdsrcを使うことができました。しかし、catのプロセスがひとつ増えてしまっています。リダイレクトを使って以下のようにしました。

gst-launch-1.0 -eq fdsrc fd=0 blocksize=65536 ! \
    h265parse ! mpegtsmux alignment=7 ! \
    srtclientsink uri="srt://$HOST:$PORT" < $FIFO &

これでプロセスも増やさずに済みました。
さらにこれをdockerを使って新しいlibsrtを使うようにしたのが、結論のところのスクリプトです。

Discussion

ログインするとコメントできます