<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Categories on Basil Papadimas</title><link>https://papadim.as/categories/</link><description>Recent content in Categories on Basil Papadimas</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><atom:link href="https://papadim.as/categories/index.xml" rel="self" type="application/rss+xml"/><item><title>Denmark</title><link>https://papadim.as/denmark/</link><pubDate>Tue, 25 Aug 2026 00:00:00 +0000</pubDate><guid>https://papadim.as/denmark/</guid><description/></item><item><title>Portugal</title><link>https://papadim.as/portugal/</link><pubDate>Sat, 04 Jul 2026 00:00:00 +0000</pubDate><guid>https://papadim.as/portugal/</guid><description/></item><item><title>Real-time Video Streaming with SRT</title><link>https://papadim.as/real-time-video-streaming-with-srt/</link><pubDate>Tue, 09 Sep 2025 00:00:00 +0000</pubDate><guid>https://papadim.as/real-time-video-streaming-with-srt/</guid><description>&lt;h2 id="the-challenge-remote-video-streaming-setup"&gt;The Challenge: Remote Video Streaming Setup&lt;/h2&gt;
&lt;p&gt;In this article, I&amp;rsquo;ll share a generic setup for reliably streaming video with sub-second latency over the internet. I recently installed surveillance cameras in a remote house that connects to the internet via Starlink. I decided to host the software myself to process the streams, run motion detection, and record automatically, rather than pay a subscription to the camera manufacturer.&lt;/p&gt;
&lt;h2 id="understanding-the-problem"&gt;Understanding the Problem&lt;/h2&gt;
&lt;p&gt;This turned out trickier than I expected! The software I needed was already there, but I faced many different architectural options, and each role in that architecture had multiple candidates. Let me define the problem better: I needed to transport the frames captured by the camera across the internet so my server could actually ingest them.&lt;/p&gt;
&lt;h2 id="hardware-limitations-and-constraints"&gt;Hardware Limitations and Constraints&lt;/h2&gt;
&lt;p&gt;I can easily set up RTSP streams into Frigate that perform perfectly through go2rtc, as long as the cameras are in the same local network as the streaming server. However, in this case the only other computer in the Starlink LAN was a Raspberry PI, which doesn&amp;rsquo;t have the hardware I need to run AI models for object detection on the frames. The Raspberry PI 5 doesn&amp;rsquo;t even have the capability to offload transcoding to hardware, so for video I can only forward the format that the cameras themselves produce and stream via RTSP. I&amp;rsquo;ll only use the Raspberry PI (I&amp;rsquo;ll call it &amp;ldquo;rpi&amp;rdquo; from here on) to provide a connection to the Starlink LAN from my remote server.&lt;/p&gt;
&lt;h2 id="first-attempt-tailscale-for-remote-network-access"&gt;First Attempt: Tailscale for Remote Network Access&lt;/h2&gt;
&lt;p&gt;To remotely access the Starlink LAN from my remote server (I&amp;rsquo;ll call it just &amp;ldquo;server&amp;rdquo; from here on), I initially chose Tailscale. This is a great choice since it uses the Wireguard (UDP) protocol under the hood and establishes direct (mesh) connections between computers in its virtual network. Through its 4via6 subnet routers, it also allowed me to directly address requests to the cameras from my server, even though the Starlink LAN subnet address space conflicted with my server&amp;rsquo;s LAN (both were using 192.168.1.0/24 addresses, so my server addressed the cameras with IPv6 addresses that the rpi translated back into Starlink LAN IPv4 addresses before passing them to the Starlink router).&lt;/p&gt;
&lt;h2 id="tailscale-problems-and-performance-issues"&gt;Tailscale Problems and Performance Issues&lt;/h2&gt;
&lt;p&gt;However, I quickly ran into problems setting up the cameras into Frigate with IPv6 addresses (go2rtc worked, ONVIF didn&amp;rsquo;t) and I needed a proxy (nginx container that frigate routed through with network_mode) to present IPv4 addresses to frigate that I translated into the real IPv6 addresses before forwarding to Tailscale. After these fixes, I had a working stream in Frigate, but the lag was excessive (10-15 seconds at best), the quality was very low, and it often didn&amp;rsquo;t work at all. In retrospect, I mostly blame this on the architecture I used (RTSP protocol, server making requests to cameras through Tailscale, etc.) and not on Tailscale itself. However, my next step was to manually connect the rpi to the server, because I couldn&amp;rsquo;t know for sure if Tailscale was causing the problem with how it made the connection and routed packets. If I managed the connection myself, I would have a complete end-to-end view.&lt;/p&gt;
&lt;h2 id="building-a-custom-wireguard-tunnel"&gt;Building a Custom Wireguard Tunnel&lt;/h2&gt;
&lt;p&gt;To create my own tunnel, I decided to use Wireguard, even though I&amp;rsquo;m aware of protocols like MASQUE which might be even better, because most of my routed traffic would be UDP anyway. Since the Starlink terminal is behind a CGNAT, I couldn&amp;rsquo;t host the wireguard endpoint on the rpi. Instead, I hosted it on the server and had the rpi connect to it. Instead of routing the streams directly from the cameras to the server through wireguard (with iptables on the rpi), I decided to run go2rtc on the rpi to have it pull the streams from the cameras, then have the server directly pull them from go2rtc, which I hoped would be more stable. Unfortunately, I only saw marginal improvement in latency, and the blackout periods where the streams weren&amp;rsquo;t working actually intensified. At this point, I realized that my initial approach needed big changes - either changing the streaming protocol or changing the architecture so that instead of a round trip (RTSP streams require round trips!!!) the frames travel one way from the rpi to the server. I ended up doing both.&lt;/p&gt;
&lt;h2 id="experimenting-with-rtmp-and-switching-to-srt"&gt;Experimenting with RTMP and switching to SRT&lt;/h2&gt;
&lt;p&gt;Initially, I tried using RTMP as the transfer protocol for the connection between the rpi and the server, with go2rtc on the pi publishing to mediamtx on the server. This worked relatively well at the start of sessions, but it quickly drifted behind the source (I suspect too many packets were lost in transit but it kept waiting for them) and eventually stopped working altogether. After trying different ffmpeg parameters and lower resolutions/framerates, I gave up on RTMP (or trying RTMP over UDP) and decided to go straight to SRT, which ended up working with very little latency and jitter (I also tried setting up WebRTC for this link, but it was too complicated, and I preferred to keep it simple). Instead of go2rtc, I am actually running mediamtx on the client-side too to publish the SRT stream.&lt;/p&gt;
&lt;h2 id="architecture-diagram"&gt;Architecture Diagram&lt;/h2&gt;
&lt;svg width="100%" height="500" viewBox="0 0 900 500" xmlns="http://www.w3.org/2000/svg" style="max-width: 100%; height: auto;"&gt;
&lt;!-- Remote House --&gt;
&lt;rect x="50" y="50" width="250" height="400" fill="#3a3a3a" stroke="#888" stroke-width="2" rx="10"/&gt;
&lt;text x="175" y="35" text-anchor="middle" font-family="Arial" font-size="16" font-weight="bold" fill="#fff"&gt;Remote House&lt;/text&gt;
&lt;!-- Cameras --&gt;
&lt;rect x="80" y="98" width="70" height="45" fill="#4CAF50" stroke="#888" stroke-width="1" rx="5"/&gt;
&lt;text x="115" y="126" text-anchor="middle" font-family="Arial" font-size="11" fill="#fff"&gt;Camera 1&lt;/text&gt;
&lt;rect x="200" y="98" width="70" height="45" fill="#4CAF50" stroke="#888" stroke-width="1" rx="5"/&gt;
&lt;text x="235" y="126" text-anchor="middle" font-family="Arial" font-size="11" fill="#fff"&gt;Camera 2&lt;/text&gt;
&lt;!-- Raspberry Pi --&gt;
&lt;rect x="125" y="180" width="100" height="60" fill="#FF9800" stroke="#888" stroke-width="2" rx="5"/&gt;
&lt;text x="175" y="205" text-anchor="middle" font-family="Arial" font-size="14" font-weight="bold" fill="#fff"&gt;Raspberry Pi&lt;/text&gt;
&lt;text x="175" y="225" text-anchor="middle" font-family="Arial" font-size="12" fill="#fff"&gt;mediamtx&lt;/text&gt;
&lt;!-- Wireguard Client --&gt;
&lt;rect x="125" y="283" width="100" height="35" fill="#8E24AA" stroke="#888" stroke-width="1" rx="5"/&gt;
&lt;text x="175" y="305" text-anchor="middle" font-family="Arial" font-size="12" fill="white"&gt;Wireguard&lt;/text&gt;
&lt;!-- Starlink --&gt;
&lt;ellipse cx="175" cy="390" rx="50" ry="25" fill="#0066CC" stroke="#888" stroke-width="2"/&gt;
&lt;text x="175" y="396" text-anchor="middle" font-family="Arial" font-size="12" fill="white"&gt;Starlink&lt;/text&gt;
&lt;!-- Internet Connection (compact design) --&gt;
&lt;rect x="380" y="370" width="140" height="40" fill="#42A5F5" stroke="#888" stroke-width="2" rx="20"/&gt;
&lt;text x="450" y="394" text-anchor="middle" font-family="Arial" font-size="14" font-weight="bold" fill="#fff"&gt;Internet&lt;/text&gt;
&lt;!-- Server --&gt;
&lt;rect x="600" y="50" width="250" height="400" fill="#3a3a3a" stroke="#888" stroke-width="2" rx="10"/&gt;
&lt;text x="725" y="35" text-anchor="middle" font-family="Arial" font-size="16" font-weight="bold" fill="#fff"&gt;Server&lt;/text&gt;
&lt;!-- Home Assistant --&gt;
&lt;rect x="625" y="98" width="200" height="45" fill="#41BDF5" stroke="#888" stroke-width="1" rx="5"/&gt;
&lt;text x="725" y="126" text-anchor="middle" font-family="Arial" font-size="14" font-weight="bold" fill="white"&gt;Home Assistant&lt;/text&gt;
&lt;!-- Frigate + go2rtc --&gt;
&lt;rect x="625" y="175" width="200" height="70" fill="#2196F3" stroke="#888" stroke-width="1" rx="5"/&gt;
&lt;text x="725" y="205" text-anchor="middle" font-family="Arial" font-size="14" font-weight="bold" fill="white"&gt;Frigate&lt;/text&gt;
&lt;text x="725" y="225" text-anchor="middle" font-family="Arial" font-size="12" fill="white"&gt;+ go2rtc&lt;/text&gt;
&lt;!-- mediamtx Server --&gt;
&lt;rect x="625" y="278" width="200" height="45" fill="#4CAF50" stroke="#888" stroke-width="1" rx="5"/&gt;
&lt;text x="725" y="306" text-anchor="middle" font-family="Arial" font-size="14" fill="white"&gt;mediamtx&lt;/text&gt;
&lt;!-- Wireguard Server (adjusted for perfect horizontal alignment) --&gt;
&lt;rect x="625" y="372" width="200" height="36" fill="#8E24AA" stroke="#888" stroke-width="1" rx="5"/&gt;
&lt;text x="725" y="395" text-anchor="middle" font-family="Arial" font-size="12" fill="white"&gt;Wireguard Server&lt;/text&gt;
&lt;!-- Connections --&gt;
&lt;!-- Cameras to Pi --&gt;
&lt;line x1="115" y1="143" x2="155" y2="180" stroke="#888" stroke-width="2" marker-end="url(#arrowhead)"/&gt;
&lt;line x1="235" y1="143" x2="195" y2="180" stroke="#888" stroke-width="2" marker-end="url(#arrowhead)"/&gt;
&lt;text x="140" y="162" font-family="Arial" font-size="10" fill="#aaa"&gt;RTSP&lt;/text&gt;
&lt;!-- Pi to Wireguard --&gt;
&lt;line x1="175" y1="240" x2="175" y2="283" stroke="#888" stroke-width="2" marker-end="url(#arrowhead)"/&gt;
&lt;text x="185" y="262" font-family="Arial" font-size="10" fill="#FF5722"&gt;SRT&lt;/text&gt;
&lt;!-- Wireguard to Starlink --&gt;
&lt;line x1="175" y1="318" x2="175" y2="365" stroke="#888" stroke-width="2" marker-end="url(#arrowhead)"/&gt;
&lt;!-- Starlink through Internet to Server (single integrated flow, perfectly horizontal) --&gt;
&lt;line x1="225" y1="390" x2="380" y2="390" stroke="#888" stroke-width="3" marker-end="url(#arrowhead)"/&gt;
&lt;line x1="520" y1="390" x2="625" y2="390" stroke="#888" stroke-width="3" marker-end="url(#arrowhead)"/&gt;
&lt;!-- Wireguard Server to mediamtx --&gt;
&lt;line x1="725" y1="372" x2="725" y2="323" stroke="#888" stroke-width="2" marker-end="url(#arrowhead)"/&gt;
&lt;text x="735" y="348" font-family="Arial" font-size="10" fill="#FF5722"&gt;SRT&lt;/text&gt;
&lt;!-- mediamtx to Frigate --&gt;
&lt;line x1="725" y1="278" x2="725" y2="245" stroke="#888" stroke-width="2" marker-end="url(#arrowhead)"/&gt;
&lt;!-- Frigate to Home Assistant --&gt;
&lt;line x1="725" y1="175" x2="725" y2="143" stroke="#888" stroke-width="2" marker-end="url(#arrowhead)"/&gt;
&lt;!-- Arrow marker definition --&gt;
&lt;defs&gt;
&lt;marker id="arrowhead" markerWidth="10" markerHeight="7" refX="10" refY="3.5" orient="auto"&gt;
&lt;polygon points="0 0, 10 3.5, 0 7" fill="#888"/&gt;
&lt;/marker&gt;
&lt;/defs&gt;
&lt;/svg&gt;
&lt;h2 id="wireguard-configuration"&gt;Wireguard Configuration&lt;/h2&gt;
&lt;p&gt;Finally, noting that server-side, wg-easy, frigate and mediamtx all run as docker containers that share the same network (subnet 10.42.42.0/24), with static IP&amp;rsquo;s: 10.42.42.42 (wg-easy), 10.42.42.43 (frigate), 10.42.42.253 (mediamtx), you might find the following hook / routing table helpful. Client-side, having these different addresses lets the rpi send different packets to different parts of the stack, as well as let me connect to services directly inside the containers from my laptop, for debugging.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;PostUp = nft add table inet wg_table; \
nft add chain inet wg_table prerouting { type nat hook prerouting priority -100 \; }; \
nft add chain inet wg_table postrouting { type nat hook postrouting priority 100 \; }; \
nft add rule inet wg_table postrouting ip saddr 10.8.0.0/24 oifname eth0 masquerade; \
nft add rule inet wg_table postrouting ip6 saddr fdcc:ad94:bacf:61a4::cafe:0/112 oifname eth0 masquerade; \
nft add rule inet wg_table prerouting ip daddr 10.8.0.1 dnat to 10.42.42.1; \
nft add rule inet wg_table prerouting ip6 daddr fdcc:ad94:bacf:61a4::cafe:1 dnat to fdcc:ad94:bacf:61a3::1; \
nft add rule inet wg_table prerouting ip daddr 10.8.0.253 dnat to 10.42.42.253; \
nft add chain inet wg_table input { type filter hook input priority 0 \; policy accept \; }; \
nft add rule inet wg_table input udp dport 51820 accept; \
nft add rule inet wg_table input tcp dport 51821 accept; \
nft add chain inet wg_table forward { type filter hook forward priority 0 \; policy accept \; }; \
nft add rule inet wg_table forward iifname &amp;#34;wg0&amp;#34; accept; \
nft add rule inet wg_table forward oifname &amp;#34;wg0&amp;#34; accept;
&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>Postcards from Italy</title><link>https://papadim.as/postcards-from-italy/</link><pubDate>Mon, 01 Jul 2024 00:00:00 +0000</pubDate><guid>https://papadim.as/postcards-from-italy/</guid><description>&lt;p&gt;I took these images in Rome, where I spent one week as one of three Greek university students selected to compete at the 2024 European Huawei Seeds for the Future startup competition. We worked hard, had fun, and learned about technology, leadership and entrepreneurship from industry leaders and distinguished mentors. I&amp;rsquo;m incredibly grateful for this learning opportunity and I can’t wait to apply the knowledge I&amp;rsquo;ve gained to my future career!&lt;/p&gt;</description></item><item><title>Kefalonia in May</title><link>https://papadim.as/kefalonia-in-may/</link><pubDate>Thu, 02 May 2024 00:00:00 +0000</pubDate><guid>https://papadim.as/kefalonia-in-may/</guid><description>&lt;p&gt;Secretly the best time to visit.&lt;/p&gt;</description></item><item><title>Norway &amp; Denmark</title><link>https://papadim.as/norway-denmark/</link><pubDate>Thu, 15 Feb 2024 00:00:00 +0000</pubDate><guid>https://papadim.as/norway-denmark/</guid><description/></item></channel></rss>