4. 請求 API

請求 API 旨在允許 V4L2 處理現代裝置(無狀態編解碼器、複雜攝像頭管道等)和 API(Android Codec v2)的需求。其中一個需求是屬於同一管道的裝置能夠在每個幀的基礎上重新配置和密切協作。另一個需求是支援無狀態編解碼器,這需要將控制元件應用於特定幀(也稱為“每幀控制元件”),以便有效使用。

雖然最初的用例是 V4L2,但只要它們使用媒體控制器,就可以將其擴充套件到其他子系統。

在沒有請求 API 的情況下支援這些功能並非總是可行,如果可行,效率會非常低:使用者空間必須重新整理媒體管道上的所有活動,為下一幀重新配置它,將要處理的緩衝區與該配置一起排隊,並等到它們都可用於出隊後才能考慮下一幀。這破壞了擁有緩衝區佇列的目的,因為實際上一次只會排隊一個緩衝區。

請求 API 允許將管道的特定配置(媒體控制器拓撲 + 每個媒體實體的配置)與特定緩衝區相關聯。這允許使用者空間提前安排多個具有不同配置的任務(“請求”),並知道該配置將在需要時應用以獲得預期的結果。請求完成時的配置值也可用於讀取。

4.1. 一般用法

請求 API 擴充套件了媒體控制器 API,並與子系統特定的 API 協作以支援請求使用。在媒體控制器級別,請求是從支援的媒體控制器裝置節點分配的。然後,透過請求檔案描述符以不透明的方式管理其生命週期。儲存在請求中的配置資料、緩衝區控制代碼和處理結果透過擴充套件為支援請求的子系統特定的 API 訪問,例如採用顯式 request_fd 引數的 V4L2 API。

4.2. 請求分配

使用者空間使用 ioctl MEDIA_IOC_REQUEST_ALLOC 為媒體裝置節點分配請求。這會返回一個表示請求的檔案描述符。通常,會分配多個此類請求。

4.3. 請求準備

然後,標準 V4L2 ioctl 可以接收請求檔案描述符,以表達該 ioctl 是該請求的一部分,而不是立即應用。請參閱 ioctl MEDIA_IOC_REQUEST_ALLOC 以獲取支援此功能的 ioctl 列表。使用 request_fd 引數設定的配置會被儲存而不是立即應用,並且排隊到請求的緩衝區在請求本身排隊之前不會進入常規緩衝區佇列。

4.4. 請求提交

一旦指定了請求的配置和緩衝區,就可以透過在請求檔案描述符上呼叫 ioctl MEDIA_REQUEST_IOC_QUEUE 來將其排隊。請求必須至少包含一個緩衝區,否則會返回 ENOENT。排隊的請求不能再被修改。

注意

對於 memory-to-memory devices,您只能將請求用於輸出緩衝區,而不能用於捕獲緩衝區。嘗試將捕獲緩衝區新增到請求將導致 EBADR 錯誤。

如果請求包含多個實體的配置,則各個驅動程式可能會同步,以便在處理緩衝區之前應用請求的管道的拓撲。由於硬體限制,可能無法實現完美的原子性,因此媒體控制器驅動程式會盡最大努力實現。

注意

不允許將排隊請求與直接排隊緩衝區混合使用:無論先使用哪種方法,都會將其鎖定到位,直到呼叫 VIDIOC_STREAMOFF 或裝置被 關閉。如果在之前透過請求排隊緩衝區或反之亦然的情況下嘗試直接排隊緩衝區,將導致 EBUSY 錯誤。

控制元件仍然可以在沒有請求的情況下設定,並且會立即應用,無論是否正在使用請求。

注意

透過請求和直接設定相同的控制元件可能會導致未定義的行為!

使用者空間可以 poll() 請求檔案描述符,以便等待請求完成。一旦請求的所有關聯緩衝區都可用於出隊,並且所有關聯控制元件都已使用完成時的值更新,則該請求被視為已完成。請注意,使用者空間不需要等待請求完成即可將其緩衝區出隊:請求完成一半時可用的緩衝區可以獨立於請求的狀態出隊。

完成的請求包含請求執行後設備的狀態。使用者空間可以透過使用請求檔案描述符呼叫 ioctl VIDIOC_G_EXT_CTRLS 來查詢該狀態。為已排隊但尚未完成的請求呼叫 ioctl VIDIOC_G_EXT_CTRLS 將返回 EBUSY,因為驅動程式在請求進行中時可能隨時更改控制元件值。

4.5. 回收和銷燬

