/************************************************************************** * framegrabberCV Version 0.1 * * framegrabber modified to convert frames to OpenCV Mats * * Copyright (C) 2013 by Matthew Witherwax (lemoneer) * * lemoneer@outlook.com * * blog.lemoneerlabs.com * * * * based on V4L2 Specification, Appendix B: Video Capture Example * * (http://linuxtv.org/downloads/v4l-dvb-apis/capture-example.html) * * and work by Matthew Witherwax on v4l2grab * * (https://github.com/twam/v4l2grab) * *************************************************************************** *************************************************************************** BSD LICENSE Copyright (c) 2013, Matthew Witherwax All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************************************************************************/ #include #include #include #include #include #include /* getopt_long() */ #include /* low-level i/o */ #include #include #include #include #include #include #include #include #include #include "opencv2/core/core_c.h" #include "opencv2/highgui/highgui_c.h" #define CLEAR(x) memset(&(x), 0, sizeof(x)) enum io_method { IO_METHOD_READ, IO_METHOD_MMAP, IO_METHOD_USERPTR, }; struct buffer { void *start; size_t length; }; static char *dev_name = "/dev/video0"; static enum io_method io = IO_METHOD_MMAP; static int fd = -1; struct buffer *buffers; static unsigned int n_buffers; static int process_buf; static int frame_count = 1; static int set_format; static unsigned int width = 640; static unsigned int height = 480; static unsigned int fps = 30; static unsigned int timeout = 1; static unsigned int timeouts_max = 1; // Added to track processing a subset of the captures static int captured_count = 0; static int processed_count = 0; static int capture_offset = 1; /* Allowed formats: V4L2_PIX_FMT_YUYV, V4L2_PIX_FMT_MJPEG, V4L2_PIX_FMT_H264 * The default will not be used unless the width and/or height is specified * but the user does not specify a pixel format */ static unsigned int pixel_format = V4L2_PIX_FMT_MJPEG; /* Signal Handling * Clean up on Ctrl-C as opposed to leaving * the device in an inconsistent state*/ static int s_interrupted = 0; static void s_signal_handler (int signal_value) { s_interrupted = 1; } static void s_catch_signals (void) { struct sigaction action; action.sa_handler = s_signal_handler; action.sa_flags = 0; sigemptyset (&action.sa_mask); sigaction (SIGINT, &action, NULL); sigaction (SIGTERM, &action, NULL); } 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 fh, int request, void *arg) { int r; do { r = ioctl(fh, request, arg); } while (-1 == r && EINTR == errno); return r; } static void process_image(const void *p, int size) { if (process_buf) { if (captured_count % capture_offset != 0) { captured_count += 1; return; } captured_count += 1; processed_count += 1; CvMat mat; IplImage * img; mat = cvMat(480, 640, CV_8UC3, (void*)p); // decode the image img = cvDecodeImage(&mat, 1); // release the image cvReleaseImage(&img); } } 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 EIO: /* Could ignore EIO, see spec. */ /* fall through */ default: errno_exit("read"); } } process_image(buffers[0].start, buffers[0].length); 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 EIO: /* Could ignore EIO, see spec. */ /* fall through */ default: errno_exit("VIDIOC_DQBUF"); } } assert(buf.index < n_buffers); process_image(buffers[buf.index].start, buf.bytesused); 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 EIO: /* 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[i].start && buf.length == buffers[i].length) break; assert(i < n_buffers); process_image((void *) buf.m.userptr, buf.bytesused); if (-1 == xioctl(fd, VIDIOC_QBUF, &buf)) errno_exit("VIDIOC_QBUF"); break; } return 1; } static void grab_frames(void) { clock_t begin, end; double time_spent; unsigned int count; unsigned int timeout_count; count = frame_count; timeout_count = timeouts_max; begin = clock(); while (count-- > 0) { for (;;) { if (s_interrupted) { fprintf(stderr, "\nInterrupt received - aborting capture\n"); return; } fd_set fds; struct timeval tv; int r; FD_ZERO(&fds); FD_SET(fd, &fds); /* Timeout. */ tv.tv_sec = timeout; 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) { if (timeout_count > 0) { timeout_count--; } else { fprintf(stderr, "select timeout\n"); exit(EXIT_FAILURE); } } if (read_frame()) break; /* EAGAIN - continue select loop. */ } } end = clock(); time_spent = (double)(end - begin) / CLOCKS_PER_SEC; fprintf(stderr, "Captured %i frames and Processed %i in %f seconds\n", captured_count, processed_count, time_spent); } static void mainloop(void) { grab_frames(); } 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.index = i; buf.m.userptr = (unsigned long) buffers[i].start; buf.length = buffers[i].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[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); 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; struct v4l2_streamparm frameint; 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; if (set_format) { fmt.fmt.pix.width = width; fmt.fmt.pix.height = height; fmt.fmt.pix.pixelformat = pixel_format; fmt.fmt.pix.field = V4L2_FIELD_INTERLACED; if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt)) errno_exit("VIDIOC_S_FMT"); if (fmt.fmt.pix.pixelformat != pixel_format) { fprintf(stderr,"Libv4l didn't accept pixel format. Can't proceed.\n"); exit(EXIT_FAILURE); } /* Note VIDIOC_S_FMT may change width and height. */ } else { /* Preserve original settings as set by v4l2-ctl for example */ if (-1 == xioctl(fd, VIDIOC_G_FMT, &fmt)) errno_exit("VIDIOC_G_FMT"); } CLEAR(frameint); /* Attempt to set the frame interval. */ frameint.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; frameint.parm.capture.timeperframe.numerator = 1; frameint.parm.capture.timeperframe.denominator = fps; if (-1 == xioctl(fd, VIDIOC_S_PARM, &frameint)) fprintf(stderr, "Unable to set frame interval.\n"); /* 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" "Version 1.0\n" "Options:\n" "-d | --device name Video device name [%s]\n" "-h | --help Print this message\n" "-m | --mmap Use memory mapped buffers [default]\n" "-r | --read Use read() calls\n" "-u | --userp Use application allocated buffers\n" "-W | --width Set image width\n" "-H | --height Set image height\n" "-I | --interval Set frame interval (fps) [%i]\n" "-f | --format Set pixel format [YUYV | MJPG | H264]\n" "-t | --timeout Set capture timeout in seconds [%i]\n" "-T | --timeouts-max Set the maximum number of timeouts [%i]\n" "-p | --process Converts frames to OpenCV Mats\n" "-o | --offset Offset of frame to capture [%i]\n" "-c | --count Number of frames to grab [%i]\n" "", argv[0], dev_name, fps, timeout, timeouts_max, capture_offset, frame_count); } static const char short_options[] = "d:hmruW:H:I:f:t:T:po:c:"; 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'}, { "width", required_argument, NULL, 'W'}, { "height", required_argument, NULL, 'H'}, { "interval", required_argument, NULL, 'I'}, { "format", required_argument, NULL, 'f'}, { "timeout", required_argument, NULL, 't'}, { "timeouts-max", required_argument, NULL, 'T'}, { "process", no_argument, NULL, 'p'}, { "offset", required_argument, NULL, 'o'}, { "count", required_argument, NULL, 'c'}, { 0, 0, 0, 0} }; int main(int argc, char **argv) { s_catch_signals (); for (;;) { int idx; int c; c = getopt_long(argc, argv, short_options, long_options, &idx); 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; case 'W': // set width width = atoi(optarg); set_format++; break; case 'H': // set height height = atoi(optarg); set_format++; break; case 'I': // set fps fps = atoi(optarg); break; case 'f': // set pixel format if (strcmp(optarg, "YUYV") == 0 || strcmp(optarg, "yuyv") == 0) { pixel_format = V4L2_PIX_FMT_YUYV; set_format++; } else if (strcmp(optarg, "MJPG") == 0 || strcmp(optarg, "mjpg") == 0) { pixel_format = V4L2_PIX_FMT_MJPEG; set_format++; } else if (strcmp(optarg, "H264") == 0 || strcmp(optarg, "h264") == 0) { pixel_format = V4L2_PIX_FMT_H264; set_format++; } break; case 't': // set timeout timeout = atoi(optarg); break; case 'T': // set max timeout timeouts_max = atoi(optarg); break; case 'p': process_buf++; break; case 'o': // set offset capture_offset = atoi(optarg); if (capture_offset < 1) capture_offset = 1; break; case 'c': errno = 0; frame_count = strtol(optarg, NULL, 0); if (errno) errno_exit(optarg); break; default: usage(stderr, argc, argv); exit(EXIT_FAILURE); } } clock_t begin, end; double time_spent; begin = clock(); // startup open_device(); init_device(); start_capturing(); end = clock(); time_spent = (double)(end - begin) / CLOCKS_PER_SEC; fprintf(stderr, "Startup took %f seconds\n", time_spent); mainloop(); begin = clock(); // shutdown stop_capturing(); uninit_device(); close_device(); end = clock(); time_spent = (double)(end - begin) / CLOCKS_PER_SEC; fprintf(stderr, "Shutdown took %f seconds\n", time_spent); fprintf(stderr, "\n"); return 0; }