| pick your distro, get ZFS on root
kldload — your platform, your way, free
Source

Appliance Recipe: Live TV Streaming (SRT/HLS/DASH/IPTV)

A kldload appliance that captures over-the-air or HDMI video via a capture card, encodes it with ffmpeg, and distributes it as live streams — SRT for broadcast-grade contribution, HLS/DASH for web viewers, IPTV multicast for LAN distribution. Carrier-grade live video, not webcam quality.

What this replaces: Commercial broadcast encoding/distribution hardware. A capture card, a kldload box, and ffmpeg.

SRT (Secure Reliable Transport) is the protocol that actual broadcast infrastructure uses — CNN, ESPN, BBC. It was open-sourced by Haivision in 2017 and replaced proprietary protocols across the industry. Sub-second latency, built-in AES encryption, handles packet loss and jitter over the public internet. Combined with ffmpeg (which does everything) and kldload (ZFS for recordings, WireGuard for private streams, NVIDIA for hardware encoding), you get a broadcast-grade streaming system on commodity hardware. The four output formats cover every use case: SRT for contribution (remote cameras, field reporters), HLS/DASH for web viewers (any browser), IPTV multicast for LAN distribution (smart TVs, VLC, set-top boxes). One ffmpeg command, four simultaneous outputs.

Architecture

┌─────────────────────────────────────────────────────────────────────────┐
│                          kldload Streamer                             │
│                                                                         │
│  OTA Antenna ──► Tuner/Capture Card ──► /dev/video0                    │
│  HDMI Source ──► HDMI Capture Card  ──► /dev/video1                    │
│                                                                         │
│                    ┌──────────┐                                         │
│                    │  ffmpeg  │                                         │
│                    │ (decode  │                                         │
│                    │  encode) │                                         │
│                    └────┬─────┘                                         │
│                         │                                               │
│              ┌──────────┼──────────┬──────────────┐                    │
│              │          │          │              │                    │
│         ┌────▼───┐ ┌───▼────┐ ┌──▼──────┐ ┌────▼────────┐           │
│         │  SRT   │ │  HLS   │ │  DASH   │ │  IPTV       │           │
│         │:9000   │ │ nginx  │ │ nginx   │ │  multicast  │           │
│         │        │ │:8080   │ │:8080    │ │  udp://     │           │
│         └────────┘ └────────┘ └─────────┘ └─────────────┘           │
│              │                     │                                    │
│    SRT callers                Web viewers          LAN TVs/VLC         │
│   (remote contributors)      (any browser)        (multicast)          │
└─────────────────────────────────────────────────────────────────────────┘

Hardware

Component Examples Cost
Mini PC / server Any x86_64 with USB 3.0 (or PCIe slot) $200-500
HDMI capture card Elgato HD60 X, AVerMedia Live Gamer, Magewell USB Capture $100-400
OTA TV tuner (optional) HDHomeRun, Hauppauge WinTV $50-150
GPU (optional, for NVENC) NVIDIA GTX 1650+ $150-300
USB stick kldload installer $5

For software encoding (no GPU): a modern 4-core CPU handles 1080p30 x264 with veryfast preset. For hardware encoding: NVIDIA NVENC does 4K60 with minimal CPU usage.


Step 1: Install kldload

cat > /tmp/answers.env << 'EOF'
KLDLOAD_DISTRO=debian
KLDLOAD_DISK=/dev/sda
KLDLOAD_HOSTNAME=tv-streamer
KLDLOAD_USERNAME=admin
KLDLOAD_PASSWORD=changeme
KLDLOAD_PROFILE=server
KLDLOAD_NVIDIA_DRIVERS=1
KLDLOAD_NET_METHOD=dhcp
EOF
kldload-install-target --config /tmp/answers.env

Step 2: Install streaming packages

# ffmpeg with full codec support
apt install -y ffmpeg

# SRT library and tools
apt install -y libsrt-openssl-dev srt-tools

# nginx for HLS/DASH serving
apt install -y nginx

# Video4Linux utils
apt install -y v4l-utils

