跳转至

V4L2数据获取流程

整个过程相关的数据结构有如下几个:

struct v4l2_capability     m_cap;       /* 驱动能力 */
struct v4l2_format         m_fmt;       /* 数据格式 */
struct v4l2_fmtdesc        m_desc_fmt;  /* 格式描述 */
struct v4l2_requestbuffers m_rb;        /* 请求帧缓冲 */
struct v4l2_buffer         m_buf;       /* 数据缓冲 */
struct v4l2_frmsizeenum    m_frmsize;   /* 帧大小描述 */

1. 打开设备节点

#define DEFAULT_CAMERA_PATH "/dev/video0"
m_fd = open(DEFAULT_CAMERA_PATH, O_RDWR);
if(m_fd < 0) {
    perror("open failed.");
    return -1;
}

2. 可使用ioctl VIDIOC_QUERYCAP 查询当前驱动能力。

memset(&m_cap, 0, sizeof(m_cap));
if(ioctl(m_fd, VIDIOC_QUERYCAP, &m_cap) < 0) {
    perror("ioctl failed");
    return -1;
}
if(m_cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) {
    printf("V4L2_CAP_VIDEO_CAPTURE supportted\n");
}
if(m_cap.capabilities & V4L2_CAP_STREAMING) {
    printf("V4L2_CAP_STREAMING supportted\n");
}
if(m_cap.capabilities & V4L2_CAP_READWRITE) {
    printf("V4L2_CAP_READWRITE supportted\n");
}

3. 设置数据格式

/* set data format */
memset(&m_fmt, 0x0, sizeof(m_fmt));
m_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
m_fmt.fmt.pix.width = 640;
m_fmt.fmt.pix.height = 480;
m_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
m_fmt.fmt.pix.field       = V4L2_FIELD_ANY;
if(ioctl(m_fd, VIDIOC_S_FMT, &m_fmt) < 0) {
    perror("ioctl VIDIOC_S_FMT failed!");
    return -1;
}

4. 请求帧缓冲

/* request framebuffer */
memset(&m_rb, 0x0, sizeof(m_rb));
m_rb.count = DEFAULT_BUFFER_COUNT;
m_rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
m_rb.memory = V4L2_MEMORY_MMAP;
if(ioctl(m_fd, VIDIOC_REQBUFS, &m_rb) < 0) {
    perror("ioctl VIDIOC_REQBUFS failed!");
    return -1;
}

5. 映射buffer到用户空间

m_rb.count上一步请求的BUFFER数量,即v4l2_requestbuffers中定义 video_buffer用户空间的buffer结构体,用来存放mmap相关信息,定义如下 struct video_buffer { unsigned int length; unsigned int offset; unsigned char *start; };

m_video_buffers = (struct video_buffer *)calloc(m_rb.count, sizeof(struct video_buffer));
/* map the framebuffer to userspace */
for(int index_buf = 0; index_buf < m_rb.count; index_buf++) {
    /* calloc the userspace buffer */
    memset(&m_buf, 0x0, sizeof(m_buf));
    m_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    m_buf.memory = V4L2_MEMORY_MMAP;
    m_buf.index = index_buf;
    if(ioctl(m_fd, VIDIOC_QUERYBUF, &m_buf) < 0) {
        perror("ioctl VIDIOC_QUERYBUF failed!");
        return -1;
    }
    m_video_buff_size = m_buf.length;
    /* map to userspace  */
    m_video_buffers[index_buf].start = (unsigned char *)mmap(NULL,
                                                             m_buf.length,
                                                             PROT_READ | PROT_WRITE, 
                                                             MAP_SHARED, 
                                                             m_fd, 
                                                             m_buf.m.offset);
    if(m_video_buffers[index_buf].start == MAP_FAILED) {
        perror("map video buf failed!");
        return -1;
    }
    /* queue the buffer */
    memset(&m_buf, 0x0, sizeof(m_buf));
    m_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    m_buf.memory = V4L2_MEMORY_MMAP;
    m_buf.index = index_buf;
    if(ioctl(m_fd, VIDIOC_QBUF, &m_buf) < 0) {
        perror("ioctl VIDIOC_QBUF failed!");
        return -1;
    }
}
printf("v4l2_buf size: %d\n", m_buf.length);

