3.4. 流式 I/O (DMA 缓冲区导入)¶
DMABUF 框架提供了一种在多个设备之间共享缓冲区的通用方法。支持 DMABUF 的设备驱动程序可以将 DMA 缓冲区导出到用户空间作为文件描述符(称为导出器角色),可以使用先前为不同或相同设备导出的文件描述符从用户空间导入 DMA 缓冲区(称为导入器角色),或者两者都支持。本节介绍 V4L2 中的 DMABUF 导入器角色 API。
有关将 V4L2 缓冲区导出为 DMABUF 文件描述符的详细信息,请参阅 DMABUF 导出。
当 v4l2_capability
结构体的 capabilities
字段中的 V4L2_CAP_STREAMING
标志被设置时,输入和输出设备支持流式 I/O 方法。此结构体由 VIDIOC_QUERYCAP ioctl 返回。是否支持通过 DMABUF 文件描述符导入 DMA 缓冲区,由调用 VIDIOC_REQBUFS ioctl 并将内存类型设置为 V4L2_MEMORY_DMABUF
来确定。
此 I/O 方法专用于在不同设备之间共享 DMA 缓冲区,这些设备可以是 V4L 设备或其他与视频相关的设备(例如,DRM)。缓冲区(平面)由驱动程序代表应用程序分配。接下来,这些缓冲区使用特定于分配器驱动程序的 API 导出到应用程序作为文件描述符。只有这样的文件描述符才会被交换。描述符和元信息在 v4l2_buffer
结构体中传递(或者在多平面 API 的情况下在 v4l2_plane
结构体中传递)。驱动程序必须通过调用 VIDIOC_REQBUFS 并使用所需的缓冲区类型切换到 DMABUF I/O 模式。
3.4.1. 示例:使用 DMABUF 文件描述符启动流式 I/O¶
struct v4l2_requestbuffers reqbuf;
memset(&reqbuf, 0, sizeof (reqbuf));
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.memory = V4L2_MEMORY_DMABUF;
reqbuf.count = 1;
if (ioctl(fd, VIDIOC_REQBUFS, &reqbuf) == -1) {
if (errno == EINVAL)
printf("Video capturing or DMABUF streaming is not supported\\n");
else
perror("VIDIOC_REQBUFS");
exit(EXIT_FAILURE);
}
缓冲区(平面)文件描述符通过 VIDIOC_QBUF ioctl 动态传递。在多平面缓冲区的情况下,每个平面都可以与不同的 DMABUF 描述符关联。尽管缓冲区通常是循环使用的,但应用程序可以在每次 VIDIOC_QBUF 调用时传递不同的 DMABUF 描述符。
3.4.2. 示例:使用单平面 API 排队 DMABUF¶
int buffer_queue(int v4lfd, int index, int dmafd)
{
struct v4l2_buffer buf;
memset(&buf, 0, sizeof buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_DMABUF;
buf.index = index;
buf.m.fd = dmafd;
if (ioctl(v4lfd, VIDIOC_QBUF, &buf) == -1) {
perror("VIDIOC_QBUF");
return -1;
}
return 0;
}
3.4.3. 示例 3.6. 使用多平面 API 排队 DMABUF¶
int buffer_queue_mp(int v4lfd, int index, int dmafd[], int n_planes)
{
struct v4l2_buffer buf;
struct v4l2_plane planes[VIDEO_MAX_PLANES];
int i;
memset(&buf, 0, sizeof buf);
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
buf.memory = V4L2_MEMORY_DMABUF;
buf.index = index;
buf.m.planes = planes;
buf.length = n_planes;
memset(&planes, 0, sizeof planes);
for (i = 0; i < n_planes; ++i)
buf.m.planes[i].m.fd = dmafd[i];
if (ioctl(v4lfd, VIDIOC_QBUF, &buf) == -1) {
perror("VIDIOC_QBUF");
return -1;
}
return 0;
}
捕获或显示的缓冲区通过 VIDIOC_DQBUF ioctl 出队。驱动程序可以在 DMA 完成和此 ioctl 之间的任何时间解锁缓冲区。当调用 VIDIOC_STREAMOFF、VIDIOC_REQBUFS 或设备关闭时,也会解锁内存。
对于捕获应用程序,通常的做法是排队多个空缓冲区,开始捕获并进入读取循环。在这里,应用程序等待直到可以出队一个已填充的缓冲区,并在不再需要数据时重新排队该缓冲区。输出应用程序填充并排队缓冲区,当堆叠足够的缓冲区时,启动输出。在写入循环中,当应用程序用完空闲缓冲区时,它必须等待直到可以出队并重用一个空缓冲区。有两种方法可以暂停应用程序的执行,直到可以出队一个或多个缓冲区。默认情况下,当传出队列中没有缓冲区时,VIDIOC_DQBUF 会阻塞。当 open()
函数被赋予 O_NONBLOCK
标志时,如果没有可用的缓冲区,VIDIOC_DQBUF 会立即返回并返回 EAGAIN
错误代码。select()
和 poll()
函数始终可用。
要启动和停止捕获或显示应用程序,请调用 VIDIOC_STREAMON 和 VIDIOC_STREAMOFF ioctl。
注意
VIDIOC_STREAMOFF 作为副作用,会从两个队列中删除所有缓冲区并解锁所有缓冲区。由于在多任务系统上没有“立即”执行任何操作的概念,如果应用程序需要与其他事件同步,则应检查捕获或输出缓冲区的 v4l2_buffer
timestamp
。
实现 DMABUF 导入 I/O 的驱动程序必须支持 VIDIOC_REQBUFS、VIDIOC_QBUF、VIDIOC_DQBUF、VIDIOC_STREAMON 和 VIDIOC_STREAMOFF ioctl,以及 select()
和 poll()
函数。