ラズパイ4BとUSBカメラでH264ハードウェアエンコードして、HLSでブラウザ視聴して、Python-OpenCVでも音ズレ無くAI顔認識までストリーミングしてみた(備忘録)

ラズパイ4BとUSBカメラでOpenCV-Pythonで顔認識して、H264ハードウェアエンコードして、音声も載せて、HLSでブラウザによるライブ配信してみた備忘録 Raspberry Pi (ラズパイ)

今回はラズパイ4BとUSBカメラを使って、HLS(HTTP Live Streaming)方式によるライブストリーミングに挑戦です。しかもブラウザ視聴ができて、OpenCV-Pythonによる加工動画やAI顔認識も実現できました。
前回記事ではRTMP方式だった為にブラウザ視聴できませんでしたが、今回はついにH264エンコード動画のブラウザ視聴ができたんです。
しかもラズパイ4Bのビデオ用ハードウェアアクセラレータを使ってFFmpegでH264ハードウェアエンコードができるようになりました。
今回はなかなかスゴイですよ。

スポンサーリンク

とりあえず、以下の動画をご覧ください。
ラズパイ4Bに接続したUSBカメラで撮影して、OpenCV-Pythonで出力した動画をH264エンコードして、AndroidスマホのブラウザGoogle Chromeでストリーミング視聴している様子です。
マイクロフォン音声も再生しています。

どうでしょうか。
カメラ動画にOpenCVでフレームレートテキストを上書きして出力してH264ハードウェアエンコードしています。
スマホのブラウザのプレーヤーには遅延はありますが、ソフトウェアエンコードよりも遅延は少ないです。
映像と音声のズレは無く、ほぼ30fpsでライブストリーミングがバッチリできていますよね。
実際の所、ブラウザ再生では30fps出ているかどうかは測定していませんが、OpenCV-Pythonでここまで滑らかにストリーミングできれば十分ですね。

そして、Python-OpenCVの顔認識(Face Detect)を使った動画はこんな感じです。
これは、pixabay にあるphotosNtravelさん作成の著作権フリー動画を使用しています。
(音声は消去しました)

どうでしょうか。
この群衆のフリー動画はあえて解像度を落としている為、あまり認識率が良くないのですが、実際はもっと認識率が良いです。
特にテレビ画面を撮影しているとよくわかりますよ。(著作権上公開できませんが…)
スマホブラウザによるAI顔認識のライブストリーミングでここまでできれば十分ですよね。
実際はマイクロフォン音声も音ズレ無くストリーミングできていますよ。スゴイっすよね。

では、今回やった実験を紹介します。
因みに私はラズパイもLinuxもFFmpegもプログラミングもド素人です。
もし誤りや勘違いがありましたら、コメント投稿でお知らせください。

    【目次】

  1. 今回の環境
  2. ブラウザで視聴するならHLS方式
  3. H264ハードウェアエンコードとソフトウェアエンコードの違い
  4. NGINXとFFmpeg本体をインストールしておく
  5. HLS用のNGINX環境設定
  6. HLS方式の事前の設定
  7. HLS用のFFmpegテストコマンド
  8. OpenCV-Python の出力画像を FFmpeg でHLS配信してみる
  9. 顔認識(Face Detect)を試す
  10. 音ズレ対策
  11. トラブルシューティング
  12. まとめ

1.今回の環境

使ったもの

今回使ったものは、前回記事と同じ組み合わせです。
ラズパイ4BはSDカードを使わず、USB-SSDブートにしています。

(図01-01)

今回使用したラズパイ4Bおよび周辺機器の構成写真

今回使用したラズパイ4Bおよび周辺機器の構成写真

Raspberry Pi 4 model B

2020年頃にRSオンラインで購入した、OKdo製のRaspberry Pi 4 Model B 4GBの全部入りセットを使っています。

USB-SSD

