I have video4Linux on my TODO list for some time. Now i have some down time, Let’s have a look.
User Land fun Link to heading
Starting with something fun. Detecting your camera and taking picture/video on CLI. This is mostly from the following answer 1.
$ v4l2-ctl --list-devices
Integrated Camera: Integrated C (usb-0000:00:14.0-5):
/dev/video0
/dev/video1
/dev/media0
Then we can get more info on both interfaces. video0
is the actual camera interface. The other one is just metadata.
v4l2-ctl --device=/dev/video0 --all
v4l2-ctl --device=/dev/video1 --all
To continue the CLI fun, let’s play video from the camera with ffplay
ffplay /dev/video0
Finally, here is how to take a picture or video with ffmpeg
ffmpeg -f v4l2 -video_size 1280x720 -i /dev/video0 -frames 1 out.jpg
ffmpeg -f v4l2 -framerate 30 -video_size 1280x720 -input_format mjpeg -i /dev/video0 -c copy out.mkv
UVC driver - uvc_probe Link to heading
Switching into the kernel, Let’s start with UVC (USB Video Class driver). This is the driver for USB cameras that hooks into v4l API.
Starting with UVC driver registration in drivers/media/usb/uvc/uvc_driver.c
static int __init uvc_init(void)
{
int ret;
uvc_debugfs_init();
ret = usb_register(&uvc_driver.driver);
...
return 0;
}
module_init(uvc_init);
And uvc_driver
is defined as follows
struct uvc_driver uvc_driver = {
.driver = {
.name = "uvcvideo",
.probe = uvc_probe,
.disconnect = uvc_disconnect,
.suspend = uvc_suspend,
.resume = uvc_resume,
.reset_resume = uvc_reset_resume,
.id_table = uvc_ids,
.supports_autosuspend = 1,
},
};
Let’s look at uvc_probe
in more depth. uvc_probe
is called on usb interfaces and hooks up to UVC ids defined in the same file uvc_ids
static int uvc_probe(struct usb_interface *intf,
const struct usb_device_id *id)
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
dev->udev = usb_get_dev(udev);
dev->intf = usb_get_intf(intf);
/* Parse the Video Class control descriptor. */
if (uvc_parse_control(dev) < 0) {
uvc_dbg(dev, PROBE, "Unable to parse UVC descriptors\n");
goto error;
}
/* Parse the associated GPIOs. */
if (uvc_gpio_parse(dev) < 0) {
uvc_dbg(dev, PROBE, "Unable to parse UVC GPIOs\n");
goto error;
}
/* Register the V4L2 device. */
if (v4l2_device_register(&intf->dev, &dev->vdev) < 0)
goto error;
/* Scan the device for video chains. */
if (uvc_scan_device(dev) < 0)
goto error;
/* Initialize controls. */
if (uvc_ctrl_init_device(dev) < 0)
goto error;
/* Register video device nodes. */
if (uvc_register_chains(dev) < 0)
goto error;
ret = uvc_status_init(dev);
uvc_probe
does a lot of things, let’s try to break it down.
Scanning - uvc_scan_device Link to heading
Looking at the comment, It looks exactly as it sounds. it loops terminal and create chains and adds them into the device structure.
/*
* Scan the device for video chains and register video devices.
*
* Chains are scanned starting at their output terminals and walked backwards.
*/
static int uvc_scan_device(struct uvc_device *dev)
{
struct uvc_video_chain *chain;
struct uvc_entity *term;
list_for_each_entry(term, &dev->entities, list) {
if (!UVC_ENTITY_IS_OTERM(term))
continue;
/*
* If the terminal is already included in a chain, skip it.
* This can happen for chains that have multiple output
* terminals, where all output terminals beside the first one
* will be inserted in the chain in forward scans.
*/
if (term->chain.next || term->chain.prev)
continue;
chain = uvc_alloc_chain(dev);
if (uvc_scan_chain(chain, term) < 0) {
}
list_add_tail(&chain->list, &dev->chains);
}
if (list_empty(&dev->chains)) {
dev_info(&dev->udev->dev, "No valid video chain found.\n");
return -1;
}
uvc_scan_chain
looks through the chains in terminal. More details later in uvc_scan_chain_entity
static int uvc_scan_chain(struct uvc_video_chain *chain,
struct uvc_entity *term)
{
struct uvc_entity *entity, *prev;
uvc_dbg(chain->dev, PROBE, "Scanning UVC chain:");
entity = term;
prev = NULL;
while (entity != NULL) {
/* Entity must not be part of an existing chain */
if (entity->chain.next || entity->chain.prev) {
uvc_dbg(chain->dev, DESCR,
"Found reference to entity %d already in chain\n",
entity->id);
return -EINVAL;
}
/* Process entity */
if (uvc_scan_chain_entity(chain, entity) < 0)
return -EINVAL;
/* Forward scan */
if (uvc_scan_chain_forward(chain, entity, prev) < 0)
return -EINVAL;
/* Backward scan */
prev = entity;
if (uvc_scan_chain_backward(chain, &entity) < 0)
return -EINVAL;
}
return 0;
}
/* ------------------------------------------------------------------------
* UVC device scan
*/
/*
* Scan the UVC descriptors to locate a chain starting at an Output Terminal
* and containing the following units:
*
* - one or more Output Terminals (USB Streaming or Display)
* - zero or one Processing Unit
* - zero, one or more single-input Selector Units
* - zero or one multiple-input Selector Units, provided all inputs are
* connected to input terminals
* - zero, one or mode single-input Extension Units
* - one or more Input Terminals (Camera, External or USB Streaming)
*
* The terminal and units must match on of the following structures:
*
* ITT_*(0) -> +---------+ +---------+ +---------+ -> TT_STREAMING(0)
* ... | SU{0,1} | -> | PU{0,1} | -> | XU{0,n} | ...
* ITT_*(n) -> +---------+ +---------+ +---------+ -> TT_STREAMING(n)
*
* +---------+ +---------+ -> OTT_*(0)
* TT_STREAMING -> | PU{0,1} | -> | XU{0,n} | ...
* +---------+ +---------+ -> OTT_*(n)
*
* The Processing Unit and Extension Units can be in any order. Additional
* Extension Units connected to the main chain as single-unit branches are
* also supported. Single-input Selector Units are ignored.
*/
static int uvc_scan_chain_entity(struct uvc_video_chain *chain,
struct uvc_entity *entity)
{
I think I need to deeper look at the UVC. I found a document 3 which has example of camera USB chip.
Registration - uvc_register_chains Link to heading
uvc_register_chains
is called at the end of uvc_probe
.
static int uvc_register_chains(struct uvc_device *dev)
{
struct uvc_video_chain *chain;
int ret;
list_for_each_entry(chain, &dev->chains, list) {
ret = uvc_register_terms(dev, chain);
if (ret < 0)
return ret;
}
return 0;
}
uvc_register_terms
is called. moving along!
static int uvc_register_terms(struct uvc_device *dev,
struct uvc_video_chain *chain)
{
list_for_each_entry(term, &chain->entities, chain) {
if (UVC_ENTITY_TYPE(term) != UVC_TT_STREAMING)
continue;
stream = uvc_stream_by_id(dev, term->id);
stream->chain = chain;
ret = uvc_register_video(dev, stream);
In uvc_register_video
, uvc_ioctl_ops
is passed to uvc_register_video_device
static int uvc_register_video(struct uvc_device *dev,
struct uvc_streaming *stream)
{
int ret;
/* Initialize the streaming interface with default parameters. */
ret = uvc_video_init(stream);
if (ret < 0) {
dev_err(&stream->intf->dev,
"Failed to initialize the device (%d).\n", ret);
return ret;
}
if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
stream->chain->caps |= V4L2_CAP_VIDEO_CAPTURE
| V4L2_CAP_META_CAPTURE;
else
stream->chain->caps |= V4L2_CAP_VIDEO_OUTPUT;
uvc_debugfs_init_stream(stream);
/* Register the device with v4l. */
return uvc_register_video_device(dev, stream, &stream->vdev,
&stream->queue, stream->type,
&uvc_fops, &uvc_ioctl_ops);
}
UVC to v4l Link to heading
uvc_ioctl_ops
is defined in drivers/media/usb/uvc/uvc_v4l2.c
with the operations that v4l needs.
const struct v4l2_ioctl_ops uvc_ioctl_ops = {
.vidioc_querycap = uvc_ioctl_querycap,
.vidioc_enum_fmt_vid_cap = uvc_ioctl_enum_fmt_vid_cap,
.vidioc_enum_fmt_vid_out = uvc_ioctl_enum_fmt_vid_out,
.vidioc_g_fmt_vid_cap = uvc_ioctl_g_fmt_vid_cap,
.vidioc_g_fmt_vid_out = uvc_ioctl_g_fmt_vid_out,
.vidioc_s_fmt_vid_cap = uvc_ioctl_s_fmt_vid_cap,
.vidioc_s_fmt_vid_out = uvc_ioctl_s_fmt_vid_out,
.vidioc_try_fmt_vid_cap = uvc_ioctl_try_fmt_vid_cap,
.vidioc_try_fmt_vid_out = uvc_ioctl_try_fmt_vid_out,
.vidioc_reqbufs = uvc_ioctl_reqbufs,
.vidioc_querybuf = uvc_ioctl_querybuf,
.vidioc_qbuf = uvc_ioctl_qbuf,
.vidioc_expbuf = uvc_ioctl_expbuf,
.vidioc_dqbuf = uvc_ioctl_dqbuf,
.vidioc_create_bufs = uvc_ioctl_create_bufs,
.vidioc_streamon = uvc_ioctl_streamon,
.vidioc_streamoff = uvc_ioctl_streamoff,
.vidioc_enum_input = uvc_ioctl_enum_input,
.vidioc_g_input = uvc_ioctl_g_input,
.vidioc_s_input = uvc_ioctl_s_input,
.vidioc_queryctrl = uvc_ioctl_queryctrl,
.vidioc_query_ext_ctrl = uvc_ioctl_query_ext_ctrl,
.vidioc_g_ext_ctrls = uvc_ioctl_g_ext_ctrls,
.vidioc_s_ext_ctrls = uvc_ioctl_s_ext_ctrls,
.vidioc_try_ext_ctrls = uvc_ioctl_try_ext_ctrls,
.vidioc_querymenu = uvc_ioctl_querymenu,
.vidioc_g_selection = uvc_ioctl_g_selection,
.vidioc_g_parm = uvc_ioctl_g_parm,
.vidioc_s_parm = uvc_ioctl_s_parm,
.vidioc_enum_framesizes = uvc_ioctl_enum_framesizes,
.vidioc_enum_frameintervals = uvc_ioctl_enum_frameintervals,
.vidioc_subscribe_event = uvc_ioctl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
.vidioc_default = uvc_ioctl_default,
};
I forgot to mention that v4l2_device_register
is called by many drivers, not just UVC. it’s called by any driver register media with v4l
int v4l2_device_register(struct device *dev, struct v4l2_device *v4l2_dev)
{
if (v4l2_dev == NULL)
return -EINVAL;
INIT_LIST_HEAD(&v4l2_dev->subdevs);
spin_lock_init(&v4l2_dev->lock);
v4l2_prio_init(&v4l2_dev->prio);
kref_init(&v4l2_dev->ref);
get_device(dev);
v4l2_dev->dev = dev;
if (dev == NULL) {
/* If dev == NULL, then name must be filled in by the caller */
if (WARN_ON(!v4l2_dev->name[0]))
return -EINVAL;
return 0;
}
return 0;
}
ioctl usage and definition Link to heading
Here is small example of V4l ioctl from 4. The code opens /dev/video0
and does ioctl to get cap struct.
/**
* Video for Linux version 2 (V4L2) example 1 - print device capabilities
*
* Based on
* - https://gist.github.com/Circuitsoft/1126411
* - https://jayrambhia.com/blog/capture-v4l2
*
* Kyle M. Douglass, 2018
* kyle.m.douglass@gmail.com
*/
#include <errno.h>
#include <fcntl.h>
#include <linux/videodev2.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>
static const char DEVICE[] = "/dev/video0";
/**
* Prints the capabilities of the device.
*/
int print_capabilities(int fd) {
int ret;
struct v4l2_capability caps = {0};
ret = ioctl(fd, VIDIOC_QUERYCAP, &caps);
if (ret == -1) {
perror("Querying device capabilities");
return errno;
}
printf("Driver Caps:\n"
" Driver: \"%s\"\n"
" Card: \"%s\"\n"
" Bus: \"%s\"\n"
" Version: %u.%u.%u\n"
" Capabilities: %08x\n",
caps.driver,
caps.card,
caps.bus_info,
(caps.version >> 16) & 0xFF,
(caps.version >> 8) & 0xFF,
(caps.version ) & 0XFF,
caps.capabilities);
return 0;
}
int main(void) {
int fd;
// Open the device file
fd = open(DEVICE, O_RDWR);
if (fd < 0) {
perror(DEVICE);
return errno;
}
print_capabilities(fd);
close(fd);
return 0;
}
Under the hood, the ioctl gets to uvc_ioctl_querycap
and picks up info from the chain.
static int uvc_ioctl_querycap(struct file *file, void *fh,
struct v4l2_capability *cap)
{
struct uvc_fh *handle = file->private_data;
struct uvc_video_chain *chain = handle->chain;
struct uvc_streaming *stream = handle->stream;
strscpy(cap->driver, "uvcvideo", sizeof(cap->driver));
strscpy(cap->card, handle->stream->dev->name, sizeof(cap->card));
usb_make_path(stream->dev->udev, cap->bus_info, sizeof(cap->bus_info));
cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING
| chain->caps;
return 0;
}