3.4. 流式 I/O (DMA 缓冲区导入)

DMABUF 框架提供了一种在多个设备之间共享缓冲区的通用方法。 支持 DMABUF 的设备驱动程序可以将 DMA 缓冲区作为文件描述符导出到用户空间(称为导出者角色),使用先前为不同或相同设备导出的文件描述符从用户空间导入 DMA 缓冲区(称为导入者角色),或者两者兼而有之。 本节介绍 V4L2 中的 DMABUF 导入器角色 API。

有关将 V4L2 缓冲区导出为 DMABUF 文件描述符的详细信息,请参阅 DMABUF 导出

VIDIOC_QUERYCAP ioctl 返回的 struct v4l2_capabilitycapabilities 字段中的 V4L2_CAP_STREAMING 标志已设置时,输入和输出设备支持流式 I/O 方法。 是否支持通过 DMABUF 文件描述符导入 DMA 缓冲区由调用 VIDIOC_REQBUFS ioctl 并将内存类型设置为 V4L2_MEMORY_DMABUF 来确定。

此 I/O 方法专用于在不同设备之间共享 DMA 缓冲区,这些设备可能是 V4L 设备或其他与视频相关的设备(例如 DRM)。 缓冲区(平面)由驱动程序代表应用程序分配。 接下来,这些缓冲区使用特定于分配器驱动程序的 API 作为文件描述符导出到应用程序。 仅交换此类文件描述符。 描述符和元信息在 struct v4l2_buffer 中(或者在多平面 API 的情况下,在 struct 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 会从两个队列中删除所有缓冲区,并解锁所有缓冲区作为副作用。 由于在多任务处理系统中没有“现在”执行任何操作的概念,因此如果应用程序需要与其他事件同步,它应该检查捕获或输出缓冲区的 struct v4l2_buffer timestamp

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