V4L2 API:

Part I - Video for Linux API

参考:

https://github.com/Jin992/robo_vision_mavalink/blob/c10d67cafcd65ac9efbf7bd84b2b117e00764fbf/Video/VideoCapture.cpp

采集步骤:

打开设备->获取和设置设备属性->设置输出输入->(申请缓冲及缓冲管理)->获取数据->关闭设备

打开设备

fd = open(v4l_device, O_RDWR, 0)

获取设备信息

struct v4l2_capability cam_cap;  
ret = ioctl(cap_fd, VIDIOC_QUERYCAP, &cam_cap);
printf("Driver Name: %s\nCard Name: %s\nBus info: %s\nDriver Version: %u.%u.%u\n",
           cam_cap.driver,
           cam_cap.card,
           cam_cap.bus_info,
           (cam_cap.version >> 16) & 0XFF, (cam_cap.version >> 8) & 0XFF, cam_cap.version & 0XFF);
struct v4l2_capability {
	__u8	driver[16];     /* i.e. "bttv" */
	__u8	card[32];       /* i.e. "Hauppauge WinTV" */
	__u8	bus_info[32];   /* "PCI:" + pci_name(pci_dev) */
	__u32   version;        /* should use KERNEL_VERSION() */
	__u32	capabilities;   /* Device capabilities */
	__u32	reserved[4];
};

获取图像信息

struct v4l2_format cam_format;
cam_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(dev_fd[dev_id], VIDIOC_G_FMT, &cam_format)
printf("    width : %d  height : %d\n\
    pixelformat : %d\n\
    field : %d\n\
    bytesperline : %d\n\
    sizeimage : %d\n\
    colorspace : %d\n\
    priv : %d\n",\
    cam_format.fmt.pix.width,\
    cam_format.fmt.pix.height,\
    cam_format.fmt.pix.pixelformat,\
    cam_format.fmt.pix.field, \
    cam_format.fmt.pix.bytesperline, \
    cam_format.fmt.pix.sizeimage, \
    cam_format.fmt.pix.colorspace, \
    cam_format.fmt.pix.priv);
struct v4l2_fmtdesc cam_fmtdesc; 
struct v4l2_frmsizeenum fsize;

cam_fmtdesc.index = 0;
cam_fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
printf("Support format:\n");
while(ioctl(fd, VIDIOC_ENUM_FMT, &cam_fmtdesc) != -1) {
    printf("  %d. %4s(%s)\n", cam_fmtdesc.index + 1, (char *)&cam_fmtdesc.pixelformat, cam_fmtdesc.description);
    cam_fmtdesc.index++;

    // enumerate frame sizes
    fsize.index = 0;
    fsize.pixel_format = cam_fmtdesc.pixelformat;
    while ((ret = ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsize)) == 0)
    {
        fsize.index++;
        if (fsize.type == V4L2_FRMSIZE_TYPE_DISCRETE)
        {
            printf("    discrete: %ux%u:   ", fsize.discrete.width, fsize.discrete.height);
            // enumerate frame rates
            struct v4l2_frmivalenum fival;
            fival.index = 0;
            fival.pixel_format = cam_fmtdesc.pixelformat;
            fival.width = fsize.discrete.width;
            fival.height = fsize.discrete.height;
            sizes_buf[fsize.index-1][0] = fsize.discrete.width;
            sizes_buf[fsize.index-1][1] = fsize.discrete.height;
            while ((ret = ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &fival)) == 0)
            {
                fival.index++;
                if (fival.type == V4L2_FRMIVAL_TYPE_DISCRETE)
                {
                    printf("%u/%u ", fival.discrete.numerator, fival.discrete.denominator);
                }
                else
                    printf("I only handle discrete frame intervals...\n");
            }
            printf("\n");
        }
        else if (fsize.type == V4L2_FRMSIZE_TYPE_CONTINUOUS)
        {
            printf("  continuous: %ux%u to %ux%u\n",
                fsize.stepwise.min_width, fsize.stepwise.min_height,
                fsize.stepwise.max_width, fsize.stepwise.max_height);
        }
        else if (fsize.type == V4L2_FRMSIZE_TYPE_STEPWISE)
        {
            printf("  stepwise: %ux%u to %ux%u step %ux%u\n",
                fsize.stepwise.min_width,  fsize.stepwise.min_height,
                        fsize.stepwise.max_width,  fsize.stepwise.max_height,
                        fsize.stepwise.step_width, fsize.stepwise.step_height);
        }
        else
        {
            printf("  fsize.type not supported: %d\n", fsize.type);
        }
    }
}

