Video4linux

From wikinotes

A collection of device drivers, APIs, and utils for video, radio etc. on linux.
Enables manual configuration of video/recording devices.
Also known as v4l.

NOTE:

The default video device is whichever device is registered to /dev/video0.

Documentation

git (v4l, dvb) https://git.linuxtv.org/media_tree.git/tree
git (v4l-utils) https://git.linuxtv.org/v4l-utils.git/tree

Tutorials

arch-wiki: webcam setup https://wiki.archlinux.org/title/Webcam_setup

Locations

/dev/v4l/* v4l devices symlinked here

Install

pacman -S v4l-utils       # archlinux

usermod -a -G video will  # add your user to group 'video'

Usage

Tools

v4l2-ctl         # control v4l2 devices
v4l2-compliance  # test video4linux drivers
v4l2-sysfs-path  # information about available devices
v4l2-dbg         # information to debug drivers

v4l2-ctl

# map avail devices, and their `/dev/video*` devices
v4l2-ctl --list-devices  

# list available controls (general, and specific to devices)
v4l2-ctl --list-ctrls
v4l2-ctl -d /dev/video0 --list-ctrls

# get control configuration
v4l2-ctl -d /dev/video0 --get-ctrl=backlight_compensation
v4l2-ctl -d /dev/video0 --set-ctrl=backlight_compensation=1

Configuration

Set Default Webcam

Setting a default webcam under systemd is tricky because:

  • Your default webcam is generally the device at /dev/video0.
  • /dev/video0 is chosen by systemd-udev
  • systemd-udev does not directly support overwriting device names in udev rules.


You can however use udev rules to start a systemd service that renames devices so they appear properly.
I wrote a script that renames devices to make a webcam the default.


set-default-webcam.sh


#!/usr/bin/env bash
set -e
#
# DESCRIPTION:
#    remaps devices associated with a `v4l2` exposed webcam
#    to `/dev/video0` so they become the system default webcam.
#
#    All remapped devices are renamed to `/dev/video${N}.orig`
#    then are symlinked to the desired `video${N}` number.


VERBOSE=


validate_has_v4l2_utils() {
    if ! which v4l2-ctl 2>&1 > /dev/null ; then
        echo "[ERROR] you must install 'v4l2-utils'"
        exit 1
    fi
}


match_video_device_ids() {
    # """ return newline-separated list of devices associated with provided device-name
    #
    # Args:
    #     $1: a device-name, as listed by `v4l2-ctl --list-devices`
    # """
    v4l2-ctl --list-devices \
        | grep -Pzo "$1"'[^\n]+(\n\t.*)+' `# match target, and it's /dev/* entries` \
        | grep -a '/dev/video'            `# ignore /dev/media or others` \
        | sed 's/ <-.*//'                 `# get real source, not symlinks` \
        | awk '{$1=$1};1'
}


rename_video_device() {
    # """ renames video device, adding '.orig' suffix
    #
    # Args:
    #     $1: a device path (ex: /dev/video2)
    # Returns:
    #     the new path '/dev/video2.orig'
    # """
    local device="$1"

    # avoids creating '/dev/video0.orig.orig'
    # if target is a device already renamed
    if echo "${device}" | grep '\.orig$' 2>&1 > /dev/null ; then
        echo "${device}"

    else
        local NEW_PATH="${device}.orig"

        # if source not symlink, and target doesn't exist
        if ! test -L "${device}" && ! test -e "${NEW_PATH}" ; then
            mv "${device}" "${NEW_PATH}"
        fi
        echo "${NEW_PATH}"
    fi
}


video_links_to_path() {
    # """ Find '/dev/video[0-9]+' entries that link against provided '/dev/video[0-9]+.orig'
    #
    # Args:
    #    $1: a '/dev/video[0-9]+.orig' path, to find symlinks for
    #
    # Returns:
    #    newline separated list of matching '/dev/video[0-9]+' paths
    # """
    ls -l /dev/video* \
        | grep '\->' \
        | grep "$1" \
        | awk -F' ' '{ print $9 }'
}


link_video_device() {
    # """ `ln -s $1 $2` -- removing $2 if it already exists and is symlink
    #
    # Args:
    #     $1: link source
    #     $2: link dest
    # """
    local src="$1"
    local dst="$2"

    # if target is already a symlink, delete it
    if test -L "${dst}" ; then 
        local orphan_device=$(realpath "${dst}")
        rm "${dst}"

    elif test -e "${dst}" ; then
        echo "[ERROR] ${dst} already exists and is not symlink"
        exit 1
    fi

    ln -s "${src}" "${dst}"
}


