Files
rpiCam/stream_picamera2.py
fegger 6faf505f4b Add PiCam network streamer with auto-setup for Pi 5
Initial commit of the MJPEG streaming server for Raspberry Pi.
Supports Pi Camera Module (via picamera2), USB webcams (via OpenCV),
and auto-detection of camera type.

Includes:
- `stream_picamera2.py` — optimized Pi CSI camera streamer
- `stream_usb.py` — generic USB camera streamer
- `stream.py` — auto-detects camera and launches correct streamer
- `image-setup/` — SD card imaging scripts and Pi 5 headless setup
  guide for first-boot automation with systemd service
- `requirements.txt` — Python dependencies
2026-05-23 10:39:31 +02:00

142 lines
4.2 KiB
Python

from flask import Flask, Response, render_template_string
from picamera2 import Picamera2
import io
import logging
import threading
import time
app = Flask(__name__)
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
class Camera:
def __init__(self, resolution=(640, 480), framerate=30):
self.resolution = resolution
self.framerate = framerate
self.picam2 = None
self.frame = None
self.lock = threading.Lock()
self.running = False
self.thread = None
def start(self):
self.picam2 = Picamera2()
config = self.picam2.create_video_configuration(
main={"size": self.resolution, "format": "RGB888"},
controls={"FrameRate": self.framerate}
)
self.picam2.configure(config)
self.picam2.start()
# Allow camera to warm up
time.sleep(2)
self.running = True
self.thread = threading.Thread(target=self._capture, daemon=True)
self.thread.start()
logging.info("Camera started.")
def _capture(self):
while self.running:
try:
arr = self.picam2.capture_array()
# Encode as JPEG
import cv2
ret, buf = cv2.imencode('.jpg', arr)
if ret:
with self.lock:
self.frame = buf.tobytes()
time.sleep(0.001) # Brief sleep to yield
except Exception as e:
logging.error(f"Capture error: {e}")
time.sleep(0.1)
def get_frame(self):
with self.lock:
return self.frame
def stop(self):
self.running = False
if self.thread:
self.thread.join(timeout=2.0)
if self.picam2:
self.picam2.stop()
self.picam2.close()
logging.info("Camera stopped.")
camera = None
HTML_PAGE = """
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>PiCam Stream</title>
<style>
body {
background: #111;
color: #eee;
font-family: system-ui, -apple-system, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
margin: 0;
}
h1 { margin-bottom: 0.5rem; }
p { color: #888; margin-bottom: 1rem; }
img {
max-width: 95vw;
max-height: 80vh;
border: 2px solid #333;
border-radius: 8px;
background: #000;
}
</style>
</head>
<body>
<h1>PiCam Live Stream</h1>
<p>{{ resolution }} @ {{ framerate }} fps</p>
<img src="{{ url_for('video_feed') }}" alt="Live stream">
</body>
</html>
"""
@app.route('/')
def index():
return render_template_string(HTML_PAGE, resolution=camera.resolution, framerate=camera.framerate)
@app.route('/video_feed')
def video_feed():
def generate():
while True:
frame = camera.get_frame()
if frame is None:
time.sleep(0.01)
continue
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
return Response(generate(), mimetype='multipart/x-mixed-replace; boundary=frame')
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(description='PiCam MJPEG Streamer using picamera2')
parser.add_argument('--host', default='0.0.0.0', help='Interface to bind (default: 0.0.0.0)')
parser.add_argument('--port', type=int, default=5000, help='Port to bind (default: 5000)')
parser.add_argument('--width', type=int, default=640, help='Capture width')
parser.add_argument('--height', type=int, default=480, help='Capture height')
parser.add_argument('--fps', type=int, default=30, help='Target framerate')
args = parser.parse_args()
camera = Camera(resolution=(args.width, args.height), framerate=args.fps)
camera.start()
try:
logging.info(f"Starting server on http://{args.host}:{args.port}")
app.run(host=args.host, port=args.port, threaded=True, debug=False)
except KeyboardInterrupt:
pass
finally:
camera.stop()