ラズパイ4BのストレージはSDカードを使わず、USB-SSDブートにしています。(過去のこちらの記事参照

BUFFALO (バッファロー)

【名称】 SSD 外付け 250GB 超小型 コンパクト ポータブル PS5/PS4対応(メーカー動作確認済) USB3.2Gen1 ブラック

【型式】 SSD-PUT250U3-B/N

USBカメラ

前回と同じで、Logicool製 C920n です。
オートフォーカスで、ステレオマイクロフォン内蔵です。

USB3.0ハブ

エレコム U3H-T410SBK

パソコン環境等

Windows 10

Visual Studio Code/ SSHリモート使用

有線LAN環境

動画のスマホは Android 10 、Wi-Fi環境です。

Raspberry Pi 4Bのシステム環境

ブートローダバージョン

ラズパイ4BのEEPROMのブートローダのバージョンは以下です。
2022年1月からアップデートしていませんが、defaultバージョンがアップデートされていないのでそのままです。

$ sudo rpi-eeprom-update
BOOTLOADER: up to date
   CURRENT: 2022年  1月 25日 火曜日 14:30:41 UTC (1643121041)
    LATEST: 2022年  1月 25日 火曜日 14:30:41 UTC (1643121041)
   RELEASE: default (/lib/firmware/raspberrypi/bootloader/default)
            Use raspi-config to change the release.

  VL805_FW: Dedicated VL805 EEPROM
     VL805: up to date
   CURRENT: 000138a1
    LATEST: 000138a1

今回のOSは Ubuntu Desktop を使った

今までUbuntu Serverを使っていましたが、Python-OpenCVでimshow関数が使えなかったので、結局はUbuntu Desktopを使うことにしました。
Ubuntu Serverに比べてメモリを多く消費するのは仕方ないところです。

~$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.3 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.3 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy

カーネルバージョン

ラズパイ4Bのカーネルのバージョンは以下です。

$ cat /proc/version
Linux version 5.15.0-1044-raspi (buildd@bos02-arm64-022) (gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #47-Ubuntu SMP PREEMPT Tue Nov 21 11:33:46 UTC 2023

Python環境

Pythonはpyenv環境です。

pyenvバージョン

$ pyenv --version
pyenv 2.3.32-6-gac32a20f

Pythonバージョン

Pythonバージョンは以下です。

~$ python -V
Python 3.10.10

pip による各パッケージバージョン

pipによる各Pythonパッケージのバージョンは以下です。

$ pip list --format=freeze
bottle==0.12.25
numpy==1.26.2
opencv-contrib-python==4.8.1.78
pip==23.3.1
setuptools==69.0.2
somepackage==1.2.3
wheel==0.42.0

NGINXバージョン

今回使ったサーバー用ソフトウェアはNGINXです。
バージョンは以下です。

~$ nginx -V
nginx version: nginx/1.18.0 (Ubuntu)

FFmpegバージョン

今回使用したFFmpegのバージョンは以下です。

~$ ffmpeg -version
ffmpeg version 4.4.2-0ubuntu0.22.04.1+esm3 Copyright (c) 2000-2021 the FFmpeg developers
built with gcc 11 (Ubuntu 11.4.0-1ubuntu1~22.04)

使用したネットワーク環境

今回は、ローカルエリアネットワークのみの実験です。
ラズパイ4Bとルーターは有線LANで接続し、スマホとルーターとはWi-Fiで接続しています。

2.ブラウザで視聴するならHLS方式

前回記事ではRTMP方式でライブストリーミングを試しましたが、視聴する側はVLCプレーヤーなどのアプリを使わなければならず、ブラウザでは視聴できませんでした。

そこで、ネットで調べたところ、HLS(HTTP Live Streaming)という方式があることを知りました。
Appleが独自に開発した方式らしく、Safariだけでなく、Google Chromeなど多くのブラウザで視聴ができるのです。

HLS方式はRTMPより設定が少々面倒ですが、ブラウザで視聴できた方が汎用性は高いし、Google ColaboratoryなどによるAI学習に利用しやすいという利点があると思います。

3.H264ハードウェアエンコードとソフトウェアエンコードの違い

MJPEG動画をH264エンコードする時、ハードウェアエンコードとソフトウェアエンコードがありますが、ラズパイ4Bにはビデオ用ハードウェアアクセラレータが装備されているため、ハードウェアエンコードが可能です。
ハードウェアエンコードの方がCPUの消費を抑えられるので、利点が大きいです。

前回記事のRTMP方式では、ハードウェアエンコードを何度も試しましたが、なぜかできず、半分諦めていました。
ですが、今回で扱うHLS方式ならば難無くH264ハードウェアエンコードが実現できました。

まず、エンコードを実行して、ブラウザストリーミングが可能になるまでの時間(最初のtsファイルが作成されるまでの時間)を比べてみると、以下のようになりました。

ソフトウェアエンコード: 約20秒
ハードウェアエンコード: 約11秒

明らかにハードウェアエンコードの方が処理は速いですね。

では、CPU使用率やメモリ使用率を比べてみましょう。

H264ソフトウェアエンコードの場合、ラズパイ4B Ubuntu Desktop のディスプレイでシステムモニターを見ると以下のようになりました。CPU使用率は4コアとも100%に張り付いている状態が分かると思います。

(図03-01)

H264ソフトウェアエンコード中のラズパイ4BのCPU使用率グラフ

H264ソフトウェアエンコード中のラズパイ4BのCPU使用率グラフ

次に、H264ハードウェアエンコードの場合、下図のようにCPU使用率は4コアとも余裕があります。メモリ使用率も下がっていることが分かると思います。

(図03-02)

H264ハードウェアエンコード中のラズパイ4BのCPU使用率グラフ

H264ハードウェアエンコード中のラズパイ4BのCPU使用率グラフ

このように、ソフトウェアエンコードよりもハードウェアエンコードの方が処理は速く、CPU使用率も抑えられるので利点は大きいですね。

4.NGINXとFFmpeg本体をインストールしておく

前回記事と同様、サーバーのNGINXとFFmpeg本体を予めインストールしておきます。
ただ、RTMPを使わず、HLSだけを使うのならば、NGINXは本体のみでOKです。

sudo apt install nginx
sudo apt install ffmpeg

5.HLS用のNGINX環境設定

ネットの情報では多くが /etc/nginx/nginx.conf ファイルを編集するように書かれていますが、前回記事のNGINX環境設定でも散々述べたように、 /etc/nginx/nginx.conf ファイルは変更しない方が良いと思われます。

今回のHLS設定の場合はHTTPを使うため、httpディレクティブ内で設定を読み込むようにすれば良いわけです。
すると、/etc/nginx/nginx.conf を開いてみてみると、httpディレクティブ内の最後の方に以下の記述があります。

include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;

このことから、 /etc/nginx/sites-enabled/ フォルダ内に設定ファイルを置けば良いのですが、前回記事でも述べているように /sites-enabled/ フォルダ内はシンボリックリンクを置く場所で、個人用設定ファイル本体は /etc/nginx/sites-available/ フォルダに作成します。

ここではとりあえず、vi エディタで myweb というファイル名で新たに作成します。拡張子は不要です。

sudo vi /etc/nginx/sites-available/myweb

そして、HLS用の設定については、以下のサイトを参考にして設定します。

https://www.nginx.com/blog/video-streaming-for-remote-learning-with-nginx/

このサイトの設定例では以下のようになっていました。

http {
    default_type application/octet-stream;

    server {
        listen 80;
        location /tv {
            root /tmp/hls; 
        }
    }

    types {
        application/vnd.apple.mpegurl m3u8;
        video/mp2t ts;
        text/html html;
    }
}

これをこのまま参考にして記述すると、動かないので注意です。
何故かと言うと、大元の /etc/nginx/nginx.conf にあるhttpディレクティブが重複しているからです。

また、 nginx.conf のhttpディレクティブ内には、default_type application/octet-stream; という記述が既にありますね。
それに、include /etc/nginx/mime.types; という記述もあります。これはMIMEタイプを指定する mime.types というファイルを含んでいるということです。
そして、/etc/nginx/mime.types の中身を覗いてみると、application/vnd.apple.mpegurl m3u8; という記述もあるし、video/mp2t ts; という記述もありました。
よって、重複したものを削除し、最低限のものを記述してポート番号を8080とすると、以下のようになります。

server {
    listen 8080;
    location / {
        root /var/www/html/myweb;
    }
}

これは1つのバーチャルホストを作成したわけで、つまりは、レンタルサーバー界隈で言えば、自分用のドメインを1つ作成した感じだと思えばいいかもです。

そして、今回はポート番号を8080としていますが、特殊ポート以外の好きなポートで良いと思います。
因みにファイアウォール(Ubuntuの場合はufw)でそのポートを開放しておくことを忘れずに。

本来はドメインを作成する時に、DNSサーバーを置いてドメインネームを作成しますが、自分のローカル環境の場合はDNSサーバーが無いので、とりあえずポート番号でドメインを割り当てた、というイメージで考えれば分かり易いかもです。ポート8080がドメインmywebという感じです。

そして、location / {} では、トップページに関する設定です。
root /var/www/html/myweb; は、HLS用のHTMLファイルを置くディレクトリを指定しています。これについては後で説明します。

そして、これを保存し、その後、シンボリックリンクを /etc/nginx/sites-enabled/ に作成します。

sudo ln -s /etc/nginx/sites-available/myweb /etc/nginx/sites-enabled/myweb

こうすれば、いろいろ設定を試したい時に気軽に複数の設定を保存しておいて、必要な時にシンボリックリンクを作成して、不要になった時にそれを削除しておけば良いです。

因みに、シンボリックリンク削除は remove ではなく、 unlink を使います。

sudo unlink /etc/nginx/sites-enabled/myweb

6.HLS方式の事前の設定

前回記事のRTMPとは異なり、HLS方式の場合はWebブラウザで視聴します。
よって、トップページのHTMLを作成しなければならず、それからWeb動画プレーヤーを指定していきます。
そして、HLS用のファイル書き出しフォルダを予め作成します。

06-01. HLS用のHTMLファイル設定

NGINXサーバーでは先ほど述べたように、デフォルト設定ではルート配下の /var/www/html 内の index.nginx-debian.html にアクセスしてしまうため、/var/www/htmlフォルダ内に自分用のフォルダを新たに作って置きます。つまり、自分用のドメインフォルダを作るようなものです。
ここでは適当に myweb というフォルダを作成しておきます。

sudo mkdir /var/www/html/myweb

そして、index.html を作成します。

sudo vi /var/www/html/myweb/index.html

HTMLは以下のサイトにサンプルが明記されているのでそれを参考にします。

(参考サイト)
https://github.com/video-dev/hls.js

<!DOCTYPE html>
<html>
  <head>
	  <!-- <script src="https://cdn.jsdelivr.net/npm/hls.js@1"></script> -->
    <!-- Or if you want the latest version from the main branch -->
    <!-- <script src="https://cdn.jsdelivr.net/npm/hls.js@canary"></script> -->
    <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
  </head>
  <body>
    <video id="video" controls width="640" height="480" preload="none"></video>
    <script>
      var video = document.getElementById('video');
      var videoSrc = './stream/playlist.m3u8';

      if (Hls.isSupported()) {
        var hls = new Hls();
        hls.loadSource(videoSrc);
        hls.attachMedia(video);
      }
      else if (video.canPlayType('application/vnd.apple.mpegurl')) {
        video.src = videoSrc;
      }
    </script>
  </body>
</html>

13行目の ./stream/playlist.m3u8 というファイルは、自分用にHLS用の書き出しファイルとして作成したものです。これについては後で説明しています。

これで、ラズパイ4BのローカルIPアドレスが 192.168.0.10 で、先ほどのNGINX設定の節で設定したポート番号が8080の場合は、ブラウザで以下のように入力すれば、この index.html が表示されます。

http://192.168.0.10:8080

06-02. ストリーミング動画の書き出し用フォルダを作成しておく

次に、/var/www/html/myweb/ 配下にFFmpegでHLS用の動画分割ファイルの書き出し用フォルダを作成します。

ここでは適当に stream という名前のフォルダを作成します。

sudo mkdir /var/www/html/myweb/stream

7.HLS用のFFmpegテストコマンド

では、とりあえずは自分がいろいろ試した結果、音ズレ無く、長時間でも動いたHLS用FFmpegコマンドを紹介します。

これは、ルート配下の /var/ フォルダ内にストリーミング用ファイルを書き込むために、sudo権限でコマンドを実行する必要があります。

sudo ffmpeg -y \
-f v4l2 -input_format mjpeg -s 640x480 -thread_queue_size 8192 -i /dev/video0 \
-f alsa -thread_queue_size 8192 -itsoffset 0.1 -i hw:2,0 \
-c:v h264_v4l2m2m -pix_fmt yuv420p -b:v 2500k \
-c:a aac -ac 2 -ar 48k -b:a 384k \
-f hls \
-hls_time 9 -hls_list_size 30 -hls_allow_cache 1 -m3u8_hold_counters 5 -http_persistent 1 -http_multiple 1 \
-hls_segment_filename /var/www/html/myweb/stream/stream_%d.ts \
-hls_base_url ./ \
-hls_flags delete_segments \
/var/www/html/myweb/stream/playlist.m3u8

コマンドオプションの意味は次で説明します。

07-01. コマンドオプション解説

公式の以下のサイトを参照しながら説明します。

https://ffmpeg.org/ffmpeg-formats.html

-y

動画をファイルに書き込む時、同じ名前のファイルがある場合、YかNか選択するのが面倒なとき、強制的に上書きします。

-f

このオプションは入力や出力のフォーマットを明示的に指定するものです。

v4l2

前回記事でも述べましたが、Video for Linux 2 の略で、LinuxにおけるUSBカメラのデバイスドライバです。

alsa

前回記事でも述べましたが、Advanced Linux Sound Architectureの略で、Linuxにおけるサウンドシステムのデバイスドライバです。
ラズパイのUSBカメラのマイクはこれを指定します。

-input_format

前回記事で説明したのと同じです。 -pix_fmt と混同しないように注意です。
以下のコマンドで対応可能なフォーマットを確認しておきます。

ffprobe -hide_banner -f v4l2 -list_formats all -i /dev/video0

カメラ C920n の場合は yuyv422 よりも mjpeg の方が高速だったので、以下のように指定します。

-input_format mjpeg

-s

これも前回記事で説明したのと同じで、入力画像サイズを指定します。
以下のコマンドで対応可能な画角サイズを調べておきます。

ffprobe -hide_banner -f v4l2 -list_formats all -i /dev/video0

今回もカメラ C920n のデフォルト値 640×480 を使用します。

-s 640x480

-thread_queue_size

これも前回記事で説明したのと同じで、デバイスから読み込む時にバッファに貯め込むパケットサイズらしいです。
今回も同じく以下のように設定します。

-thread_queue_size 8192

-i

これも前回記事で説明したのと同じで、入力デバイスを指定します。
映像デバイスを調べるには以下のコマンドです。

v4l2-ctl --list-devices

今回のビデオ入力は以下になります。

-i /dev/video0

サウンドデバイスは以下のコマンドで調べます。

arecord -l

そして、得られた結果は自分の場合は以下のように指定しました。

-i hw:2,0

これについてはいろいろ注意点がありますので、前回記事を参照してください。
ラズパイを起動する度にデバイス番号が変わるという問題点の解消方法を説明しています。

-itsoffset

これも前回記事で説明したのと同じで、カメラのマイクロフォン音声の開始位置を映像より早めたり遅らせたりするオフセット値を設定します。
なぜか映像(ビデオ)側に設定してもうまくいかなかったので、音声だけオフセット値を指定すれば良いと思います。

今回の場合は、以下のように 0.1秒 音声を遅らせました。

-itsoffset 0.1

早めたい場合は負の値を入力すればOKです。
パソコンのブラウザで再生する場合と、スマホのブラウザで再生する場合では設定が少々異なります。
自分の環境に合わせて調整してみてください。

-c:v

これも前回記事で説明したのと同じで、出力側のビデオ(映像)コーデックを指定します。

h264_v4l2m2m

今回のHLSではRTMPではできなかったラズパイ4Bのビデオ用ハードウェアアクセラレータが使用できて、H264ハードウェアエンコードができるようになりました。
その場合は以下のように指定します。

-c:v h264_v4l2m2m

これはコマンド実行から最初のtsファイルが作成されるまで約11秒かかりました。

因みに、FFmpegで使用可能なビデオエンコード一覧は以下のコマンドで調べられます。

ffmpeg -codecs
libx264

これは前回記事で説明したようにFFmpegのソフトウェアエンコードです。

-c:v libx264

これはコマンド実行から最初のtsファイルが作成されるまで約20秒かかりました。

-pix_fmt

これも前回記事で説明したのと同じで、出力側の映像の色空間YUV データの並び方、ビット深度、UV のサブサンプリング方式を指定します。
-input_format とは異なり、また、後で説明する -pixel_format とも異なるので要注意です。
-pixel_formatrawvideo入力に対して指定するオプションなので注意です。

対応可能な一覧は、以下のコマンドで調べられます。

ffmpeg -pix_fmts

今回もH264の基本設定である yuv420p を使います。

-pix_fmt yuv420p

-g

これは H264 エンコードでキーフレームのGOPサイズを指定しますが、実は、このオプションは今回使いません。
GOPについては良く知らないので調べてみて下さい。

なぜ、このオプションを使っていないのかと言うと、H264ハードウェアエンコードの h264_v4l2m2m の場合、-g オプションは無視される為です。
実行した後、以下のメッセージが出ると思います。

Failed to set gop size: Invalid argument

これをいろいろ調べると、h264_v4l2m2m の場合は無視して良いとのことでした。

-r

これはフレームレートを指定するオプションですが、前回記事で説明したように、カメラ C920n の場合は可変フレームレートで入力されてくるので、音ズレを無くすためにも可変フレームレートのまま出力する必要があります。
つまり、-r オプション無しにします。
そうしないとマイク音声と音ズレが生じてしまいます。
ネット上の多くの情報では -r オプションを指定している例があって注意が必要なオプションです。

-b:v

これは映像(ビデオ)のビットレートを指定します。
ビットレートとは、Bit Per Second (bps) で、1秒間に処理するビット数です。
これはいろいろ変えて最適な値を自分で探ってみればよいと思います。
とりあえず、以下のサイトを参照してみてください。

YouTube にアップロードする動画におすすめのエンコード設定

これによれば、640×480 の480p動画の場合のビットレートのおすすめ設定は、2.5Mbps となっていたので、以下の設定で良いのではないかと思います。

-b:v 2500k

-c:a

これも前回記事で説明したのと同じで、サウンドの出力コーデックを指定します。
今回も多くのプレーヤーが対応している aac を使います。

-c:a aac

また、FFmpegで対応可能なコーデック一覧は映像と同様、以下のコマンドで調べられます。

ffmpeg -codecs

-ac

これも前回記事で説明したのと同じで、オーディオのチャンネル数を指定します。
1だとモノラル、2だとステレオです。

-ac 2

-ar

これも前回記事で説明したのと同じで、オーディオのサンプリングレートを指定します。
以下の記事
YouTube にアップロードする動画におすすめのエンコード設定
により、とりあえずDVDと同じ 48k とします。

-ar 48k

-b:a

これはオーディオのビットレートを指定します。
以下の記事
YouTube にアップロードする動画におすすめのエンコード設定
により、とりあえず、 384k とします。

-b:a 384k

-f hls

-f オプションで出力形式を hls として明示的に指定しています。

-hls_time

これはHLS用のオプションで、分割された ts ファイル1つの動画収録時間を指定します。
ネット上の多くの例では -hls_time 2 と指定していますが、今回の実験ではChromeブラウザのキャッシュを削除した後の初回起動時に動画が再生されないという問題が生じました。
2回目以降はキャッシュが機能したせいなのか、問題無く動画が再生できました。
この原因は長らく不明だったのですが、ついに突き止めました。
以下のように長めに設定すればこの症状は無くなりました。

-hls_time 9

-hls_list_size

分割された ts ファイルの個数を指定します。
今回は30個までとしました。

-hls_list_size 30

-hls_allow_cache

これは、 -hls_allow_cache 0 としてしまうと、ブラウザChromeの初回起動時(キャッシュ削除後)に動画が再生されない症状が出たため、明示的に以下の設定にします。

-hls_allow_cache 1

-m3u8_hold_counters

これはブラウザの更新ボタンを押したときに、動画の最大ロード数を指定します。
今回は5回までロード可能としました。

-m3u8_hold_counters 5

これを指定しないと、ブラウザを更新したら2度とそのストリームは再生できなくなります。

-http_persistent

永続的なHTTP接続にするかどうかを指定します。
デフォルトで有効になっていますが、ストリーミングなので、ここでは明示的に指定しておきます。

-http_persistent 1

HLSをダウンロードする場合には 0 にするらしいです。

-http_multiple

HTTP セグメントをダウンロードする場合、複数の HTTP 接続を使用します。HTTP/1.1 サーバーではデフォルトで有効になっていますが、ここではとりあえず明示的に指定します。

-http_multiple 1

-hls_segment_filename

これは分割されたtsファイルのファイル名を指定します。
ここでは以下のようにしました。

-hls_segment_filename /var/www/html/myweb/stream/stream_%d.ts

こうすると、

stream_01.ts
stream_02.ts
stream_03.ts
……………

という感じに自動的に番号が振られます。

-hls_base_url

HLSファイルのルートを決めます。

-hls_base_url ./

-hls_flags delete_segments

これを指定すると、-hls_list_size 30 と指定した場合、30個以上の不要なtsファイルは自動的に削除されます。
これによってディスク領域を効率的に運用できます。

07-02. コマンドの実行

先に紹介したコマンドを実行すると、/var/www/html/myweb/stream 配下に playlist.m3u8 というファイルが作成され、stream_*.ts というファイルが合計30個作成されます。30個作成し終わると、全体のファイル数は同じなのですが、番号は上がっていき、古いファイル順に自動的に上書きされていきます。

/var/www/html/myweb/stream$ ls -a
.              stream_12.ts  stream_17.ts  stream_22.ts  stream_27.ts  stream_32.ts  stream_6.ts
..             stream_13.ts  stream_18.ts  stream_23.ts  stream_28.ts  stream_33.ts  stream_7.ts
playlist.m3u8  stream_14.ts  stream_19.ts  stream_24.ts  stream_29.ts  stream_34.ts  stream_8.ts
stream_10.ts   stream_15.ts  stream_20.ts  stream_25.ts  stream_30.ts  stream_35.ts  stream_9.ts
stream_11.ts   stream_16.ts  stream_21.ts  stream_26.ts  stream_31.ts  stream_36.ts

07-03. ブラウザからアクセスしてストリーミング再生

stream_*.ts というファイルが1つ以上作成されたら、パソコンやスマホのブラウザURL入力欄に以下のアドレスでアクセスします。
(ラズパイのIPアドレスが 192.168.0.10 の場合)

http://192.168.0.10:8080

すると、まずは下図の様に表示されると思います。

(図07-03-01)

ブラウザ(Google Chrome)でLAN内のラズパイ4Bのindex.htmlにアクセスした様子

ブラウザ(Google Chrome)でLAN内のラズパイ4Bのindex.htmlにアクセスした様子

これはhttpで接続する為にセキュリティ上問題があるということです。
つまり、httpsでない(SSLで暗号化していない)ために安全ではないというメッセージです。
今回はローカルエリアネットワーク(LAN)内のためにこのメッセージは無視して、上図のように「サイトへ移動」をクリックします。
すると、動画プレーヤーが表示されると思いますので、再生ボタンをクリックすればストリーミングが再生すると思います。

8.OpenCV-Python の出力画像を FFmpeg でHLS配信してみる

では、テスト用HLS配信ができたならば、今度はOpenCVを使ったPythonプログラミングで出力した動画データをHLS配信して、ブラウザでストリーミング視聴してみたいと思います。実はこれが本来の目的なんです。
OpenCV-Pythonについては、過去にこちらの記事で扱ったことがあるので参照してみてください。

OpenCV-Pythonで出力した動画は、たいてい imshow 関数でウィンドウ表示させると思います。
(※imshow関数は、OSがUbuntu Serverでは表示できませんのでUbuntu Desktopにすれば表示できます。)

ならば、第一の疑問点はOpenCV-Pythonで出力したデータをどうやってFFmpegに渡すか、ということですよね。
要は以下の様な流れにしたいわけです。

カメラ動画データ
↓
OpenCV-Python
↓
FFmpeg H264 エンコード
↓
HLS出力

OpenCV-PythonからFFmpegにデータを渡す方法が判明すれば全て解決するはずです。
これについては、実は以下のサイトを参考にさせていただきました。

https://zenn.dev/nkte8/articles/2021-10-12-r01

08-01. OpenCV-Pythonのインストール

OpenCV-Pythonのインストールについては、過去のこちらの記事ではGUI無しの opencv-python-headless をインストールしましたが、今回はOSをUbuntu Desktopに変えたこともあって、GUI有りの opencv-contrib-python をインストールしました。
これは、pip で以下のようにインストールすればOKです。

pip install opencv-contrib-python

requirements.txt を使う場合は、それに opencv-contrib-python を記載しておけばOKです。

08-02. OpenCV-Pythonコード

まず、Pythonコードの例として、以下を紹介します。
これは、OpenCVを使って、カメラ画像にフレームレートのテキストを重ねて表示させるプログラムです。
画像出力は30fps以下に抑えるようにしてあります。

import cv2
import sys
import time

# 画像にテキストを上書きする関数
def put_txt(img, str_txt, x, y, fscl, col_r, col_g, col_b, tn):
    cv2.putText(img,
                text=str_txt,
                org=(x, y),
                fontFace=cv2.FONT_HERSHEY_SIMPLEX,
                fontScale=fscl,
                color=(col_r, col_g, col_b),
                thickness=tn,
                lineType=cv2.LINE_4)

# 動画を標準出力する関数
def writeFrame(cap, fps):
    ret, frame = cap.read()

    if ret:
        fps_txt = str(fps).rjust(3) + " fps"
        # フレームレート値テキスト(黒縁取り)を画像に上書きする
        put_txt(frame, fps_txt, 0, 50, 2.0, 0, 0, 0, 5)
        put_txt(frame, fps_txt, 0, 50, 2.0, 255, 255, 255, 2)

        #sys.stdoutに書き込む場合、bufferはbytesを扱う。buffer無しは文字列を扱う
        sys.stdout.buffer.write(frame)
        return True
    else:
        return False


cap = cv2.VideoCapture(0)

cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'))
#cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 1) # 露出をマニュアルにすると固定フレームレートに近くなる
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
cap.set(cv2.CAP_PROP_FPS, 30)

loop_fps = 30.0
loop_interval_time = 1.0 / loop_fps
pre_loop_time = 0.0

disp_fps = 0
disp_fps_count = 0
pre_fps_time = 0.0

while(True):
    if (time.time() - pre_loop_time) >= loop_interval_time:
        pre_loop_time = time.time()

        if writeFrame(cap, disp_fps):
            disp_fps_count += 1
        else:
            pass

        # フレームレート計算
        if (time.time() - pre_fps_time) >= 1.0:
            pre_fps_time = time.time()
            disp_fps = disp_fps_count
            disp_fps_count = 0
        else:
            pass
    else:
        pass

    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

ここの大きなポイントは、27行目の

sys.stdout.buffer.write

です。
これを使えば、OpenCVで加工したパラパラ漫画のMJPEG動画を標準出力にバイナリデータで出力することができます。
そうしたら、FFmpegのビデオ(映像)入力オプションを -i - とすれば、FFmpegに画像データを渡すことができるわけです。

また、今回使ったUSBカメラ C920n の場合、最高フレームレートは 30fps ですが、特にプログラミングで30fpsに強制しなくても、30fps以上出ることはありません。
プログラミングでわざわざwhileループを30fps以下にしているのは、今後固定フレームレートにしたい可能性があるため、自己流プログラミングで解決したいと思ったからです。

実はUSBカメラ C920n の場合は可変フレームレートで、デバイス側設定ではどうやっても固定フレームレートにできなかったんです。
露出をマニュアルにすると固定フレームレートになるかもと思って、以下の設定で、オート(自動)露出をマニュアル露出にしてみました。

cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 1)

かなり固定に近いフレームレートにはなりましたが、それでも完全固定とはなりませんでした。
それならば、プログラミングで自由にフレームレートを変えられるようにしたかったというわけです。

ということで、以下の設定

cap.set(cv2.CAP_PROP_FPS, 30)

でデバイス側の最高フレームレートを 30fps にしておき、あとはwhileループでフレームレートを自由に決定するという方式にしました。
ただ、今回は、 cap.read() を通過する時間が可変のため、30fps以下の可変フレームレートになっています。

08-03. FFmpeg コマンド例

では、Pythonコードの実行とFFmpegコマンドを繋げた例を紹介します。
さきほどのPythonコードのファイル名を ffmpeg_test02_9.py とした場合です。

python /home/xxxxxxxx/mypy/ffmpeg_test02_9.py | \
sudo ffmpeg -y \
-f rawvideo -pixel_format bgr24 -video_size 640x480 -thread_queue_size 8192 -use_wallclock_as_timestamps 1 -i - \
-f alsa -thread_queue_size 8192 -use_wallclock_as_timestamps 1 -itsoffset 0.2 -i hw:2,0 \
-c:v h264_v4l2m2m -pix_fmt yuv420p -b:v 2500k \
-num_capture_buffers 64 \
-c:a aac -ac 2 -ar 48k -b:a 384k -af aresample=async=1000 \
-hls_time 9 -hls_list_size 30 -hls_allow_cache 1 -m3u8_hold_counters 5 -http_persistent 1 -http_multiple 1 \
-hls_segment_filename /var/www/html/myweb/stream/stream_%d.ts \
-hls_base_url ./ \
-hls_flags delete_segments \
/var/www/html/myweb/stream/playlist.m3u8

08-03-01. Pythonコード実行コマンドとFFmpegコマンドをパイプ(縦棒)で繋げる

先に紹介したPythonコードでは、標準出力に動画を出力していましたが、それをFFmpegに渡すためには、最初にPythonコード実行コマンドを記述し、次に縦棒(パイプ)でFFmpegコマンドをつなぎます。
パイプとは、標準出力から標準入力へと渡す意味があるそうです。
以下の場合、コマンドAの標準出力からコマンドBの標準入力へ渡すという意味だそうです。

コマンドA | コマンドB

08-03-02. FFmpeg オプションについて

これについては以下の公式ドキュメントを参考に紹介します。

FFmpeg Formats Documentation

先ほど紹介したコマンドオプションの意味と重複するところは説明を割愛します。

rawvideo

今回はPythonでMotion JPEG (MJPEG)形式で標準出力しているものをFFmpegの入力に使用していますが、ネット上の多くの情報では入力を rawvideo としていました。それに習って、rawvideo としています。

標準出力ではMJPEGだと思ったので、rawvideo ではなく、 mjpeg と指定すれば良いのかと思いましたが、公式ドキュメントでは mjpeg 指定している例はありませんでした。現に mjpeg 指定しても、まともに動かないので、 rawvideo で良いっぽいです。

ただ、rawvideo にはビデオパラメータを指定するヘッダ情報が無いので、後で紹介する pixel_format やタイムスタンプを指定しなければならないようです。

-pixel_format

このオプションは、入力が rawvideo の時専用に指定するもので、先ほど紹介した –pix_fmt とは異なるようです。
このコマンドが無いとうまく動きませんでした。

これは yuv420p だとうまく表示されませんでしたので、とりあえず bgr24 で良いかと思います。

-pixel_format bgr24
-video_size

rawvideo を指定した場合はこれでビデオサイズを指定します。
-s オプションもありますが、rawvideo の場合は -video_size で明示的に指定しなければならないそうです。
ここでは、先に紹介したPythonコードで画角を 640x480 にしていたのでそれに習って以下のようにします。

-video_size 640x480
-use_wallclock_as_timestamps

このコマンドについては、ビデオ(映像)とマイクロフォン音声のズレ対策のためのものです。
以下の記事を参考にしました。

【参考】

https://qiita.com/mitayuki6/items/73943628b625e0b2ab30

要するに、Pythonで書き出した標準出力のストリームには、タイムスタンプが書き出されていないために、新たにこのオプションを使ってラズパイ4B本体のタイムスタンプで書き出すという意味です。

また、公式ドキュメントによれば、「ウォールクロックのタイムスタンプを使う」と書いてありますが、ウォールクロックとはストリームを書き出しているその機器のクロックという意味だそうです。
ゼロの場合は無効、1の場合は有効です。

-use_wallclock_as_timestamps 1
-itsoffset

ここでは -itsoffset 0.2 として、マイク音声の入力開始時間を0.2秒遅らせましたが、パソコンのブラウザで再生する場合とスマホのブラウザで再生する場合は異なりました。自分のスマホの場合は-itsoffset 0.3 でした。

-i –

-i - というオプションは、入力に標準入力を使うという意味です。
先ほど説明したように、Pythonコードで動画を標準出力しているので、パイプ(縦棒)’|’で繋げた標準入力を使います。

-num_capture_buffers

これを設定しないと、1時間後にストリーミングが停止してしまう状態に落ち入りました。
よって、以下の設定にするとそれを回避できました。

-num_capture_buffers 64
-af aresample=async=1000

ターミナルのログで以下のメッセージが出ると思います。

Non-monotonous DTS in output stream 0:0; previous: 525495600, current: 525492000; changing to 525495601. This may result in incorrect timestamps in the output file.

DTSとは Decode Time Stamp の略で、自分もよくわからないのですが、どうやらデコードを開始したときに記録するタイムスタンプらしいです。

たぶん、可変フレームレートなので、音声と映像がズレていたのかと思われます。

この対策として、出力側のオプション -async を使って音声同期すると良いっぽい情報がありました。

この async という名前は自分も勘違いしてドロ沼にハマリそうだったのですが、実は、JavaScriptやPythonなどの async とは意味が異なり、audio-sync → a-sync という意味で、音声同期という意味のオプションです。

ただ、この async オプションは公式サイトでは説明がなかなか見当たりませんでした。どうして?

よく探すと以下の公式ドキュメントにあるっぽいです。

https://ffmpeg.org/ffmpeg-resampler.html#Resampler-Options

ただ、これを読んでもよく分かりません。

多くのサイトでは、-async 1 は特別な設定で、この場合は初回のみ同期して、後は同期しないという設定らしく、多くは -async 100 としているところが多いのですが、これはやり過ぎで、-async 10 とした方が良いというサイトもありました。

しかし、自分にはどういう動作をするのかさっぱり分からなかったのです。

そこで、比較的分かり易い以下の公式ドキュメントを参照しました。

https://ffmpeg.org/ffmpeg-filters.html#aresample-1

そして、このコマンドを試すことにしました。

-af aresample=async=1000

-af はaudio filter の略です。

aresample というオプションは、audio-resample の略です。

公式ドキュメントによると、タイムスタンプを一致させるために、1 秒あたり最大 1000 サンプルの補正を使用して、指定されたタイムスタンプまでサンプルを伸長(ストレッチ)または圧縮(スクイーズ)するとのことです。

つまり、音声が48000Hzであれば、1秒間に48000サンプルということなので、音がズレた際に自動的に1000サンプルをストレッチまたはスクイーズするらしいです。

1秒間のうち最大1000サンプルなので、1000サンプルより少ない補正もあるわけで、特に大き過ぎでもなく、音楽視聴のようなシビアなものでなければまぁまぁいいんじゃないかなと思いました。

ただ、この出力オプションは入力ストリームにタイムスタンプがあることが大前提になりますので、さきほど紹介した、use_wallclock_as_timestamps オプションが必須になると思います。

なお、Non-monotonous DTS というメッセージはタイムスタンプ補正する時に出現するのかも知れません。
よって、コマンド実行の最初は必ず表示されるものと思えば良いのかと思います。(たぶん…)

9.顔認識(Face Detect)を試す

では、いよいよ顔認識を試してみます。
今回はネットの情報で多くの使用例があるお馴染みの haarcascade_frontalface_default.xml を使ってみます。
これについては、以下のサイトを参考にさせていただきました。

【参考】

https://algorithm.joho.info/programming/python/opencv-haar-cascade-face-detection-py/

09-01. AI学習済みxmlファイルのダウンロード

OpenCV-Pythonをインストールすると、AI学習済みの顔認識ファイルが自動的にインストールされているらしいのですが、xmlファイルの場所がよくわからなかったので、GitHubからダウンロードして使ってみます。

まず、以下のサイトから、haarcascade_frontalface_default.xml をダウンロードします。

https://github.com/opencv/opencv/tree/master/data/haarcascades

それを自分の好きなフォルダにコピーしておきます。
たったこれだけで、OpenCVから簡単に顔認識(顔検出)プログラムを実装することができます。

09-02. 顔認識用のPythonコード

では、自己流の以下のPythonコードを紹介してみます。

# 顔認識 Face Detect
import cv2
import sys
import time

# 画像にテキストを上書きする関数
def put_txt(img, str_txt, x, y, fscl, col_r, col_g, col_b, tn):
    cv2.putText(img,
                text=str_txt,
                org=(x, y),
                fontFace=cv2.FONT_HERSHEY_SIMPLEX,
                fontScale=fscl,
                color=(col_r, col_g, col_b),
                thickness=tn,
                lineType=cv2.LINE_4)

# 顔認識処理を施し、画像を標準出力する関数
def writeFrame(cap, fps):
    ret, frame = cap.read()
    if ret:
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        faces = face_cascade.detectMultiScale(gray, minSize=(50, 50))
        for x, y, w, h in faces:
            cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 255, 0), 2)
            # "Face Detect"(顔検出)したことをテキストで表示させる
            put_txt(frame, "Face Detect",400, 50, 1.2, 100, 100, 200, 2)

        # フレームレート値テキスト(黒縁取り)を画像に上書きする
        fps_txt = str(fps).rjust(3) + " fps"
        put_txt(frame, fps_txt, 0, 50, 2.0, 0, 0, 0, 5)
        put_txt(frame, fps_txt, 0, 50, 2.0, 255, 255, 255, 2)

        sys.stdout.buffer.write(frame)
        sys.stdout.buffer.flush()
        return True
    else:
        return False

