Raspberry Piを使ってStreamingするならMjpg-Streamer。今回はRaspberry PiにPythonでWebサーバーを立ち上げ、Mjpg-Streaminer、FFmpegを使って、Streaming、録画、写真の撮影が出来るアプリを作成したいと思います。
必要なハード
- Raspberry PI 3 model B+ (他のモデルでもOK)
- MicroSD:16GB。 (容量が4G以上ならOK)
- ロジクール ウェブカメラ C270 ブラック HD 720P ウェブカム
- 撮影用のUSBカメラです。
- USBカメラであれば特にこれにこだわりません。
OSのインストール
インストールはPI 3 model B+のWifiを利用して、Raspberry Pi にOSをInstallに従って行いました。
設定は下記の通り。
- OS : Raspberry Pi OS Lite(32-bit)
- ホスト名 : raspberry
- ユーザー名: pi
- パスワード: raspberry
今回はサーバがメインなのでGUIの必要無し。Raspberry PIの負担を下げる為にCUIを選択しました。LANケーブル無しでインストール出来、インストール後直ぐにSSHが使えるのでとても便利です。
PCからRaspberry piに “ssh pi@raspberry.local”( パスワードは”raspberry”) でつなぎホームディレクトリを確認すると下記の様になっています。
Home dir
pi@raspberrypi:~ $ ls -al
total 36
drwxr-xr-x 5 pi pi 4096 Jun 14 06:14 .
drwxr-xr-x 3 root root 4096 May 7 15:42 ..
-rw------- 1 pi pi 1136 Jun 17 23:04 .bash_history
-rw-r--r-- 1 pi pi 220 May 7 15:42 .bash_logout
-rw-r--r-- 1 pi pi 3523 May 7 15:42 .bashrc
drwx------ 3 pi pi 4096 Jun 14 00:48 .gnupg
-rw-r--r-- 1 pi pi 807 May 7 15:42 .profile
後は、sudo apt-get update と sudo apt-get upgrade で最新の状態にしてOSのインストール完了です。
Mjpg-StreamerとFFmpegのインストール
今回はMjpg-StreamerとFFmpegをインストールします。
先ずはMjpg-Streamer。
このインストールでは”git”を使います。先ずは、”sudo apt-get install git”で”git”を先にインストールして下さい。その後USBカメラをRaspberry Piに繋いで下記を実行して下さい。
cd ~/
sudo apt install -y git cmake libjpeg-dev
git clone https://github.com/neuralassembly/mjpg-streamer.git
cd mjpg-streamer/mjpg-streamer-experimental
make
sudo make install
mjpg-streamerの動作を確認します。ホームディレクトリに新たに”mjpg-streamer”が作成されています。その下の”mjpg-streamer-experimental”ディレクトリに移動して(手順通りにInstallしていれば、そこにいるはず)スクリプトファイル”start.sh”を実行。下記の様な画面になればOKです。

mjpg-streamerは8080ポートを使用しているので、PCのブラウザのURL欄に”raspberry.local:8080″と入力すると、下記の様な画面が表示されます。

