MEIC-A, MEIC-T
Departamento
de Engenharia
Informática
S TUDENTS I DENTIFICATION :
Number: Name:
2010/2011
Nuno.Roma@ist.utl.pt
1 Introduction
1.1 Objectives
The main purpose of this assignment is to provide the students with a straight and close contact to the
operation of the image/video acquisition devices usually available in most personal computers. The
outcome will be the design and implementation of a computer program that acquires and stores a digital
image obtained using a conventional webcam.
#include <linux/videodev2.h>
Figure 1 shows the interaction between a user application and a V4L2 driver, covering usage of the
video capture interface, provided by the /dev/video character device.
YUV 4:4:4
Each of the three Y’CbCr components have the same sample rate. This scheme is sometimes used in
high-end film scanners and cinematic postproduction (see Figure 2).
YUV 4:2:2
The two chroma components are sampled at half the sample rate of luma: the horizontal chroma resolu-
tion is halved. This reduces the bandwidth of a video signal by one-third with little to no visual difference
(see Figure 3).
YUV 4:2:0
This scheme is used in JPEG/JFIF stillframes, as well as in H.26x and MPEG-x video coding standards.
Cb and Cr are each subsampled by a factor of 2 horizontally and a factor of 2 vertically (see Figure 4).
• Packed formats, where Y, Cb (U) and Cr (V) samples are packed together into macropixels, which
are stored in a single array;
• Planar formats, where each component is stored as a separate array; the final image is a composi-
tion of the three separate planes.
The image format that will be adopted by the V4L2 driver is parameterized by the application pro-
gram using the v4l2_format structure. Some examples of possible parameters are:
V4L2_PIX_FMT_YUV420 - Planar format with 1/2 horizontal and vertical chroma resolution (4:2:0).
In this format, the three components are separated into three sub-images or planes. The Y plane is
first. The Y plane has one byte per pixel. The Cb plane is half the width and half the height of the
Y plane (and of the image). Each Cb belongs to four pixels, a two-by-two square of the image. For
example, Cb0 belongs to Y’00, Y’01, Y’10, and Y’11. Following the Cb plane is the Cr plane, just
like the Cb plane. V4L2_PIX_FMT_YVU420 is the same except the Cr plane comes first, then
the Cb plane.
Example, for a 4 ×4 pixel image (each cell is one byte):
start + 0: Y’00 Y’01 Y’02 Y’03
start + 4: Y’10 Y’11 Y’12 Y’13
start + 8: Y’20 Y’21 Y’22 Y’23
start + 12: Y’30 Y’31 Y’32 Y’33
start + 16: Cb00 Cb01
start + 18: Cb10 Cb11
start + 20: Cr00 Cr01
start + 22: Cr10 Cr11
In [1] it is presented a complete description of all image formats supported by the driver. Never-
theless, it should be noted that most acquisition devices do not offer the whole set of these formats.
Whenever the desired format is not provided, conversion procedures will have to be implemented in or-
der to obtain the acquired data in the desired format. As an example, to convert the 4:2:2 image format to
the 4:2:0 format, the new components Cr’ and Cb’ should have half the horizontal resolution than the Y
component. As such, Cr’00 should have the average value between Cr00 and Cr10; Cr’01 is the average
value between Cr01 and Cr11, and so on.
A single mechanism exists to negotiate all data formats using the aggregate struct v4l2_format
and the VIDIOC_G_FMT and VIDIOC_S_FMT ioctl().
The VIDIOC_S_FMT ioctl() is a major turning-point in the initialization sequence. Prior to this
point multiple panel applications can access the same device concurrently to select the current input,
change controls or modify other properties. The first VIDIOC_S_FMT ioctl() execution assigns a
logical stream (video data, VBI data etc.) exclusively to one file descriptor.
Exclusive means no other application (more precisely, no other file descriptor) can grab this stream or
change device properties inconsistent with the negotiated parameters. Therefore, only the file descriptor
owning the stream can make changes. Accordingly, multiple file descriptors which grabbed different
logical streams prevent each other from interfering with their settings. When for example video overlay
is about to start or already in progress, simultaneous video capturing may be restricted to the same
cropping and image size.
When applications omit the VIDIOC_S_FMT ioctl(), its locking side effects are implied by the
next step, the selection of an I/O method with the VIDIOC_REQBUFS ioctl() or implicitly with the
first read() or write() call.
Generally only one logical stream can be assigned to a file descriptor.
1.2.6 Input/Output
The V4L2 API defines several different methods to read from or write to a device:
All drivers exchanging data with applications must support at least one of them.
The classic I/O method using the read() and write() function is automatically selected after
opening a V4L2 device. When the driver does not support this method, attempts to read or write will fail
at any time. The other methods must be negotiated using the VIDIOC_REQBUFS ioctl().
A- Read/Write
Input and output devices support the read() and write() function, respectively, when the
V4L2_CAP_READWRITE flag in the capabilities field of struct v4l2_capability returned by the
VIDIOC_QUERYCAP ioctl() is set.
Drivers make use of the CPU to copy the data, although DMA mechanisms are also available to or
from user memory. No meta-information, like frame counters or timestamps, are passed. This informa-
tion is necessary to recognize frame dropping and to synchronize with other data streams. This is the
simplest I/O method, requiring little or no setup to exchange data.
Input and output devices support this I/O method when the V4L2_CAP_STREAMING flag in the
capabilities field of struct v4l2_capability returned by the VIDIOC_QUERYCAP ioctl() is
set. Since there are two streaming methods, applications must call the VIDIOC_REQBUFS ioctl()
to determine if the memory mapping flavor is supported.
Streaming is an I/O is a method where only pointers to buffers are exchanged between application
and driver, the data itself is not copied. Memory mapping is primarily intended to map buffers in device
memory into the application’s address space.
A driver can support many sets of buffers. Each set is identified by a unique buffer type value. The
sets are independent and each set can hold a different type of data. To access different sets at the same
time different file descriptors must be used.
To allocate the device buffers, applications should call the VIDIOC_REQBUFS ioctl() with the
desired number of buffers and buffer type, for example V4L2_BUF_TYPE_VIDEO_CAPTURE. This
ioctl() can also be used to change the number of buffers or to free the allocated memory, provided
that none of the buffers are still mapped.
Before applications can access the buffers, they must map them into their address space with the
mmap() function. The location of the buffers in the device memory can be determined with the VID-
IOC_QUERYBUF ioctl(). The m.offset and length, returned in a struct v4l2_buffer, are
passed as the sixth and second parameters to the mmap() function. The offset and length values must
not be modified, since the buffers are allocated in physical memory, as opposed to virtual memory which
can be swapped out to disk. Applications should free the buffers as soon as possible with the munmap()
function.
To enqueue and dequeue a buffer, applications should use the VIDIOC_QBUF and VIDIOC_DQBUF
ioctl(). The status of a buffer being mapped, enqueued, full or empty can be determined at any
time using the VIDIOC_QUERYBUF ioctl(). Two methods exist to suspend execution of the ap-
plication until one or more buffers can be dequeued. By default VIDIOC_DQBUF blocks when no
buffer is in the outgoing queue. When the O_NONBLOCK flag was given to the open() function,
VIDIOC_DQBUF returns immediately with an EAGAIN error code when no buffer is available. The
select() or poll() functions are always available.
To start and stop capturing or playing, applications should call the VIDIOC_STREAMON and VID-
IOC_STREAMOFF ioctl(). The VIDIOC_STREAMOFF removes all buffers from both queues as
Input and output devices support this I/O method when the V4L2_CAP_STREAMING flag in the
capabilities field of struct v4l2_capability returned by the VIDIOC_QUERYCAP ioctl() is
set. Moreover, the VIDIOC_REQBUFS ioctl() should be called to determine if this particular user
pointer method is supported.
This I/O method combines the advantages of the read/write and memory mapping methods. Buffers
are allocated by the application itself, and can reside for example in virtual or shared memory. Only
pointers to data are exchanged, these pointers and meta-information are passed in struct v4l2_buffer.
The driver must be switched into user pointer I/O mode by calling the VIDIOC_REQBUFS with the
desired buffer type. No buffers are allocated beforehand. Consequently, they are not indexed and cannot
be queried like memory mapped buffers with the VIDIOC_QUERYBUF ioctl().
Buffer addresses and sizes are passed on the fly with the VIDIOC_QBUF ioctl(). Although
buffers are commonly cycled, applications can pass different addresses and sizes at each VIDIOC_QBUF
call. If required by the hardware, the driver swaps memory pages within physical memory to create a
continuous area of memory. This happens transparently to the application in the virtual memory subsys-
tem of the kernel. When buffer pages have been swapped out to disk, they are brought back and finally
locked in physical memory for DMA.
Filled or displayed buffers are dequeued with the VIDIOC_DQBUF ioctl(). The driver can
unlock the memory pages at any time between the completion of the DMA and this ioctl(). The
memory is also unlocked when VIDIOC_STREAMOFF is called, VIDIOC_REQBUFS is invoked, or
when the device is closed. Applications must take care not to free buffers without dequeuing. Firstly,
because the buffers remain locked until further, wasting physical memory. Secondly, the driver will not
be notified when the memory is returned to the application’s free list and subsequently reused for other
purposes, possibly completing the requested DMA and overwriting valuable data.
Capturing applications usually enqueue a number of empty buffers, to start capturing and enter the
read loop. Here the application waits until a filled buffer can be dequeued, and re-enqueues the buffer
when the data is no longer needed. Output applications fill and enqueue buffers, when enough buffers
are stacked up output is started. In the write loop, when the application runs out of free buffers it must
wait until an empty buffer can be dequeued and reused. Two methods exist to suspend execution of
the application until one or more buffers can be dequeued. By default VIDIOC_DQBUF blocks when
no buffer is in the outgoing queue. When the O_NONBLOCK flag was given to the open() function,
VIDIOC_DQBUF returns immediately with an EAGAIN error code when no buffer is available. The
select() or poll() functions are always available.
To start and stop capturing or playing applications should call the VIDIOC_STREAMON and VID-
IOC_STREAMOFF ioctl(). The VIDIOC_STREAMOFF removes all buffers from both queues and
unlocks all buffers as a side effect. Similarly to the memory mapping streaming IO mechanism, if an
application needs to synchronize with another event it should examine the struct v4l2_buffer times-
tamp of captured buffers, or set the field before enqueuing buffers for output.
Drivers implementing user pointer I/O must support the ioctl() VIDIOC_REQBUFS, VID-
IOC_QBUF, VIDIOC_DQBUF, VIDIOC_STREAMON and VIDIOC_STREAMOFF, as well as the
select() and poll() functions.
These states, and the operations which cause transitions between them, come together as shown in
Figure 5 [2].
struct v4l2_buffer
{
__u32 index;
enum v4l2_buf_type type;
__u32 bytesused;
__u32 flags;
enum v4l2_field field;
struct timeval timestamp;
struct v4l2_timecode timecode;
__u32 sequence;
/* memory location */
enum v4l2_memory memory;
union {
__u32 offset;
unsigned long userptr;
} m;
__u32 length;
__u32 input;
__u32 reserved;
};
The index field is a sequence number identifying the buffer; it is only used with memory-mapped
buffers. Like other objects which can be enumerated in the V4L2 interface, memory-mapped buffers
start with index 0 and go up sequentially from there.
The type field describes the type of the buffer, usually V4L2_BUF_TYPE_VIDEO_CAPTURE or
V4L2_BUF_TYPE_VIDEO_OUTPUT.
• V4L2_BUF_FLAG_MAPPED indicates that the buffer has been mapped into user space. It is only
applicable to memory-mapped buffers.
• V4L2_BUF_FLAG_QUEUED: the buffer is in the driver’s incoming queue.
• V4L2_BUF_FLAG_DONE: the buffer is in the driver’s outgoing queue.
• V4L2_BUF_FLAG_KEYFRAME: the buffer holds a key frame - useful in compressed streams.
• V4L2_BUF_FLAG_PFRAME and V4L2_BUF_FLAG_BFRAME are also used with compressed
streams; they indicated predicted or difference frames.
• V4L2_BUF_FLAG_TIMECODE: the timecode field is valid.
• V4L2_BUF_FLAG_INPUT: the input field is valid.
struct v4l2_requestbuffers
{
__u32 count;
enum v4l2_buf_type type;
enum v4l2_memory memory;
__u32 reserved[2];
};
The type field describes the type of I/O to be done; it will usually be ei-
ther V4L2_BUF_TYPE_VIDEO_CAPTURE for a video acquisition device or
V4L2_BUF_TYPE_VIDEO_OUTPUT for an output device. There are other types, but they are
beyond the scope of this assignment.
If the application wants to use memory-mapped buffers, it will set memory to
V4L2_MEMORY_MMAP and count to the number of buffers it wants to use. If the driver
2 Procedure
2.1 Capture of images
Implement an application program that makes use of the V4L2 driver to capture a digital images from
a webcam. The application should acquire a single still image and write it to an output file or to the
standard output, in a raw (YUV) format.
SYNOPSIS:
./imagecapture [options]
OPTIONS:
EXAMPLE:
References
[1] M. Schimek, B. Dirks, H. Verkuil, M. Rubli, A. Walls, and M. Chehab. Video for linux two API
specification. http://www.linuxtv.org/downloads/video4linux/API/V4L2_API/, August 2009.
[2] The Video4Linux2 API: an introduction. http://lwn.net/Articles/203924/, October 2006.
1
Available at: http://www.linuxtv.org/downloads/v4l-dvb-apis/capture-example.html
#include <linux/videodev2.h>
typedef enum {
IO_METHOD_READ,
IO_METHOD_MMAP,
IO_METHOD_USERPTR,
} io_method;
struct buffer {
void * start;
size_t length;
};
static void
errno_exit (const char * s)
{
fprintf (stderr, "%s error %d, %s\n",
s, errno, strerror (errno));
exit (EXIT_FAILURE);
}
static int
xioctl (int fd,
int request,
void * arg)
{
int r;
return r;
}
static void
process_image (const void * p)
{
fputc (’.’, stdout);
fflush (stdout);
}
static int
read_frame (void)
{
struct v4l2_buffer buf;
unsigned int i;
switch (io) {
case IO_METHOD_READ:
if (-1 == read (fd, buffers[0].start, buffers[0].length)) {
case EIO:
/* Could ignore EIO, see spec. */
/* fall through */
default:
errno_exit ("read");
}
}
process_image (buffers[0].start);
break;
case IO_METHOD_MMAP:
CLEAR (buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
case EIO:
/* Could ignore EIO, see spec. */
/* fall through */
default:
errno_exit ("VIDIOC_DQBUF");
}
}
process_image (buffers[buf.index].start);
break;
case IO_METHOD_USERPTR:
CLEAR (buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_USERPTR;
case EIO:
/* Could ignore EIO, see spec. */
/* fall through */
default:
errno_exit ("VIDIOC_DQBUF");
}
}
break;
}
return 1;
}
count = 100;
FD_ZERO (&fds);
FD_SET (fd, &fds);
/* Timeout. */
tv.tv_sec = 2;
tv.tv_usec = 0;
if (-1 == r) {
if (EINTR == errno)
continue;
errno_exit ("select");
}
if (0 == r) {
fprintf (stderr, "select timeout\n");
exit (EXIT_FAILURE);
}
if (read_frame ())
break;
static void
stop_capturing (void)
{
enum v4l2_buf_type type;
switch (io) {
case IO_METHOD_READ:
/* Nothing to do. */
break;
case IO_METHOD_MMAP:
case IO_METHOD_USERPTR:
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
break;
}
}
static void
start_capturing (void)
{
unsigned int i;
enum v4l2_buf_type type;
switch (io) {
case IO_METHOD_READ:
/* Nothing to do. */
break;
case IO_METHOD_MMAP:
for (i = 0; i < n_buffers; ++i) {
struct v4l2_buffer buf;
CLEAR (buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
break;
case IO_METHOD_USERPTR:
for (i = 0; i < n_buffers; ++i) {
struct v4l2_buffer buf;
CLEAR (buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_USERPTR;
buf.index = i;
buf.m.userptr = (unsigned long) buffers[i].start;
buf.length = buffers[i].length;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
break;
}
}
static void
uninit_device (void)
{
unsigned int i;
switch (io) {
case IO_METHOD_READ:
free (buffers[0].start);
break;
case IO_METHOD_MMAP:
for (i = 0; i < n_buffers; ++i)
if (-1 == munmap (buffers[i].start, buffers[i].length))
errno_exit ("munmap");
break;
case IO_METHOD_USERPTR:
for (i = 0; i < n_buffers; ++i)
free (buffers[i].start);
break;
}
free (buffers);
}
static void
init_read (unsigned int buffer_size)
{
buffers = calloc (1, sizeof (*buffers));
if (!buffers) {
fprintf (stderr, "Out of memory\n");
exit (EXIT_FAILURE);
}
buffers[0].length = buffer_size;
buffers[0].start = malloc (buffer_size);
if (!buffers[0].start) {
fprintf (stderr, "Out of memory\n");
exit (EXIT_FAILURE);
}
}
static void
init_mmap (void)
{
struct v4l2_requestbuffers req;
CLEAR (req);
if (req.count < 2) {
fprintf (stderr, "Insufficient buffer memory on %s\n",
dev_name);
exit (EXIT_FAILURE);
}
if (!buffers) {
fprintf (stderr, "Out of memory\n");
exit (EXIT_FAILURE);
}
CLEAR (buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = n_buffers;
buffers[n_buffers].length = buf.length;
buffers[n_buffers].start =
mmap (NULL /* start anywhere */,
buf.length,
PROT_READ | PROT_WRITE /* required */,
MAP_SHARED /* recommended */,
fd, buf.m.offset);
if (MAP_FAILED == buffers[n_buffers].start)
errno_exit ("mmap");
}
}
static void
init_userp (unsigned int buffer_size)
{
struct v4l2_requestbuffers req;
unsigned int page_size;
CLEAR (req);
req.count = 4;
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_USERPTR;
if (!buffers) {
fprintf (stderr, "Out of memory\n");
exit (EXIT_FAILURE);
}
if (!buffers[n_buffers].start) {
fprintf (stderr, "Out of memory\n");
exit (EXIT_FAILURE);
}
}
}
static void
init_device (void)
{
struct v4l2_capability cap;
struct v4l2_cropcap cropcap;
struct v4l2_crop crop;
struct v4l2_format fmt;
unsigned int min;
switch (io) {
case IO_METHOD_READ:
if (!(cap.capabilities & V4L2_CAP_READWRITE)) {
fprintf (stderr, "%s does not support read i/o\n",
dev_name);
exit (EXIT_FAILURE);
}
break;
case IO_METHOD_MMAP:
case IO_METHOD_USERPTR:
if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
fprintf (stderr, "%s does not support streaming i/o\n",
dev_name);
exit (EXIT_FAILURE);
}
break;
}
CLEAR (cropcap);
cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
CLEAR (fmt);
switch (io) {
case IO_METHOD_READ:
init_read (fmt.fmt.pix.sizeimage);
break;
case IO_METHOD_MMAP:
init_mmap ();
break;
case IO_METHOD_USERPTR:
init_userp (fmt.fmt.pix.sizeimage);
break;
}
}
static void
close_device (void)
{
if (-1 == close (fd))
errno_exit ("close");
fd = -1;
}
static void
open_device (void)
{
struct stat st;
if (!S_ISCHR (st.st_mode)) {
fprintf (stderr, "%s is no device\n", dev_name);
exit (EXIT_FAILURE);
}
if (-1 == fd) {
fprintf (stderr, "Cannot open ’%s’: %d, %s\n",
dev_name, errno, strerror (errno));
exit (EXIT_FAILURE);
}
}
static void
usage (FILE * fp,
int argc,
char ** argv)
{
fprintf (fp,
"Usage: %s [options]\n\n"
"Options:\n"
"-d | --device name Video device name [/dev/video]\n"
"-h | --help Print this message\n"
"-m | --mmap Use memory mapped buffers\n"
"-r | --read Use read() calls\n"
"-u | --userp Use application allocated buffers\n"
"",
argv[0]);
}
int
main (int argc,
char ** argv)
{
dev_name = "/dev/video";
for (;;) {
int index;
int c;
if (-1 == c)
break;
switch (c) {
case 0: /* getopt_long() flag */
break;
case ’d’:
dev_name = optarg;
break;
case ’h’:
usage (stdout, argc, argv);
exit (EXIT_SUCCESS);
case ’m’:
io = IO_METHOD_MMAP;
break;
case ’r’:
io = IO_METHOD_READ;
break;
case ’u’:
io = IO_METHOD_USERPTR;
break;
default:
usage (stderr, argc, argv);
exit (EXIT_FAILURE);
}
}
open_device ();
init_device ();
start_capturing ();
mainloop ();
stop_capturing ();
uninit_device ();
close_device ();
exit (EXIT_SUCCESS);
return 0;
}