查询视频设备支持的功能

我们可以根据支持的功能实现相应的接口

static void enumerate_menu(int fd, unsigned int id, struct v4l2_queryctrl *queryctrl)
{
    printf("   Menu items:\n");
    struct v4l2_querymenu querymenu;
    memset(&querymenu, 0, sizeof(querymenu));
    querymenu.id = id;

    for (querymenu.index = queryctrl->minimum;
         querymenu.index <= queryctrl->maximum;
         querymenu.index++) {
        if (0 == ioctl(fd, VIDIOC_QUERYMENU, &querymenu)) {
            printf("\t%s\n", querymenu.name);
        }
    }
}
void enum_video_ctrls(int fd)
{
    struct v4l2_queryctrl qctrl;

    printf("=======================\n");
    printf("Discovering controls:\n");

    memset(&qctrl, 0, sizeof(qctrl));
    qctrl.id = V4L2_CTRL_FLAG_NEXT_CTRL;  //V4L2_CTRL_CLASS_USER  V4L2_CTRL_CLASS_MPEG
    while (0 == ioctl(fd, VIDIOC_QUERYCTRL, &qctrl))
    {
        /*if (V4L2_CTRL_ID2CLASS(qctrl.id) != V4L2_CTRL_CLASS_MPEG)
            break;*/

        if (!(qctrl.flags & V4L2_CTRL_FLAG_DISABLED)) {
            printf("---\n[Controls] %s\n", qctrl.name);
         
            printf("   id: %d\n", qctrl.id);
            switch (qctrl.type)
            {
                case V4L2_CTRL_TYPE_INTEGER:
                    printf("   type: INTEGER\n");
                    break;
                case V4L2_CTRL_TYPE_BOOLEAN:
                    printf("   type: BOOLEAN\n");
                    break;
                case V4L2_CTRL_TYPE_MENU:
                    printf("   type: MENU\n");
                    enumerate_menu(fd, qctrl.id, &qctrl);
                    break;
                case V4L2_CTRL_TYPE_BUTTON:
                    printf("   type: BUTTON\n");
                    break;
            }
            printf("   minimum: %d\n", qctrl.minimum);
            printf("   maximum: %d\n", qctrl.maximum);
            printf("   step: %d\n", qctrl.step);
            printf("   default_value: %d\n", qctrl.default_value);
            printf("   flags: %d\n\n", qctrl.flags);
        }
        qctrl.id |= V4L2_CTRL_FLAG_NEXT_CTRL;
    }
}

设置配置

/*设置帧率fps*/
parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
parm.parm.capture.timeperframe.numerator = 1;
parm.parm.capture.timeperframe.denominator = fps;
if(dev_id == LOCAL_CAMERA){
	parm.parm.capture.capturemode = mode;
}
if(ioctl(dev_fd[dev_id], VIDIOC_S_PARM, &parm) < 0) {
	printf("set frame rate failed\n");
	close(dev_fd[dev_id]);
	dev_fd[dev_id] = -1;
	return -1;
}
/*设置帧格式,长,宽,采样类型等*/
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = dev_frame_width;
    fmt.fmt.pix.height = dev_frame_height;
    fmt.fmt.pix.bytesperline = dev_frame_width;
    fmt.fmt.pix.priv = 0;
    fmt.fmt.pix.sizeimage = 0;
	fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420; // V4L2_PIX_FMT_YUV420 V4L2_PIX_FMT_MJPEG  V4L2_PIX_FMT_H264
	if(ioctl(dev_fd[dev_id], VIDIOC_S_FMT, &fmt) < 0) {
        printf("set format failed\n");
        close(dev_fd[dev_id]);
        dev_fd[dev_id] = -1;
        return -1;
    }

