Play Cam video on Pi via projector
Goal
Set up a 2×2 video wall using the Raspberry Pi, connected to a projector via HDMI, and then project it onto the wall. The easiest path on a Pi is to let FFmpeg tile the 4 RTSP feeds, and pipe to mpv.
Test on PC first
Use mpv (ffmpeg → mpv)
Quick test:
1
2
3
mpv --no-audio --profile=low-latency \
--demuxer-lavf-o=rtsp_transport=tcp \
rtsp://admin:PASS@Cam_IP:554/Streaming/Channels/102
2×2 video wall:
1
2
3
4
5
6
7
8
ffmpeg -fflags nobuffer -flags low_delay -rtsp_transport tcp \
-i "rtsp://admin:PASS@CAM1_IP/Streaming/Channels/102" \
-i "rtsp://admin:PASS@CAM2_IP/Streaming/Channels/102" \
-i "rtsp://admin:PASS@CAM3_IP/Streaming/Channels/102" \
-i "rtsp://admin:PASS@CAM4_IP/Streaming/Channels/102" \
-filter_complex "[0:v]scale=960:540[v0];[1:v]scale=960:540[v1];[2:v]scale=960:540[v2];[3:v]scale=960:540[v3];\
[v0][v1][v2][v3]xstack=inputs=4:layout=0_0|960_0|0_540|960_540,format=yuv420p" \
-f matroska - | mpv --no-audio --fullscreen -
Performance Setting
- Pi 4/5 is fine with 4× 720p H.264; 4× H.265 may choke. If needed, drop each
scale
to640:360
. - Prefer wired Ethernet; Wi-Fi + 4 RTSP feeds can stutter.
Shell Function
1
2
3
4
5
6
7
8
9
10
nvrsub() {
ffmpeg -hide_banner -loglevel warning -rtsp_transport tcp \
-thread_queue_size 512 -i "rtsp://admin:PASS@CAM1_IP:554/Streaming/Channels/102" \
-thread_queue_size 512 -i "rtsp://admin:PASS@CAM2_IP:554/Streaming/Channels/102" \
-thread_queue_size 512 -i "rtsp://admin:PASS@CAM3_IP:554/Streaming/Channels/102" \
-thread_queue_size 512 -i "rtsp://admin:PASS@CAM4_IP:554/Streaming/Channels/102" \
-filter_complex "[0:v]scale=640:360[v0];[1:v]scale=640:360[v1];[2:v]scale=640:360[v2];[3:v]scale=640:360[v3];\
[v0][v1][v2][v3]xstack=inputs=4:layout=0_0|640_0|0_360|640_360,format=yuv420p" \
-f matroska - | mpv --no-audio --fullscreen --profile=low-latency --hwdec=auto-safe -
}
Save and reload: source ~/.zshrc
.
Use:
1
nvrsub
Hands on Raspberry Pi
Install on Raspberry Pi OS (Bookworm/Bullseye)
1
2
sudo apt update
sudo apt install ffmpeg mpv
Extra niceties:
1
2
sudo apt install libavcodec-extra # widest codec set
sudo apt install fonts-dejavu-core # if you'll use drawtext labels
Test GUI on Pi by SSH
SSH X11 forwarding: run the app on the Pi, window pops up on PC.
Fast path (works on Linux PC)
SSH to Pi:
1
2
3
sudo apt update
sudo apt install xauth x11-apps # xeyes/xclock for testing
# (optional) sudo apt install mesa-utils # glxinfo/glxgears tests
Make sure SSH allows X11:
1
sudo nano /etc/ssh/sshd_config
Set (or add):
1
2
X11Forwarding yes
X11UseLocalhost yes
Then:
1
sudo systemctl restart ssh
Open another session, from PC → Pi:
1
2
3
ssh -X -C pi@<pi-ip> # try -Y if some apps complain
echo $DISPLAY # should print something like: localhost:10.0
xeyes & # or xclock/xcalc - a window should appear on your PC
Now launch any Pi GUI app the same way (from that SSH session).
Tip: X11 forwarding is okay for light GUIs. Don’t try video playback this way; use ffmpeg/mpv pipeline instead.
That little eyeball is xeyes. It’s a tiny X11 demo that follows your mouse, and it proves X11 forwarding is working: the app is running on the Pi, but the window is drawn on your PC.
The
&
just runs it in the background so your shell prompt comes back.- See it with
jobs
- Bring it back:
fg %1
- Kill it:
kill %1
(or just close the window)
- See it with
Use X11 forwarding for light GUIs
From your existing ssh -X
or -Y
session, try:
1
2
3
xclock &
xcalc &
nm-connection-editor & # edit Wi-Fi/Ethernet (Pi OS Bookworm uses NM)
Good for config tools and simple apps. But don’t stream video this way.
After this test, just exit the ssh -X -C
session.
Launch GUI on the Pi’s projector from your PC
That will pop the 2×2 mosaic on the projector (not PC).
Interactive (ssh first → then run GUI there)
From PC:
1
ssh pi@PI_IP
Now you’re inside the Pi’s shell (prompt shows something like pi@raspberry). Re-do this step first -> shell function. Then:
1 2 3 4 5 6
# 1 Grant once per boot - this edits the Pi's display permissions DISPLAY=:0 XAUTHORITY=/home/pi/.Xauthority xhost +SI:localuser:pi # 2 Launch a test GUI ON THE PI'S SCREEN DISPLAY=:0 XAUTHORITY=/home/pi/.Xauthority xeyes & # 3 Launch your camera wall ON THE PI'S SCREEN DISPLAY=:0 XAUTHORITY=/home/pi/.Xauthority nvrsub
Make the grant automatic (systemd user unit)
From your PC (one shot to install on the Pi):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
ssh pi@PI_IP 'mkdir -p ~/.config/systemd/user && cat > ~/.config/systemd/user/xhost-localuser.service <<EOF [Unit] Description=Allow local SSH user to access X/XWayland After=graphical-session.target PartOf=graphical-session.target [Service] Type=oneshot Environment=DISPLAY=:0 Environment=XAUTHORITY=/home/pi/.Xauthority ExecStart=/usr/bin/xhost +SI:localuser:pi [Install] WantedBy=graphical-session.target EOF systemctl --user daemon-reload systemctl --user enable --now xhost-localuser.service'
Now each time the Pi’s desktop starts, the permission is granted.
Then, whenever you want to launch stuff (interactive):
1 2 3
ssh pi@PI_IP DISPLAY=:0 XAUTHORITY=/home/pi/.Xauthority xeyes & DISPLAY=:0 XAUTHORITY=/home/pi/.Xauthority nvrsub
Or as a one-liner from another device (quotes keep it on the remote side):
1 2
ssh pi@PI_IP \ 'DISPLAY=:0 XAUTHORITY=/home/pi/.Xauthority nvrsub'
Alias (optional)
Add this alias to Pi’s ~/.bashrc
:
1
2
# draw GUI on the Pi's local screen from SSH
alias onpi='DISPLAY=:0 XAUTHORITY=/home/pi/.Xauthority '
Reload:
1
2
source ~/.bashrc
type onpi
Use:
1
2
onpi xeyes
onpi nvrsub
This works because the alias expands to:
DISPLAY=:0 XAUTHORITY=/home/pi/.Xauthority <your command here>