📹

Raspberry Pi 5 と USBカメラ と ffmpeg で作るライブストリーミング

2024/06/04に公開10

背景

祖父の介護の関係で SwitchBot の見守りカメラを購入しました。値段もそこまで高くは無く機能は概ね満足しているのですが、特定の部屋の監視をしていると、やはりX分前の映像というものが気になってきます(何分前までこの部屋にいた、等です)。

クラウドサービスを契約すればそういった事を解決できそうですが、やはりもっとピンポイントな機能が欲しいと思ってしまいます。そこで今回は、かねてより興味があった Raspberry Pi を購入し、USBカメラを使ってそういった機能を作っていきたいと思います。

なお、本記事は安価に作成する事が目的ではなく、あくまで自前の見守りカメラを作成し自己満足する事が目的となります。

Raspberry Pi 5 は比較的最近のバージョンで(2024年2月より日本国内販売開始[1])、このバージョンに言及された開発記事はそれ以前のものと比較するとそこまで多くありませんでしたので、動作検証報告の意味も込めて記事にしてみようと思いました。

製品購入

Raspberry Pi 5

私は以下の Vesiri 社製の Raspberry Pi 5 を購入しました。
https://www.amazon.co.jp/dp/B0CTQRCH8H

リンクが切れた時に備えて、製品の内容について軽く書いておきます。本製品の MicroSD カードにはOSはプリインストールされていません。

  • raspberry pi 5 kit ラズベリーパイ5 8GBボード
  • アクティブクーラー(冷却装置)
  • PD電源アダプター
  • 64GB MicroSDカード
  • ABS黒色ケース
  • MicroHDMIケーブル

USBカメラ

Raspberry Pi 5 の基盤にはカメラ用のインターフェースが付いており、専用のカメラを接続する事で高解像度なカメラでも高速にデータ通信が可能になりますが、確実に動作保証されたカメラをイマイチ発見できなかったため、汎用性を求めて以下の「バッファロー モデル名:BSW305MBK」USBカメラを購入しました。

https://www.amazon.co.jp/dp/B09241T966

こちらは問題無く動作しました。

OSインストール

この商品では、MicroSDカードにOSはインストールされていないので、自分で用意する必要があります。以下の記事を参考にする事で、すんなりインストールできました。

https://zenn.dev/thorie/articles/548emb-raspberry-pi-5-os-setup

OSについて記載しておきます。uname -a の結果は以下です。

Linux raspberrypi 6.6.20+rpt-rpi-2712 #1 SMP PREEMPT Debian 1:6.6.20-1+rpt1 (2024-03-07) aarch64 GNU/Linux

構築環境

以下のようなシンプルな環境を構築しました。今回、SSH のみで操作するので、Raspberry Pi にはキーボードもディスプレイも接続していません。

ライブストリーミング

配信方式

今回、結論としては HLS(HTTP Live Streaming) 方式を採用します。

本記事を書くまでライブ配信の方法について知らなかったのですが、やはり実装する上で以下の点は重要と考えました。

  • ブラウザで簡単に確認できる
  • 実装のステップが短い

配信方式としては他にも、Real-Time Messaging Protocol (RTMP) や WebRTC などがありますが、HLS に比べて非常に手間がかかりそうだったため、断念しました。HLS は数十秒程度の遅延が発生しますのでご留意ください。

ソフトウェア構成

ソフトウェア構成としては非常に単純で、Webサービスとして Nginx を、HLS配信の配備として FFmpeg を使います。

また本記事は、この記事を非常に参考にして(ほぼ丸パクリ)作られました。

USBカメラの動作確認

まずは SSH で Raspberry Pi に接続します。OS書き込み時に設定した username と hostname を入力します。

ssh username@hostname

ffmpeg を使って動画撮影をテストしてみます。下記のコマンドで作成された output.mp4 をダウンロードして、VLC media player などで確認します。動画が正常に確認できれば、テスト完了です。

ffmpeg -f v4l2 -i /dev/video0 -f alsa -i plughw:2,0 -c:v libx264 -crf 23 -preset veryfast -c:a aac -b:a 128k output.mp4

ChatGPT に聞いてみたところ、以下のオプションの説明を得ました。

  • -f v4l2 -i /dev/video0: USBカメラからV4L2(Video4Linux2)形式で映像を取得。
  • -f alsa -i hw:0: ALSA(Advanced Linux Sound Architecture)形式でオーディオデバイスから音声を取得。
  • -c:v libx264: 映像コーデックとしてH.264を使用。
  • -crf 23: 映像品質を23に設定。
  • -preset veryfast: 高速なエンコーディング設定。
  • -c:a aac: 音声コーデックとしてAACを使用。
  • -b:a 128k: 音声ビットレートを128 kbpsに設定。
  • output.mp4: 出力ファイル名。

-iで入力しているデバイス識別番号は各環境に依存しますので、下記を参考にしてください。

USBデバイスの Video 識別番号のチェック方法

次のコマンドを入力すると

v4l2-ctl --list-devices

