ラズベリーパイゼロ2Wを動画ストリーミングサーバにしてみる


ラズベリーパイゼロ2Wにカメラモジュールを取り付けるでラズベリーパイゼロ2W(以後ゼロツーと表記します)にカメラモジュールを付けてみました。


今回はゼロツーをストリーミングサーバにして、カメラに写っている内容を他のパソコンやスマホから確認出来るようにしてみます。




ゼロツーをストリーミングサーバにするには、Picamera2というパイソンのライブラリを利用します。

The Picamera2 Library - A libcamera-based Python library for Raspberry Pi cameras(PDF)


Picamera2は下記のコマンドでインストールします。

$ sudo apt install python3-picamera2

ストリーミング用のコードを作成します。

$ micro stream_server.py
#!/usr/bin/env python3

import time
import io
import logging
import socketserver
from threading import Condition
from http import server

from picamera2 import Picamera2
from picamera2.encoders import JpegEncoder
from picamera2.outputs import FileOutput

# ----------------------------------------------------------------------
# MJPEGストリーミング用クラス定義
# ----------------------------------------------------------------------

# 画像データを保持し、複数のHTTPリクエストにデータを配信するためのクラス
class StreamingOutput(io.BufferedIOBase):
    def __init__(self):
        self.frame = None
        self.condition = Condition()

    def write(self, buf):
        # 新しいフレームが書き込まれるたびに通知し、フレームを更新
        with self.condition:
            self.frame = buf
            self.condition.notify_all()

# HTTPリクエストを処理するためのハンドラー
class StreamingHandler(server.BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == '/stream.mjpg':
            # MJPEGストリーム配信のためのHTTPヘッダーを設定
            self.send_response(200)
            self.send_header('Age', 0)
            self.send_header('Cache-Control', 'no-cache, private')
            self.send_header('Pragma', 'no-cache')
            self.send_header('Content-Type', 'multipart/x-mixed-replace; boundary=FRAME')
            self.end_headers()
            try:
                # フレームの無限ループ配信
                while True:
                    with output.condition:
                        output.condition.wait() # 新しいフレームが来るまで待機
                        frame = output.frame
                    
                    # 各フレームのヘッダーとデータを送信
                    self.wfile.write(b'--FRAME\r\n')
                    self.send_header('Content-Type', 'image/jpeg')
                    self.send_header('Content-Length', len(frame))
                    self.end_headers()
                    self.wfile.write(frame)
                    self.wfile.write(b'\r\n')
            except Exception as e:
                logging.warning(
                    'Removed streaming client %s: %s',
                    self.client_address, str(e))
        else:
            # その他のパス(ルート/など)へのアクセスに対する応答
            self.send_error(404) # 404 Not Found

class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer):
    allow_reuse_address = True
    daemon_threads = True

# ----------------------------------------------------------------------
# メイン処理
# ----------------------------------------------------------------------

# カメラ初期化
picam2 = Picamera2()
# Zero 2 Wの負荷を考慮し、解像度を抑えることを検討
# V2.1のフル解像度(3280x2464)はメモリ不足や遅延の原因になるため、720p程度が推奨
config = picam2.create_video_configuration(main={"size": (1280, 720)})
picam2.configure(config)

output = StreamingOutput()
# エンコーダーを設定し、ストリーム出力先を定義
picam2.start_recording(JpegEncoder(), FileOutput(output))

try:
    # サーバーを起動
    address = ('', 8000) # すべてのIPでポート8000を開く
    server = StreamingServer(address, StreamingHandler)
    print(f"Webストリーミングサーバーを開始しました。IPアドレス: http://<PiのIPアドレス>:8000/stream.mjpg")
    server.serve_forever()
finally:
    # 終了時に録画を停止
    picam2.stop_recording()

コードを保存した後、コードを実行する前にゼロツーに割り当てられたIPアドレスを下記コマンドで確認しておきます。

$ ifconfig

wlan0の項目を探し、そこに記載されているIPアドレスをメモしておきます。

