DEV Community

Vere
Vere

Posted on

Why a USB Capture Card Works in OBS but Fails in FFmpeg on Linux

OBS Studio displays a clean picture, but FFmpeg returns Invalid argument, Device or resource busy, or a black frame from the same USB capture card. That narrows the problem: the device delivered a stream under at least one set of settings. Other /dev/video* nodes and advertised modes still need to be tested on their own.

The two applications may be opening different device nodes. They may also request different pixel formats, frame sizes, frame rates, or access the device in a different order.

Debug the path from the kernel outward. Do not start by changing encoder presets.

Map the path before testing

For a class-compliant USB Video Class (UVC) capture device, a typical Linux path has six stages:

HDMI source
  -> capture hardware
  -> USB link
  -> uvcvideo and Video4Linux2 (V4L2)
  -> /dev/videoN
  -> FFmpeg or OBS
Enter fullscreen mode Exit fullscreen mode

The local HDMI loop-out, if the device has one, is a separate branch. A clean loop-out proves that the source reached part of the hardware. It does not prove that the USB capture stream is present or that Linux negotiated the intended format.

Product specifications can also use different numbers for HDMI input, USB capture output, and HDMI passthrough. This capture-card signal-path and specification checklist explains those fields. Record the value for the USB capture output before choosing an FFmpeg mode.

You will need v4l-utils, FFmpeg, and usbutils. Package names and installation commands vary by distribution.

Find the correct video node

Start with the device list:

v4l2-ctl --list-devices
Enter fullscreen mode Exit fullscreen mode

One physical capture device can expose more than one /dev/videoN node. The Linux V4L2 API also uses the same naming scheme for capture, output, metadata, and other media functions. Opening the first numbered node is guesswork.

While the OBS preview is active, this command can reveal which node the OBS process has open:

fuser -v /dev/video*
Enter fullscreen mode Exit fullscreen mode

Match that node to the friendly device name from v4l2-ctl --list-devices, write down the resolution, frame rate, and video format selected in OBS, then close the OBS preview before testing FFmpeg.

Query every node instead:

for dev in /dev/video*; do
  printf '\n== %s ==\n' "$dev"
  v4l2-ctl --device="$dev" --info 2>/dev/null
done
Enter fullscreen mode Exit fullscreen mode

Look for Video Capture or Video Capture Multiplanar in the device capabilities. Streaming is also expected for the normal streaming path. The --info output corresponds to the V4L2 VIDIOC_QUERYCAP operation, which reports the driver, card name, bus location, and capabilities of the opened node.

If present, stable udev links are easier to use in scripts than a number that can change after a reboot or hot-plug:

ls -l /dev/v4l/by-id/ /dev/v4l/by-path/ 2>/dev/null
Enter fullscreen mode Exit fullscreen mode

Use the stable link in later commands when it clearly maps to the capture node.

Read the formats instead of guessing them

Once the node is known, use v4l2-ctl to list every format, frame size, and interval it reports:

v4l2-ctl \
  --device=/dev/video2 \
  --list-formats-ext
Enter fullscreen mode Exit fullscreen mode

Then ask FFmpeg how it sees the same device. The video4linux2 input documentation describes the same device-node, format, size, and frame-rate options:

ffplay \
  -f video4linux2 \
  -list_formats all \
  /dev/video2
Enter fullscreen mode Exit fullscreen mode

Replace /dev/video2 with the node found in the previous step.

V4L2 applications negotiate a data format with the driver. The Linux kernel documentation states that drivers enumerate formats supported by the hardware rather than converting them in kernel space. A mode that looks reasonable, such as 1920x1080 at 60 fps in YUYV, may simply be absent.

Copy one complete advertised combination: pixel format, width, height, and frame rate. Do not combine the best value from four separate rows.

For an advertised MJPEG 1080p30 mode, test:

ffplay \
  -f video4linux2 \
  -input_format mjpeg \
  -video_size 1920x1080 \
  -framerate 30 \
  /dev/video2
Enter fullscreen mode Exit fullscreen mode

For an advertised YUYV 720p30 mode:

ffplay \
  -f video4linux2 \
  -input_format yuyv422 \
  -video_size 1280x720 \
  -framerate 30 \
  /dev/video2
Enter fullscreen mode Exit fullscreen mode

Use the format name shown by FFmpeg on your system. The V4L2 FourCC and FFmpeg input name are related, but their spelling is not always identical.

If one exact mode opens and another fails, basic device enumeration is working. The remaining failure is tied to the requested mode or the path used to carry it.

Separate capture from encoding

An FFmpeg command can fail after the device opens. Decoding, filtering, encoding, disk I/O, or the output container may be the next fault. Send ten seconds to the null muxer first:

ffmpeg \
  -f video4linux2 \
  -input_format mjpeg \
  -video_size 1920x1080 \
  -framerate 30 \
  -i /dev/video2 \
  -t 10 \
  -f null -
Enter fullscreen mode Exit fullscreen mode

This test still reads and decodes the source, but it removes the recording codec and storage destination. Watch the console for negotiation errors, corrupt frames, timestamp warnings, and frame progress that stalls or runs below real time.

If the null test is stable, make a short capture without transcoding the MJPEG stream:

