> Linux教程 > Linux学习 >

Linux下面实现视频捕获

Linux下面的视频捕获,主要用的是v4l,或者v4l2.

文档很长,其中有些是关于驱动方面如何实现的,对wine来说,我们只关心应用层方面的东西,说白了其实很简单。Linux下面,所有的设备都是一样的访问模式,

首先调用用open打开设备,利用ioctl可以对设备进行控制,比如设置视频数据流格式,视频尺寸,访问模式等等。然后根据不同的访问模式,采用不同的方法读取视频数据,比如一般的read方法,内存映射mmap,或者userptr。,然后将读取的数据,根据设置的不同格式进行解码,v4l定义了不同的格式,不如RGB24,SRGB,YUMK,当然,我们最终所获取的数据都得转化为RGB24格式,不过其他格式也有各自的优点,根据不同的场合需要选择不同的格式。当然,设备支持的数据格式,跟驱动的实现有关,驱动程序并不一定支持所有的数据格式。在使用ioctl进行设置的时候,如果设备不支持,就会返回错误。

说了半天没用的废话,其实最有效理解它们的方法就是看附录中提供的视频捕获的例子程序  CODE:
/*
*  V4L2 video capture example
*
*  This program can be used and distributed without restrictions.
*/

#include
#include
#include
#include

#include              /* getopt_long() */

#include               /* low-level i/o */
#include
#include
#include
#include
#include
#include
#include

#include           /* for videodev2.h */

#include

#define CLEAR(x) memset (&(x), 0, sizeof (x))

typedef enum {
         IO_METHOD_READ,
         IO_METHOD_MMAP,
         IO_METHOD_USERPTR,
} io_method;

struct buffer {
         void *                  start;
         size_t                  length;
};

static char *           dev_name        = NULL;
static io_method        io              = IO_METHOD_MMAP;
static int              fd              = -1;
struct buffer *         buffers         = NULL;
static unsigned int     n_buffers       = 0;

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;

         do r = ioctl (fd, request, arg);
         while (-1 == r && EINTR == errno);

         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)) {
                         switch (errno) {
                         case EAGAIN:
                                 return 0;

                         case EI
                                 /* 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;

                 if (-1 == xioctl (fd, VIDIOC_DQBUF, &buf)) {
                         switch (errno) {
                         case EAGAIN:
                                 return 0;

                         case EI
                                 /* Could ignore EIO, see spec. */

                                 /* fall through */

                         default:
                                 errno_exit ("VIDIOC_DQBUF");
                         }
                 }

                 assert (buf.index < n_buffers);

                 process_image (buffers[buf.index].start);

                 if (-1 == xioctl (fd, VIDIOC_QBUF, &buf))
                         errno_exit ("VIDIOC_QBUF");

                 break;

         case IO_METHOD_USERPTR:
                 CLEAR (buf);

                 buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                 buf.memory = V4L2_MEMORY_USERPTR;

                 if (-1 == xioctl (fd, VIDIOC_DQBUF, &buf)) {
                         switch (errno) {
                         case EAGAIN:
                                 return 0;

                         case EI
                                 /* Could ignore EIO, see spec. */

                                 /* fall through */

                         default:
                                 errno_exit ("VIDIOC_DQBUF");
                         }
                 }

                 for (i = 0; i < n_buffers; ++i)
                         if (buf.m.userptr == (unsigned long) buffers.start
                             && buf.length == buffers.length)
                                 break;

                 assert (i < n_buffers);

                 process_image ((void *) buf.m.userptr);

                 if (-1 == xioctl (fd, VIDIOC_QBUF, &buf))
                         errno_exit ("VIDIOC_QBUF");

                 break;
         }

         return 1;
}

