//* WebCam access example based on video4linux v2 *

#include <fcntl.h> #include <stdio.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #include <errno.h> #include <sys/ioctl.h> #include <sys/mman.h> //mmap #include <stdlib.h> #include <string.h> #include <assert.h> #include <linux/types.h> #include <linux/videodev2.h> struct buffer { void * start; size_t length; }; const char VideoDeviceName0[] = "/dev/video0"; #define fname_maxlen (64) const char CapturedImageNameIO[] = "image_io"; // .ppm will be appended const char CapturedImageNameMM[] = "image_mm"; // .ppm will be appended static int xioctl (int fd, int request, void* arg) { int status; do { status = ioctl (fd, request, arg); } while (-1==status && EINTR==errno); return status; } int ppm_save_rgb24(const char* name, int cols, int rows, void* buffer, int bufferSize); int ppm_save_yuyv(const char* name, int cols, int rows, void* buffer, int bufferSize); int main(int argc, char **argv) { const char* VideoDeviceName = VideoDeviceName0; int status; if (argc>1) { VideoDeviceName = argv[1]; } // Open the device int webcam = open(VideoDeviceName, O_RDWR|O_NONBLOCK, 0); if (-1==webcam) { fprintf(stderr, "ERROR: cannot open the device %s\n", VideoDeviceName); exit(EXIT_FAILURE); } // Query the device capacity struct v4l2_capability cap; status = xioctl (webcam, VIDIOC_QUERYCAP, &cap); if (0!=status) { fprintf(stderr, "ERROR: ioctl VIDIOC_QUERYCAP returned status of %d\n", status); } if ( !(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) ) { fprintf(stderr, "ERROR: the device does not support image capture\n"); exit(EXIT_FAILURE); } if ( !(cap.capabilities & V4L2_CAP_READWRITE) ) { fprintf(stderr, "WARNING: the device does not support read i/o\n"); } if ( !(cap.capabilities & V4L2_CAP_STREAMING) ) { fprintf(stderr, "WARNING: the device does not support streaming\n"); } // Select video input and video standard, and/or tune TV card hare struct v4l2_cropcap cropcap; memset(&cropcap, sizeof(cropcap), 0); cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; status = xioctl (webcam, VIDIOC_CROPCAP, &cropcap); if (0==status) { struct v4l2_crop crop; // memset(&crop, sizeof(crop), 0); crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; crop.c = cropcap.defrect; // reset to default status = xioctl (webcam, VIDIOC_S_CROP, &crop); if (0!=status) { if (EINVAL==errno) fprintf(stderr, "WARNING: the device does not support cropping\n"); else ; // Errors ignored } } else { // Errors ignored } struct v4l2_format form; memset(&form, sizeof(form), 0); form.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; form.fmt.pix.width = 640; form.fmt.pix.height = 480; // common format: V4L2_PIX_FMT_YUYV, desired format: V4L2_PIX_FMT_RGB24 form.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; // form.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24; form.fmt.pix.field = V4L2_FIELD_INTERLACED; status = xioctl (webcam, VIDIOC_S_FMT, &form); if (0!=status) { fprintf(stderr, "ERROR: ioctl VIDIOC_S_FMT returned status of %d (format %d is not supported)\n", status, (int)form.fmt.pix.pixelformat); exit(EXIT_FAILURE); } // Note VIDIOC_S_FMT may change width and height! int min = form.fmt.pix.width * 2; if (form.fmt.pix.bytesperline < min) form.fmt.pix.bytesperline = min; min = form.fmt.pix.bytesperline * form.fmt.pix.height; if (form.fmt.pix.sizeimage < min) form.fmt.pix.sizeimage = min; // Note: the minimum required buffer size is form.fmt.pix.sizeimage int minbuffersize = form.fmt.pix.sizeimage; // Demonstrate reading using read i/o if ( cap.capabilities & V4L2_CAP_READWRITE ) { char *buffer = malloc(minbuffersize); const int bufferSize = minbuffersize; int bytes_read = 0; int i; // Let's take some 100 frames for (i=0; i<100; ++i) { // Wait for a new frame to become available struct timeval timeout; timeout.tv_sec = 2; timeout.tv_usec = 0; fd_set fds; FD_ZERO (&fds); FD_SET (webcam, &fds); status = select (webcam+1, &fds, NULL, NULL, &timeout); if (-1==status) { if (EINTR==errno) continue; fprintf (stderr, "ERROR: select failed\n"); exit (EXIT_FAILURE); } if (0==status) { fprintf (stderr, "ERROR: webcam tiemout\n"); exit (EXIT_FAILURE); } // Capture a new frame bytes_read = read(webcam, buffer, bufferSize); if(bytes_read>0) { char fname[fname_maxlen]; sprintf(fname, "%s-%03d.ppm", CapturedImageNameIO, i); if (V4L2_PIX_FMT_RGB24==form.fmt.pix.pixelformat) { status = ppm_save_rgb24(fname, form.fmt.pix.width, form.fmt.pix.height, buffer, bytes_read); if (0!=status) { fprintf(stderr, "ERROR: cannot write to file %s\n", fname); free(buffer); exit(EXIT_FAILURE); } } else if (V4L2_PIX_FMT_YUYV==form.fmt.pix.pixelformat){ status = ppm_save_yuyv(fname, form.fmt.pix.width, form.fmt.pix.height, buffer, bytes_read); if (0!=status) { fprintf(stderr, "ERROR: cannot write to file %s\n", fname); free(buffer); exit(EXIT_FAILURE); } } else { status=-1; /* format not supported by us */ } } else { // Note: if we did not wait for a frame negative number could indicate that the frame was not ready yet as we did open the device as non-blocking fprintf(stderr, "ERROR: Error reading from the camera: %d\n", bytes_read); } } free(buffer); } // Demonstrate reading using memory map io (streaming) if ( cap.capabilities & V4L2_CAP_STREAMING ) { struct buffer* buffers = NULL; unsigned int n_buffers = 0; unsigned int i; // Initialize buffer struct v4l2_requestbuffers req; memset(&req, sizeof(req), 0); req.count = 4; req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; req.memory = V4L2_MEMORY_MMAP; status = xioctl (webcam, VIDIOC_REQBUFS, &req); if (0!=status ) { fprintf(stderr, "ERROR: ioctl VIDIOC_REQBUFS returned status %d\n", status); exit (EXIT_FAILURE); } if (req.count < 2) { fprintf (stderr, "ERROR: Insufficient buffer memory on %s\n", VideoDeviceName); exit (EXIT_FAILURE); } n_buffers = req.count; buffers = calloc(n_buffers, sizeof(*buffers)); if (0==buffers) { fprintf (stderr, "ERROR: Out of memory\n"); exit (EXIT_FAILURE); } for (i = 0; i < n_buffers; ++i) { struct v4l2_buffer buf; memset(&buf, sizeof(buf), 0); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; status = xioctl (webcam, VIDIOC_QUERYBUF, &buf); if (0!=status) { fprintf(stderr, "ERROR: ioctl VIDIOC_QUERYBUF for buffer %d returned status %d\n", i, status); exit (EXIT_FAILURE); } buffers[i].length = buf.length; buffers[i].start = mmap (NULL /* start anywhere */, buf.length, PROT_READ | PROT_WRITE /* required */, MAP_SHARED /* recommended */, webcam, buf.m.offset); if (MAP_FAILED==buffers[i].start) { fprintf(stderr, "ERROR: MemoryMap failed for buffer %d of %d\n", i, n_buffers); exit (EXIT_FAILURE); } } // end of for loop for each buffer // Start capturing for (i = 0; i < n_buffers; ++i) { struct v4l2_buffer buf; memset(&buf, sizeof(buf), 0); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; status = xioctl (webcam, VIDIOC_QBUF, &buf); if (0!=status) { fprintf(stderr, "ERROR: ioctl VIDIOC_QBUF returned status %d\n", status); exit (EXIT_FAILURE); } } enum v4l2_buf_type type1; type1 = V4L2_BUF_TYPE_VIDEO_CAPTURE; status = xioctl (webcam, VIDIOC_STREAMON, &type1); if (0!=status) { fprintf(stderr, "ERROR: ioctl VIDIOC_STREAMON returned status %d\n", status); exit (EXIT_FAILURE); } // Let's take some 100 frames for (i=0; i<100; ++i) { // Wait for a new frame to become available struct timeval timeout; timeout.tv_sec = 2; timeout.tv_usec = 0; fd_set fds; FD_ZERO (&fds); FD_SET (webcam, &fds); status = select (webcam+1, &fds, NULL, NULL, &timeout); if (-1==status) { if (EINTR==errno) continue; fprintf (stderr, "ERROR: select failed\n"); exit (EXIT_FAILURE); } if (0==status) { fprintf (stderr, "ERROR: webcam tiemout\n"); exit (EXIT_FAILURE); } // Read one frame struct v4l2_buffer buf; memset(&buf, sizeof(buf), 0); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; // Hold onto the buffer that holds the most recently captured image status = xioctl (webcam, VIDIOC_DQBUF, &buf); if (0!=status) { if (EAGAIN==errno) continue; // read again else { fprintf(stderr, "ERROR: ioctl VIDIOC_DQBUF returned status %d\n", status); exit(EXIT_FAILURE); } } assert (buf.index < n_buffers); // process_image (buffers[buf.index].start, buffers[buf.index].length); { char* buffer = buffers[buf.index].start; int bytes_read = buffers[buf.index].length; char fname[fname_maxlen]; sprintf(fname, "%s-%03d.ppm", CapturedImageNameMM, i); if (V4L2_PIX_FMT_RGB24==form.fmt.pix.pixelformat) { status = ppm_save_rgb24(fname, form.fmt.pix.width, form.fmt.pix.height, buffer, bytes_read); if (0!=status) { fprintf(stderr, "ERROR: cannot write to file %s\n", fname); free(buffer); exit(EXIT_FAILURE); } } else if (V4L2_PIX_FMT_YUYV==form.fmt.pix.pixelformat){ status = ppm_save_yuyv(fname, form.fmt.pix.width, form.fmt.pix.height, buffer, bytes_read); if (0!=status) { fprintf(stderr, "ERROR: cannot write to file %s\n", fname); free(buffer); exit(EXIT_FAILURE); } } else { status=-1; /* format not supported by us */ } } // Release hod of the locked recent buffer status = xioctl (webcam, VIDIOC_QBUF, &buf); if (0!=status) { fprintf(stderr, "ERROR: ioctl VIDIOC_QBUF returned status %d\n", status); // exit (EXIT_FAILURE); } } // Stop capturing enum v4l2_buf_type type2; type2 = V4L2_BUF_TYPE_VIDEO_CAPTURE; status = xioctl (webcam, VIDIOC_STREAMOFF, &type2); if (0!=status) { fprintf(stderr, "ERROR: ioctl VIDIOC_STREAMON returned status %d\n", status); exit (EXIT_FAILURE); } // Shutdown buffers for (i = 0; i < n_buffers; ++i) status = munmap (buffers[i].start, buffers[i].length); if (0!=status) { fprintf(stderr, "ERROR: MemoryMap unmapping failed\n"); exit (EXIT_FAILURE); } } close (webcam); return(EXIT_SUCCESS); } int ppm_save_rgb24(const char* name, int cols, int rows, void* buffer, int bufferSize) { char header[128]; sprintf(header, "P6\n%d %d 255\n", cols, rows); int file = open (name, O_WRONLY|O_CREAT, 0666); if (file==0) return(-1); write (file, header, strlen(header)); write (file, buffer, bufferSize); close (file); return(0); } static int clamp(int i) { if (i<0) return(0); else if (i<256) return(i); else return(255); } int ppm_save_yuyv(const char* name, int cols, int rows, void* buffer, int bufferSize) { char header[128]; sprintf(header, "P6\n%d %d 255\n", cols, rows); FILE* file = fopen (name, "w"); if (file==0) return(-1); fputs(header, file); { int Y0, Y1, Cb, Cr; // gamma pre-corrected input [0;255] int y0,y1, pb, pr; // temporaries char r0,r1,g0,g1,b0,b1; // temporaries int iterations = cols*rows/2; int i = 0; while( i < iterations ) { unsigned int packed_value = *((int*)buffer+i); Y0 = (char)(packed_value & 0xFF); Cb = (char)((packed_value >> 8) & 0xFF); Y1 = (char)((packed_value >> 16) & 0xFF); Cr = (char)((packed_value >> 24) & 0xFF); // Strip sign values after shift (i.e. unsigned shift) Y0 = Y0 & 0xFF; Cb = Cb & 0xFF; Y1 = Y1 & 0xFF; Cr = Cr & 0xFF; y0 = 255*(Y0 - 16)/219; y1 = 255*(Y1 - 16)/219; pb = 255*(Cb - 128)/224; pr = 255*(Cr - 128)/224; // Generate first pixel r0 = clamp(( 298 * y0 + 409 * pr + 128) >> 8); g0 = clamp(( 298 * y0 - 100 * pb - 208 * pr + 128) >> 8); b0 = clamp(( 298 * y0 + 516 * pb + 128) >> 8); // Generate next pixel - must reuse pb & pr as 4:2:2 r1 = clamp(( 298 * y1 + 409 * pr + 128) >> 8); g1 = clamp(( 298 * y1 - 100 * pb - 208 * pr + 128) >> 8); b1 = clamp(( 298 * y1 + 516 * pb + 128) >> 8); fprintf( file, "%c%c%c%c%c%c",r0,g0,b0,r1,g1,b1); // Output two pixels i++; } } fclose (file); return(0); }