6.启动数据流

enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(ioctl(m_fd, VIDIOC_STREAMON, &type) < 0) {
    perror("ioctl VIDIOC_STREAMON failed!");
    return -1;
}

7.获取帧数据(写入到文件)

int ret;
static int frame_count=1;
struct v4l2_buffer v4lbuffer;

/* pop fb from queue */
memset(&v4lbuffer, 0x0, sizeof(v4lbuffer));
v4lbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4lbuffer.memory = V4L2_MEMORY_MMAP;
if(ioctl(m_fd, VIDIOC_DQBUF, &v4lbuffer) < 0) {
    perror("ioctl VIDIOC_DQBUF failed!");
    return ret;
}
m_rb_current = v4lbuffer.index;
m_total_bytes = v4lbuffer.bytesused;


int out_fd;
unsigned char *file_base;
char out_fname[50];
sprintf(out_fname, "./abc_%04d.jpeg", frame_count);

LOG_DEBUG("***save_fbdata_to_file by mmap.");
out_fd = open(out_fname, O_RDWR | O_CREAT | O_TRUNC, 0755);
if(out_fd < 0) {
    perror("open path failed!");
    return out_fd;
}

/* make file length */
lseek(out_fd, m_total_bytes - 1, SEEK_END);
write(out_fd, "", 1);   /* because of copy on write? */
printf("total_bytes:%d\n", m_total_bytes);
printf("current:%d\n", m_rb_current);

/* write operation here */
file_base = (unsigned char *)mmap(NULL, m_total_bytes,
                                  PROT_READ | PROT_WRITE,
                                  MAP_SHARED,
                                  out_fd, 0);
if(file_base == MAP_FAILED) {
    perror("mmap file failed!");
    return -1;
}

LOG_DEBUG("memcpying...");
memcpy(file_base, m_video_buffers[m_rb_current].start, m_total_bytes);
/* release the resource */
munmap(file_base, m_total_bytes);
close(out_fd);
frame_count++;

/* push back fb to queue */
LOG_DEBUG("push back fb to queue");
memset(&v4lbuffer, 0x0, sizeof(v4lbuffer));
v4lbuffer.index = m_rb_current;
v4lbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
v4lbuffer.memory = V4L2_MEMORY_MMAP;
if(ioctl(m_fd, VIDIOC_QBUF, &v4lbuffer) < 0) {
    perror("ioctl VIDIOC_QBUF failed!");
    return -1;
}
//if(frame_count > 24)exit(1);
return frame_count;

8.停止数据流

enum v4l2_buf_type type;
type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if(ioctl(m_fd, VIDIOC_STREAMOFF, &type) < 0) {
    perror("ioctl VIDIOC_STREAMOFF failed!");
    return -1;
}

最后附上main.cpp

#include <iostream>
#include <time.h>
#include <signal.h>
#include "v4l2_capturer.h"

v4l2_capturer capturer;

void capturer_sigint_handler(int signal)
{
    capturer.stop();
    exit(1);
}

/* TODO: listen SIGINT and call capturer.stop() when it come. */
int main(int argc, char const *argv[])
{
    int frame_count=0;
    std::cout << "Hello World!" << std::endl;

    /* register signal handler function. */
    signal(SIGINT, capturer_sigint_handler);

    capturer.init();
    capturer.query_supported_format_new();
    capturer.start();

    int i=25;
    char filename[10];

    while(1){
        frame_count=capturer.get_frame();
        if(frame_count==25*10)break;
    }
    /*capturer.get_frame();
    capturer.save_fbdata_to_file_by_mmap("./out.jpg");*/
    capturer.stop();
    return 0;
}