# 顔認識用のxmlファイルを読み込む
face_cascade = cv2.CascadeClassifier('/home/xxxxxxxx/mypy/haarcascade_frontalface_default.xml')

cap = cv2.VideoCapture(0)

cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc('M', 'J', 'P', 'G'))
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
cap.set(cv2.CAP_PROP_FPS, 30)

loop_fps = 30.0
loop_interval_time = 1.0 / loop_fps
pre_loop_time = 0.0

disp_fps = 0
disp_fps_count = 0
pre_fps_time = 0.0

while(True):
    if (time.time() - pre_loop_time) >= loop_interval_time:
        pre_loop_time = time.time()

        if writeFrame(cap, disp_fps):
            disp_fps_count += 1
        else:
            pass

        # フレームレート計算
        if (time.time() - pre_fps_time) >= 1.0:
            pre_fps_time = time.time()
            disp_fps = disp_fps_count
            disp_fps_count = 0
        else:
            pass
    else:
        pass

    if cv2.waitKey(1) & 0xFF == ord('q'):
        break

cap.release()
cv2.destroyAllWindows()

40行目のところでAI学習済みxmlファイルを読み込みます。

face_cascade = cv2.CascadeClassifier('/home/xxxxxxxx/mypy/haarcascade_frontalface_default.xml')

