3.3. 流式I/O (用户指针)¶
当通过ioctl VIDIOC_QUERYCAP ioctl返回的v4l2_capability
结构体的capabilities
字段中的V4L2_CAP_STREAMING
标志被设置时,输入输出设备支持此I/O方法。如果支持特定的用户指针方法(不仅仅是内存映射),则必须通过调用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 会阻塞。当O_NONBLOCK
标志传递给open()
函数时,如果没有可用缓冲区,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]