1. はじめに
MotionBoard Cloudのストリーミング動画の再生機能を使い、USBカメラの映像をリアルタイムに表示したいと思っていろいろ調べてみました。
現状ではHLSとMPEG-DASHの再生に対応していますが、以下にはHLSでの配信実験結果を書きます。
2. HLSとは
HTTP Live Streamingの略で、HTTPベースで動画のストリーミングができるプロトコルです。
3. MotionBoardとは
データベースやセンサーデータなどを可視化できるソフトウェアです。
商用ソフトウェアですが、各種データベースとの接続や高度な可視化・分析機能が使用できます。
4. 前提環境
- MotionBoard Cloud
- Raspberry Pi 4B (4GB)
- Raspbian Buster Lite
- 適当な市販のUSBカメラ
5. 連携手順
Raspberry Piの初期セットアップは済んでいるものとします。
FFmpegのインストール
Webカメラの映像をts形式に変換すると同時にm3u8形式のプレイリストを生成する為に使用します。
sudo apt-get install ffmpeg
Webサーバー
pythonを使用して簡易的なものを建てます。
以下の例ではポート番号は8000としています。
レスポンスヘッダにAccept-Ranges、Access-Control-Allow-Origin、Access-Control-Allow-Headersを含めないとMotionBoard側でエラーとなり、動かないので注意します。
headers["Accept-Ranges"] = 'bytes'
headers["Access-Control-Allow-Origin"] = '*'
headers["Access-Control-Allow-Headers"] = '*'
server.pyとして~/streamに作成。
mkdir ~/stream
cd ~/stream
vi server.py
#!/usr/bin/env python
import codecs
import mimetypes
import os
import re
import sys
import threading
import webbrowser
if sys.version_info[0] > 2:
from urllib.parse import urlparse
from http.server import HTTPServer
from http.server import BaseHTTPRequestHandler
else:
from urlparse import urlparse
from BaseHTTPServer import HTTPServer
from BaseHTTPServer import BaseHTTPRequestHandler
folder = "."
port = 8000
browse = False
redirects = []
index_page = "index.html"
not_found_page = ""
args = sys.argv[1:]
while len(args) > 0:
arg = args.pop(0)
if (arg == "--port" or arg == "-p") and len(args) > 0 and args[0].isdigit():
port = int(args.pop(0))
if (arg == "--index-page" or arg == "-i") and len(args) > 0:
index_page = args.pop(0)
if (arg == "--not-found-page" or arg == "-e") and len(args) > 0:
not_found_page = args.pop(0)
elif (arg == "--redirect-map" or arg == "-r") and len(args) > 0:
with codecs.open(args.pop(0), "r", "utf-8") as open_file:
data = open_file.read()
lines = re.split(r"\r\n?|\n", data)
while len(lines) > 0:
line = lines.pop(0)
match = re.compile("([^ ]*) *([^ ]*)").match(line)
if match and len(match.groups()[0]) > 0 and len(match.groups()[1]) > 0:
redirects.append({
"source": match.groups()[0],
"target": match.groups()[1] })
elif arg == "--browse" or arg == "-b":
browse = True
elif not arg.startswith("-"):
folder = arg
class HTTPRequestHandler(BaseHTTPRequestHandler):
def handler(self):
pathname = urlparse(self.path).path
location = folder + pathname;
status_code = 0
headers = {}
buffer = None
for redirect in redirects:
if redirect["source"] == pathname:
status_code = 301
headers = { "Location": redirect["target"] }
if status_code == 0:
if os.path.exists(location) and os.path.isdir(location):
if location.endswith("/"):
location += index_page
else:
status_code = 302
headers = { "Location": pathname + "/" }
if status_code == 0:
if os.path.exists(location) and not os.path.isdir(location):
status_code = 200
else:
status_code = 404
location = folder + "/" + not_found_page
if os.path.exists(location) and not os.path.isdir(location):
with open(location, "rb") as binary:
buffer = binary.read()
headers["Content-Length"] = len(buffer)
headers["Accept-Ranges"] = 'bytes'
headers["Access-Control-Allow-Origin"] = '*'
headers["Access-Control-Allow-Headers"] = '*'
headers["cache-control"] = 'public, max-age=0, must-revalidate'
extension = os.path.splitext(location)[1]
if extension == '.m3u8':
content_type = 'audio/x-mpegurl'
elif extension == '.ts':
content_type = 'video/MP2T'
else:
content_type = mimetypes.types_map[extension]
if content_type:
headers["Content-Type"] = content_type
print(str(status_code) + " " + self.command + " " + self.path)
sys.stdout.flush()
self.send_response(status_code)
for key in headers:
self.send_header(key, headers[key])
self.end_headers()
if self.command != "HEAD":
if status_code == 404 and buffer == None:
self.wfile.write(str(status_code))
elif (status_code == 200 or status_code == 404) and buffer != None:
self.wfile.write(buffer)
def do_GET(self):
self.handler();
def do_HEAD(self):
self.handler();
def log_message(self, format, *args):
return
server = HTTPServer(("localhost", port), HTTPRequestHandler)
url = "http://localhost:" + str(port)
print("Serving '" + folder + "' at " + url + "...")
if browse:
threading.Timer(1, webbrowser.open, args=(url,)).start()
sys.stdout.flush()
try:
server.serve_forever()
except (KeyboardInterrupt, SystemExit):
server.server_close()
##FFmpegのテスト
(Webカメラの映像は/dev/video0としています)
ffmpeg -f alsa -thread_queue_size 1024 \ -i hw:1 \
-f v4l2 -thread_queue_size 512 -input_format yuyv422 -video_size 800x600 \
-i /dev/video0 \
-filter_complex scale=800x600,fps=12 \
-c:v h264_omx -b:v 764k -g 24 \
-c:a aac -b:a 64k \
-flags +cgop+global_header \
-f hls \ -hls_time 2 -hls_list_size 3
-hls_allow_cache 0 \
-hls_segment_filename stream_%d.ts \
-hls_flags delete_segments \
playlist.m3u8
##動作確認
- FFmpeg実行
cd ~/stream
ffmpeg -f alsa -thread_queue_size 1024 \ -i hw:1 \
-f v4l2 -thread_queue_size 512 -input_format yuyv422 -video_size 800x600 \
-i /dev/video0 \
-filter_complex scale=800x600,fps=12 \
-c:v h264_omx -b:v 764k -g 24 \
-c:a aac -b:a 64k \
-flags +cgop+global_header \
-f hls \ -hls_time 2 -hls_list_size 3
-hls_allow_cache 0 \
-hls_segment_filename stream_%d.ts \
-hls_flags delete_segments \
playlist.m3u8
- Webサーバー実行
python server.py
-
ngrokでサーバー公開
(ngrok初期設定済みの状態で)
ngrok http 8000
と実行し、
ngrokの管理画面上からhttpsのURLを確認してMotionBoardに設定します。
あとはRaspberry Piの起動時にこれらを自動実行するなりなんなりすればお手軽hls配信サーバーとして動作します。