Search code examples
c++linuxioctlv4l2

How to capture h264 video continuously using v4l2 API using C++?


Below modified code, (original code saves a frame into the image), captures a frame and saves into a disk in a mp4 file. I am trying to change the code to capture the h264 video frames from web cam(Logitech c920). The web cam supports the h264 video streaming.

How do I modify code to capture and store streaming video in real-time until user exits from the program?

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <linux/ioctl.h>
#include <linux/types.h>
#include <linux/v4l2-common.h>
#include <linux/v4l2-controls.h>
#include <linux/videodev2.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <string.h>
#include <fstream>
#include <string>

#include <iostream>
#include <vector>
#include <cstring>
#include <cassert>

using namespace std;

// Specific to WEB Cam
short unsigned int CAM_WIDTH=1920;
short unsigned int CAM_HEIGHT=1080;

int process() {
    // 1.  Open the device
    int fd; // A file descriptor to the video device
    fd = open("/dev/video0",O_RDWR);
    if(fd < 0){
        perror("Failed to open device, OPEN");
        return 1;
    }
    
    // 2. Ask the device if it can capture frames
    
    v4l2_capability capability;
    if(ioctl(fd, VIDIOC_QUERYCAP, &capability) < 0){
        // something went wrong... exit
        perror("Failed to get device capabilities, VIDIOC_QUERYCAP");
        return 1;
    }
    
    // 3. Set Image format
    v4l2_format imageFormat;
    imageFormat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    imageFormat.fmt.pix.width = CAM_WIDTH;
    imageFormat.fmt.pix.height = CAM_HEIGHT;
    imageFormat.fmt.pix.pixelformat = V4L2_PIX_FMT_H264; // V4L2_PIX_FMT_MJPEG;
    imageFormat.fmt.pix.field = V4L2_FIELD_NONE;
    // tell the device you are using this format
    if(ioctl(fd, VIDIOC_S_FMT, &imageFormat) < 0){
        perror("Device could not set format, VIDIOC_S_FMT");
        return 1;
    }
    
    // 4. Request Buffers from the device
    v4l2_requestbuffers requestBuffer = {0};
    requestBuffer.count = 10; // one request buffer
    requestBuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // request a buffer which we can use for capturing frames
    requestBuffer.memory = V4L2_MEMORY_MMAP;

    if(ioctl(fd, VIDIOC_REQBUFS, &requestBuffer) < 0){
        perror("Could not request buffer from device, VIDIOC_REQBUFS");
        return 1;
    }
    
    // 5. Query the buffer to get raw data i.e. ask for the you requested buffer
    // and allocate memory for it
    v4l2_buffer queryBuffer = {0};
    queryBuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    queryBuffer.memory = V4L2_MEMORY_MMAP;
    queryBuffer.index = 5;
    
    if(ioctl(fd, VIDIOC_QUERYBUF, &queryBuffer) < 0){
        perror("Device did not return the buffer information, VIDIOC_QUERYBUF");
        return 1;
    }
    // use a pointer to point to the newly created buffer
    // mmap() will map the memory address of the device to
    // an address in memory
    char* buffer = (char*)mmap(NULL, queryBuffer.length, PROT_READ | PROT_WRITE, MAP_SHARED,
                        fd, queryBuffer.m.offset);
    memset(buffer, 0, queryBuffer.length);
    
    // 6. Get a frame
    // Create a new buffer type so the device knows whichbuffer we are talking about
    v4l2_buffer bufferinfo;
    memset(&bufferinfo, 0, sizeof(bufferinfo));
    bufferinfo.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    bufferinfo.memory = V4L2_MEMORY_MMAP;
    bufferinfo.index = 0;
 
    // Activate streaming
    int type = bufferinfo.type;
    if(ioctl(fd, VIDIOC_STREAMON, &type) < 0){
        perror("Could not start streaming, VIDIOC_STREAMON");
        return 1;
    }
    
/***************************** Begin looping here *********************/
    // Queue the buffer
    if(ioctl(fd, VIDIOC_QBUF, &bufferinfo) < 0){
        perror("Could not queue buffer, VIDIOC_QBUF");
        return 1;
    }
    
    // Dequeue the buffer
    if(ioctl(fd, VIDIOC_DQBUF, &bufferinfo) < 0){
        perror("Could not dequeue the buffer, VIDIOC_DQBUF");
        return 1;
    }
    // Frames get written after dequeuing the buffer
    cout << "Buffer has: " << (double)bufferinfo.bytesused / 1024
            << " KBytes of data" << endl;
    
    // Write the data out to file
    ofstream outFile;
    //    outFile.open("webcam_output.jpeg", ios::binary| ios::app);
    outFile.open("webcam_output.mp4", ios::binary| ios::app);
    int bufPos = 0, outFileMemBlockSize = 0;  // the position in the buffer and the amount to copy from
                                        // the buffer
    
    int remainingBufferSize = bufferinfo.bytesused; // the remaining buffer size, is decremented by
                                                    // memBlockSize amount on each loop so we do not overwrite the buffer
    
    uint8_t* outFileMemBlock = NULL;  // a pointer to a new memory block
    int itr = 0; // counts the number of iterations
    while(remainingBufferSize > 0) {
        bufPos += outFileMemBlockSize;  // increment the buffer pointer on each loop
                                        // initialize bufPos before outFileMemBlockSize so we can start
                                        // at the beginning of the buffer
    
        outFileMemBlockSize = 1024;    // set the output block size to a preferable size. 1024 :)
        outFileMemBlock = new uint8_t[sizeof(uint8_t) * outFileMemBlockSize];
 
        // copy 1024 bytes of data starting from buffer+bufPos
        memcpy(outFileMemBlock, buffer+bufPos, outFileMemBlockSize);
        outFile.write(outFileMemBlock,outFileMemBlockSize);
 
        // calculate the amount of memory left to read
        // if the memory block size is greater than the remaining
        // amount of data we have to copy
        if(outFileMemBlockSize > remainingBufferSize)
            outFileMemBlockSize = remainingBufferSize;
 
        // subtract the amount of data we have to copy
        // from the remaining buffer size
        remainingBufferSize -= outFileMemBlockSize;
        // display the remaining buffer size
        cout << itr++ << " Remaining bytes: "<< remainingBufferSize << endl;
    }
    
    // Close the file
    outFile.close();
    
/******************************** end looping here **********************/
    // end streaming
    if(ioctl(fd, VIDIOC_STREAMOFF, &type) < 0){
        perror("Could not end streaming, VIDIOC_STREAMOFF");
        return 1;
    }
    close(fd);
    return 0;
}

int main() {
  process();
}

Update I went through this example from v4l docs, but I am unable to run this?

#include "../libv4l/include/libv4l2.h"

Since above header files are missing, I was not able to find out how to include those header files.


Solution

  • This looks like very complex to write the low level code to capture the webcam. But we can use the OpenCV which is more robust and does the same thing as what the question is asking. If you don't use opencv webcam feature, then you take a look at how OpenCV video capture is implemented.

    Try this code -

    #include "opencv2/opencv.hpp"
    using namespace cv;
    int main(int, char**)
    {
        VideoCapture cap(0); // open the default camera
        if(!cap.isOpened())  // check if we succeeded
            return -1;
        Mat edges;
        namedWindow("frame",1);
        for(;;)
        {
            Mat frame;
            cap >> frame; 
            imshow("frame", frame);
            if(waitKey(30) >= 0) break;
        }
        // the camera will be deinitialized automatically in VideoCapture destructor
        return 0;
    }
    

    more on how to do it in available in opencv docs