To build a sub-second latency RTSP player on a Raspberry Pi, you need to bypass standard software decoding and tap the dedicated H.264 hardware block. The most reliable stack for this in 2026 is mpv media player utilizing the v4l2m2m hardware decoder, running on a Raspberry Pi 4 Model B (4GB) with Raspberry Pi OS Bookworm (64-bit). This setup reliably pulls 1080p/30fps streams from Hikvision, Dahua, and Reolink cameras with less than 400ms of glass-to-glass latency, provided you drop the player buffer and enforce TCP interleaved transport.
Hardware Spec Sheet & Parts List
Do not attempt this build on a Pi 4 with 2GB of RAM. The Wayland display server combined with libmpv and network buffers will easily consume 1.4GB during stream switching, leading to out-of-memory (OOM) kills. The Pi 5 is an alternative, but its lack of a dedicated H.264 hardware decode block (relying on software decode or newer pipeline quirks) makes the Pi 4 4GB the current benchmark for stable, low-latency H.264 RTSP playback.
| Component | Exact Variant / Specification | Why This Specific Part |
|---|---|---|
| Microcontroller | Raspberry Pi 4 Model B (4GB RAM) | Includes dedicated H.264 hardware decode block (v4l2m2m). |
| Storage | 32GB Samsung EVO Plus (A2 rated) | A2 rating ensures high random IOPS for OS and swap responsiveness. |
| Power Supply | Official 5.1V 3A USB-C Power Supply | Prevents brownout warnings when HDMI and WiFi draw peak current. |
| Controls | 6x6mm Tactile Pushbutton + 5mm Red LED | For physical stream toggling without needing a mouse/keyboard. |
| Resistor | 330Ω (1/4W) | Current limiting for the 5mm status LED on 3.3V GPIO. |
Wiring the Physical Control Interface
We are adding a physical button to cycle through camera feeds and an LED to indicate when a stream is loading. This is crucial for kiosk-style deployments where you don't want to leave a wireless keyboard exposed.
| GPIO Pin (BCM) | Physical Pin | Component | Wiring Destination |
|---|---|---|---|
| GPIO 17 | Pin 11 | Tactile Button | Button Leg 1 -> GND (Pin 9) |
| GPIO 27 | Pin 13 | 330Ω Resistor | Resistor -> LED Anode -> LED Cathode -> GND (Pin 14) |
The Python RTSP Player Code
This script uses the python-mpv bindings. Before running the code, install the required system libraries and Python packages:
sudo apt update
sudo apt install python3-mpv libmpv1 python3-gpiozero
pip3 install python-mpv --break-system-packages
The code below targets the Raspberry Pi 4 (Bookworm 64-bit). It forces hardware decoding via v4l2m2m and uses the low-latency profile to drop the default 2-second playback buffer.
import mpv
import time
import sys
from gpiozero import Button, LED
from signal import pause
# --- PIN DEFINITIONS ---
BTN_SWITCH_CAM = 17 # Physical button to toggle cameras
LED_STATUS = 27 # Status LED
# --- STREAM CONFIGURATION ---
# Replace with your actual camera IPs, credentials, and RTSP paths.
# Reference: https://www.reolink.com/blog/how-to-access-reolink-cameras-via-rtsp/
CAMERAS = [
"rtsp://admin:password123@192.168.1.100:554/h264Preview_01_main",
"rtsp://admin:password123@192.168.1.101:554/h264Preview_01_main"
]
current_cam = 0
def setup_player():
"""Initialize mpv with Pi 4 hardware decoding and low latency."""
try:
player = mpv.MPV(
ytdl=False,
fullscreen=True,
hwdec='v4l2m2m', # Taps the Pi 4 H.264 hardware block
profile='low-latency', # Drops buffer for real-time viewing
untimed=True, # Prevents A/V sync drift on live streams
rtsp_transport='tcp' # Forces TCP interleaved to prevent UDP drops
)
return player
except Exception as e:
print(f"[FATAL] Failed to initialize mpv: {e}")
sys.exit(1)
def switch_camera():
"""GPIO callback to cycle through the camera list."""
global current_cam
led.on()
current_cam = (current_cam + 1) % len(CAMERAS)
print(f"Switching to Camera {current_cam + 1}")
try:
player.play(CAMERAS[current_cam])
except mpv.MPVError as e:
print(f"[ERROR] Stream failed to load: {e}")
time.sleep(0.5)
led.off()
if __name__ == "__main__":
led = LED(LED_STATUS)
btn = Button(BTN_SWITCH_CAM, pull_up=True, bounce_time=0.05)
btn.when_pressed = switch_camera
player = setup_player()
print(f"Loading initial stream: {CAMERAS[current_cam]}")
player.play(CAMERAS[current_cam])
try:
pause() # Keep script alive to listen for GPIO events
except KeyboardInterrupt:
player.terminate()
print("Player stopped gracefully.")
Debugging Common RTSP Stream Failures
RTSP is notoriously fragile compared to HLS or WebRTC. When your stream fails to load or drops out, follow this diagnostic path.
The First Three Things to Check
- Ping the Camera IP: Run
ping 192.168.1.100. If you see packet loss, your WiFi signal is too weak or the camera's ethernet crimp is failing. RTSP over TCP will stall completely on dropped packets. - Verify the URL Format: RTSP URLs are case-sensitive and brand-specific. Hikvision uses
/Streaming/Channels/101, while Reolink uses/h264Preview_01_main. Double-check the exact path via the manufacturer's documentation. - Check Concurrent Stream Limits: Most consumer IP cameras limit RTSP connections to 1 or 2 simultaneous sessions. If you have BlueIris, an NVR, and this Pi script all pulling the main stream, the camera will reject the Pi with a connection error.
Exact Error Strings and Ranked Causes
Error 1: avformat_open_input() failed: Protocol not found
- Cause A: You omitted the
rtsp://prefix in the URL string. - Cause B: Your
libmpvbuild was compiled without RTSP/FFmpeg support (rare on standard Pi OS, but possible if you compiled from source).
Error 2: Connection refused
- Cause A: The camera's RTSP port (usually 554) is blocked by a local firewall or VLAN isolation rule.
- Cause B: You exceeded the camera's maximum concurrent RTSP session limit. Switch the Pi to pull the "Sub-stream" (lower resolution) instead of the "Main-stream".
Error 3: Nonmatching transport or Server returned 461 Unsupported transport
- Cause: The player attempted to negotiate UDP for the video payload, but the camera or your router's IGMP snooping is dropping UDP multicast/unicast packets.
- Fix: This is why the code above explicitly sets
rtsp_transport='tcp'. TCP interleaved wraps the RTSP video data inside the reliable TCP control connection.
Extending and Simplifying the Build
To Simplify (No Python Required):
If you don't need GPIO button controls and just want a dedicated kiosk display, strip out Python entirely. Create a bash script that launches mpv directly on boot via Wayland:
mpv --fullscreen --hwdec=v4l2m2m --profile=low-latency --rtsp-transport=tcp "rtsp://admin:pass@192.168.1.100:554/stream1"
To Extend (IR Remote Control):
Add a TSOP38238 IR receiver module. Wire the data pin to GPIO 18 (which supports hardware PWM). Use the lirc library to map a standard TV remote's "Channel Up/Down" buttons to the switch_camera() function, allowing you to control the Pi from across the room without a physical tether.
Frequently Asked Questions
How to reduce RTSP stream latency on Raspberry Pi?
Latency in RTSP comes from two places: the camera's encoding buffer and the player's decoding buffer. On the camera side, log into the web UI and change the bitrate control from VBR (Variable) to CBR (Constant), and set the I-frame interval to match the framerate (e.g., 30 for 30fps). On the Pi side, the profile='low-latency' flag in our Python script tells mpv to disable its internal jitter buffer, displaying frames the millisecond they are decoded.
Can Raspberry Pi decode multiple 4K RTSP streams simultaneously?
No. The Raspberry Pi 4 has a single hardware video decoder capable of H.264 up to 4K, but it cannot decode two 4K streams at once. Furthermore, the Pi 4 lacks hardware decoding for H.265 (HEVC), which most modern 4K cameras use. If you need a 2x2 grid of 4K cameras, you must step up to an x86 mini-PC with an Intel N100 chip to utilize QuickSync hardware decoding, or use a Pi 5 with software decoding (which will max out the CPU and run hot).
Why does my RTSP stream freeze after 10 minutes on Pi?
This is almost always caused by UDP packet loss. When RTSP runs over UDP, lost packets mean the player misses a delta-frame. The player then freezes, waiting for the next I-frame (keyframe) to rebuild the image. If your camera is set to send an I-frame only every 4 seconds, your stream will freeze for 4 seconds. The permanent fix is forcing TCP transport (rtsp_transport='tcp'), which guarantees packet delivery and automatically requests retransmissions for dropped data.