xxxxxxxx のところは自分のユーザー名で、mypy は自分が作成した作業用フォルダですが、これは好きなところに保存して、それのフルパスを入力すれば良いと思います。

21行目でカメラ画像をグレースケールに変換します。
以前、このブログのこちらの記事群でディープラーニングを扱ったことがありますが、その時もカラー画像よりもグレースケールにした方が認識は容易で速いことが分かりましたし、顔検出程度ならばそうした方が良いかと思います。

次に、22行目の以下の所

face_cascade.detectMultiScale(gray, minSize=(50, 50))

ここで、グレースケールの画像から顔検出された座標とサイズがリスト形式で出力されます。
検出の最小サイズを 50×50 pixel としています。

24行目で顔検出した座標とサイズから四角い枠を画像に上書きします。

そして、26行目で”Face Detect”という文字を画像に上書きします。

その他は先に紹介したコードと同じなので説明を割愛します。

09-03. FFmpegコマンド

では、先に紹介したPythonコードとFFmpegコマンドをパイプ(縦棒)で繋げて、顔認識動画をHLS出力するFFmpegコマンドを紹介します。
先の顔認識用Pythonコードのファイル名を ffmpeg_face04.py とした場合です。

python /home/xxxxxxxx/mypy/ffmpeg_face04.py | \
sudo ffmpeg -y \
-f rawvideo -pixel_format bgr24 -video_size 640x480 -thread_queue_size 8192 -use_wallclock_as_timestamps 1 -i - \
-f alsa -thread_queue_size 8192 -use_wallclock_as_timestamps 1 -itsoffset 0.7 -i hw:2,0 \
-c:v h264_v4l2m2m -pix_fmt yuv420p -b:v 2500k \
-num_capture_buffers 64 \
-c:a aac -ac 2 -ar 48k -b:a 384k -af aresample=async=1000 \
-hls_time 9 -hls_list_size 30 -hls_allow_cache 1 -m3u8_hold_counters 5 -http_persistent 1 -http_multiple 1 \
-hls_segment_filename /var/www/html/myweb/stream/stream_%d.ts \
-hls_base_url ./ \
-hls_flags delete_segments \
/var/www/html/myweb/stream/playlist.m3u8

