V4L2文檔翻譯(十二)

http://linuxtv.org/downloads/v4l-dvb-apis/io.html

第三節:輸入和輸出

V4L2 API定義了一些不同的方法來從設備讀取或寫入,所有需要與應用程序交換數據的驅動最少必須支持其中之一。

在打開一個V4L2設備後會自動選擇使用經典的I/O方法read()和write(),當驅動不支持寫或讀時會失敗。

其他的方法必須通過協商。應用程序通過VIDIOC_REQBUFS ioctl來選擇內存映射或用戶緩存的I/O流,I/O同步方法未定義。

儘管應用程序不直接接受圖片數據,視頻overlay也可被考慮做其他I/O方法。初始化視頻overlay是通過VIDIOC_S_FMT ioctl,其他信息見“視頻Overlay接口”章節。

通常一個I/O方法,包括overlay,是與每一個文件描述符關聯的。只是以下情況是例外,若應用程序不與驅動進行數據交換(錶盤程序,見“打開關閉設備”章節),以及驅動准許使用同一個文件描述符進行視頻捕捉和overlay,這是爲了對V4L和早期版本的V4L2兼容。

VIDIOC_S_FMT和VIDIOC_REQBUFS會在某方面允許這種情況,但是​對於關閉和重打開設備來說簡單驅動不需要支持I/O方法切換(第一次切換read/write後)。之後的小節描述了各種I/O方法的細節。

讀/寫

若VIDIOC_QUERYCAP ioctl返回的struct v4l2_capability中capabilities包含了V4L2_CAP_READWRITE時,輸入和輸出設備需要分別支持read()和write()函數。

驅動可能需要CPU進行數據拷貝操作,但是他們也可以通過支持DMA來處理用戶內存,所以此I/O方法並不一定比其他只是交換緩衝區指針的方法效率低。因爲像幀計數器或時間戳傳輸的無元數據的一些類型,所以它不優先考慮,此信息對於識別掉幀和與其他數據流同步來說很有必要。無論怎樣,這是個簡單的IO方法,需要很少甚至不需要任何的設置來交換數據。它允許在命令行中使用(vidvtrl工具的虛構的):

> vidctrl /dev/video --input=0 --format=YUYV --size=352x288
> dd if=/dev/video of=myimage.422 bs=202752 count=1

應用程序使用read()函數從設備中讀取,使用write()函數對設備進行寫入,若驅動與應用程序交換數據則必須聲明一個I/O方法,但是不一定像是這樣。若讀取或寫入支持了,那麼驅動還必須支持select()和poll()函數。

在驅動等級select()和poll()是一樣的,select()是非常重要的選項。

I/O流 (內存映射)

若VIDIOC_QUERYCAP ioctl後返回的struct v4l2_capability中capabilities包含了V4L2_CAP_STREAMING標誌則輸入、輸出設備要支持這個I/O方法。有兩種流方法,應用程序通過VIDIOC_REQBUFS ioctl決定內存映射特性是否支持。

應用程序和驅動只交換緩存指針,而數據本身並不被拷貝的I/O方法叫做“流”。內存映射即是將設備內存中的緩存映射到應用程序的地址空間去,設備內存可以是比如做視頻捕捉等時顯卡上的視頻內存。無論如何,作爲長久以來效率最高的I/O方法,非常多的驅動支持流,他們將在可DMA操作的主內存中申請緩存。

一個驅動可支持許多套緩存,每一套通過一個獨一無二的緩存類型值定義。他們都是互相獨立的,而且每一套緩存處理不同的數據類型。要同時訪問不同的緩存必須通過使用不同的文件描述符。

應用程序通過VIDIOC_REQBUFS ioctl申請設備緩存,同時要帶入需求的緩存數量和緩存類型,比如V4L2_BUF_TYPE_VIDEO_CAPTURE。這個ioctl也可以用來改變緩存數量或釋放已申請的內存,對已映射的緩存無效。

應用程序在操作緩存前必須要通過mmap()函數將他們映射到應用程序地址空間中,而所映射的緩存在設備內存中具體哪個位置是由VIDIOC_QUERYBUF ioctl決定的。在單一平面API中,返回的struct v4l2_buffer中m.offset和length成員用作mmap()函數的第六個和第二個參數。多平面API中,struct v4l2_buffer結構體包含了struct v4l2_plane結構體集合,每一個都包含了各自的,m.offset和length。當使用多平面API時,每個緩存衝的每個平面都需要分別映射,所以調用mmap()的次數就等於緩存數乘以每個緩存內的平面數量。offset和length的值必須不能修改。切記,緩存被分配在物理內存中,不同於虛擬內存的是,可以與硬盤做交換。應用程序在執行了munmap()函數後要儘快釋放緩存。

例3.1 單平面API中的緩存映

struct v4l2_requestbuffers reqbuf;
struct {
    void *start;
    size_t length;
} *buffers;
unsigned int i;

memset(&reqbuf, 0, sizeof(reqbuf));
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
reqbuf.memory = V4L2_MEMORY_MMAP;
reqbuf.count = 20;

