The videobuf2 API

Author:CJOK

Contact:cjok.liao#gmail.com

SinaWeibo:@廖野cjok

 

原文地址:http://lwn.net/Articles/447435/

 

Video4linux2驅動主要負責從sensor(通常是通過DMA)上獲取視頻資料然後把這些視頻幀傳輸到使用者空間,大量資料的傳輸是性能要考慮。出於此目的,V4L2定義了複雜的API去處理流資料。V4L2子系統為實現這些API也加入了不少複雜的程式碼,當然大部分的程式碼沿用了V4L。為 了使驅動工程師更方便實現視頻驅動程式碼,videobuf子系統提供了一組用於管理流IO buffer的介面。

Linux kernel在不斷的更新和完善,videobuf在2009已經被videobuf2取代,相比videobuf,videobuf2更加完善,更加實用。本文用於介紹videobuf2。

為什麼出現videobuf2?雖然videobuf工作的很好,但是在一些方面並不是那麼完善,各種不同的API相當依賴buffer的類型,也沒有提供相關的介面用於視訊緩衝區管理。Videobuf2提供了一組統一的API介面,允許驅動自己有更多的配置。

Buffers

像原來的的videobuf一樣,videobuf2也實現了三種buffer類型。
Vmalloc buffer這 類buffer由vmalloc()分配,因此在內核空間虛擬位址上是連續的,drivers working with these buffers almost invariably end up copyingthe data once as it passes through the system;
contiguous DMA buffers在實體記憶體上連續,通常硬體設備需要在連續物理位址空間上執行DMA操作;
S/G DMA buffers在實體記憶體上是不連續的,如果硬體上支援scatter/gather DMA,驅動可以使用這種方式。

 

驅動程式使用不同的類型buffer,需要包含以下對應的標頭檔:

#include <media/videobuf2-vmalloc.h>

#include <media/videobuf2-dma-contig.h>

#include <media/videobuf2-dma-sg.h>

和videobuf相比,videobuf2有一個好處就是能夠是一個驅動程式同時支援多種類型的buffer,上面的標頭檔不會相互衝突,而且videobuf2提供的這三種buffer類型的操作介面非常類似。

 

結構體vb2_buffer代表一個視訊緩衝區,此結構體定義在<media/videobuf2-core.h>。通常驅動程式都想 保存每個緩衝區特有的資訊,因此,驅動程式會自己定義一個緩衝區結構體,然後把vb2_buffer嵌入在其中。可是,videobuf2作者沒有讀過 Neil Brown’s object-oriented design patterns in the kernel(內核中的物件導向設計模型),沒有提供分配這些結構體的函數。這就意味著驅動程式需要把自訂結構體的大小通知videobuf2子系統, 而且vb2_buffer實例必須放在自訂結構體的第一個成員域。



管理buffer的回呼函數

一個視頻驅動程式需要實現一組用於管理buffer的回呼函數並註冊到videobuf2子系統,在原來的videobuf中也有類似的回呼函數。這五個函數如下:

    int (*buf_init)(struct vb2_buffer *vb);

    int (*buf_prepare)(struct vb2_buffer *vb);

    void (*buf_queue)(struct vb2_buffer *vb);

    int (*buf_finish)(struct vb2_buffer *vb);

    void (*buf_cleanup)(struct vb2_buffer *vb);

videobuf2會呼叫buf_init()對每個分配好的新buffer執行一些必要的額外初始化,如果buffer_init返回錯誤碼將會中斷緩衝佇列的設置;

buf_prepare()在用戶空間入隊這些buffer時被呼叫(比如,回應VIDIOC_QBUF操作),在這些buffer被流I/O使用之前完成一些必要的準備工作。呼叫Buf_queue()把buffers傳遞給驅動程式,開始流I/O操作。

譯者注:buf_queue是回應VIDIOC_STREAMON操作,開始流IO操作,然後會往buffers中填充視頻資料。

Buf_finish()在buffers傳回到用戶空間前被呼叫,你可能需要對這個回呼函數考慮的問題是,驅動程式已經知道buffer有一幀新 的資料傳遞到使用者空間,事實上,還需要告訴videobuf2。一個可能的答案是完成流I/O資料到buffer的操作通常是在中斷上下文處理的,而 buf_finish()會在進程上下文呼叫。