これが、”mjpg-streamer”のHPです。HP中段下にある写真(ドアの写真)がカメラの画像です。左側にメニューを使ってスナップショットやStreamingが出来ます。
”start.sh”は殆どがコメントでコマンドは下記の2行のみです。
export LD_LIBRARY_PATH="$(pwd)"
./mjpg_streamer -i "./input_uvc.so" -o "./output_http.so -w ./www"
1行目は環境変数の設定。2行目がMjpg-streamerの実行コマンドです。Mjpg-streamerは実行時に幾つかのパラメタを設定する必要が有ります。主はパラメタ一は以下の通り。
| Option | parameters | Description |
| -i | input-plugin.soの指定 PI_カメラ: “./input_raspicam .so [parameters]”USB_カメラ: “./input_uvc.so [parameters]” | |
| -d | カメラの指定 Default: /dev/video0(現在Activeなカメラ) | |
| -r | 画面の解像度の指定 QQVGA QCIF CGA QVGA CIF PAL VGA SVGA XGA HD SXGA UXGA FHD または、 数字で指定 640×480 Default: 640×480 | |
| -f | 1秒間にフレーム数 (camera may coerce to different value) | |
| -q | JPEGの品質(0-100)を指定。Default: 85 | |
| -o | output-plugin.soの指定 ’./output_http.so [parameters]” | |
| -w | Web関係のファイルが入っているfolderのパス (サブフォルダは不可) | |
| -p | TCP が使うポート Default: 8080 | |
| -l | IPアドレスの指定。 | |
| -h | ヘルプに表示 | |
| -v | version information | |
| -b | backgroundへ |
オプション -i と -oは必ず指定します。./mjpg_streamer -i “./input_uvc.so” -o “./output_http.so -w ./www” は、この表から
- 入力(-i)
- カメラ:USBカメラ
- 解像度:640x480
- 出力(-o)
- Web関係のファイル: ./www
- 使用ポート: 8080
と設定した事になります(正確にはこの他にもデフォルト値が適応さていますが)。
次は、FFmpegをインストール
FFmpegはStreamingを録画する時に使用します。インストールは簡単で、”sudo apt install ffmpeg”でOKでした。
ネット環境
今回のネット環境は下記の様にRaspberry Pi とデスクトップPCを1つのルータ内に配置しています。Raspberry Pi にはSSHでPCから接続。さらに、Raspberry Pi にSAMBAを立ち上げています。Raspberry Pi には電源のみの接続です。

アプリの製作
必要なアプリをインストールしたのでアプリの製作に入ります。
アプリの概要
下記の様にStreamingと写真の表示と保存、保存したデータの再生が出来るアプリを目指します。

