OV9281 V4L2 capture fails with VIDIOC_STREAMON Invalid argument on Raspberry Pi

Hi, I’m stuck getting an OV9281-B0225 (Rev. B) - the 1MP OV9281 Mono Global Shutter MIPI camera with M12 mount - working on Raspberry Pi. I’m using a Pi Zero 2W and also tried a Pi 4B, running Raspberry Pi OS (tried both Lite and Full), mostly over SSH.

I followed the setup guide here: Redirecting...

The weird part: ls /dev/video0 confirms the camera is connected, so the module is detected. But if I try to capture through V4L2 it throws errors UNTIL I run libcamera-still once. And I have to do that again after every reboot.

The error I get when streaming:

v4l2-ctl --stream-mmap --stream-count=-1 -d /dev/video0 --stream-to=/dev/null
returns:
VIDIOC_STREAMON returned -1 (Invalid argument)

I noticed capture only works when quantization=full range in the V4L2 video format capture, but I can’t set that parameter directly from the terminal. My guess is the quantization setting is the problem, but I don’t know how to force it.

Honestly, I never got it to work properly. Has anyone seen this? I really need to use the V4L2 drivers for capturing images.

Thanks for the detailed report. A few things to confirm so we can narrow this down.

First, can you paste the output of:

v4l2-ctl --get-fmt-video -d /dev/video0

before and after running libcamera-still? I specifically want to compare the quantization field in both cases.

Also, when you say it only works with quantization=full range, where are you seeing/setting that? Is it something you tested in a separate tool or app, or is that from the format listing itself?

Sure. Before running libcamera-still, v4l2-ctl --get-fmt-video basically shows the format, and streaming fails with the VIDIOC_STREAMON -1 error. After I run libcamera-still once, the same v4l2-ctl stream command starts working.

For the quantization thing - I can see it as part of the V4L2 format description, and the only time capture actually succeeds is when it’s set to full range. But there’s no obvious way to set it through the terminal. v4l2-ctl doesn’t seem to expose it as a settable control, or at least I couldn’t find a flag for it.

ls /dev/video0 works fine the whole time, so the driver sees the sensor.

OK. The fact that the device node exists (/dev/video0) but STREAMON fails with EINVAL until libcamera initializes it is a strong hint - the sensor is being detected but the pipeline isn’t fully configured until libcamera touches it.

Can you confirm a couple of environment details:

  • Which exact OS image and kernel version (uname -a)?
  • Are you booting with the camera overlay enabled (e.g. dtoverlay for the OV9281), or using the Arducam libcamera stack?
  • Did you run the install script from the guide, and did it complete without errors?

Also, since you mentioned quantization=full range, that points at the format negotiation rather than a hardware fault. Let’s verify the format right after a fresh reboot (before touching libcamera) and see what quantization reports.

Kernel is the stock Raspberry Pi OS kernel, nothing custom. I did enable the camera overlay per the guide, and I ran the install script - it seemed to finish fine, no obvious errors.

Here’s the annoying part - I’ve already replaced basically everything:

  • New ribbon cable
  • Swapped the Pi Zero 2W for a Pi 4B
  • New micro SD card
  • Fresh reformat/reinstall, wiped everything, clean OS more than once
  • Tried both Pi OS Full and Lite
  • Even tried the second camera, since I ordered 2 of them

Same behavior on every combination. So I’m fairly convinced it’s not the cable, not the board, not the SD card, and not a bad unit. It has to be something in the V4L2/quantization config.

Full output of the failing stream command, for reference:
v4l2-ctl --stream-mmap --stream-count=-1 -d /dev/video0 --stream-to=/dev/null
→ VIDIOC_STREAMON returned -1 (Invalid argument)

Thanks - that rules out a lot. Replacing the cable, board, SD card, OS image, and even the second camera unit and still reproducing exactly the same VIDIOC_STREAMON returned -1 (Invalid argument) means we’re almost certainly looking at a pipeline/format negotiation issue, not hardware.

Two things to try so we can confirm the quantization theory:

  1. Right after a reboot, dump the format:
    v4l2-ctl --get-fmt-video -d /dev/video0

  2. Then run libcamera-still -o test.jpg once, and immediately dump the format again with the same command. Compare the quantization line in both.

If the working case always shows quantization=Full range and the failing case shows the other value, that’s our answer. Paste both outputs and I’ll take a look.

Here’s what I see. After a reboot, before running libcamera-still, streaming fails with the Invalid argument error. After I run libcamera-still -o test.jpg once, streaming works.

The capture only really behaves when quantization=full range is active, but I still can’t find a way to set that from the terminal directly - not via v4l2-ctl and not by any flag I can find. The moment I stop/reboot, I’m back to running libcamera-still first.

Is there a config file or an environment variable for the libcamera stack where I can force that quantization so the V4L2 path doesn’t need the libcamera-still trick?

That matches what I expected. So to summarize where we are:

  • The camera is detected (ls /dev/video0 works) on both the Pi Zero 2W and Pi 4B, on both OS images.
  • V4L2 capture fails with VIDIOC_STREAMON returned -1 (Invalid argument) on a fresh boot.
  • Running libcamera-still once primes the pipeline, after which the same v4l2-ctl --stream-mmap ... command works - until the next reboot.
  • Capture only succeeds when quantization=full range, which isn’t a directly settable V4L2 control in this stack.

So the practical workaround for now is: run libcamera-still -o test.jpg (or any libcamera capture) once after each boot to initialize the sensor/pipeline before you start using V4L2. The underlying cause is the format/quantization negotiation, which libcamera-still performs correctly but a bare v4l2-ctl stream does not.

I’ve sent you some additional config options privately to try for forcing the pipeline initialization - check your messages. Also please post back on the forum thread you started so we keep everything in one place; I’ve replied there too. If the private config still doesn’t let you skip the libcamera-still step, let us know and we’ll dig further.

Thanks for responding so quickly to my forum post. All is sorted now! The tip to prime the pipeline and the config you sent did the trick. Appreciate the help.

Great to hear! To wrap up for anyone finding this thread later:

Root cause: on this OV9281-B0225 Rev. B setup (Pi Zero 2W / Pi 4B, Raspberry Pi OS full or lite), the sensor is detected (/dev/video0 present) but the V4L2 pipeline isn’t fully negotiated on a cold boot, so a bare v4l2-ctl --stream-mmap --stream-count=-1 -d /dev/video0 --stream-to=/dev/null returns VIDIOC_STREAMON returned -1 (Invalid argument). Capture only succeeds when quantization=full range, which isn’t directly settable via v4l2-ctl in this stack.

Workaround: initialize the pipeline with a libcamera capture (e.g. libcamera-still -o test.jpg) once after each boot, and/or apply the pipeline config we shared privately. Hardware swaps (ribbon, Pi board, SD card, second camera unit) and fresh OS reinstalls were not the cause.

If anyone else hits this, start by comparing v4l2-ctl --get-fmt-video -d /dev/video0 before and after a libcamera capture - the quantization field is the tell.