# Optional: NVIDIA NVENC (if GPU installed)
# The NVIDIA driver from the kldload install includes NVENC support

Step 3: Verify capture card

# List video devices
v4l2-ctl --list-devices

# Check capabilities
v4l2-ctl -d /dev/video0 --all

# Test capture (5 seconds)
ffmpeg -f v4l2 -i /dev/video0 -t 5 -c copy /tmp/test-capture.ts

# For HDMI capture cards, check supported formats
v4l2-ctl -d /dev/video0 --list-formats-ext

Step 4: ZFS datasets for media

# Live stream segments (HLS/DASH — high write, short retention)
zfs create -o mountpoint=/srv/stream \
           -o compression=off \
           -o recordsize=1M \
           -o atime=off \
           -o logbias=throughput \
           rpool/srv/stream

# Recordings (long-term archive)
zfs create -o mountpoint=/srv/recordings \
           -o compression=zstd \
           -o recordsize=1M \
           rpool/srv/recordings

# nginx web root
mkdir -p /srv/stream/hls /srv/stream/dash

Step 5: Configure nginx for HLS/DASH

# /etc/nginx/sites-available/streaming
server {
    listen 8080;
    server_name _;

    # HLS
    location /hls {
        alias /srv/stream/hls;
        types {
            application/vnd.apple.mpegurl m3u8;
            video/mp2t ts;
        }
        add_header Cache-Control no-cache;
        add_header Access-Control-Allow-Origin *;
    }

    # DASH
    location /dash {
        alias /srv/stream/dash;
        types {
            application/dash+xml mpd;
            video/mp4 m4s mp4;
        }
        add_header Cache-Control no-cache;
        add_header Access-Control-Allow-Origin *;
    }

    # Simple player page
    location / {
        root /srv/stream/web;
        index index.html;
    }
}
ln -sf /etc/nginx/sites-available/streaming /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
systemctl restart nginx

Simple HLS player page:

<!-- /srv/stream/web/index.html -->
<!DOCTYPE html>
<html><head>
  <title>kldload Live TV</title>
  <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
</head><body style="background:#000;margin:0;display:flex;justify-content:center;align-items:center;height:100vh">
  <video id="video" controls autoplay style="max-width:100%;max-height:100%"></video>
  <script>
    const video = document.getElementById('video');
    if (Hls.isSupported()) {
      const hls = new Hls();
      hls.loadSource('/hls/live.m3u8');
      hls.attachMedia(video);
    } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
      video.src = '/hls/live.m3u8';
    }
  </script>
</body></html>

Step 6: Streaming with ffmpeg

Software encoding (CPU — x264)

# Capture → HLS + DASH + SRT simultaneously
ffmpeg -f v4l2 -video_size 1920x1080 -framerate 30 -i /dev/video0 \
  -f alsa -i hw:1,0 \
  \
  -map 0:v -map 1:a -c:v libx264 -preset veryfast -b:v 4000k -maxrate 4500k -bufsize 8000k \
  -c:a aac -b:a 128k -ar 44100 \
  \
  -f hls -hls_time 4 -hls_list_size 10 -hls_flags delete_segments \
  /srv/stream/hls/live.m3u8 \
  \
  -f dash -seg_duration 4 -window_size 10 -remove_at_exit 1 \
  /srv/stream/dash/live.mpd \
  \
  -f mpegts "srt://0.0.0.0:9000?mode=listener&latency=200" \
  \
  -f mpegts "udp://239.1.1.1:5000?pkt_size=1316"

Hardware encoding (NVIDIA NVENC)

ffmpeg -f v4l2 -video_size 1920x1080 -framerate 30 -i /dev/video0 \
  -f alsa -i hw:1,0 \
  \
  -map 0:v -map 1:a -c:v h264_nvenc -preset p4 -b:v 6000k -maxrate 7000k -bufsize 12000k \
  -c:a aac -b:a 128k -ar 44100 \
  \
  -f hls -hls_time 4 -hls_list_size 10 -hls_flags delete_segments \
  /srv/stream/hls/live.m3u8 \
  \
  -f mpegts "srt://0.0.0.0:9000?mode=listener&latency=200"