if (-1 == ioctl (fd, VIDIOC_REQBUFS, &reqbuf)) {
    if (errno == EINVAL)
        printf("Video capturing or mmap-streaming is not supported\n");
    else
        perror("VIDIOC_REQBUFS");

    exit(EXIT_FAILURE);
}

/* We want at least five buffers. */

if (reqbuf.count < 5) {
    /* You may need to free the buffers here. */
    printf("Not enough buffer memory\n");
    exit(EXIT_FAILURE);
}

buffers = calloc(reqbuf.count, sizeof(*buffers));
assert(buffers != NULL);

for (i = 0; i < reqbuf.count; i++) {
    struct v4l2_buffer buffer;

    memset(&buffer, 0, sizeof(buffer));
    buffer.type = reqbuf.type;
    buffer.memory = V4L2_MEMORY_MMAP;
    buffer.index = i;

    if (-1 == ioctl (fd, VIDIOC_QUERYBUF, &buffer)) {
        perror("VIDIOC_QUERYBUF");
        exit(EXIT_FAILURE);
    }

    buffers[i].length = buffer.length; /* remember for munmap() */

    buffers[i].start = mmap(NULL, buffer.length,
                PROT_READ | PROT_WRITE, /* recommended */
                MAP_SHARED,             /* recommended */
                fd, buffer.m.offset);

    if (MAP_FAILED == buffers[i].start) {
        /* If you do not exit here you should unmap() and free()
           the buffers mapped so far. */
        perror("mmap");
        exit(EXIT_FAILURE);
    }
}

/* Cleanup. */

for (i = 0; i < reqbuf.count; i++)
    munmap(buffers[i].start, buffers[i].length);

例3.2 多平面API中的緩存映射

struct v4l2_requestbuffers reqbuf;
/* Our current format uses 3 planes per buffer */
#define FMT_NUM_PLANES = 3

struct {
    void *start[FMT_NUM_PLANES];
    size_t length[FMT_NUM_PLANES];
} *buffers;
unsigned int i, j;

memset(&reqbuf, 0, sizeof(reqbuf));
reqbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
reqbuf.memory = V4L2_MEMORY_MMAP;
reqbuf.count = 20;

if (ioctl(fd, VIDIOC_REQBUFS, &reqbuf) < 0) {
    if (errno == EINVAL)
        printf("Video capturing or mmap-streaming is not supported\n");
    else
        perror("VIDIOC_REQBUFS");

    exit(EXIT_FAILURE);
}

/* We want at least five buffers. */

if (reqbuf.count < 5) {
    /* You may need to free the buffers here. */
    printf("Not enough buffer memory\n");
    exit(EXIT_FAILURE);
}

buffers = calloc(reqbuf.count, sizeof(*buffers));
assert(buffers != NULL);

for (i = 0; i < reqbuf.count; i++) {
    struct v4l2_buffer buffer;
    struct v4l2_plane planes[FMT_NUM_PLANES];

    memset(&buffer, 0, sizeof(buffer));
    buffer.type = reqbuf.type;
    buffer.memory = V4L2_MEMORY_MMAP;
    buffer.index = i;
    /* length in struct v4l2_buffer in multi-planar API stores the size
     * of planes array. */
    buffer.length = FMT_NUM_PLANES;
    buffer.m.planes = planes;

    if (ioctl(fd, VIDIOC_QUERYBUF, &buffer) < 0) {
        perror("VIDIOC_QUERYBUF");
        exit(EXIT_FAILURE);
    }

    /* Every plane has to be mapped separately */
    for (j = 0; j < FMT_NUM_PLANES; j++) {
        buffers[i].length[j] = buffer.m.planes[j].length; /* remember for munmap() */

        buffers[i].start[j] = mmap(NULL, buffer.m.planes[j].length,
                 PROT_READ | PROT_WRITE, /* recommended */
                 MAP_SHARED,             /* recommended */
                 fd, buffer.m.planes[j].m.offset);

        if (MAP_FAILED == buffers[i].start[j]) {
            /* If you do not exit here you should unmap() and free()
               the buffers and planes mapped so far. */
            perror("mmap");
            exit(EXIT_FAILURE);
        }
    }
}

/* Cleanup. */

for (i = 0; i < reqbuf.count; i++)
    for (j = 0; j < FMT_NUM_PLANES; j++)
        munmap(buffers[i].start[j], buffers[i].length[j]);

概念上講,流驅動包含兩個緩存序列,一個傳入序列、一個傳出序列。他們使同步捕捉或輸出操作從應用程序分開來鎖定到視頻時鐘上,因爲應用程序可能受限於隨機的硬盤或網絡延遲,還有其他進程的搶佔,因此減少了數據丟失的可能性。這些序列由FIFO管道組成,緩存可以在輸入管道上進行輸出,可以在輸出管道上進行捕捉。

驅動可能會一直需要一個隊列緩存的最小數,在沒有緩存限制的情況下應用程序可以預先裝配、解除和處理。他們還可以在緩存解除前以不同的順序進行隊列裝配,驅動可以用任何順序對空緩存進行填充。緩存的索引號並沒有任何規定,只要是唯一的就好。

