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_STREAMOFFVIDIOC_REQBUFS 或设备关闭时,也会解锁内存。

对于捕获应用程序,通常的做法是排队多个空缓冲区,开始捕获并进入读取循环。在这里,应用程序等待直到可以出队一个已填充的缓冲区,并在不再需要数据时重新排队该缓冲区。输出应用程序填充并排队缓冲区,当堆叠足够的缓冲区时,启动输出。在写入循环中,当应用程序用完空闲缓冲区时,它必须等待直到可以出队并重用一个空缓冲区。有两种方法可以暂停应用程序的执行,直到可以出队一个或多个缓冲区。默认情况下,当传出队列中没有缓冲区时,VIDIOC_DQBUF 会阻塞。当 open() 函数被赋予 O_NONBLOCK 标志时,如果没有可用的缓冲区,VIDIOC_DQBUF 会立即返回并返回 EAGAIN 错误代码。select()poll() 函数始终可用。

要启动和停止捕获或显示应用程序,请调用 VIDIOC_STREAMONVIDIOC_STREAMOFF ioctl。

注意

VIDIOC_STREAMOFF 作为副作用,会从两个队列中删除所有缓冲区并解锁所有缓冲区。由于在多任务系统上没有“立即”执行任何操作的概念,如果应用程序需要与其他事件同步,则应检查捕获或输出缓冲区的 v4l2_buffer timestamp

实现 DMABUF 导入 I/O 的驱动程序必须支持 VIDIOC_REQBUFSVIDIOC_QBUFVIDIOC_DQBUFVIDIOC_STREAMONVIDIOC_STREAMOFF ioctl,以及 select()poll() 函数。