コマンドオプションの解説は先に説明したのと殆ど同じなので割愛します。
ただ、ALSAの音声入力については、 -itsoffset 0.7 としています。
これは顔認識映像の初回のエンコード時間が長い為に、音声のスタート位置も遅らせる必要がありました。
これもパソコンのブラウザとスマホのブラウザでは設定が異なるので、自分の環境に合わせて調整してみてください。

10.音ズレ対策

今回は自己流の以下の対策で、今のところ音ズレ無しです。我ながらよく解決できたと思っています。

10-01. 入力の可変FPS動画を固定に修正しないこと

個人的に何度も実験した結果ですが、前回記事のRTMP方式の音ズレ対策で説明したように、可変フレームレートを固定フレームレートに修正せずに、そのまま出力することが大事だと思います。

よって、FFmpegの -r オプションは使わずに可変フレームレート動画のままスルーさせて、H264エンコード出力させています。

10-02. 映像と音の入力開始位置を合わせる

先ほど紹介したように、-itsoffset を使って初回の映像と音の入力開始位置を合わせることが大事です。
特にOpenCV-Pythonによる加工動画を使う時には、処理時間がかかるため、その都度オフセット値を決める必要があります。

10-03. rawvideo には新たにタイムスタンプを書き込む

Pythonからの標準出力動画の rawvideo では、タイムスタンプが無いため、 入力時に -use_wallclock_as_timestamps 1 を使ってラズパイ4Bマシンのタイムでビデオやオーディオデータにタイムスタンプを書き込む必要があると思われます。
たぶん、フレーム単位で動画や音声のヘッダに書き込まれると個人的に想像しています。