初始化所有映射緩存要在其沒有進入隊列的時候進行,驅動很難做到這一點。對於捕捉應用來說,通常是先裝配所有已映射緩存,然後開始捕捉並進入讀循環中。應用程序會一直等到一個被填充的緩存能被解除(移除隊列),然後若不再需要其數據了就重新將其放入隊列。輸出程序填充緩存,然後將其放入隊列中,當累計了足夠的緩存後開始VIDIOC_STREAMON。在寫循環中,若應用程序把空閒緩存用完了,那麼必須等待有空的buffer可以移除隊列和再利用。

應用程序通過VIDIOC_QBUF和VIDIOC_DQBUF來使一個緩存入列和出列。緩存的狀態是已映射、已入列、滿還是空可以在任何時候通過VIDIOC_QUERYBUF ioctl進行查詢。有兩個方法使應用程序的執行掛起來等待可出列的緩存。VIDIOC_DQBUF會在傳出序列中沒有緩存時自動阻塞,若打開設備的時候設置了O_NONBLOCK標誌則VIDIOC_DQBUF會在沒有可用緩存時返回EAGAIN。select()或poll()函數總是可用的。

應用程序通過調用VIDIOC_STREAMON和VIDIOC_STREAMOFF ioctl來開始、停止捕捉或輸出。注意VIDIOC_STREAMOFF會移除掉所有的緩存。因爲在多任務操作系統中並沒有“當前”的概念,所以如果應用程序需要與其他項目進行同步,那麼它應該檢查struct v4l2_buffer中捕捉的或輸出的緩存timestamp。

聲明瞭內存映射I/O的驅動必須支持VIDIOC_REQBUFS, VIDIOC_QUERYBUF, VIDIOC_QBUF, VIDIOC_DQBUF, VIDIOC_STREAMON和VIDIOC_STREAMOFF ioctl,和mmap()、munmap()、select()及poll()函數。

I/O流 (用戶指針)

若VIDIOC_QUERYCAP ioctl後返回的struct v4l2_capability中capabilities包含了V4L2_CAP_STREAMING標誌則輸入、輸出設備要支持這個I/O方法。是否需要支持用戶指針方法由VIDIOC_REQBUFS ioctl決定。

此I/O方法結合了高級讀寫以及內存映射方法。緩存(平面)通過應用程序本身進行申請,可以貯存在虛擬或共享內存中,需要交換的只是數據指針。這些指針和元數據在struct v4l2_buffer(對多平面API來說是struct v4l2_plane)中。若調用VIDIOC_REQBUFS且帶有相應緩存類型,則驅動必須切換到用戶指針I/O模式。不需要預先分配緩存(平面),因此他們沒有索引,也不能像映射緩存那樣通過VIDIOC_QUERYBUF ioctl進行查詢。

例3.3 初始化用戶指針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使用。

填充的或顯示的緩存通過VIDIOC_DQBUF ioctl出列。驅動可以在DMA完成和這個ioctl間的任何時候解鎖內存頁。當VIDIOC_STREAMOFF、VIDIOC_REQBUFS調用或設備關閉時內存也會被解鎖。應用程序必須要注意,不要把未出列的緩存釋放掉。那樣的話,這些緩存一直是鎖定的,浪費着物理內存。還有一點需要注意的是,當內存被應用程序釋放然後利用爲其他目的的時候,驅動並不會被通知,比如完成了請求的DMA以及更新了有效數據。

對於捕捉應用程序來說,將一定數量的空緩存入列是很正常的,他們是爲了開啓捕捉以及進入讀循環。應用程序會等待一個填充後的緩存何時能被出列,然後當其數據不再需要的時候將此緩存重新入列。輸出應用程序填充緩存,然後將緩存入列,當累積了足夠的緩存輸出動作就開始了。在寫循環中,若應用程序用光了空閒緩存,那麼他必須等到某個空緩存可以被出列,然後重新利用它。應用程序通過掛起的方式等待緩存有兩種途徑,默認的是當傳出序列中沒有緩存時VIDIOC_DQBUF會阻塞住。而如果在打開設備時候open()設定了O_NONBLOCK參數,那麼VIDIOC_DQBUF在這種情況下會直接返回EAGAIN錯誤代碼。這select()和poll()往往都是有效的。

可以通過VIDIOC_STREAMON和VIDIOC_STREAMOFF來控制捕捉或輸出應用程序的開始和停止。需要注意的是,VIDIOC_STREAMOFF存在着一定的副作用,就是它會將所有序列衝的緩存移除,並將他們解鎖。由於在多任務系統中並沒有“當前”的概念,所以如果一個應用程序需要同其他事件進行同步,應該通過檢查捕捉或輸出的緩存struct v4l2_buffer中的timestamp成員來達成同步的目的。

實現用戶指針I/O方法的驅動必須支持VIDIOC_REQBUFS,VIDIOC_QBUF,VIDIOC_DQBUF,VIDIOC_STREAMON及VIDIOC_STREAMOFF ioctl,還有select()和poll()函數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章