Tuesday, April 22, 2025

Down the Raspberry Pi Rabbit Hole

The world of microcomputers like the Raspberry Pi or the ESP32 is broad and ever-expanding. I've only had the chance to work with an original Raspberry Pi way back in school, and I just began a small project with an ESP32 today (more on that to come). Needless to say, my experience is limited. But last year, I was presented with a challenge at work: get a video stream from a Ubiquiti security camera to display on a Raspberry Pi so we could monitor part of the store. An update had broken the functionality of their previous setup, and now it was on me to fix it.

So why not just use a Viewport?

Viewport is Ubiquiti’s all-in-one camera monitoring hub. It’s plug-and-play, purpose-built for their surveillance systems, and ideal for monitoring multiple streams with ease.

But the downside? The price tag: $200. Compare that to “free” (minus my many hours of labor, which probably exceeded $200, but hey). We already had the Raspberry Pi—may as well use it, right?

My first objective was to investigate the previous setup and figure out what broke.

The device ran RPIsurv, a neat little program that turned a Raspberry Pi into a dedicated video or image streaming device. It could display up to four separate streams at once. The video output ran through omxplayer, which—surprise—had been deprecated in the update that broke the setup.

Enter VLC, the new video king on Raspberry Pi.

I wasn’t upset to discover that VLC checks all the boxes: versatile, open-source, widely supported, and capable of handling nearly any media format. Unsurprisingly, VLC supports the same media stream format used by many live video feeds: RTSP (Real-Time Streaming Protocol).

My mission was now clear: use VLC to stream the camera feed.

Simple, right? Hah.

The Hacky Solution

I should mention—I did all this on Raspberry Pi OS (with Desktop), not Lite as I probably should have. (More on that later.)

I started by grabbing the RTSP link from the camera’s settings in the UniFi dashboard. But something looked off:

rtsps://192.168.1.1:7441/example_stream_id?enableSrtp

RTSPS? That wasn’t going to work. I tried anyway. No dice—stream error.

After a bit of digging, I learned that a few key changes were needed to make the link work. This was likely obfuscated for security reasons (or just to make my life harder). Either way, I’m just a dude playing with tech at the mercy of its creators.

Here’s what I had to change:

  1. Change rtsps to rtsp
  2. Change the port from 7441 to 7447  
  3. Remove ?enableSrtp

A couple of quick edits, and—voilà! VLC successfully streamed the live camera feed.

Cue self-congratulations, back-patting, and a well-earned lunch.

Except... when I got back, the stream had died.

Crashing Streams & Band-Aids

I rebooted VLC to make sure it wasn’t a fluke. It worked, but not for long. Turns out, VLC isn’t built for 24/7 streaming. Over time, memory leaks or resource exhaustion cause it to crash or hang. You can tweak VLC’s buffer settings or enable keep-alive packets, but even with those adjustments, the stream still crashed every few hours.

If you need to tweak those settings, they’re here:

  • Caching: Tools > Preferences > Show Settings (All) > Input/Codecs > Network Caching (ms) > Increase to 3000ms or more.
  • Keep-Alive: Preferences > All > Input/Codecs > Demuxers > RTP/RTSP > Enable "Use RTP over RTSP (TCP)" and set keep-alive.

Neither helped.

So I asked myself: why not just reboot VLC?

It wasn’t pretty, but the stream didn’t need elegance. It just needed to work most of the time.

The Auto-Reboot Workaround

(Before you recreate this solution, just know—it’s hacky and lame. There’s a better one below.)

First, VLC setup:

  1. Tools > Preferences > Check "Fullscreen"
  2. Audio > Uncheck "Enable Audio" (especially important for two-party consent states)
  3. Input/Codecs > Demuxers > RTP/RTSP > Enable "Use RTP over RTSP (TCP)"

Then, I made sure the stream restarted on boot:

  1. Navigate to /home/username/.config
  2. Create a folder named autostart
  3. In autostart, create a file called autovlc.desktop with the following:
[Desktop Entry]
Type=Application
Exec=vlc rtsp://192.168.0.1:7447/streamlink

Reboot to confirm it worked.

Now, to handle VLC crashing, I wrote a script to kill and relaunch it:

On the desktop, create close-launch-vlc.sh:

#!/bin/sh
killall vlc
wait
gio launch /home/username/.config/autostart/autovlc.desktop

Then, in the terminal, make it executable :

chmod +x /home/username/Desktop/close-launch-vlc.sh