譯者注:buf_finish()在每個buffer出隊傳遞到用戶空間前被呼叫,通常在用戶空間訪問buffers前做一些必要操作。

Buf_cleanup()在buffer釋放前被呼叫,用來做一些清理工作。

 

Other videobuf2 callbacks

在原來的videobuf版本中,驅動程式只提供上述的回呼函數來管理緩衝區,videobuf2新增了一些回呼函數用來完善對緩衝區的操作。

int (*queue_setup)(
   struct vb2_queue *q,
   unsigned int *num_buffers,

    unsigned int *num_planes,
   unsigned long sizes[],

    void *alloc_ctxs[]);

queue_setup()在用戶空間執行ioctl:VIDIOC_REQBUFS操作時被呼叫,作用是用來建立緩衝佇列。結構體 vb2_queue用來描述緩衝佇列,num_buffers參數是應用程式申請的緩衝數。Num_planes是每個buffer中的視頻層數目。 Size陣列包含了每個視頻層的大小(bytes)。

Alloc_ctxs指標陣列包含了每個視頻層的“分配上下文”,當前還只被contiguous DMA模式使用到。使用contiguous DMA的驅動程式需要呼叫獲取/登出上下文資訊:

void *vb2_dma_contig_init_ctx(struct device *dev)

voidvb2_dma_contig_cleanup_ctx(void *alloc_ctx)

 

下面兩個回呼函數用於開始和停止獲取視頻資料:

    int (*start_streaming)(struct vb2_queue *q);

    int (*stop_streaming)(struct vb2_queue *q);

start_streaming()用來回應ioctl:VIDIOC_STREAMON操 作開始抓取視頻資料,如果驅動程式實現了read()方法也會呼叫到start_streaming()。
Stop_streaming()用來回應 ioctl:VIDIOC_STREAMOFF操作停止抓取視頻資料,等待DMA停止後函數才會返回。這裡值得注意的是,呼叫 stop_streaming()後,videobuf2子系統會回收掉所有傳遞到驅動程式的buffers,此時驅動程式不能訪問這些buffers。

 

Locking

最後兩個回呼函數是用於保護視頻設備訪問——鎖機制:

呼叫驅動程式訪問視頻設備時需要獲取到鎖;

Videobuf2子系統呼叫到驅動的回呼函數前應該先獲取到設備訪問鎖。比如,使用者空間獲取視頻資料會導致start_streaming()呼叫,需 要通過videobuf2子系統呼叫到相關的設備驅動程式,所以呼叫start_streaming()時,將要持有設備鎖。

在這樣的情況下,有一個需要考慮的問題:用戶空間呼叫ioctl:VIDIOC_DQBUF從內核獲取緩衝區的資料,當緩衝區不可用時,進程可能會阻塞。

void (*wait_prepare)(struct vb2_queue *q);

void (*wait_finish)(struct vb2_queue *q);

在進行VIDIOC_DQBUF操作阻塞等待一個buffer資料之前呼叫wait_prepare()去釋放設備鎖,一旦退出阻塞,會呼叫 wait_finish()獲取設備鎖。可能這兩個回呼函數用release_lock()和reacquire_lock()名字更好。

 

Queue setup

上面介紹了vb2_ops,瞭解了視頻驅動程式如何註冊到videobuf2子系統。實現需要構建vb2_ops,並實現其中的回呼函數:

static const struct vb2_ops my_special_callbacks = {
.queue_setup = my_special_queue_setup,

                                         /* ... */

    };

然後,建立一個videobuf2佇列(通常是在設備註冊或者設備open的時候),驅動需要分配一個vb2_queue結構體:

 struct vb2_queue {

enum v4l2_buf_type          type;

unsigned int                io_modes;

unsigned int               io_flags;

const struct vb2_ops         *ops;

const struct vb2_mem_ops     *mem_ops;

void                         *drv_priv;

unsigned int                 buf_struct_size;

    /* Lots of private stuff omitted */

    };

這個結構體需要實現上面的成員,type代表buffer類型,通常是V4L2_BUF_TYPE_VIDEO_CAPTURE,io_modes是一個標誌位元,用來描述以何種方式來訪問buffer:

VB2_MMAP:緩衝區由內核空間分配,通過mmap映射緩衝區到用戶空間。通常vmalloc和contiguous DMA buffers來分配此方式的緩衝區;

VB2_USERPTR:緩衝區由使用者空間分配,通常需要設備支援集散IO才可以分配用戶空間緩衝區。有趣的是,videobuf2支持分配連續的緩衝區 在使用者空間,但是需要運用一些特別的機制,比如android的pmem驅動。在使用者空間分配連續大量的頁面是不支援的。

VB2_READ,VB2_WRITE:使用者空間緩衝區提供read()和write()方式訪問視頻設備。

Mem_ops成員是緩衝區操作函數集合,提供給上層videobuf2子系統操作緩衝區,內核已經實現三組供我們使 用: vb2_vmalloc_memops,vb2_dma_contig_memops和 vb2_dma_sg_memops,如果這三組都不符 合,驅動工程師可以自己實現一組。在撰寫本文時,內核主線還有沒有提供記憶體操作的驅動程式。

最後,drv_priv是驅動私有的指標(通常指向device結構),buf_struct_size用於表示buffer結構體的大小。Vb2_queue結構體實現後,呼叫以下函數進行初始化:

intvb2_queue_init(struct vb2_queue *q)

在設備關閉時呼叫 vb2_queue_release()進行銷毀。

Device operations

到現在我們已經分析了videobuf2底層的基礎設施,接下來看下videobuf2子系統是怎麼把使用者空間的操作和視頻設備驅動關聯起來。通常 第一步驅動程式需要實現大量的ioctl()呼叫介面,大部分的函數介面只是簡單的獲取設備鎖,然後直接呼叫videobuf2實現的ops。下面我們來 看這些ops:

    int vb2_querybuf(struct vb2_queue *q, struct v4l2_buffer *b);

    int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req);

    int vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b);

    int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b,

                         bool nonblocking);

    int vb2_streamon(struct vb2_queue *q, enum v4l2_buf_type type);

    int vb2_streamoff(struct vb2_queue *q, enum v4l2_buf_type type);

    int vb2_mmap(struct vb2_queue *q, struct vm_area_struct *vma);

    unsigned int vb2_poll(struct vb2_queue *q, struct file *file,

                           poll_table *wait);

    size_t vb2_read(struct vb2_queue *q, char __user *data, size_t count,

                                             loff_t *ppos, int nonblock);

    size_t vb2_write(struct vb2_queue *q, char __user *data, size_t count,

                                              loff_t *ppos, int nonblock);

這些函數介面遮罩了底層大量緩衝區管理的細節,使程式碼十分簡潔,提供了統一的介面給上層呼叫。

 

最後還有一點工作需要做:獲取資料放到緩衝區裡。對於vmalloc buffers,通常是通過memcpy()這樣的操作來完成,但是videobuf2提供了一個有用函數:

void *vb2_plane_vaddr(struct vb2_buffer*vb, unsigned int plane_no)

返回在緩衝區中給定的內核虛擬位址。

 

Contiguous DMA驅動需要獲取處理

    dma_addr_t vb2_dma_contig_plane_paddr(struct vb2_buffer *vb,

                                          unsigned int plane_no);

對於scatter/gather DMA驅動,提供的介面略顯複雜:

struct vb2_dma_sg_desc {

    unsigned long              size;

    unsigned int               num_pages;

    struct scatterlist         *sglist;

};

static inline struct vb2_dma_sg_desc *vb2_dma_sg_plane_desc(

    struct vb2_buffer *vb, unsigned int plane_no)

驅動程式從videobuf2獲取到能用於設備的scatterlist。

 

對於這三種情況,緩衝區將會填充滿需要傳遞到使用者空間的幀資料。Vb2_buffer結構體中內嵌了一個v4l2_buf,通常包含圖像大小、序號、時間幀等資訊。最後呼叫以下函數:

void vb2_buffer_done(struct vb2_buffer *vb,enum vb2_buffer_state state)

state參數:

         VB2_BUF_STATE_DONE,通知videobuf2子系統有一個buffer處理完成;

         VB2_BUF_STATE_ERROR,通知videobuf2子系統有一個buffer處理時出現ERROR。

 

本文大致介紹總結videobuf2 API,如果想瞭解完整的介面函數,可以參考內核源碼。內核提供一個虛擬視頻驅動實例供參考(vivi.c)。

    全站熱搜

    立你斯 發表在 痞客邦 留言(0) 人氣()