- Pythonでサーバを立ち上げそのHP画面を上記の様に設定。
- ユーザーから要求を受けたPythonでサーバはStreaming、静止画に対し以下で対応。
- 表示: Mjpg-streamerサーバと
- 録画: ffmpeg、Mjpg-streamerサーバと
静止画とStreamingの取り込みはHTMLコードはデモプログラムから以下に通り。
- 静止画の取り込み: <img src = “./?action=snapshot ” />
- Streamingの場合: <img src=”./?action=stream” />
動画の録画はffmpegを使う
mjpg-streamerの出力http://raspberrypi.local:8080/?action=streamをffmpgの入力にすれば、Streamingの録画が出来る事が分かりました。コマンドは以下の通り、
ffmpeg -i http://raspberrypi.local:8080/?action=stream -an -r 10 -vcodec libx264 ./file.mp4
- -i : 入力先の指定。今回は、mjpg-streamerのStream出力 http://raspberrypi.local:8080/?action=streamを指定
- -an :音声無し
- -r 10:1秒間に10コマ。フレーム数の指定。
- -vcodec:コーデックの指定。
- 最後に:保存ファイル名
これで録画が開始されます。録画の終了はmjpg-streamerにCtrl-Cを送ります。
プログラムの説明
プログラムは下記の5つです。
- stream.py:メインのプログラム。Pythonで書かれたWebServerです。
- stream.html:サーバーHPのHTMLファイル。
- stream.css:HPのCSSファイル。
- st_mjpg.sh:mjpg-streamer起動用のスクリプトファイル。
- favicon.ico:ファビコン用のファイル。
先ずは、stream.py。pythonで書かれたWebサーバーです。subprocess.Popen(), subprocess.run()を使って、mjpg-streamer、ffmpgを管理しています。
stream.py
from http.server import HTTPServer, SimpleHTTPRequestHandler
from urllib.parse import urlparse,parse_qs
import subprocess
from subprocess import PIPE
import os
flg_state = 0
proc_pt = subprocess.Popen("./st_mjpg.sh",shell=True)
class MyHandler(SimpleHTTPRequestHandler):
def do_GET(self):
global flg_state, proc_pt
self.send_response(200)
fl = 1
if self.chk_file() == 0:
parsed = urlparse(self.path)
params = parse_qs(parsed.query)
a = next(iter(params))
#----- Stream Command -----------------
if a == "1":
cmd = params['1'][0]
#----- flg_state -----------
# 0: Stream
# 1: start/Stop rec
# 2: take photo
# 3: Replay video
#----- Start Streaming -----------
if cmd == '_start':
flg_state = 0;
#----- Rec -----------
elif cmd == '_rec':
if flg_state != 1:
subprocess.run("rm file.mp4".split())
cmd = "ffmpeg -i http://raspberrypi.local:8080/?action=stream -an -r 10 -vcodec libx264 ./file.mp4"
proc_pt = subprocess.Popen(cmd, shell=True)
flg_state = 1;
else :
proc_pt.kill()
cmd = "killall -s INT ffmpeg"
proc = subprocess.run(cmd.split())
flg_state = 0;
#----- take picture -----------
elif cmd == '_photo':
flg_state = 2;
cmd = "wget -O ./phto.jpg http://raspberrypi.local:8080/?action=snapshot"
proc = subprocess.run(cmd.split())
#----- show picture -----------
elif cmd == '_rephoto':
flg_state = 2;
#----- Relay -----------
elif cmd == '_replay':
flg_state = 3
#----- Data transfer --------------
elif a == "80":
self.send_header('Content-type', "text/plain")
self.end_headers()
fl = 0;
if os.path.exists("./phto.jpg") == True :
fl = 1;
if os.path.exists("./file.mp4") == True :
fl += 2;
buf = str(flg_state) + ',' + str(fl) + ','
self.wfile.write(buf.encode())
fl = 0
#-------------------------------------
else :
fl = 0
if fl == 1:
f = open("stream.html",'rb')
self.send_header('Content-type', "text/html")
self.end_headers()
self.wfile.write(f.read())
f.close()
#-------------------------------------------------
def chk_file(self):
global flg_state
a = 0
if self.path == "/":
self.path = "/stream.html"
dataType = "text/html"
flg_state = 0
a = 1
if self.path.endswith(".css"):
dataType = "text/css"
a = 1
if self.path.endswith(".jpg"):
dataType = "image/jpeg"
a = 1
if self.path.endswith(".ico"):
dataType = "text/plain"
a = 1
if self.path.endswith(".mp4"):
dataType = "video/mp4"
a = 1
if a == 1:
self.path = "." + self.path
f = open(self.path,'rb')
self.send_header('Content-type', dataType)
self.end_headers()
self.wfile.write(f.read())
f.close()
return a
#-------------------------------------------------
host = ''
port = 8010
httpd = HTTPServer((host, port), MyHandler)
print('serving at port', port)
httpd.serve_forever()
- 8行:proc_pt = subprocess.Popen(“./st_mjpg.sh”,shell=True)
- ここで、mjpg-streamerを起動しています。
- スクリプトファイル、”st_mjpg.sh”の説明は後ほど
- 34行:if cmd == ‘_start’:
- ”Start”ボタンが押されるとここが実行されます。
- 38行:elif cmd == ‘_rec’:
- Take項目の、”Video”ボタンが押されるとここが実行されます。
- 39から43行が録画開始処理
- 44から48行が録画終了処理
- 51行:elif cmd == ‘_photo’:
- wgetを使って、mjpg-streamerの写真をサーバーに保存
- 57行:elif cmd == ‘_rephoto’:
- Replay項目の、”Photo”ボタンが押されるとここが実行されます。
- 61行:elif cmd == ‘_replay’:
- Replay項目の、”Video”ボタンが押されるとここが実行されます。
- 65から76行: サーバーの状態をクライアントに送信。送信データは2つ。最初がサーバの状態。次はファイルの有無。
- 129行:port = 8010
- Webサーバーのポートを、”8010”としています。
次は、st_mjpg.sh。stream.py内で使用されるmjpg-streamerを実行する為のスクリプトファイル。mjpg-streamerインストール時に作成された”start.sh”を参照しています。
st_mjpg.sh
#!/bin/sh
cd /home/pi/mjpg-streamer/mjpg-streamer-experimental
mjpg_streamer \
-i "input_uvc.so -f 10 -r 320x240 -d /dev/video0 -n" \
-o "output_http.so -w ./www -p 8080"
- 3行:mjpg_streamerのディレクトリへ移動
- 本ページの説明通りにmjpg_streamerをインストールしていれば、mjpg_streamerのパスはこれになります。
- もし違う場所にインストールしているのなら、パスを変更して下さい。
- 5行:-i “input_uvc.so -f 10 -r 320×240 -d /dev/video0 -y -n”
- -i 入力関係の指定。”input_uvc.so 以下にパラメータ指定
- -f:フレームの指定。今回は1秒間に10枚
- -r:解像度の指定。320x240
- -d:カメラディバイスの指定。/dev/video0と認識されたいました。
- -n:pan/tilt/focus/等の設定を行わない。このカメラにそれらの機能は無い。
- 6行:-o “output_http.so -w ./www -p 8080”
- -o 出力関係の指定。”output_http.so 以下にパラメータを指定
- -w:ウェブコンテンツのあるディレクトリ。インストール時に作られた、”www”フォルダーの位置をしてしています。
- -p:使用するポート。今回は、8080を指定。
次は、”stream.html”。 HP用のHTMLです。
stream.html
<!doctype html>
<html>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='width=device-width,initial-scale=1'>
<link rel='stylesheet' type='text/css' href='./stream.css' >
<title>Net Stream</title>
</head>
<body>
<div class='b_frame'>
<div class='t_font'><u>Stream 1.0</u></div>
<div class='p_frame'>
<img id="_stream" width="320" height="240" style='display:none'>
<video controls id='_video' width="320" height="240" style='display:none'>
<p>動画の再生に対応していません</p>
</video>
</div>
<div class="_menu">
<div class="input-group">
<label>Stream</label>
<button style="visibility:hidden;">none</button>
<button type='button' id='_start' onclick='onBtn_Btn("_start")'>Start</button>
<label>Take</label>
<button type='button' id='_rec' onclick='onBtn_Btn("_rec")'>Video</button>
<button type='button' id='_photo' onclick='onBtn_Btn("_photo")'>Photo</button>
<label>Replay</label>
<button type='button' id='_replay' onclick='onBtn_Btn("_replay")'>Video</button>
<button type='button' id='_rephoto' onclick='onBtn_Btn("_rephoto")'>Photo</button>
<label>Download</label>
<a href="./file.mp4" download="file.mp4"><button id='_dlv' type="button">Video</button></a>
<a href="./phto.jpg" download="phto.jpg"><button id='_dlp' type="button">Photo</button></a>
<label>Control</label>
<button style="visibility:hidden;">none</button>
<button id='_control' type='button' onclick='onBtn_Btn("_control")'>Control</button>
</div>
<form method='get' id='Btn_main'>
<input name="1" id='Btn_sub' style="display:none">
</form>
</div>
</div>
<script>
// 0:state
// 1: file 0:none 1:jpg 2:mp4 3:both
var para=['0','0'];
var xhr = new XMLHttpRequest();
var a,b;
xhr.open('GET', "http://raspberrypi.local:8010/?80=0");
xhr.send();
xhr.onreadystatechange = function() {
if(xhr.readyState === 4 && xhr.status === 200) {
console.log( xhr.responseText );
b=0;
for(a = 0; a < 2; a++) {
para[a]='';
while( xhr.responseText[b] != ',') {
para[a] += xhr.responseText[b];
b ++;
}
b ++;
}
a = Number(para[0]);
switch(a) {
case 0: // Streaming
document.getElementById('_video').style.display = "none";
document.getElementById('_stream').src = "http://raspberrypi.local:8080/?action=stream";
document.getElementById('_stream').style.display = "block";
break;
case 1: // Start rec
document.getElementById('_rec').style.backgroundColor = 'red';
document.getElementById('_rec').innerHTML = "Stop";
document.getElementById('_video').style.display = "none";
document.getElementById('_stream').src = "http://raspberrypi.local:8080/?action=stream";
document.getElementById('_stream').style.display = "block";
break;
case 2: // Photo
document.getElementById('_video').style.display = "none";
document.getElementById('_stream').src = "./phto.jpg";
document.getElementById('_stream').style.display = "block";
break;
case 3: // Replay
document.getElementById('_stream').style.display = "none";
document.getElementById('_video').style.display = "block";
document.getElementById('_video').src = "./file.mp4";
break;
}
if(a == 1) b = ['_start', '_photo', '_control'];
if(a > 1) b = ['_rec', '_photo', '_control'];
if(a) b.forEach(element => set_Btn(element));
a = Number(para[1]);
switch(a){
case 0: b = ['_replay', '_rephoto', '_dlv', '_dlp'];
break;
case 1: b = ['_replay', '_dlv'];
break;
case 2: b = ['_rephoto', '_dlp'];
break;
}
if(a < 3) b.forEach(element => set_Btn(element));
}
}
function onBtn_Btn(_id) {
if( _id != '_control')
{
document.getElementById('Btn_sub').value = _id;
document.getElementById('Btn_main').submit();
}
else
window.open("http://raspberrypi.local:8080/control.htm", '_blank');
}
function set_Btn(_id) {
document.getElementById(_id).disabled = true;
document.getElementById(_id).style.backgroundColor = "gray";
}
</script>
</body>
</html>
- 13行:写真、Streamingの場合は、<Img> タグを有効にして使用しています。
- 14行:Videoの再生は、<video> タグを有効にして表示しています。
- 59行:サーバーからのパラメータの取得とHPの設定
- 120から128行:HP内でボタンを押された時の処理。
次は、”stream.css”。HP用のCSSファイルです。
stream.css
@charset "UTF-8";
.t_font {
font-size: 32px;
font-weight: bold;
font-style: italic;
text-align: center;
color: #0ff;
}
.b_frame {
width: 370px;
background: #363636;
border-radius: 50px;
border-style: ridge;
border-width: 5px 15px;
border-color: sienna;
margin: 0 auto
}
.p_frame {
width: 324px;
height: 240px;
background: #363636;
border-radius: 10px;
border-style: ridge;
border-width: 5px;
border-color: sienna;
margin: 10px auto;
text-align: center;
}
button {
display: inline;
margin: 4px 2px;
line-height: 15px;
width: 80px;
font-size: 15px;
font-weight: bold;
font-style: italic;
cursor: pointer;
color: #fff;
background: #228b22;
border-radius: 20px;
text-align: center;
padding-left: 4px;
}
.input-group>label {
display: inline-block;
font-size: 16px;
font-weight: bold;
font-style: italic;
padding-left: 15px;
min-width: 40%;
color: #0ff;
margin-left: 10px;
}
._menu {
display: block;
flex-wrap: nowrap;
min-width: 280px;
background: #363636;
border-radius: 4px;
margin: 15px auto 30px;
}
最後は、”favicon.ico” ですが、これは画像ファイルで特に説明無し。
プログラムの実行
これをダウンロードして解凍すると”stream”と言うフォルダーが出来ます。これをRaspberry Piのホームディレクトリにコピーして下さい。コピー後のホームディレクトリと”stream”ディレクトリの内容は以下の様になります。