こうすると、映像と音との同期が実現できるのだと思われます。特に入力が可変フレームレート映像の場合はなおさらです。
それによって、次に紹介する自動補正も使えるようになるようです。

10-04. 自動で音ズレを修正

先ほど紹介したように、 -af aresample=async=1000 で音ズレを自動で補正してくれるようです。

この場合は、音が映像とズレた場合、音声データを最大で1000サンプル補正してくれるようです。
ただし、これを有効にするためには、動画と音にタイムスタンプがあることが大前提です。

11.トラブルシューティング

今回のHLS配信で自分が遭遇したトラブルシューティングを紹介してみます。

11-01. ALSAデバイスが認識しない、およびラズパイ起動毎にサウンドカードIDが変わってしまうときの対処

起動毎にサウンドカードIDが変わってしまうときは、 /etc/modprobe.d/alsa-base.conf というファイルを書き換えます。
これについては、前回の以下の記事を参照してください。

https://www.mgo-tec.com/blog-entry-raspi4b-ffmpeg-rtmp01.html#title10-03

この他にも、外部電源対応のUSB-SSDを使う場合は、USB-SSDの電源を入れてから20秒後にラズパイの電源を入れると認識しやすいということがありました。

11-02. ストリーミングが途中で止まる対策

ターミナルに以下のメッセージが出て、ストリーミングが途中で停止してしまいました。