设置输入输出源 video Inputs and Outputs

输入输出视频源切换
VIDIOC_G_INPUT
VIDIOC_S_INPUT
ioctl(fd, VIDIOC_S_INPUT, &g_input/*&input.index*/)

请求BUF

struct v4l2_requestbuffers req = {0};
    req.count = TEST_BUFFER_NUM;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;
    if(ioctl(dev_fd[dev_id], VIDIOC_REQBUFS, &req) < 0) {
        printf("v4l_capture_setup: VIDIOC_REQBUFS failed\n");
        close(dev_fd[dev_id]);
        dev_fd[dev_id] = -1;
        return -1;
    }

三种交换数据的方法,

  • 直接 read/write、
  • 内存映射(V4L2_MEMORY_MMAP)
  • 用户指针(V4L2_MEMORY_USERPTR)

内存映射(V4L2_MEMORY_MMAP):

struct capture_testbuffer {
    unsigned char *start;
    size_t offset;
    unsigned int length;
    unsigned int bytesused;
};

int v4l_start_capturing(int dev_id) {
    unsigned int i;
    struct v4l2_buffer buf;
    enum v4l2_buf_type type;

    for(i = 0; i < TEST_BUFFER_NUM; i++) {
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;

        if(ioctl(dev_fd[dev_id], VIDIOC_QUERYBUF, &buf) < 0) {
            printf("VIDIOC_QUERYBUF error\n");
            return -1;
        }

        cap_buffers[dev_id][i].length = buf.length;
        cap_buffers[dev_id][i].offset = (size_t)buf.m.offset;
        cap_buffers[dev_id][i].start = mmap(NULL, cap_buffers[dev_id][i].length,
                                    PROT_READ | PROT_WRITE, MAP_SHARED, dev_fd[dev_id], cap_buffers[dev_id][i].offset);

        if(cap_buffers[dev_id][i].start == MAP_FAILED) {
            printf("v4l_start_capturing mmap  error\n");
            return -1;
        }
    }

    for(i = 0; i < TEST_BUFFER_NUM; i++) {
        memset(&buf, 0, sizeof(buf));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;
        buf.m.offset = cap_buffers[dev_id][i].offset;

        if(ioctl(dev_fd[dev_id], VIDIOC_QBUF, &buf) < 0) {
            printf("VIDIOC_QBUF error\n");
            return -1;
        }
    }

启动数据流

VIDIOC_STREAMON

    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if(ioctl(dev_fd[dev_id], VIDIOC_STREAMON, &type) < 0) {
        printf("VIDIOC_STREAMON error\n");
        return -1;
    }

    printf("v4l_start_capturing v4l2 ok\n");
    return 0;
}

获取数据:

VIDIOC_QBUF// 把帧放入队列
VIDIOC_DQBUF// 从队列中取出帧

struct v4l2_buffer buf;
memset(buf, 0, sizeof(struct v4l2_buffer));
buf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf->memory = V4L2_MEMORY_MMAP;

if(ioctl(cap_fd, VIDIOC_DQBUF, buf) < 0) {
	printf("v4l_get_capture_data failed\n");
	return -1;
}

*frame = cap_buffers[dev_id][buf.index].start;
*size = buf.bytesused; 

if(ioctl(cap_fd, VIDIOC_QBUF, buf) < 0)

停止数据流

VIDIOC_STREAMOFF

int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(dev_fd, VIDIOC_STREAMOFF, &type);

取消映射

for(i = 0; i < TEST_BUFFER_NUM; i++) {
	if(cap_buffers[dev_id][i].start > 0)
		munmap(cap_buffers[dev_id][i].start, cap_buffers[dev_id][i].length);

	cap_buffers[dev_id][i].start = NULL;
	cap_buffers[dev_id][i].length = 0;
}

关闭设备

close(dev_fd);