美文网首页
【转】Linux V4L2 Camera 编程

【转】Linux V4L2 Camera 编程

作者: RonZheng2010 | 来源:发表于2019-03-05 22:49 被阅读0次

    V4L2(Video For Linux Two) 是Linux内核提供给应用程序访问音、视频驱动的统一接口。这里描述的是如何从遵循V4L2规范的Camera设备读取Video帧。

    1. 打开设备

    int fd = open (“/dev/video0”, O_RDWR | O_NONBLOCK, 0);
    

    2. 查询设备的Capability

    查询设备的capability,可以从capability判断设备的类型、特性等。这一步不是必需的,但如果程序需要支持多种型号的设备,capability就很有用了。

    struct v4l2_capability cap;
    int ret = ioctl (fd, VIDIOC_QUERYCAP, &cap);
    

    一般的设备支持read()和write(),读写需要复制数据。对于Camera设备,应用程序和驱动只交换缓存指针,不拷贝数据。这种I/O方法叫做“流”。

    如果capability包含V4L2_CAP_STREAMING标志,则支持“流”。

    3. 设置视频源

    如果一个video设备有多个视频源,则可能需要使用VIDIOC_S_INPUT,在视频源间切换。

    struct v4l2_input inp;
    inp.index = 0;
    ret = ioctl (fd, VIDIOC_S_INPUT, &inp);
    

    4. 设置Capture参数

    Capture参数包括模式、帧率等。

    struct v4l2_streamparm params;
    memset (&params, 0, sizeof(params));
    params.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    params.parm.capture.capturemode = V4L2_MODE_HIGHQUALITY;
    params.parm.capture.timeperframe.numerator = 1;
    params.parm.capture.timeperframe.denominator = 25;
    
    ret = ioctl (fd, VIDIOC_S_PARM, &params);
    

    5. 设置视频参数

    视频参数包括缓存类型、视频大小、场类型、帧格式等。

    struct v4l2_format fmt;
    memset(&fmt, 0, sizeof(fmt));
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.width = width;
    fmt.fmt.pix.height = height;
    fmt.fmt.pix_mp.field = V4L2_FIELD_NONE;
    fmt.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV21;
    
    ret = ioctl (fd, VIDIOC_S_FMT, &fmt);
    

    设备可能不支持指定的视频参数,可以用VIDIOC_G_FMT检查设置是否成功。

    struct v4l2_format fmt;
    ret = ioctl (fd, VIDIOC_G_FMT, &fmt);
    

    6. 分配缓存

    Camera设备有三个队列。缓存刚分配时放在EMPTY队列,等待设备填充时放在IN队列,设备填充好时放在OUT队列。

    下面的代码中,使用V4L2_MEMORY_MMAP要求设备分配6个缓存。这时缓存放在EMPTY队列。

    struct v4l2_requestbuffers rb;
    memset (&rb, 0, sizeof(rb));
    rb.type   = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    rb.memory = V4L2_MEMORY_MMAP;
    rb.count  = 6;
    
    ret = ioctl (fd, VIDIOC_REQBUFS, &rb);
    

    V4L2_MEMORY_MMAP之外还可能有其他选择,如V4L2_MEMORY_DMABUF。这时缓存不是设备分配,而是应用程序传进去。它可能来自其他Video设备,或任何能分配DMABUF的设备。

    7. 得到所有缓存,放入设备的 EMPTY 队列

    下面的代码中,使用索引值依次得到6个缓存。对每个缓存,使用VIDIOC_QUERYBUF从EMPTY 队列取出,再使用VIDIOC_QBUF放到 IN 队列。如果应用层代码需要处理帧,则可以使用mmap()将它映射到虚拟地址空间。

    void* mmap[6];
    int mlen;
    struct v4l2_buffer buf;
    for (int i = 0; i < 6; i++) 
    {
        memset (&buf, 0, sizeof(struct v4l2_buffer));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = i;
        
        // get it from EMPTY  queue.
        ret = ioctl (fd, VIDIOC_QUERYBUF, &buf);
    
        // map it to file descriptor
        mmap[i] = mmap (0, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset);
        mlen = buf.length;
    
        // put it into IN queue.
        ret = ioctl (fd, VIDIOC_QBUF, &buf);
    }
    
    

    如果设备支持,也可以使用VIDIOC_EXPBUF导出buffer,得到dmafd。

    • 其他模块,如OpenGL,可以使用这个dmafd显示图像。
    • 也可以使用send_fd()将dmafd传给其他进程处理。这样做的好处是避免大块数据的复制。
    struct v4l2_exportbuffer expbuf;
    memset (&expbuf, 0, sizeof(expbuf));
    expbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    expbuf.index = i;
    ret = ioctl (fd, VIDIOC_EXPBUF, &expbuf);
    
    int dmfd = expbuf.fd;
    

    8. 开始Capture

    使用VIDIOC_STREAMON要求设备开始工作。

    enum v4l2_buf_type type;
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl (fd, VIDIOC_STREAMON, &type);
    

    9. 读帧

    下面的代码监听设备,如果有数据,则使用VIDIOC_DQBUF读取帧,然后传给其他线程做进一步处理。

    fd_set fds;
    FD_ZERO (&fds);
    FD_SET (fd, &fds);
    ret = select (fd + 1, &fds, NULL, NULL, NULL);
    if (ret > 0)
    {
        struct v4l2_buffer buf; 
        memset (&buf, 0, sizeof(v4l2_buffer));
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        ret = ioctl (fd, VIDIOC_DQBUF, buf);
    
        // hand it over for next step
        ...
    }
    

    10. 归还缓存

    其他线程处理帧后,使用VIDIOC_QBUF将缓存归还给设备。前面已经做过这件事了。

    struct v4l2_buffer buf;
    memset (&buf, 0, sizeof(struct v4l2_buffer));
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index = i;
    ret = ioctl (fd, VIDIOC_QBUF, &buf);
    

    11. 缓存 单平面 vs 多平面

    这个例子中演示的设备是单平面模式 V4L2_BUF_TYPE_VIDEO_CAPTURE,就是一个v4l2_buffer缓存中只有一个平面。

    另外一种设备是多平面模式V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,就是一个v4l2_buffer缓存中有一个或多个平面。

    参考资料

    Linux之V4L2基础编程
    https://www.cnblogs.com/emouse/archive/2013/03/04/2943243.html

    ioctl VIDIOC_EXPBUF
    https://linuxtv.org/downloads/v4l-dvb-apis/uapi/v4l/vidioc-expbuf.html#vidioc-expbuf

    Streaming I/O (DMA buffer importing)
    https://blog.csdn.net/jk198310/article/details/78365224

    V4L2文档翻译:输入和输出
    https://blog.csdn.net/airk000/article/details/25033269

    相关文章

      网友评论

          本文标题:【转】Linux V4L2 Camera 编程

          本文链接:https://www.haomeiwen.com/subject/ovzguqtx.html