ffmpeg \
  -f video4linux2 \
  -input_format mjpeg \
  -video_size 1920x1080 \
  -framerate 30 \
  -i /dev/video2 \
  -t 10 \
  -c:v copy \
  capture-test.mkv
Enter fullscreen mode Exit fullscreen mode

For an uncompressed YUYV input, use a lossless codec rather than -c:v copy if your chosen container does not accept that raw format:

ffmpeg \
  -f video4linux2 \
  -input_format yuyv422 \
  -video_size 1280x720 \
  -framerate 30 \
  -i /dev/video2 \
  -t 10 \
  -c:v ffv1 \
  capture-test.mkv
Enter fullscreen mode Exit fullscreen mode

These are diagnostic recordings, not recommended production settings.

Check whether another process owns the stream

Close the OBS preview, browser camera tabs, video-call software, and desktop camera apps. Then check the node:

fuser -v /dev/video2
Enter fullscreen mode Exit fullscreen mode

If fuser is unavailable:

lsof /dev/video2
Enter fullscreen mode Exit fullscreen mode

V4L2 format selection can assign a logical stream to a file descriptor and restrict conflicting changes from another file descriptor. A background application may therefore leave FFmpeg with Device or resource busy, or it may hold settings that differ from the command you are testing.

Permissions produce a different failure:

ls -l /dev/video2
getfacl /dev/video2
id
Enter fullscreen mode Exit fullscreen mode

Do not solve a persistent access problem with chmod 666. Device permissions are commonly recreated after reconnecting, and world-writable access is broader than needed. Fix the relevant user, group, session ACL, container mapping, or service policy for the distribution instead.

Verify the negotiated USB link

The connector shape does not tell you the active USB speed. Inspect the physical tree:

lsusb -t
Enter fullscreen mode Exit fullscreen mode

The -t option prints the physical USB device hierarchy. Find the capture device and note whether the path reports 480M, 5000M, or another negotiated rate. That number is a link rate, not guaranteed application throughput.

Pixel format changes the traffic dramatically. The V4L2 YUYV format stores four bytes for every two pixels. A 1920x1080 frame at 30 fps is therefore about:

1920 x 1080 x 30 x 2 bytes = 124,416,000 bytes/second
Enter fullscreen mode Exit fullscreen mode

That is roughly 995 Mb/s before USB protocol overhead. It cannot fit through a 480 Mb/s USB 2.0 link as uncompressed YUYV. A device may offer MJPEG at that resolution because the compressed stream needs less bus bandwidth, while the exact data rate varies with the content and device.

If a card expected to use SuperSpeed appears under a 480M branch, test it directly on another documented SuperSpeed port with the required cable. Then repeat the same advertised mode through the original hub and compare lsusb -t plus the kernel log. Change one physical component per run.

Watch the kernel while reproducing the failure

On a systemd-based distribution, follow kernel messages in one terminal:

sudo journalctl -k -f |
  grep -Ei 'usb|uvc|video|xhci'
Enter fullscreen mode Exit fullscreen mode

In another terminal, run the shortest failing FFmpeg command. Note the exact time of any USB disconnect, reset, UVC probe error, or endpoint message.

A nearby warning is not automatically the cause. Repeat the same mode and confirm that the same message appears with the same failure. Then try one controlled change, such as a lower advertised frame rate, MJPEG instead of YUYV, or a direct host connection.

For a saved excerpt:

sudo journalctl -k --since "5 minutes ago" --no-pager
Enter fullscreen mode Exit fullscreen mode

journalctl -k limits the view to kernel messages. Access to the system journal depends on the distribution and user privileges.

Read the error as a layer

Result Layer to inspect next
No related /dev/video* node appears USB enumeration, cable, port, power, or driver binding
The node returns Permission denied Device ACL, group, session, container, or service access
The node returns Device or resource busy Another process or an already-owned logical stream
FFmpeg returns Invalid argument during open Device node, pixel format, frame size, or frame rate
One advertised mode works and a larger mode fails Format combination, USB link, or device limit
The null output is stable but the recording fails Encoder, container, storage, or output settings
OBS works but the copied OBS mode fails in FFmpeg Compare the exact node and collect both applications' logs
The device resets only under sustained capture USB path, power, cable, host controller, or device firmware

The table narrows the next test. It does not identify a defective component by itself.

Collect a useful bug report

Save these items before contacting a vendor or filing an issue:

uname -a
v4l2-ctl --device=/dev/video2 --all
v4l2-ctl --device=/dev/video2 --list-formats-ext
lsusb -t
Enter fullscreen mode Exit fullscreen mode

Also include:

  • the complete FFmpeg command and unedited console output
  • the HDMI source resolution and frame rate
  • whether the local passthrough display works
  • a short kernel-log excerpt from the same test
  • the result of one lower-bandwidth advertised mode
  • whether OBS used the same node, format, size, and frame rate

The shortest reliable workflow is node, capabilities, exact advertised mode, null-output test, process ownership, USB tree, then kernel log. At the end, "works in OBS but not FFmpeg" should be a specific mismatch that someone else can reproduce.


AI-assistance disclosure: AI was used during drafting and editing. The technical claims were checked against the linked documentation, but the commands have not been run on every capture device or Linux distribution.

Top comments (0)