As we know, your Raspberry Pi can be used for a plethora of things, one such application is live streaming video from your Raspberry Pi camera over the web.
This is a fun project to do for all ages, and implement in and around the house. And, if integrated with motion sensor can actually be used as a full-fledged surveillance camera. At the end of this article you will be able to write a code to stream the video, and access it on any device connected to the same network as that of your Raspberry Pi. However, video streaming over the web has its own difficulties, so in this code we will be using a simple format for the video called MJPEG. The source code for this program is part of the official PiCamera Package and can be accessed from their website as well.
What is MJPEG?
MJPEG or Motion JPEG is a video compression format wherein the video comprises of several compressed individual JPEGs in a sequence. This results in a high quality outcome which doesn’t affect the video quality even if one frame is dropped. It however, does consume more bandwidth and storage. But since we are only streaming and not storing the video it is of little disadvantage to us.
Hardware Requirements:
- Raspberry Pi (I used, model 3 B +) – make sure to have the OS installed and running
- Internet connection (either over WiFi or ethernet)
- Laptop, or mobile phone connected to the same network
- Camera module
- Power cable
- Monitor
- HDMI connector
- USB or Bluetooth mouse
- USB or Bluetooth keyboard
Setup:
Hardware
- Connect the Raspberry Pi camera module (for any help on that, check out our article on that https://iot4beginners.com/how-to-capture-image-and-video-in-raspberry-pi/ ).
- Use the HDMI cable to connect your Raspberry Pi to a display
- Connect your mouse and keyboard as well (through USB or Bluetooth)
- Connect your Raspberry Pi to the internet. Either over WiFi or ethernet.
Software:
After connecting your Raspberry Pi to the internet, find the IP address of the network it is connected to.
- Open the terminal
- type ifconfig
- next to wlan0 you will see the IP address, as in the image below next to inet
4. Either write down or copy that IP address somewhere, because you’re going to need it very soon!
Code:
import io import picamera import logging import socketserver from threading import Condition from http import server PAGE="""\ <html> <head> <title>Raspberry Pi - Surveillance Camera</title> </head> <body> <center><h1>Raspberry Pi - Surveillance Camera</h1></center> <center><img src="stream.mjpg" width="640" height="480"></center> </body> </html> """ class StreamingOutput(object): def __init__(self): self.frame = None self.buffer = io.BytesIO() self.condition = Condition() def write(self, buf): if buf.startswith(b'\xff\xd8'): # New frame, copy the existing buffer's content and notify all clients it's available self.buffer.truncate() with self.condition: self.frame = self.buffer.getvalue() self.condition.notify_all() self.buffer.seek(0) return self.buffer.write(buf) class StreamingHandler(server.BaseHTTPRequestHandler): def do_GET(self): if self.path == '/': self.send_response(301) self.send_header('Location', '/index.html') self.end_headers() elif self.path == '/index.html': content = PAGE.encode('utf-8') self.send_response(200) self.send_header('Content-Type', 'text/html') self.send_header('Content-Length', len(content)) self.end_headers() self.wfile.write(content) elif self.path == '/stream.mjpg': 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) self.end_headers() class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer): allow_reuse_address = True daemon_threads = True with picamera.PiCamera(resolution='640x480', framerate=24) as camera: output = StreamingOutput() #Uncomment the next line to change your Pi's Camera rotation (in degrees) #camera.rotation = 90 camera.start_recording(output, format='mjpeg') try: address = ('', 8000) server = StreamingServer(address, StreamingHandler) server.serve_forever() finally: camera.stop_recording()
Code Explanation:
First we import some libraries and functions needed for the code to run.
import io import picamera import logging import socketserver from threading import Condition from http import server
We then write a code for the page on which the live stream will be displayed when you access it.
PAGE="""\ <html> <head> <title>Raspberry Pi - Surveillance Camera</title> </head> <body> <center><h1>Raspberry Pi - Surveillance Camera</h1></center> <center><img src="stream.mjpg" width="640" height="480"></center> </body> </html> """
The first task is to define a class for the output stream. This will be used throughout the program to be able to access the video and continuously stream it using the IP address.
class StreamingOutput(object): def __init__(self): self.frame = None self.buffer = io.BytesIO() self.condition = Condition() def write(self, buf): if buf.startswith(b'\xff\xd8'): # New frame, copy the existing buffer's content and notify all clients it's available self.buffer.truncate() with self.condition: self.frame = self.buffer.getvalue() self.condition.notify_all() self.buffer.seek(0) return self.buffer.write(buf)
We then have to write the code for the actual streaming process. This means now only for smooth streaming of the MJPEG video, but also writing the case in which an error occurs and the output message for that. This is a very important part of the code as it is the mainframe for the video to be streamed live with minimal delay and errors.
class StreamingHandler(server.BaseHTTPRequestHandler): def do_GET(self): if self.path == '/': self.send_response(301) self.send_header('Location', '/index.html') self.end_headers() elif self.path == '/index.html': content = PAGE.encode('utf-8') self.send_response(200) self.send_header('Content-Type', 'text/html') self.send_header('Content-Length', len(content)) self.end_headers() self.wfile.write(content) elif self.path == '/stream.mjpg': 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) self.end_headers() class StreamingServer(socketserver.ThreadingMixIn, server.HTTPServer): allow_reuse_address = True daemon_threads = True
Only in the next part of the code do we actually start the recording from the camera. As most of you know, we can assign a resolution, frame rate and format for the picamera, and instam of storing the output we simply assign the output to a variable.
If in case, while streaming you see that the video is tilted, uncomment the line as instructed below and type in the value(in degrees) that you want to see the video tilted by.
with picamera.PiCamera(resolution='640x480', framerate=24) as camera: output = StreamingOutput() #Uncomment the next line to change your Pi's Camera rotation (in degrees) #camera.rotation = 90 camera.start_recording(output, format='mjpeg') try: address = ('', 8000) server = StreamingServer(address, StreamingHandler) server.serve_forever() finally: camera.stop_recording()
Now run the program.
Once it is running, open your browser in any device connected to the same network, and in the address bar type http://<IP address>:8000/
For me, it was http://192.168.43.4:8000
Output:
Sample output video:
This program is a fun way to get into the real time application oriented projects using Raspberry Pi, as well as learn to integrate different domains of coding together. Although web streaming sounds hard and takes a while to wrap yourself around, atleast go through the code to see the flow of the program. This aids in understanding how to break down a goal into parts, which will help immensely when trying to write your own.
It is both fun to learn and understand, but more so to use! Go ahead try moving your setup around to see how far you can go away and still access it, what range you can cover in terms of area. Hope you have fun with this project, I sure did!