[h264_v4l2m2m @ 0xaaaaec249880] All capture buffers returned to userspace. Increase num_capture_buffers to prevent device deadlock or dropped packets/frames.

この対策として、FFmpegの以下のオプションを出力側に追加すれば消えました。

-num_capture_buffers 64

デフォルトは20らしいです。

11-03. NGINX設定が反映されない時

NGINXの個人用設定ファイルを正しく修正したのに、設定が反映されずにストリーミングができないことに何度も遭遇しました。

その原因の多くは、アクセスするブラウザにキャッシュが残っていることが原因です。
キャッシュを削除すれば、反映されることが多いので試してみて下さい。

11-04. ブラウザでダウンロード画面になってしまう対策

ブラウザでアクセスしたら、ダウンロード画面になってしまい、何度もnginx confファイルを修正しても一向に改善されなかったことがありました。

これも、ブラウザのキャッシュを全て削除したら治りました。
RTMP方式と同じですね。

11-05. Job for nginx.service failed because the control process exited with error code.というエラー

NGINXの環境設定ファイルを修正して、sudo systemctl restart nginx コマンドを使ってNGINXを再起動したところ、以下のエラーが出ました。

Job for nginx.service failed because the control process exited with error code.
See "systemctl status nginx.service" and "journalctl -xeu nginx.service" for details.