Multi-bitrate adaptive (ABR)

ffmpeg -f v4l2 -video_size 1920x1080 -framerate 30 -i /dev/video0 \
  -f alsa -i hw:1,0 \
  \
  -map 0:v -map 0:v -map 0:v -map 1:a \
  \
  -c:v:0 libx264 -b:v:0 5000k -s:v:0 1920x1080 -preset veryfast \
  -c:v:1 libx264 -b:v:1 2500k -s:v:1 1280x720 -preset veryfast \
  -c:v:2 libx264 -b:v:2 1000k -s:v:2 854x480 -preset veryfast \
  -c:a aac -b:a 128k \
  \
  -f hls -hls_time 4 -hls_list_size 10 -hls_flags delete_segments \
  -var_stream_map "v:0,a:0 v:1,a:0 v:2,a:0" \
  -master_pl_name live.m3u8 \
  /srv/stream/hls/stream_%v.m3u8

Step 7: SRT — the carrier-grade transport

SRT over WireGuard is the ultimate private streaming setup. SRT provides its own AES encryption, but running it over WireGuard means double encryption + the WireGuard tunnel is invisible to port scanners. Your live stream traverses the internet as indistinguishable encrypted UDP — nobody can even tell it's video. For multi-site operations (church with two campuses, sports venue with remote production, corporate all-hands from multiple offices), the WireGuard mesh connects the sites and SRT carries the video. Latency penalty: ~1ms from WireGuard, negligible compared to encoding latency.

SRT (Secure Reliable Transport) is what actual broadcast infrastructure uses. Sub-second latency, handles packet loss and jitter, AES-128/256 encryption, and works over the public internet.

SRT listener (the streamer — accepts incoming callers)

# Already running from the ffmpeg command above on port 9000
# Callers connect to: srt://your-ip:9000

SRT caller (a remote contributor sending video TO the server)

# Remote camera/contributor sends to the streamer
ffmpeg -f v4l2 -i /dev/video0 -f alsa -i hw:0,0 \
  -c:v libx264 -preset veryfast -b:v 4000k \
  -c:a aac -b:a 128k \
  -f mpegts "srt://streamer-ip:9001?mode=caller&latency=200"

Receive SRT caller and mix into the live stream

# On the streamer — accept a caller on port 9001
ffmpeg \
  -f v4l2 -i /dev/video0 \
  -f mpegts -i "srt://0.0.0.0:9001?mode=listener&latency=200" \
  -filter_complex "[0:v][1:v]hstack=inputs=2[out]" \
  -map "[out]" -c:v libx264 -preset veryfast -b:v 6000k \
  -f hls -hls_time 4 /srv/stream/hls/live.m3u8

SRT over WireGuard

For private streaming between two kldload nodes:

# Streamer sends to receiver over WireGuard
ffmpeg -f v4l2 -i /dev/video0 -f alsa -i hw:1,0 \
  -c:v libx264 -preset veryfast -b:v 5000k -c:a aac -b:a 128k \
  -f mpegts "srt://10.200.0.2:9000?mode=caller&latency=120&passphrase=secret123"

Step 8: IPTV multicast (LAN distribution)

For distributing to smart TVs, VLC, and set-top boxes on the LAN:

# Multicast stream (any device on the LAN can tune in with VLC)
ffmpeg -f v4l2 -i /dev/video0 -f alsa -i hw:1,0 \
  -c:v libx264 -preset veryfast -b:v 4000k -c:a aac -b:a 128k \
  -f mpegts "udp://239.1.1.1:5000?pkt_size=1316&ttl=10"

Watch with VLC:

vlc udp://@239.1.1.1:5000

Or add to an M3U playlist:

#EXTM3U
#EXTINF:-1,Live TV - kldload
udp://@239.1.1.1:5000

Step 9: The receiver (live TV appliance)

A second kldload box that receives the stream and displays it — your own TV station receiver.

# Install on the receiver
apt install -y ffmpeg vlc mpv

# Receive SRT stream and display
mpv "srt://streamer-ip:9000?mode=caller&latency=200"

# Or receive and re-stream to a local HDMI output
ffmpeg -i "srt://10.200.0.1:9000?mode=caller&latency=200" \
  -c copy -f v4l2 /dev/video0

# Or receive and serve HLS locally for smart TVs
ffmpeg -i "srt://10.200.0.1:9000?mode=caller&latency=200" \
  -c copy -f hls -hls_time 2 -hls_list_size 5 \
  /srv/stream/hls/live.m3u8

ZFS for live streaming recordings is the right tool for the job. The stream dataset uses compression=off and recordsize=1M because live video segments are already compressed (H.264/H.265) and write sequentially. The recordings dataset uses compression=zstd because archived transport streams often have padding bytes and metadata that compress well. Hourly snapshots of recordings mean you can recover any accidentally deleted show. syncoid over WireGuard replicates recordings to a backup site — your archive survives a hardware failure. This is the same workflow professional broadcasters use for compliance recording, except they pay for SANs and tape libraries.

Step 10: Record everything to ZFS

# Record the live stream with timestamps
ffmpeg -f v4l2 -video_size 1920x1080 -framerate 30 -i /dev/video0 \
  -f alsa -i hw:1,0 \
  -c:v libx264 -preset veryfast -b:v 8000k -c:a aac -b:a 192k \
  -f segment -segment_time 3600 -strftime 1 \
  "/srv/recordings/%Y-%m-%d_%H-%M-%S.ts" \
  \
  -c:v libx264 -preset veryfast -b:v 4000k -c:a aac -b:a 128k \
  -f hls -hls_time 4 -hls_list_size 10 -hls_flags delete_segments \
  /srv/stream/hls/live.m3u8

Hourly ZFS snapshots of recordings:

echo '0 * * * * root zfs snapshot rpool/srv/recordings@hourly-$(date +\%Y\%m\%d-\%H)' >> /etc/crontab

Replicate recordings to backup over WireGuard:

syncoid rpool/srv/recordings 10.200.0.2:rpool/srv/recordings-backup

Step 11: systemd service

cat > /etc/systemd/system/live-stream.service << 'EOF'
[Unit]
Description=Live TV streaming (ffmpeg)
After=network-online.target

[Service]
Type=simple
ExecStart=/usr/bin/ffmpeg \
  -f v4l2 -video_size 1920x1080 -framerate 30 -i /dev/video0 \
  -f alsa -i hw:1,0 \
  -map 0:v -map 1:a \
  -c:v libx264 -preset veryfast -b:v 4000k -maxrate 4500k -bufsize 8000k \
  -c:a aac -b:a 128k -ar 44100 \
  -f hls -hls_time 4 -hls_list_size 10 -hls_flags delete_segments \
  /srv/stream/hls/live.m3u8
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now live-stream

Verify

# Check capture device
v4l2-ctl --list-devices

# Check ffmpeg is encoding
systemctl status live-stream
journalctl -u live-stream -f

# Test HLS stream
curl -s http://localhost:8080/hls/live.m3u8 | head -10

# Test SRT stream (from another machine)
ffplay "srt://streamer-ip:9000?mode=caller&latency=200"

# Check IPTV multicast
vlc udp://@239.1.1.1:5000

# Check stream health with eBPF
bpftrace -e 'tracepoint:net:net_dev_xmit /str(args.name) == "eth0"/ { @bytes = hist(args.len); }'

Bill of materials

Component Cost
Mini PC (4-core, 8GB RAM) $200
HDMI capture card (Elgato/Magewell) $150-400
OTA antenna + tuner (optional) $50-100
NVIDIA GPU (optional, for NVENC) $150-300
kldload on USB Free
Total (software encoding) ~$400
Total (hardware encoding) ~$700

Compare to a commercial broadcast encoder that costs orders of magnitude more and locks you into proprietary formats and vendor support contracts.