main() {
    validate_has_v4l2_utils

    if [ $# -lt 1 ] ; then
        echo "[ERROR] requires a single argument (string matching a 'v4l2-ctl --list-devices' device name')"
        echo
        echo "EXAMPLE:"
        echo "    ./set-default-webcam.sh 'Logitech Webcam C930e'"
        echo
        exit 1
    fi

    local V4L2_DEVICE_MATCHER
    while [ $# -gt 0 ] ; do
        case $1 in
            -h|--help)
                echo "set-default-webcam.sh  [-h] [-v] [V4L2_DEVICE_NAME]"
                echo
                echo "DESCRIPTION:"
                echo "    remaps devices associated with a 'v4l2' exposed webcam"
                echo "    to '/dev/video0' so they become the system default webcam."
                echo
                echo "    All remapped devices are renamed to '/dev/video#.orig'"
                echo "    then are symlinked to the desired 'video#' number."
                echo
                echo "PARAMS:"
                echo "    V4L2_DEVICE_NAME:"
                echo "        a full or partial device name, as returned by 'v4l2-ctl --list-devices'"
                echo
                echo "    -v"
                echo "        debug logging"
                echo
                echo "    -h --help"
                echo "        print this help screen"
                echo
                echo
                echo "EXAMPLE:"
                echo "    ./set-default-webcam.sh 'Logitech Webcam C930e'"
                exit 0
                ;;
            -v|--verbose)
                VERBOSE=1
                shift
                ;;
            *)
                V4L2_DEVICE_MATCHER="$1"
                shift
                ;;
        esac
    done

    # 0-indexed
    MATCHING_CAM_DEVICES=($(match_video_device_ids "${V4L2_DEVICE_MATCHER}"))

    # if webcam is already /dev/video0, nothing to do
    if [[ "$(realpath /dev/video0)" == "${MATCHING_CAM_DEVICES[0]}" ]] ; then
        test -n "$VERBOSE" && echo "Nothing to do"
        exit 0

    # otherwise, swap as many video devices as required 
    # to satisfy MATCHING_CAM_DEVICES
    # starting with /dev/video0
    else
        for i in "${!MATCHING_CAM_DEVICES[@]}"; do
            if test -L "/dev/video${i}" ; then
                prev_device=$(realpath "/dev/video${i}")
            else
                prev_device=$(rename_video_device "/dev/video${i}")
            fi
    
            new_device="$(rename_video_device ${MATCHING_CAM_DEVICES[$i]})"
            new_device_target="/dev/video${i}"

            prev_device_target="$(video_links_to_path ${new_device} | head -n 1)"
            if test -z "${prev_device_target}" ; then
                prev_device_target="$(echo ${MATCHING_CAM_DEVICES[$i]} | sed 's/\.orig$//')"
            fi

            test -n "$VERBOSE" \
                && echo "[dev-${i} symlink-1] ${new_device} /dev/video${i}"
            link_video_device "${new_device}" "${new_device_target}"

            test -n "$VERBOSE" \
                && echo "[dev-${i} symlink-2] ${prev_device} ${old_link}"
            link_video_device "${prev_device}" "${prev_device_target}"
        done
    fi
}


main "$@"


/etc/systemd/system/set-default-webcam.service


Systemd service that calls our script.
You don't need to enable this service, it will be called by udev.

[Unit]
Description=Set default webcam

[Service]
Type=oneshot
ExecStart=/home/will/.bash/bin/set-default-webcam.sh -v Logitech
StandardOutput=journal

[Install]
WantedBy=multi-user.target


/etc/udev/rules.d/50-set-default-webcam.rules


Any time a 'video4linux' device is detected,
run set-default-webcam.service to try to set the default.

ACTION=="add", SUBSYSTEM=="video4linux", TAG+="systemd", ENV{SYSTEMD_WANTS}="set-default-webcam.service"


You can test your default webcam

pacman -S vlc zvbi  # install vlc/deps
vlc v4l2://         # stream from default webcam

Manual VideoDevice Config

Use udev rules to run v4l2-ctl commands that configure your device.
See arch wiki.