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.
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 (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
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.