Yesterday I was playing around with determining webcam resolution by reverse engineering how fswebcam worked, using the ioctl VIDIOC_TRY_FMT. I keep working that evening on my code and was thinking of filing a bug against v4l2-ctl or the driver until I made a discovery: having fswebcam take a picture at a specified resolution actually changes what v4l2-ctl returns, here’s an example that’s been truncated to keep it short:
mfisch@caprica:/tmp/luvcview-0.2.6$ v4l2-ctl -V
Width/Height : 1280/720
mfisch@caprica:/tmp/luvcview-0.2.6$ fswebcam -d /dev/video0 -r640x480 /tmp/foo.jpg
Writing JPEG image to '/tmp/foo.jpg'.
mfisch@caprica:/tmp/luvcview-0.2.6$ v4l2-ctl -V
Width/Height : 640/480
Hey look, v4l2-ctl changed it’s answer! So is this a bug? I dug into the v4l2 docs and it turns out that you can make it change it’s answer again by picking a different resolution with fswebcam. This has to do with how fswebcam works.
fswebcam first calls VIDIOC_TRY_FMT and that ioctl returns and sets some parameters on the image. For example, if you request an image that’s 10000×10000 it will return back a success, but set the width and height to something like 1280×720 (depending on your hardware).
Next fswebcam calls VIDIOC_S_FMT. Unlike VIDIOC_TRY_FMT, S_FMT actually sets what the driver’s format is. So, if you take a picture at 640×480, the driver will keep that internal format as default until you call it again with a larger value. This is all described in the v4l2 spec.
So, I was wrong, the driver is fine and is in fact behaving as the spec requires. So given this information, I figured that to find the true max, I could just start with a huge value, like 10000×10000 and see what value TRY_FMT came back with. That’s where I left it last night, until a colleague, Josh Poulson, pointed me to luvcview.
luvcview can list all the supported formats and even the time intervals between frames, it prints a long list, so here’s a truncated version:
Device information:
Device path: /dev/video0
{ pixelformat = 'YUYV', description = 'YUV 4:2:2 (YUYV)' }
{ discrete: width = 640, height = 480 }
Time interval between frame: 1/30, 1/20, 1/15, 1/10, 1/5,
{ discrete: width = 640, height = 400 }
Time interval between frame: 1/30, 1/20, 1/15, 1/10, 1/5,
{ discrete: width = 352, height = 288 }
Time interval between frame: 1/30, 1/20, 1/15, 1/10, 1/5,
....
And that’s what I needed. It turns out that there’s an experimental ioctl called VIDIOC_ENUM_FRAMESIZES that luvcview is using. This information highlights the good and bad parts of the web. It was easy for me to take existing tools and dive into the problem I was trying to solve with virtually no knowledge of v4l2, but I made some bad assumptions. But it’s also thanks to the web, and my partially incorrect blog post that I know so much more now.
so did you figure out how to get fswebcam to take a picture of the requested size? i kind of missed that point. my webcam can do 640×480 but fswebcam shrinks it down to 160×120
mcnesium@fluse:~$ fswebcam -r 640×480 tmp/cam.jpg
— Opening /dev/video0…
Trying source module v4l2…
/dev/video0 opened.
No input was specified, using the first.
Adjusting resolution from 640×480 to 160×120.
— Capturing frame…
Captured frame in 0.00 seconds.
— Processing captured image…
Writing JPEG image to ‘tmp/cam.jpg’.
I don’t know unfortunately. What’s the output that shows your supported info?
src_v4l2_set_pix_format,541: Device offers the following V4L2 pixel formats:
src_v4l2_set_pix_format,554: 0: [0x30313953] ‘S910’ (S910)
src_v4l2_set_pix_format,554: 1: [0x31384142] ‘BA81’ (BA81)
those are the same as ffmpeg/avconv shows me, except that they are unsupported there:
[video4linux2 @ 0x947f3a0] R : Unsupported : S910 : 160×120 320×240 640×480
[video4linux2 @ 0x947f3a0] R : Unsupported : BA81 : 160×120
so the small one BA81 is used obviously. do you know how i can switch to the other one?