👌

【ffmpeg】簡単ネットワークカメラの作り方

2023/04/28に公開

はじめに

こちらの記事で、監視カメラアプリLiveCapture3からの映像を複数タイル状に並べて表示するJavaScriptのビューワを作りました。
このビューワは、HTTP GETでJPEG画像を配信するサーバであれば、LiveCapture3ではなくてもライブビューを表示できます。

今回は、このビューワでライブビューを確認できる、簡単ネットワークカメラを作ります。

処理概要

環境は、Ubuntu22.04をベースに記載しますが、WindowsやMacでも同様の構成で作成できます。

使用するモジュールは以下の3つです。

  • Video for Linux 2(v4l2)
    • Linux上でビデオを扱うための統一的なAPI。USB接続されたカメラ(UVC)からの映像を取得します。
    • v4l2の代わりに、Windowsの場合はDirectshow、Macの場合はAVFoundationを使用します
  • ffmpeg
    • 画像や動画を扱うコマンド型式のソフトウェア。USBカメラからの映像をJPEG画像に保存します。
  • NginX
    • 軽量なWebサーバー。カメラ映像のJPEG画像をHTTP配信する為に使用します。

個々のモジュールの詳細はWeb上にたくさんありますので、ここでは割愛します。

処理の流れは、

  1. v4l2で、接続したUSBカメラの映像をffmpegに取り込み
  2. ffmpegでカメラ映像をjpegに保存
  3. 保存されたjpeg画像をnginxでHTTP配信

という感じです。

図に書くとこんな流れになります。

環境構築(Ubuntu)

まずは前述の3つのモジュールをインストールします。

sudo apt upodate
sudo apt install -y nginx ffmpeg v4l-utils

インストール出来たら、systemctl status nginxで、NginXの状態を確認します。

systemctl status nginx

nginx.service - A high performance web server and a reverse proxy server
    Loaded: loaded (/usr/lib/systemd/system/nginx.service, enabled)
    Active: inactive (dead)

ステータスがinactive(dead)の場合は開始させます。

sudo systemctl start nginx
systemctl status nginx
nginx.service - A high performance web server and a reverse proxy server
    Loaded: loaded (/usr/lib/systemd/system/nginx.service, enabled)
    Active: active (running)

ステータスがactive(running)になったら、ウェブブラウザで実際にアクセスしてみて、下記のようなNginXのデフォルトページが表示されればOKです。

次にPCにUSBカメラを接続して、v4l2-ctl --list-devicesでデバイスパスを表示させます

v4l2-ctl --list-devices

HD Webcam eMeet C960: HD Webcam (usb-0000:00:0b.0-1):
	/dev/video0
	/dev/video1

恐らく、1つのUSBカメラにつき、複数のデバイスパスが表示されると思いますが、その中で若い番号の方を使用します。
上記の例の場合は/dev/video0を使用します。

配信準備

処理としては、ffmpegでNginXの公開ディレクトリにカメラ映像のJPEG画像を保存し続けるだけです。

NginXの公開ディレクトリは /var/www/htmlになりますので、ffmpegを使用して、/var/www/html/capture.jpgを作成させます。

sudo ffmpeg -f v4l2 -i /dev/video0 \
           -r 2 -qscale:v 2 -y \
	   -f image2 -update 1 /var/www/html/capture.jpg

引数の説明は以下です。

引数 説明
-f v4l2 入力フォーマットとしてv4l2を指定
-i /dev/video0 /dev/video0のビデオを入力
-r 2 画像取得のフレームレートを2fpsに設定
-q:v 2 JPEGの品質 範囲は2-31で、31が最も悪い品質
-y 出力を確認せずに上書きする
-f image2 出力フォーマットとしてJPEGを指定
-update 1 出力を更新する
/var/www/html/capture.jpg 出力するJPEGファイルのパス

問題なくffmpegが起動すると、秒間2枚のペースで、/var/www/html/capture.jpgを更新し続けます。

