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() 函式。