この原因を突き止めるために以下のコマンドを入力します。

sudo nginx -t

すると、以下の様に表示されました。

~$ sudo nginx -t
nginx: [emerg] a duplicate default server for 0.0.0.0:80 in /etc/nginx/sites-enabled/default:22
nginx: configuration file /etc/nginx/nginx.conf test failed

つまり、NGINXの環境設定で、confファイルのserverディレクティブ内で、default server が重複しているということでした。
それを修正したら治りました。

11-06. NGINX設定の server_name について

トラブルというわけではありませんが、NGINX設定ファイル内のseverディレクティブ内に server_name という使えそうな指令があったのでいろいろ試してみました。
しかし、全く使えませんでした。

実は、通常のLAN内では使えないんです。
LAN内でDNSサーバーを設けていれば使えるかも知れません。
また、DNSが無い場合は、hostsファイル(Windowsの場合)で設定すれば使えるのかも知れません。(試してませんが…)

12.まとめ

ラズパイでここまで高機能なストリーミングがブラウザで視聴できるなんて驚きでした。
Pythonでも十分なフレームレートでしたね。
H264ハードウェアエンコードも実現できたし、AI顔認識も比較的簡単に実装できました。
HLS方式を使えば、カメラによるライブAIも自分で構築できる道筋ができましたね。これってなかなかスゴイことですよ。
おかげでNGINXも少し理解できたし、FFmpegの便利さも十分理解できました。

ということで、今回はここまで。
ではまた…。

コメント

  1. PCBGOGO.JP より:

    突然のご連絡失礼いたします。
    PCBGOGOマーケティング部門の–と申します。

    弊社は経験豊かな基板製造・実装会社でございます。
    いつも、貴サイトの記事を楽しく拝読させていただいております。

    今回、弊社についての記事の掲載をお願いしたく、
    連絡させていただきました。

    ご興味がおありであれば、一度ご連絡をいただけますでしょうか。

    ご多用の折恐れ入りますが、ぜひご検討くださいますよう
    お願い申し上げます。

    • mgo-tec mgo-tec より:

      PCBGOGOさん

      記事をご覧いただき、ありがとうございます。
      コメント投稿されたので、名前とメールアドレスは消去して、メッセージは公開させていただきます。

      折角のご依頼ですが、現在は基板製作は行っておりませんし、その分野については自分自身にまったくノウハウがありませんのでお断りさせていただきます。
      ご要望にお応えできず、申し訳ございません。

  2. ako より:

    単純に応援コメントです
    私自身、最近 Raspberry Pi 始めました
    そして貴兄の備忘録を見つけて、情報を参考にさせてもらっています
    とても丁寧な表現に感心させていただいてます
    これからも沢山の備忘録に期待させていただきます
    ありがとうございました

    • mgo-tec mgo-tec より:

      akoさん

      記事をご覧いただき、ありがとうございます。
      なんかうれしいですね。
      久々に元気が出てきました。
      もうちょっとがんばりま~す!!!

タイトルとURLをコピーしました