3.3. 流式 I/O (用户指针)¶
当 v4l2_capability
结构体的 capabilities
字段中 V4L2_CAP_STREAMING
标志被设置时,输入和输出设备支持此 I/O 方法。该结构体由 ioctl VIDIOC_QUERYCAP ioctl 返回。 是否支持特定的用户指针方法(不仅仅是内存映射)必须通过调用 ioctl VIDIOC_REQBUFS ioctl 并将内存类型设置为 V4L2_MEMORY_USERPTR
来确定。
此 I/O 方法结合了读取/写入和内存映射方法的优点。缓冲区(平面)由应用程序本身分配,并且可以例如驻留在虚拟或共享内存中。仅交换数据指针,这些指针和元信息在 v4l2_buffer
结构体(或多平面 API 情况下的 v4l2_plane
结构体)中传递。驱动程序必须通过调用具有所需缓冲区类型的 ioctl VIDIOC_REQBUFS 来切换到用户指针 I/O 模式。不会预先分配缓冲区(平面),因此它们不会被索引,并且不能像使用 VIDIOC_QUERYBUF ioctl 映射的缓冲区一样进行查询。
3.3.1. 示例:使用用户指针初始化流式 I/O¶
struct v4l2_requestbuffers reqbuf;
memset (&reqbuf, 0, sizeof (reqbuf));
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.memory = V4L2_MEMORY_USERPTR;
if (ioctl (fd, VIDIOC_REQBUFS, &reqbuf) == -1) {
if (errno == EINVAL)
printf ("Video capturing or user pointer streaming is not supported\\n");
else
perror ("VIDIOC_REQBUFS");
exit (EXIT_FAILURE);
}
缓冲区(平面)的地址和大小通过 VIDIOC_QBUF ioctl 动态传递。尽管缓冲区通常是循环使用的,但应用程序可以在每次 VIDIOC_QBUF 调用时传递不同的地址和大小。如果硬件需要,驱动程序会交换物理内存中的内存页,以创建连续的内存区域。这在内核的虚拟内存子系统中对应用程序透明地发生。当缓冲区页被换出到磁盘时,它们会被带回并最终锁定在物理内存中以进行 DMA 操作。 [1]
已填充或显示的缓冲区使用 VIDIOC_DQBUF ioctl 出队。驱动程序可以在 DMA 完成和此 ioctl 调用之间的任何时间解锁内存页。当调用 VIDIOC_STREAMOFF、ioctl VIDIOC_REQBUFS 或关闭设备时,内存也会被解锁。应用程序必须注意不要在未出队的情况下释放缓冲区。首先,缓冲区会保持锁定更长时间,浪费物理内存。其次,当内存返回到应用程序的空闲列表并随后重新用于其他目的时,驱动程序将不会收到通知,这可能会完成请求的 DMA 并覆盖有价值的数据。
对于捕获应用程序,通常的做法是入队一些空缓冲区,开始捕获并进入读取循环。在这里,应用程序等待直到可以出队一个已填充的缓冲区,并在不再需要数据时重新入队该缓冲区。输出应用程序填充并入队缓冲区,当堆叠了足够的缓冲区后,开始输出。在写入循环中,当应用程序用完空闲缓冲区时,它必须等待直到可以出队并重用一个空缓冲区。存在两种方法来暂停应用程序的执行,直到可以出队一个或多个缓冲区。默认情况下,当输出队列中没有缓冲区时,VIDIOC_DQBUF 会阻塞。当 open()
函数被赋予 O_NONBLOCK
标志时,当没有可用的缓冲区时,VIDIOC_DQBUF 会立即返回 EAGAIN
错误代码。select() 或 poll()
函数始终可用。
要开始和停止捕获或输出应用程序,请调用 VIDIOC_STREAMON 和 VIDIOC_STREAMOFF ioctl。
注意
VIDIOC_STREAMOFF 会从两个队列中删除所有缓冲区,并作为副作用解锁所有缓冲区。由于在多任务处理系统上没有“立即”执行任何操作的概念,如果应用程序需要与其他事件同步,它应该检查捕获或输出的缓冲区的 v4l2_buffer
结构体的 timestamp
。
实现用户指针 I/O 的驱动程序必须支持 VIDIOC_REQBUFS、VIDIOC_QBUF、VIDIOC_DQBUF、VIDIOC_STREAMON 和 VIDIOC_STREAMOFF ioctl,以及 select()
和 poll()
函数。 [2]