次のように得られるので、一番上の /dev/video0 がデバイスの識別番号になります。

USB 2.0 Camera: USB 2.0 Camera (usb-xhci-hcd.1-1):
        /dev/video0
        /dev/video1
        /dev/media0
USBデバイスの Audio 識別番号のチェック方法

次のコマンドを入力すると

arecord -l

次のように得られるので、card A, XXXXXXXXX, device B: XXXXXX の A,B が識別番号になります。plughw:A,B のように入力します。

**** List of CAPTURE Hardware Devices ****
card 2: Camera [USB 2.0 Camera], device 0: USB Audio [USB Audio]
  Subdevices: 1/1
  Subdevice #0: subdevice #0

Nginx

Webサービスとして Nginx を利用します。以下のコマンドでインストールします。

apt update
apt install -y nginx

次に設定ファイルを作成します。今回セキュリティを考慮せず http プロトコルしか使用しないので、留意して実装してください。

sudo vi /etc/nginx/conf.d/stream.conf

次のように追加します。server_name は何でも良いです。

/etc/nginx/conf.d/stream.conf
server {
    listen      80;
    server_name raspberrypi;
}

テストのため、次のようなファイルを作成しておきます。

sudo sh -c 'echo "Hi" > /var/www/html/index.html'

サービスをリスタートします。

sudo /etc/init.d/nginx restart

次のコマンドで、Hiが表示されればテストは完了です。

curl http://127.0.0.1/

FFmpeg で HLS 用ファイルの出力

以下のコマンドで FFmpeg をインストールします。

sudo apt update
sudo apt install ffmpeg

専用のディレクトリを作成します。

sudo mkdir /var/www/html/stream

以下のコマンドで HLS 用のファイルが作成されます。Ctrl + C でキャンセルするまでずっと出力され続けます。

sudo ffmpeg -y -f v4l2 -s 640x480 -thread_queue_size 8192 -i /dev/video0 -f alsa -thread_queue_size 8192 -i plughw:2,0 -c:v libx264 -crf 23 -preset veryfast -c:a aac -b:a 128k -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/stream/stream_%d.ts -hls_base_url ./ -hls_flags delete_segments /var/www/html/stream/playlist.m3u8

バックグラウンド実行したい方は、上のコマンドを nohup XXXXXX & で囲んでください。XXXXXX はコマンドが入ります。

コマンドが正常であれば、次のようにファイルが出力され続けます。

xxxxx@yyyyy:/var/www/html/stream $ ll
total XXXXXX
-rw-r--r-- 1 root root   1152 Jun  4 13:31 playlist.m3u8
-rw-r--r-- 1 root root 376188 Jun  4 13:27 stream_8405.ts
-rw-r--r-- 1 root root 373744 Jun  4 13:27 stream_8406.ts
-rw-r--r-- 1 root root 368480 Jun  4 13:27 stream_8407.ts
-rw-r--r-- 1 root root 375248 Jun  4 13:27 stream_8408.ts
...
-rw-r--r-- 1 root root 416232 Jun  4 13:31 stream_8432.ts
-rw-r--r-- 1 root root 409652 Jun  4 13:31 stream_8433.ts
-rw-r--r-- 1 root root 391416 Jun  4 13:31 stream_8434.ts
-rw-r--r-- 1 root root 370360 Jun  4 13:31 stream_8435.ts
-rw-r--r-- 1 root root    119 Jun  2 22:38 stream.m3u8

ライブストリーミング用HTML

次のファイルを準備します。

/var/www/html/live.html
<!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>

ブラウザで確認

同じ Home network にいる端末からブラウザを開いて、http://XXX.XXX.XXX.XXX/live.html に接続してください(XXX.XXX.XXX.XXX は Raspberry Pi の IP アドレス)。ブラウザで映像が確認できれば、完了です。

今後の開発

本記事は以上になりますが、個人的にもう少し改良を続けます。具体的には、FFmpeg と並行して Python の OpenCV を使ってビデオファイルを読み込み、定期的なスナップショットを保存したり、動作検知を起点とした別ファイルでのビデオ保存などです。

その技術に関しては Raspberry Pi のみに依存する話では無いので記事にはしないと思いますが、何か共有できる知見やエラーがあれば、記事にして共有したいと思います。

では、良い Raspberry Pi 開発ライフを。

脚注
  1. https://raspberry-pi.ksyic.com/news/page/nwp.id/130 ↩︎

Discussion

nassynassy