実行ディレクトリは、”stream”ディレクトリです。”stream”ディレクトリに移動して、python3 stream.pyと実行して下さい。画面に下記の様なコメントが出力され、USBカメラのLEDが点灯すれば動作が開始した事が分かります。

PC側からブラウザを使用してRaspberryに接続します。PCのブラウザは、”Firefox”を使用して下さい。それ以外のブラウザでの動作は確認していません。ブラウザのURL欄に、”http://raspberrypi.local:8010/”と入力して下さい。下記のHP画面が表示されstreamingが開始されます。

初めての起動では、録画した動画も写真も無いのでそれらに関係するボタンは無効化(灰色)されています。状態に応じてボタンが有効になったり無効になったりします。
最後に
今回はPythonのWebサーバーを使って簡単にStreaming、録画、写真の撮影が出来ました。Raspberry PIのGPIOポートを使えば、暗くなった時の撮影用ライトの点灯、人感センサーを使えば人を感知した時の録画等の活用が可能です。
ちょっとがっかりな点は、mjpg-streamerは音声に対応していない点です。画像だけです。やっぱり音は有った方が良いです。
追加:録画してからそれが再生されるまでに若干時間がかかる様です。今回の環境で録画終了から再生されるまで2分位かかりました。ダウンロードは録画終了後直ぐ出来るのですが、再生のみ時間がかかります。なぜそんなに時間がかかるのか不明です。