確認の為、ウェブブラウザで、http://xxx.xxx.xxx.xxx/capture.jpg にアクセスして、カメラ映像が表示されればOKです。
(ブラウザでの表示ではJPEG画像は自動更新されません)

ビューワの修正

こちらで紹介したJavaScriptビューワーは、LiveCapture3のJPEG画像配信のパス(/SnapJpeg)にアクセスするようになっているので、ちょっと修正します。
(分かりやすいようにカメラの数は1個に変更しています)

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    
	<title>Multi Camera View for LiveCapture3</title>
	
	<style>

	body{ 
		background-color: #808080;
		margin-left: 0px;
		margin-top: 0px;
		margin-right: 0px;
		margin-bottom: 0px;
		overflow-x: hidden;
		overflow-y: hidden;
	} 
	
	</style>
	
	<script type="text/javascript">
		
		//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
		// 横に並べるカメラ映像の数
		//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
		const MAX_COL = 1

		//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
		// 縦に並べるカメラ映像の数
		//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
		const MAX_ROW = 1;
		
		//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
		// 配信サーバのJPEGまでのURL
		//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
		const ADDRESS_LIST = [
			"http://192.168.1.24/capture.jpg"
		];
		
		function init() {
			expand();
			draw();
		}
		
		function draw() {
			var canvas = document.getElementById('c1');
			var ctx = canvas.getContext('2d');
			var canW = canvas.width;
			var canH = canvas.height;
			
			var pos = 0;
			for (var row = 0; row < MAX_ROW; row++){
				for (var col = 0; col < MAX_COL; col++){
					if (pos < ADDRESS_LIST.length){
						const img = new Image();
						const c = col;
						const r = row;
						img.onload = function() {
							drawImage(ctx, img, c, r, canW, canH);
						}
						img.src = `${ADDRESS_LIST[pos]}?t=${new Date().getTime()}`;
					}
					pos++;
				}
			}
		}
		
		function drawImage(ctx, img, col, row, canW, canH) {
			
			var imgW = img.width;
			var imgH = img.height;
			
			var dstCanW = canW / MAX_COL;
			var dstCanH = canH / MAX_ROW;

			var dstW = dstCanW;
			var dstH = imgH * dstW / img.width;
			if (dstH > dstCanH) {
				dstH = dstCanH;
				dstW = imgW * dstH / img.height;
			}

			var dstX = (dstCanW - dstW) / 2 + dstCanW * col;
			var dstY = (dstCanH - dstH) / 2 + dstCanH * row;
			
			ctx.drawImage(img, dstX, dstY, dstW, dstH);
		}
		
		function expand(){
			var canvas = document.getElementById('c1');
			canvas.width = window.innerWidth;
			canvas.height = window.innerHeight;
		}
		
	</script>

</head>

<body>

	<canvas id="c1" style="background-color:gray;"></canvas>
	

	<script type="text/javascript">
		window.onload = function(){init();}
		window.onresize = function(){expand();}
		setInterval("draw()", 100);
	</script>

</body>

</html>

修正ポイントは、ADDRESS_LIST配列に、JPEG画像までのURLを入れている点です。
それによって、image.srcに渡すパスもADDRESS_LISTの項目だけになります。

img.src = `${ADDRESS_LIST[pos]}?t=${new Date().getTime()}`;

ちなみに、パスの後ろにつけている ?t=${new Date().getTime()} は、現在時刻のunixtimeをQueryStringとしてURLに追加することでブラウザのキャッシュを効かなくする昔からある手法です。

このHTMLをliveview.htmlのような名前で保存してダブルクリックでブラウザで表示させれば、USBカメラの映像が表示されます。

さいごに

今回作ったのは、非常に簡易なネットワークカメラですが、JPEG画像だけでもこれだけの動画になります。
サーバ側はJPEG圧縮しかしていないので、非常に低負荷で、ラズパイなどのSingle Board Computerでも問題なく動きますので、色々と試してみてください。

また、興味を持ったら、RTSPなどの動画ストリーム配信にも挑戦してみると面白いと思います!

Discussion