こんにちは。知識もないのにラズパイ5を購入し、日々苦しんでおります。そんな中、たぬきネット?さんの記事が目に留まり、ただいまffmegによるライブストリーミングに挑戦中です。VLCによる遠隔操作、キーボード設定、IPアドレスの固定まではなんとかできました。
 一つ質問があります。こんな私でも上記の分かりやすい説明のお陰でHLS用ファイルの出力まではできました。ストリーミングされたデータも保存することができ確認もできたところです。しかし、ライブストリーミング用HTMLファイル作成後、いざブラウザでアドレス(http://×××.×××.××.××/live.html)を開くとうまく表示されません。接続を拒否される画面になります。※ちなみに、http://×××.×××.××.××と入力すると”Hi”と表示される。
 こんな状況ですが何かアドバイスを頂けるとありがたいです。

tanukinettanukinet

コメントいただきありがとうございます。何点か確認させてください。

  1. stream フォルダの中身が正常に更新され続けているか
ll /var/www/html/stream
  1. 「接続を拒否される画面になります」とは具体的にどんなメッセージか。

  2. http://×××.×××.××.××/live.html の確認

例えば次のようにファイルを入れ替えた後

cp /var/www/html/live.html /var/www/html/live.html.bk
sudo sh -c 'echo "hogehoge" > /var/www/html/live.html'

http://×××.×××.××.××/live.html にアクセスした場合に "hogehoge" が表示されるかどうか

※ちなみに、私はGoogle Chrome ブラウザを使っています。

nassynassy

早々のご連絡ありがとうございます。上記の1.2.3について早速試して報告させていただきます。本日は環境が整っていないため、明日の午前中に投稿させていただきます。もしお時間がありましたらお手数をお掛けしますがアドバイスをお願いします。※上記1.2.3について画像も一緒に投稿します。

nassynassy

おはようございます。アドバイス頂いた1.2.3について検証してみましたので報告させていただきます。
1. stream フォルダの中身が正常に更新され続けているか?
       ➡は画像を添付します。更新され続けている気がします。
   https://storage.googleapis.com/zenn-user-upload/dfc1b95ebeca-20240830.png

  1. 「接続を拒否される画面になります」とは具体的にどんなメッセージか。
      https://storage.googleapis.com/zenn-user-upload/fc75c63ed9bc-20240830.png

3. http://×××.×××.××.××/live.html の確認
    ➡「cp /var/www/html/live.html /var/www/html/live.html.bk」の結果
 https://storage.googleapis.com/zenn-user-upload/7d24f4647a33-20240830.png

3.について、そもそもファイルが存在しないといわれました。そこで、私も気になったので指定の場所?を
 スクリーンショットします。参考になればいいのですが...。
    https://storage.googleapis.com/zenn-user-upload/752b13caba82-20240830.png

以上です。宜しくお願いします。
※ラズパイのファイアーウォールは「無効」にしてあります。

tanukinettanukinet

原因

/var/www/html/live.html が存在しないのが原因となります。

http://XXX.XXX.XXX.XXX//var/www/html の階層はリンクしています。デフォルトでは http://XXX.XXX.XXX.XXX/ へのアクセスは、http://XXX.XXX.XXX.XXX/index.html 、つまり、/var/www/html/index.html のファイルを読み取っている事になります。http://XXX.XXX.XXX.XXX/live.html は、/var/www/html/live.html を読み取る事になりますが、3.のスクリーンショットを見る限り、そのようなファイルが存在しません。2. のエラー画面も Not Found となり、これはファイルが存在しない事を意味します。

対応

/var/www/html/live.html を次の中身で作成します。

/var/www/html/live.html
<!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>
nassynassy

返信ありがとうございます。また、丁寧に答えていただき感謝します。すごく理解できました。
初歩的な質問なのですが、
” /var/www/html/live.html を次の中身で作成します。 ”
について、まず、
1.sudo vi /var/www/html/live.html
https://storage.googleapis.com/zenn-user-upload/ebd9beab215e-20240830.png

を実行し、
2.上記のプログラムを書き込み
でよろしいでしょうか?方法が間違っていたらすいません。

※1を実行すると
https://storage.googleapis.com/zenn-user-upload/4de0f3ff63fa-20240830.png
とでます。

nassynassy

連絡ありがとうございます。度々すいません。
https://storage.googleapis.com/zenn-user-upload/8b3e493b71d0-20240830.png
理解できました。swapファイルを 「:recover」で修復?削除?しようとしたものの・・・
詰まりました。
https://storage.googleapis.com/zenn-user-upload/c4480a1487a3-20240830.png
すいません。アドバイス頂けますか?削除した方が早いと思うのですが仕方がわかりません。
 教えて頂いたサイトは、警告ページの下部に4つの選択が出ますが、私のターミナルには存在しません。何か違うのでしょうか?

tanukinettanukinet

ls -la などで確認してみてください。エディタの隠しファイルがあるんだと思います。おそらくそれらを削除してしまえば解決するかと。

これらのご質問は本記事とはあまり関連が無く、Linuxやエディタの基本的な操作に関連しています。厳しい事を言うようですが、それらの対処方法はインターネットにたくさんの記事があると思われますので、まずは検索してみてはいかがでしょうか。エラーメッセージでググるなど。

Webサーバの仕組みやLinux OS の基本的操作についてあまり理解されていない場合は、まずは先にそれらを勉強するのも良いと思います。これらの基礎的な事は他に応用が効くので、勉強して損は無いはずです。

nassynassy

返信ありがとうございます。仰る通りだと思います。1から勉強し力をつけたいと思います。色々とアドバイスをいただきありがとうございました。