【转】Linux V4L2 Camera 编程

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 (¶ms, 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, ¶ms);

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 编程)