static void
mainloop                        (void)
{
         unsigned int count;

         count = 100;

         while (count-- > 0) {
                 for (;;) {
                         fd_set fds;
                         struct timeval tv;
                         int r;

                         FD_ZERO (&fds);
                         FD_SET (fd, &fds);

                         /* Timeout. */
                         tv.tv_sec = 2;
                         tv.tv_usec = 0;

                         r = select (fd + 1, &fds, NULL, NULL, &tv);

                         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;
        
                         /* EAGAIN - continue select loop. */
                 }
         }
}

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;

                 if (-1 == xioctl (fd, VIDIOC_STREAMOFF, &type))
                         errno_exit ("VIDIOC_STREAMOFF");

                 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;

                         if (-1 == xioctl (fd, VIDIOC_QBUF, &buf))
                                 errno_exit ("VIDIOC_QBUF");
                 }
                
                 type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

                 if (-1 == xioctl (fd, VIDIOC_STREAMON, &type))
                         errno_exit ("VIDIOC_STREAMON");

                 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.m.userptr   = (unsigned long) buffers.start;
                         buf.length      = buffers.length;

                         if (-1 == xioctl (fd, VIDIOC_QBUF, &buf))
                                 errno_exit ("VIDIOC_QBUF");
                 }


                 type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

                 if (-1 == xioctl (fd, VIDIOC_STREAMON, &type))
                         errno_exit ("VIDIOC_STREAMON");

                 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.start, buffers.length))
                                 errno_exit ("munmap");
                 break;

         case IO_METHOD_USERPTR:
                 for (i = 0; i < n_buffers; ++i)
                         free (buffers.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);

         req.count               = 4;
         req.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
         req.memory              = V4L2_MEMORY_MMAP;

         if (-1 == xioctl (fd, VIDIOC_REQBUFS, &req)) {
                 if (EINVAL == errno) {
                         fprintf (stderr, "%s does not support "
                                  "memory mapping\n", dev_name);
                         exit (EXIT_FAILURE);
                 } else {
                         errno_exit ("VIDIOC_REQBUFS");
                 }
         }

         if (req.count < 2) {
                 fprintf (stderr, "Insufficient buffer memory on %s\n",
                          dev_name);
                 exit (EXIT_FAILURE);
         }

         buffers = calloc (req.count, sizeof (*buffers));

         if (!buffers) {
                 fprintf (stderr, "Out of memory\n");
                 exit (EXIT_FAILURE);
         }

         for (n_buffers = 0; n_buffers < req.count; ++n_buffers) {
                 struct v4l2_buffer buf;

                 CLEAR (buf);

                 buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                 buf.memory      = V4L2_MEMORY_MMAP;
                 buf.index       = n_buffers;

                 if (-1 == xioctl (fd, VIDIOC_QUERYBUF, &buf))
                         errno_exit ("VIDIOC_QUERYBUF");

                 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;

         CLEAR (req);

         req.count               = 4;
         req.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
         req.memory              = V4L2_MEMORY_USERPTR;

         if (-1 == xioctl (fd, VIDIOC_REQBUFS, &req)) {
                 if (EINVAL == errno) {
                         fprintf (stderr, "%s does not support "
                                  "user pointer i/o\n", dev_name);
                         exit (EXIT_FAILURE);
                 } else {
                         errno_exit ("VIDIOC_REQBUFS");
                 }
         }

         buffers = calloc (4, sizeof (*buffers));

         if (!buffers) {
                 fprintf (stderr, "Out of memory\n");
                 exit (EXIT_FAILURE);
         }

         for (n_buffers = 0; n_buffers < 4; ++n_buffers) {
                 buffers[n_buffers].length = buffer_size;
                 buffers[n_buffers].start = malloc (buffer_size);

                 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;

         if (-1 == xioctl (fd, VIDIOC_QUERYCAP, &cap)) {
                 if (EINVAL == errno) {
                         fprintf (stderr, "%s is no V4L2 device\n",
                                  dev_name);
                         exit (EXIT_FAILURE);
                 } else {
                         errno_exit ("VIDIOC_QUERYCAP");
                 }
         }

         if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
                 fprintf (stderr, "%s is no video capture device\n",
                          dev_name);
                 exit (EXIT_FAILURE);
         }

         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;
         }


         /* Select video input, video standard and tune here. */


         CLEAR (cropcap);

         cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

         if (0 == xioctl (fd, VIDIOC_CROPCAP, &cropcap)) {
                 crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                 crop.c = cropcap.defrect; /* reset to default */

                 if (-1 == xioctl (fd, VIDIOC_S_CROP, &crop)) {
                         switch (errno) {
                         case EINVAL:
                                 /* Cropping not supported. */
                                 break;
                         default:
                                 /* Errors ignored. */
                                 break;
                         }
                 }
         } else {       
                 /* Errors ignored. */
         }


         CLEAR (fmt);

         fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;
         fmt.fmt.pix.width       = 640;
         fmt.fmt.pix.height      = 480;
         fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
         fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;

         if (-1 == xioctl (fd, VIDIOC_S_FMT, &fmt))
                 errno_exit ("VIDIOC_S_FMT");

         /* Note VIDIOC_S_FMT may change width and height. */

         /* Buggy driver paranoia. */
         min = fmt.fmt.pix.width * 2;
         if (fmt.fmt.pix.bytesperline < min)
                 fmt.fmt.pix.bytesperline = min;
         min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;
         if (fmt.fmt.pix.sizeimage < min)
                 fmt.fmt.pix.sizeimage = min;

         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 (-1 == stat (dev_name, &st)) {
                 fprintf (stderr, "Cannot identify '%s': %d, %s\n",
                          dev_name, errno, strerror (errno));
                 exit (EXIT_FAILURE);
         }

         if (!S_ISCHR (st.st_mode)) {
                 fprintf (stderr, "%s is no device\n", dev_name);
                 exit (EXIT_FAILURE);
         }

         fd = open (dev_name, O_RDWR /* required */ | O_NONBLOCK, 0);

         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]);
}

static const char short_options [] = "d:hmru";

static const struct option
long_options [] = {
         { "device",     required_argument,      NULL,           'd' },
         { "help",       no_argument,            NULL,           'h' },
         { "mmap",       no_argument,            NULL,           'm' },
         { "read",       no_argument,            NULL,           'r' },
         { "userp",      no_argument,            NULL,           'u' },
         { 0, 0, 0, 0 }
};

int
main                            (int                    argc,
                                  char **                argv)
{
         dev_name = "/dev/video";

         for (;;) {
                 int index;
                 int c;
                
                 c = getopt_long (argc, argv,
                                  short_options, long_options,
                                  &index);

                 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;
}/*********************************************/
上面的例子程序已经能完成基本的功能。不过它只是读取数据,并没有显示出来。要在屏幕上显示出来,可以直接写屏幕或者利用Xwindows的函数。值得一提的是,我们注意程序中使用了select函数。

(责任编辑:IT)