最後,可以丟棄或重複使用已完成的請求。在請求檔案描述符上呼叫 close() 將使該檔案描述符不可用,並且一旦核心不再使用該請求,該請求將被釋放。也就是說,如果請求已排隊,然後檔案描述符被關閉,則直到驅動程式完成請求後才會被釋放。

ioctl MEDIA_REQUEST_IOC_REINIT 將清除請求的狀態並使其再次可用。此操作不保留任何狀態:該請求就像剛剛分配一樣。

4.6. 編解碼器裝置的示例

對於諸如 codecs 之類的用例,請求 API 可用於將特定控制元件與驅動程式要應用於 OUTPUT 緩衝區的控制元件相關聯,從而允許使用者空間提前排隊多個此類緩衝區。它還可以利用請求捕獲請求完成時控制元件狀態的能力來讀回可能發生變化的資訊。

放入程式碼中,在獲得請求後,使用者空間可以為其分配控制元件和一個 OUTPUT 緩衝區

struct v4l2_buffer buf;
struct v4l2_ext_controls ctrls;
int req_fd;
...
if (ioctl(media_fd, MEDIA_IOC_REQUEST_ALLOC, &req_fd))
        return errno;
...
ctrls.which = V4L2_CTRL_WHICH_REQUEST_VAL;
ctrls.request_fd = req_fd;
if (ioctl(codec_fd, VIDIOC_S_EXT_CTRLS, &ctrls))
        return errno;
...
buf.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
buf.flags |= V4L2_BUF_FLAG_REQUEST_FD;
buf.request_fd = req_fd;
if (ioctl(codec_fd, VIDIOC_QBUF, &buf))
        return errno;

請注意,不允許將 Request API 用於 CAPTURE 緩衝區,因為沒有要報告的每幀設定。

一旦請求完全準備好,就可以將其排隊到驅動程式

if (ioctl(req_fd, MEDIA_REQUEST_IOC_QUEUE))
        return errno;

然後,使用者空間可以透過在其檔案描述符上呼叫 poll() 來等待請求完成,或者開始將 CAPTURE 緩衝區出隊。最有可能的是,它希望儘快獲得 CAPTURE 緩衝區,這可以使用常規 VIDIOC_DQBUF 完成

struct v4l2_buffer buf;

memset(&buf, 0, sizeof(buf));
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
if (ioctl(codec_fd, VIDIOC_DQBUF, &buf))
        return errno;

請注意,為了簡單起見,此示例假設每個 OUTPUT 緩衝區都將有一個 CAPTURE 緩衝區,但這並非必須如此。

然後,我們可以透過輪詢請求檔案描述符確保請求已完成後,透過呼叫 VIDIOC_G_EXT_CTRLS 在完成時查詢控制元件值。這對於我們希望在生成捕獲緩衝區後立即查詢值的易失性控制元件特別有用。

struct pollfd pfd = { .events = POLLPRI, .fd = req_fd };
poll(&pfd, 1, -1);
...
ctrls.which = V4L2_CTRL_WHICH_REQUEST_VAL;
ctrls.request_fd = req_fd;
if (ioctl(codec_fd, VIDIOC_G_EXT_CTRLS, &ctrls))
        return errno;

一旦我們不再需要該請求,我們可以透過 ioctl MEDIA_REQUEST_IOC_REINIT 回收它以供重用...

if (ioctl(req_fd, MEDIA_REQUEST_IOC_REINIT))
        return errno;

...或關閉其檔案描述符以完全處置它。

close(req_fd);

4.7. 簡單捕獲裝置的示例

對於簡單的捕獲裝置,請求可用於指定要應用於給定 CAPTURE 緩衝區的控制元件。

struct v4l2_buffer buf;
struct v4l2_ext_controls ctrls;
int req_fd;
...
if (ioctl(media_fd, MEDIA_IOC_REQUEST_ALLOC, &req_fd))
        return errno;
...
ctrls.which = V4L2_CTRL_WHICH_REQUEST_VAL;
ctrls.request_fd = req_fd;
if (ioctl(camera_fd, VIDIOC_S_EXT_CTRLS, &ctrls))
        return errno;
...
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.flags |= V4L2_BUF_FLAG_REQUEST_FD;
buf.request_fd = req_fd;
if (ioctl(camera_fd, VIDIOC_QBUF, &buf))
        return errno;

一旦請求完全準備好,就可以將其排隊到驅動程式

if (ioctl(req_fd, MEDIA_REQUEST_IOC_QUEUE))
        return errno;

然後,使用者空間可以像上面的 M2M 示例中一樣,將緩衝區出隊,等待請求完成,查詢控制元件並回收請求。