Video 4 Linux 2

Dr. Malinowski showed me an interesting example that he found online called Video 4 Linux 2 or V4L2. The mainpage for V4L2 is located here and here are the documentation and video capture example provided via links from the mainpage. Another useful link from TI’s website is the OMAP35x Linux PSP. Dr. Malinowski already tweaked the video capture example so that it would work with a /dev/video0 devices.

This code produces a Segmentation error and after going through it and debugging I figured out that it was missing CLEAR(buf) before declaring buf.type, buf.memory, and buf.index. The CLEAR(buf) can be replace with the line of code:

struct v4l2_buffer buf;
memset(&buf, sizeof(buf), 0);

Using the Video4Linux example with the corrections the following code captures 2 images and saves them as a .ppm file.

//* WebCam access example based on video4linux v2 *

/*When compiling this program on your system type in "gcc -Wall Name_of_your_file.c /usr/lib/libm.a -o Name_of_your_program"
without the "" marks. If you don't have the /usr/lib/libm.a file available on your system typing "gcc Name_of_your_file.c -lm"
without the "" marks and just run the a.out file that it gives you by typing "./a.out" without the "" marks. As of right now
the function uses the YUYV format since the RGB24 format has some issues with sizing. */

#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 <math.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       = 1280;//Here you can change your resolution to whatever your camera is best fit for!!!!!!
    form.fmt.pix.height      = 960;
    // common format: V4L2_PIX_FMT_YUYV, desired format: V4L2_PIX_FMT_RGB24
    form.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//As of right now the program uses the YUYV formate to do the color distance calculations
    // 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;

    // 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 2 frames NOTE: you can change how many frames you take with this for loop but make sure that you have
        //enough space if you are taking a very large amount of pictures.
        for (i=0; i<2; ++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
        unsigned long long int sumH=0, sumW=0, sumD=0;     // use int64_t not 32-bit long int
        int dr,dg,db,distance;                            //dr,dg,db must be signed!

        int iterations = cols*rows/2;
        int i = 0,row = 0, col = 0;
        while( i < iterations )
        {
            unsigned int packed_value = *((int*)buffer+i);

            if(row>rows){row = 0; col++;}

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

            //prints the updated pixel information to the .ppm file
            fprintf( file, "%c%c%c%c%c%c",r0,g0,b0,r1,g1,b1); // Output two pixels

            i++;
        }
    }

    fclose (file);
    return(0);
}
Print Friendly

Comments are closed.