※今回は例として192.168.1.100という値で話を進めます。


先程作成したパイソンのコードを実行します。

$ python3 stream_server.py
[0:51:27.260946731] [1274]  INFO Camera camera_manager.cpp:340 libcamera v0.6.0+rpt20251202
[0:51:27.320866639] [1277]  INFO IPAProxy ipa_proxy.cpp:180 Using tuning file /usr/share/libcamera/ipa/rpi/vc4/imx219.json
[0:51:27.340256906] [1277]  INFO Camera camera_manager.cpp:223 Adding camera '/base/soc/i2c0mux/i2c@1/imx219@10' for pipeline handler rpi/vc4
[0:51:27.340421228] [1277]  INFO RPI vc4.cpp:445 Registered camera /base/soc/i2c0mux/i2c@1/imx219@10 to Unicam device /dev/media0 and ISP device /dev/media1
[0:51:27.340532947] [1277]  INFO RPI pipeline_base.cpp:1111 Using configuration file '/usr/share/libcamera/pipeline/rpi/vc4/rpi_apps.yaml'
[0:51:27.360200140] [1274]  INFO Camera camera.cpp:1215 configuring streams: (0) 1280x720-XBGR8888/Rec709/Rec709/None/Full (1) 1920x1080-SBGGR10_CSI2P/RAW
[0:51:27.360981698] [1277]  INFO RPI vc4.cpp:620 Sensor: /base/soc/i2c0mux/i2c@1/imx219@10 - Selected sensor format: 1920x1080-SBGGR10_1X10/RAW - Selected unicam format: 1920x1080-pBAA/RAW
Webストリーミングサーバーを開始しました。IPアドレス: http://<PiのIPアドレス>:8000/stream.mjpg

最後の行に記載されている値を参考にして、別のパソコンやスマホのブラウザからゼロツーにアクセスしてみます。


今回の例であれば、ゼロツーのURLは http://192.168.1.100:8000/stream.mjpg になります。




ゼロツーを起動したら自動でストリーミングサーバが立ち上がるようにしてみます。

今回はSystemdというシステムを利用して、ストリーミングサーバーを自動起動するように設定します。


下記のコマンドでストリーミングサーバを起動するシェルスクリプトを作成します。

$ cd
$ micro start_stream.sh
#!/bin/bash
SCRIPT_PATH="/home/pi/stream_server.py" 
cd "$(dirname "$SCRIPT_PATH")"
exec python3 "$SCRIPT_PATH"

作成しましたスクリプトに実行権限を与えます。

$ sudo chmod +x start_stream.sh

Systemd用のサービスユニットのファイルを作成します。

$ sudo micro /etc/systemd/system/video-stream.service
[Unit]
Description=Picamera2 Video Streaming Server
After=network-online.target

[Service]
User=pi 
WorkingDirectory=/home/pi/ 
ExecStart=/home/pi/start_stream.sh 
RemainAfterExit=no 
Restart=always 
RestartSec=5s 

[Install]
WantedBy=multi-user.target

ストリーミング用のサービスユニットを有効にします。

$ sudo systemctl daemon-reload
$ sudo systemctl enable video-stream.service
Created symlink '/etc/systemd/system/multi-user.target.wants/video-stream.service' → '/etc/systemd/system/video-stream.service'.

設定の内容を確認してみます。

$ sudo systemctl start video-stream.service

上記のコマンドでストリーミングサーバが起動していますので、ブラウザからサーバにアクセスしてみます。


アクセスできたら、ブラウザを閉じて、

$ sudo shutdown -r now

でゼロツーを再起動して、しばらく経過したら改めてブラウザからゼロツーにアクセスしてサーバが起動しているか確認します。

京都の東本願寺で開催されているプログラミング教室で講師をしています。
詳しくはTera schoolを御覧ください。
大阪府高槻市でプログラミング教室を開設しています。
同じカテゴリーの記事
マインクラフト用ビジュアルエディタを開発しています。

詳しくはinunosinsi/mcws_blockly - githubをご覧ください。