To automate this script every two hours, I used cron. First I had to check the display number:

echo $DISPLAY

For me, it was "0", then I opened up crontab:

sudo nano /etc/crontab

At the top:

DISPLAY=:0

At the bottom:

0 */2 * * * username /home/username/Desktop/close-launch-vlc.sh

Save and exit. Now, the stream rebooted every two hours.

It wasn’t ideal, but it kept the stream alive.

The Actual Fix

Until it didn’t.

The guy sitting near the monitor got tired of fullscreen hangs and reboot hiccups. He demanded a real fix.

Fine. Fine.

So, I decided that Raspberry Pi Desktop was too resource heavy for the little 1GB Raspberry Pi 3 that I was building this on. I installed the lite version and SSH'd into the thing. VLC wasn't ideal for this purpose. I tried a few other options.

My fiddling led to the RPi refusing to boot twice, one accidental reboot when I ran the reboot command in my own laptop's terminal instead of within the SSH session, and lots of cursing. Several attempts with several tools later, I landed on ffplay

After a lot of trial and error (I'm not much of a scripter to be honest) this is the code block I ended up with:

#!/bin/bash

# === 1. Update system ===
sudo apt update && sudo apt upgrade -y

# === 2. Force HDMI output & resolution ===
sudo sed -i '/^#*hdmi_force_hotplug/d' /boot/firmware/config.txt
sudo sed -i '/^#*hdmi_group/d' /boot/firmware/config.txt
sudo sed -i '/^#*hdmi_mode/d' /boot/firmware/config.txt
sudo sed -i '/^#*config_hdmi_boost/d' /boot/firmware/config.txt

echo "hdmi_force_hotplug=1" | sudo tee -a /boot/firmware/config.txt
echo "hdmi_group=1" | sudo tee -a /boot/firmware/config.txt
echo "hdmi_mode=82" | sudo tee -a /boot/firmware/config.txt   # 1080p
echo "config_hdmi_boost=7" | sudo tee -a /boot/firmware/config.txt

# === 3. Install lightweight X server and ffmpeg ===
sudo apt install --no-install-recommends xserver-xorg xinit openbox ffmpeg -y

# === 4. Create .xinitrc with placeholder RTSP stream ===
cat <<EOF > ~/.xinitrc
#!/bin/bash

# Replace the RTSP_URL below with your own stream.
RTSP_URL="rtsp://YOUR_CAMERA_URL_HERE"

if [[ "\$RTSP_URL" == "rtsp://YOUR_CAMERA_URL_HERE" ]]; then
  echo "❌ Please edit ~/.xinitrc and set your RTSP stream URL before using startx."
  sleep 5
  exit 1
fi

ffplay -fs -noborder -an -fflags nobuffer -flags low_delay "\$RTSP_URL" > ~/ffplay.log 2>&1
EOF

chmod +x ~/.xinitrc

# === 5. Enable console auto-login ===
sudo raspi-config nonint do_boot_behaviour B2

# === 6. Add startx to bashrc only for TTY1 ===
if ! grep -q 'startx' ~/.bashrc; then
  echo '' >> ~/.bashrc
  echo 'if [ "\$(tty)" = "/dev/tty1" ]; then' >> ~/.bashrc
  echo '  startx' >> ~/.bashrc
  echo 'fi' >> ~/.bashrc
fi

echo ""
echo "✅ Setup complete!"
echo "👉 To finish setup, edit ~/.xinitrc and paste in your RTSP stream URL."
echo ""
echo "Rebooting in 5 seconds..."
sleep 5
sudo reboot

 The script can be found over on my github. The beauty of this is that its one command to replicate it now. It just requires entering in the RTSP stream.

It took some learning and fiddling, but the stream is now up 24/7 without the need for reboots or hangups. Plug and play. Without the hackiness.

If you're interested, I used a few different videos while learning how to set this up: Eugene Tkachenko sets up a stream using a camera module directly on the Raspberry Pi itself. That Project created a wireless camera very similarly to Eugene, but had some code snippets in his video that I found very useful.

A Raspberry Pi and other Internet of Things devices are powerful tools, when in the right hands. It took a while for my hands to be the right ones here. 

I've got some more projects with IoT devices coming up, one focused on my love of Pokemon, of all things! 

Until then.

Stay warm and toasty.

 

No comments:

Post a Comment

A City with Mountains on All Sides

The cities have always been my home. Accessible. Loud. Crowded. A little dirty. I worked there, shopped there, wandered late-night corners a...