Skip to content

Instantly share code, notes, and snippets.

@igniuss
Last active August 25, 2025 15:34
Show Gist options
  • Select an option

  • Save igniuss/f4c9d2563af1edbcdcada2882dda8b07 to your computer and use it in GitHub Desktop.

Select an option

Save igniuss/f4c9d2563af1edbcdcada2882dda8b07 to your computer and use it in GitHub Desktop.
USB Capture-card Streaming Utility

USB Capture Card Utility

Tries to find the correct v4l2 device to open in MPV along with the correct audio source; needed something like this when I was testing multiple capture cards and wanted a nice way to have it 'just work'

NOTICE

Only tested with 3 really cheap amazon USB 3.0 Capture Cards.

#!/bin/bash
# Find V4L2 capture device and stream with minimal latency
# Usage: ./mpv-stream.sh [device_pattern]
set -e
# Default device pattern (common capture card patterns)
DEFAULT_PATTERNS=("capture" "video" "usb" "cam" "card" "easycap")
DEVICE_PATTERN="${1:-capture}"
# Function to find V4L2 devices - returns only device path
find_v4l2_device() {
local pattern="$1"
echo "Searching for V4L2 devices with pattern: $pattern" >&2
# Check all video devices
for device in /dev/video*; do
if [ -c "$device" ]; then
# Use v4l2-ctl to get device info
if v4l2-ctl -d "$device" --all 2>/dev/null | grep -q -i "$pattern"; then
echo "Found device: $device" >&2
v4l2-ctl -d "$device" --info | head -n 5 >&2
# Return only the device path (no echo)
printf "%s" "$device"
return 0
fi
fi
done
return 1
}
# Function to list all available V4L2 devices
list_all_devices() {
echo "Available V4L2 devices:"
for device in /dev/video*; do
if [ -c "$device" ]; then
echo "=== $device ==="
v4l2-ctl -d "$device" --info 2>/dev/null | head -n 3 || true
echo
fi
done
}
# Try to find device with specified pattern
FOUND_DEVICE=""
for pattern in "$DEVICE_PATTERN" "${DEFAULT_PATTERNS[@]}"; do
echo "Trying pattern: $pattern"
FOUND_DEVICE=$(find_v4l2_device "$pattern")
if [ -n "$FOUND_DEVICE" ]; then
break
fi
done
# If no device found, show available devices and exit
if [ -z "$FOUND_DEVICE" ]; then
echo "Error: No V4L2 capture device found with pattern: $DEVICE_PATTERN"
echo
list_all_devices
echo "Please specify a different pattern or check your device connection"
exit 1
fi
echo "Using device: $FOUND_DEVICE"
# # Configure the V4L2 device to 1080p60 upfront so we don't need global demuxer opts
if command -v v4l2-ctl >/dev/null 2>&1; then
echo "Configuring $FOUND_DEVICE to 1920x1080@60..."
v4l2-ctl -d "$FOUND_DEVICE" --set-fmt-video=width=1920,height=1080,pixelformat=MJPG 2>/dev/null || \
# v4l2-ctl -d "$FOUND_DEVICE" --set-fmt-video=width=1920,height=1080,pixelformat=YUYV 2>/dev/null || \
echo "Warning: Could not set pixel format to MJPG/YUYV"
v4l2-ctl -d "$FOUND_DEVICE" --set-parm=60 2>/dev/null || echo "Warning: Could not set framerate to 60"
fi
# Check if audio is available and ensure it matches the selected V4L2 device
# Prefer PulseAudio for audio capture, fallback to ALSA only if PulseAudio is unavailable or no suitable source is found
AUDIO_DEVICE=""
AUDIO_TYPE=""
# Resolve the hardware root (USB/PCI) for a sysfs path
get_hw_root() {
local p="$1"
while [ "$p" != "/" ]; do
if [ -f "$p/idVendor" ] || [ -f "$p/vendor" ]; then
echo "$p"
return 0
fi
p=$(dirname "$p")
done
echo ""
}
# Find ALSA card number that shares the same hardware root as the given V4L2 device
find_alsa_card_for_video_device() {
local video="$1"
local vpath
vpath=$(readlink -f "/sys/class/video4linux/$(basename "$video")/device" 2>/dev/null || true)
[ -z "$vpath" ] && return 1
local vroot
vroot=$(get_hw_root "$vpath")
[ -z "$vroot" ] && return 1
for dev in /sys/class/sound/card*/device; do
[ -e "$dev" ] || continue
local cpath
cpath=$(readlink -f "$dev" 2>/dev/null || true)
[ -z "$cpath" ] && continue
local croot
croot=$(get_hw_root "$cpath")
[ -z "$croot" ] && continue
if [ "$croot" = "$vroot" ]; then
local carddir
carddir=$(basename "$(dirname "$dev")") # e.g., card2
printf "%s" "${carddir#card}"
return 0
fi
done
return 1
}
# Try PulseAudio first
PULSE_DEVICE_CANDIDATE=""
if command -v pactl >/dev/null 2>&1; then
# Try to match PulseAudio source to V4L2 device hardware root
V4L2_HW_ROOT="$(get_hw_root $(readlink -f "/sys/class/video4linux/$(basename "$FOUND_DEVICE")/device" 2>/dev/null || true))"
# List all sources and try to match by USB/PCI ID in the name
PULSE_MATCHED=""
if [ -n "$V4L2_HW_ROOT" ]; then
# Extract USB/PCI ID from the hardware root path
USB_ID="$(basename "$V4L2_HW_ROOT")"
# Try to find a source whose name contains the USB/PCI ID
PULSE_MATCHED="$(pactl list sources | awk -v RS= -v id="$USB_ID" '
$0 ~ /Source #/ && $0 !~ /monitor/ && $0 ~ /Name:[[:space:]]+.*alsa_input/ {
if ($0 ~ id) {
if (match($0, /Name:[[:space:]]+([^\n]+)/, n)) { print n[1]; exit }
}
}' | head -n1)"
fi
# Fallback to first non-monitor alsa_input source if no match
if [ -z "$PULSE_MATCHED" ]; then
PULSE_MATCHED="$(pactl list sources | awk -v RS= '
$0 ~ /Source #/ && $0 !~ /monitor/ && $0 ~ /Name:[[:space:]]+.*alsa_input/ {
if (match($0, /Name:[[:space:]]+([^\n]+)/, n)) { print n[1]; exit }
}' | head -n1)"
fi
if [ -n "$PULSE_MATCHED" ]; then
AUDIO_DEVICE="$PULSE_MATCHED"
AUDIO_TYPE="pulse"
fi
fi
# If PulseAudio is not available or no suitable source, try ALSA
if [ -z "$AUDIO_DEVICE" ]; then
ALSA_CARD_NUM="$(find_alsa_card_for_video_device "$FOUND_DEVICE" || true)"
ALSA_DEVICE_CANDIDATE=""
[ -n "$ALSA_CARD_NUM" ] && ALSA_DEVICE_CANDIDATE="plughw:$ALSA_CARD_NUM,0"
if [ -n "$ALSA_DEVICE_CANDIDATE" ]; then
AUDIO_DEVICE="$ALSA_DEVICE_CANDIDATE"
AUDIO_TYPE="alsa"
fi
fi
[ -n "$AUDIO_DEVICE" ] && [ -n "$AUDIO_TYPE" ] && echo "Selected audio ($AUDIO_TYPE): $AUDIO_DEVICE"
# MPV options for lowest latency
MPV_OPTIONS=(
"--profile=low-latency"
"--untimed"
"--video-sync=display-resample"
"--interpolation=no"
"--video-latency-hacks=yes"
"--opengl-swapinterval=0"
"--hwdec=auto-copy" # Hardware decoding if available
"--gpu-context=auto"
"--cache=no"
"--audio-buffer=0.01" # Very small audio buffer
"--log-file=./log.txt"
"--demuxer-lavf-o-add=use_wallclock_as_timestamps=yes"
)
# Build the mpv command and ensure a valid audio output backend is selected
MPV_CMD=("mpv" "${MPV_OPTIONS[@]}")
if command -v pactl >/dev/null 2>&1; then
MPV_CMD+=("--ao=pulse" "--audio-device=auto")
else
MPV_CMD+=("--audio-device=auto")
fi
# Clean up any potential whitespace in device paths
FOUND_DEVICE=$(echo "$FOUND_DEVICE" | tr -d '[:space:]')
printf 'Found device \n%s\n' $FOUND_DEVICE
printf 'Found audio \n%s\n' $AUDIO_DEVICE
printf 'Found audio type \n%s\n' $AUDIO_TYPE
if [ -n "$AUDIO_DEVICE" ]; then
# Clean audio device path too
AUDIO_DEVICE=$(echo "$AUDIO_DEVICE" | tr -d '[:space:]')
# Stream with audio
echo "Starting stream with audio (lowest latency)... with $AUDIO_TYPE audio device: $AUDIO_DEVICE"
if [ "$AUDIO_TYPE" = "alsa" ]; then
ENABLE_LSFG=1 "${MPV_CMD[@]}" \
--aid=1 \
--audio-file="av://alsa:$AUDIO_DEVICE" \
"av://v4l2:$FOUND_DEVICE"
else
ENABLE_LSFG=1 "${MPV_CMD[@]}" \
--aid=1 \
--audio-file="av://pulse:$AUDIO_DEVICE" \
"av://v4l2:$FOUND_DEVICE"
fi
else
# Stream without audio
echo "Starting video-only stream (lowest latency)..."
echo "Note: No audio device found. If you need audio, check your audio setup."
"${MPV_CMD[@]}" \
"av://v4l2:$FOUND_DEVICE"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment