1. 程式人生 > >深入理解linux核心v4l2框架之videobuf

深入理解linux核心v4l2框架之videobuf

Videobuf

下面來介紹以下videobuffer相關的一些東西。

V4L2核心api提供了一套標準的方法來處理視訊緩衝,這些方法允許驅動實現read(),mmap(), overlay()等操作。同樣也有方法支援DMAscatter/gather操作,並且支援vmallocbuffer(這個大多用在USB驅動上)

videobuf層功能是一種在v4l2驅動和使用者空間當中的依附層,這話看起來有點繞,說白了就是提供一種功能框架,用來分配和管理視訊緩衝區,它相對獨立,卻又被v4l2驅動使用。它有一組功能函式集用來實現許多標準的POSIX系統呼叫,包括read(),poll()mmap()等等,還有一組功能函式集用來實現流式

(streaming)IOv4l2_ioctl呼叫,包括緩衝區的分配,入隊和出隊以及資料流控制等操作。使用videobuf需要驅動程式作者遵從一些強制的設計規則,但帶來的好處是程式碼量的減少和v4l2框架API的一致。

緩衝型別

並不是所有的視訊裝置都使用相同的緩衝型別。實際上,有三種通用的型別:

被分散在物理和核心虛擬地址空間的緩衝,幾乎所有的使用者空間緩衝都是這種型別,

如果可能的話分配核心空間的緩衝也很有意義,但是不幸的是,這個通常需要那些支持離散聚合DMA操作的硬體裝置。

物理上離散的但是虛擬地址是連續的,換句話說,就是用vmalloc分配的核心緩衝。這些緩衝很難用於DMA操作。

物理上連續的緩衝。

videobuf可以很好地處理這三種類型的緩衝,但是在此之前,驅動程式作者必須選擇一種型別,並且以此型別為基礎設計驅動。

資料結構,回撥函式和初始化

根據選擇的型別,包含不同的標頭檔案,這些標頭檔案在include/media/下面

<media/videobuf-dma-sg.h>

<media/videobuf-vmalloc.h>

<media/videobuf-dma-contig.h>

v4l2驅動需要包含一個videobuf_queue的例項用來管理緩衝佇列,同時還要一個連結串列來維護這個佇列,另外還要一箇中斷安全的spin_lock來保護佇列的操作。

下一步就是要填充一個回撥函式集來處理實際的緩衝區佇列,這個函式集用videobuf_queue_ops來描述:

struct videobuf_queue_ops {

int *(buf_setup)(struct videobuf_queue*q, uint *count, uint *size);

int *(buf_prepare)(structvideobuf_queue *q, struct videobuf_buffer *vb,

enum v4l2_field field);

void *(buf_queue)(structvideobuf_queue*q,struct videobuf_buffer *vb);

void *(buf_release)(...);

}

buf_setupIO處理請求之前被呼叫。目的是告訴videobuf關於IO的資訊。 count引數提供一個緩衝區個數的參考,驅動必須檢查它的合理性,一個經驗是大於等於2,小於等於32個。Size引數指定了每一幀資料的大小。

buf_prepare每一個緩衝(videobuf_buffer結構描述的)將被傳遞給該回調函式,用來配置緩衝的height,widthfileds。如果field引數被設定為 VIDEOBUF_NEEDS_INIT,那麼驅動將把vb傳遞給videobuf_iolock()這個函式。除此之外,該回調函式通常也將為vb分配記憶體,最後把vb狀態置為VIDEOBUF_PREPARED

buf_queue當一個vb需要被放入IO請求佇列時,呼叫該回調。它將把這個buffer放到可用的buffer連結串列當中去,然後把狀態置為VIDEOBUF_QUEUED

buf_release當一個buffer不再使用的時候,呼叫該回調函式。驅動必須保證 buffer上沒有活躍的IO請求,之後就可以將這個buffer傳遞給合適的 free函式,根據申請的buffer型別呼叫對應的釋放函式:

scatter/gather型別的呼叫

videobuf_dma_unmap(structvideobuf_queue, videobuf_dmabuf)

videobuf_dma_free(videobuf_dmabuf)

vmalloc型別的呼叫

videobuf_vmalloc_free(videobuf_buffer)

contiguous型別的呼叫

videobuf_dma_contig_free(videobuf_queue,videobuf_buffer)

有一種方法可以保證buffer上沒有IO請求,呼叫函式

videobuf_waiton(videobuf_buffer,non_blocking, intr)

檔案操作(v4l2_file_operations)

到了這兒,很多工作也就做完了,剩下的事情就是將對videobuf的呼叫傳遞給具體的驅動實現了。首先就是開啟操作,這個操作要先對videobuf_queue進行初始化,初始化取決於申請的buffer是什麼型別,有如下三種初始化函式可供呼叫:

void videobuf_queue_sg_init(structvideobuf_queue *q,

struct videobuf_queue_ops *ops,

struct device *dev,

spinlock_t *irqlock,

enum v4l2_buf_type type,

enum v4l2_field_ field,

unsigned int msize,

void *priv

struct mutex *ext_lock)

void videobuf_queue_vmalloc_init(structvideobuf_queue *q,

struct videobuf_queue_ops *ops,

struct device *dev,

spinlock_t *irqlock,

enum v4l2_buf_type type,

enum v4l2_field field,

unsigned int mszie,

void *priv

struct mutex *ext_lock);

voidvideobuf_queue_dma_contig_init(struct videobuf_queue *q,

struct videobuf_queue_ops *ops,

struct device *dev,

spinlock_t *irqlock,

enum v4l2_buf_type type,

enum v4l2_field field,

unsigned int mszie,

void *priv

struct mutex *ext_lock);

以上三種初始化函式,有相同的引數,這些引數的從他們的名稱就可以看出來鎖代表的意義是什麼。

這裡著重說下v4l2_buf_type型別,

V4L2_BUF_TYPE_VIDEO_CAPTURE 指定buf的型別為capture,用於視訊捕獲裝置

V4L2_BUF_TYPE_VIDEO_OUTPUT 指定buf的型別output,用於視訊輸出裝置

V4L2_BUF_TYPE_VIDEO_OVERLAY 指定buf的型別為overlay,用於overlay裝置

V4L2_BUF_TYPE_VBI_CAPTURE 用於vbi捕獲裝置

V4L2_BUF_TYPE_VBI_OUTPUT 用於vbi輸出裝置

V4L2_BUF_TYPE_SLICED_VBI_CAPTURE 用於切片vbi捕獲裝置

V4L2_BUF_TYPE_SLICED_VBI_OUTPUT 用於切片vbi輸出裝置

V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY 用於視訊輸出overlay裝置

V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE 用於多平面儲存格式的視訊捕獲裝置

V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE用於多平面儲存格式的視訊輸出裝置

v4l2_field指定videofield,也就是說interleaved或者progressive的,一般都是指定為V4L2_FIELD_NONE,用於逐行掃描的裝置。

V4L2捕獲裝置驅動可以支援兩種APIread()系統呼叫和更為複雜的流機制。一般的做法是兩種都支援以確保所有的應用都可以使用該裝置。videobuf框架使得這種驅動的編寫變得更為簡單。比如說要實現read()系統呼叫,那麼驅動程式只需要呼叫

ssize_t videobuf_read_one(structvideobuf_queue *q,

char __user *data, size_t count,loff_t *ppos, int nonblocking)

ssize_t videobuf_read_streaming(structvideobuf_queue *q, char __user *data, size_t count, loff_t *ppos,int vbihack, int nonblocking)

這兩個函式都是把幀資料讀入到data當中,然後返回實際上讀取的位元組數。不同的是前者只讀取一幀資料,而後者可以選擇讀取多幀。一個典型的應用read()系統呼叫必須開啟捕獲裝置,然後返回之前停止該裝置。

poll()系統呼叫通常由以下函式來實現:

unsigned intvideobuf_poll_stream(struct file *file, struct videobuf_queue *q,

poll_table *wait)

注意,實際最終使用的q是可用的第一個buffer

當核心空間緩衝的流IO請求完成後,驅動還必須支援mmap系統呼叫以使能使用者空間可以訪問data資料。在v4l2驅動中,通常很複雜的mmap的實現被簡化了,只需要呼叫下面這個函式就可以了:

int videobuf_mmap_mapper(structvideobuf_queue *q,

struct vma_area_struct * vma)

剩下的事情就交給videobuf核心層來完成好了。

release函式需要呼叫兩個單獨的函式來完成:

void videobuf_stop(structvideobuf_queue *q);

int videobuf_mmap_free(structvideobuf_queue *q)

前者終止所有bufferIO操作。後者保證所有的bufferunmap掉,如果已經被unmap掉的話,這個buffer就會被傳遞給buf_release回撥函式。如果buffer還沒有被unmap,那麼後者將返回一個錯誤程式碼。

Ioctl操作:

v4l2api涵蓋了很長一組驅動回撥函式來響應使用者的ioctl操作,有很大一部分和流IO操作相關的都是直接呼叫到videobuf裡面來。相關的函式如下:

int videobuf_reqbufs(structvideobuf_queue *q,

structv4l2_requestbuffers *req);

int videobuf_querybuf(structvideobuf_queue *q, struct v4l2_buffer *vb)

int videobuf_qbuf(strurctvideobuf_queue *q, struct v4l2_buffer *vb)

int videobuf_dqbuf(structvideobuf_queue *q, struct v4l2_buffer *vb)

int videobuf_streamon(structvideobuf_queue *q);

int videobuf_streamoff(structvideobuf_queue *q);

Buffer的分配

講到這兒,我們討論了很多關於buffer的話題,但是卻沒有提到他們是怎麼分配的。Scatter/gather例子比較複雜,驅動程式可以完全讓videobuf層去完成buffer的分配,在這種情況下,buffer將被分配為匿名使用者空間頁並且實際上將非常分散。如果應用程式使用使用者空間的buffer的話,驅動也就不需要分配了,videobuf層將小心的呼叫get_user_pages()並且填充離散列表陣列(scatterlistarray)

如果驅動程式要自己做記憶體分配,那麼將在vidioc_reqbufs函式中進行,在呼叫了videobuf_reqbufs()之後,首先第一步就是要呼叫到

struct videobuf_dmabuf*videobuf_to_dma(struct videobuf_buffer *buf)

返回的videobuf_dmabuf包含了一對相關的域

struct scatterlist *sglist;

int sglen;

驅動必須分配合適大小的scatterlist陣列,並且將分配的記憶體片和指標對應起來,sglen指定了scatterlist陣列的大小。

驅動當中如果使用了vmalloc()來分配記憶體的話,就不用關心buffer的分配了,videobuf層將處理具體的細節,一些驅動程式使用了小技巧,就是在系統啟動的時候就分配好了dma記憶體,以避免動態申請有的時候會申請不到的問題,但是對於此類的設計,videobuf層目前還不能很好的勝任。在3.0以上的核心當中,出現了一種新的框架videobuf2,已經解決了這個問題,我們會在後面詳細介紹這個框架。

Filling緩衝區

videobuf層的最後一部分就是關於將幀資料傳遞到buffer中的實現。這一部分沒有直接的回撥函式,通常都是在裝置的中斷響應中來完成。對於所有型別的驅動,流程大概是這個樣子的:

獲取下一個buffer,並且確保有人正在等待這個buffer

得到一個記憶體指標,然後將視訊資料放到那個地方

-- 標記buffer完成,並且喚醒等待的程序

第一步,buffer可以通過驅動管理的一個連結串列獲得,這個連結串列由buf_queue回撥函式填充,所以驅動最先要是連結串列初始化為空,並且如果連結串列當中的buffer沒有一個程序在其上等待的話,是不能被移除或者填充的。

另外buffer在被mapdma之前,要把它的狀態設定為VIDEOBUF_ACTIVE,這將保證在裝置傳輸資料的時候videobuf層不去嘗試任何操作。

第二步,得到一個記憶體指標,對於scatter/gather型別的記憶體來說,可以從scatterlist當中找到記憶體指標;對於vmalloc型別的來說呼叫

void * videobuf_to_vmalloc(structvideobuf_buffer *vb)

對於連續實體記憶體型別來說呼叫

dma_addr_tvideobuf_to_dma_contig(struct videobuf_buffer *buf)

第三步,就是設定videobuf_buffer中的大小,並且把buffer的狀態設定為VIDEOBUF_DONE,然後在完成佇列上呼叫wake_up().到此,buffer就真正的屬於videobuf層了,驅動程式不用再去關心它如何被排程。

最後,一個很好的關於v4l2的例子就是drviers/media/video/vivi.c,它使用了vmalloc型別的videobuf,可以通過閱讀這份例子來學習v4l2驅動的寫法。