1. 程式人生 > >Linux下實現視訊讀取(三)---Buffer的準備和資料讀取

Linux下實現視訊讀取(三)---Buffer的準備和資料讀取

前面主要介紹的是:V4L2 的一些設定介面,如亮度,飽和度,曝光時間,幀數,增益,白平衡等。今天看看V4L2 得到資料的幾個關鍵ioctl,Buffer的申請和資料的抓取。

1. 初始化 Memory Mapping 或 User Pointer I/O.
int ioctl(int fd, int requestbuf, struct v4l2_requestbuffers * argp);
引數一:open()所產生的控制代碼。
引數二:VIDIOC_REQBUFS
引數三:in/out結構體。
struct v4l2_requestbuffers
{
 __u32 count;
 enum v4l2_buf_type type;
 enum v4l2_memory memory; //Applications set this field to V4L2_MEMORY_MMAP or V4L2_MEMORY_USERPTR
 __u32 reserved[2];
};

注意,有兩種方式的I/O。 Memory Mapping 和User Pointer。

Memory Mapping的Buffer由Driver申請為物理連續的記憶體空間(Kernel空間)。在此ioctl呼叫時被分配,需要早於mmap()動作將他們對映到使用者空間。

1.1:Memory Mapping模式詳解:
在使用Memory Mapping模式時,引數三中結構體內每個field都需要設定。

 __u32 count;   //當memory=V4L2_MEMORY_MMAP時,此處才有效。表明要申請的buffer個數。
 enum v4l2_buf_type type;  //Stream 或者Buffer的型別。此處肯定為V4L2_BUF_TYPE_VIDEO_CAPTURE
 enum v4l2_memory memory;  //既然是Memory Mapping模式,則此處設定為:V4L2_MEMORY_MMAP


注意:count是個輸入輸出函式。因為你所申請到的Buffer個數不一定就是你所輸入的Number。所以在ioctl執行後,driver會將真實申請到的buffer個數填充到此field. 這個數目有可能大於你想要申請的,也可能小與,甚至可能是0個。
應用程式可以再次呼叫ioctl--VIDIOC_REQBUFS 來修改buffer個數。但前提是必須先釋放已經 mapped 的 buffer ,可以先 munmap ,然後設定引數 count 為 0 來釋放所有的 buffer。

支援Memory  Mapping  I/O方式的前提是:v4l2_capability  中支援V4L2_CAP_STREAMING。
在這個模式下,資料本身不會被Copy,只是在Kernel和使用者態之間交換。在應用程式想要訪問到這些資料之前,它必須呼叫mmap()影射到使用者態。

同時也要注意,通過ioctl申請的記憶體,是實體記憶體,無法被交換入Disk,所以一定要釋放:munmap()。

1.2:User Pointer模式:
User Pointer模式時,應用程式實現申請。
只需要填充Type=V4L2_BUF_TYPE_VIDEO_CAPTURE, memory=V4L2_MEMORY_USERPTR

2. 詢問Buffer狀態:
int ioctl(int fd, int request, struct v4l2_buffer* argp);
引數一:open()所產生的控制代碼。
引數二:VIDIOC_QUERYBUF
引數三:v4l2_buffer 結構體。(IN/OUT引數)

注意,此ioctl是Memory Mapping的I/O方法之一。User Pointer模式不需要。在Buffer在ioctl-VIDIOC_REQBUFS執行時建立後,隨時都可以呼叫此Ioctl得到buffer資訊。

我們首先通過v4l2_buffer結構體看看引數三這個輸入輸出引數需要輸入些什麼,以及能夠得到什麼資訊。

struct v4l2_buffer
{
 __u32 index;
 enum v4l2_buf_type type;
 __u32 bytesused;
 __u32 flags;
 enum v4l2_field field;
 struct timeval timestamp;
 struct v4l2_timecode timecode;
 __u32 sequence;


 enum v4l2_memory memory;
 union {
 __u32 offset;
 unsigned long userptr;
 } m;
 __u32 length;
 __u32 input;
 __u32 reserved;
};

在呼叫ioctl--VIDIOC_QUERYBUF時,需要寫入的專案有:
enum v4l2_buf_type type; //V4L2_BUF_TYPE_VIDEO_CAPTURE
__u32 index;  // 這裡需要解釋一下,因為在呼叫ioctl-VIDIOC_REQBUFS時,建立了count個Buffer。所以,這裡index的有效範圍是:0到count-1.

在呼叫ioctl-VIDIOC_QUERYBUF後,Driver會填充v4l2_buffer 結構體內所有資訊供使用者使用。
如果一些正常:
1. flags 中:V4L2_BUF_FLAG_MAPPED, V4L2_BUF_FLAG_QUEUED and V4L2_BUF_FLAG_DONE被設定。
2. memory中,V4L2_MEMORY_MMAP被設定。
3. m.offset中,從將要mapping 的device memory頭到資料頭的offset.
4. length 中,填充當前Buffer長度。
5。其它的Field有可能設定,也有可能不被設定。

這樣,mmap()想要有的資訊就全了。而mmap()之後,Device Driver 申請的或者Device Memory就能對映到使用者空間。資料就可以被應用程式使用了。這才是ioctl-VIDIOC_QUERYBUF的關鍵作用。

3.和Driver交換buffer: 
對Camera這樣的捕獲裝置來說,Device將資料放到Buffer中,使用者得到資料。Device再次將資料放到Buffer中。
那麼Device Driver 怎樣知道哪個Buffer是可以存放資料的呢?這就用到當前這兩個ioctl-VIDIOC_QBUF, ioctl-VIDIOC_DQBUF.

ioctl-VIDIOC_QBUF: 將指定的Buffer放到輸入佇列中,即向Device表明這個Buffer可以存放東西。
ioctl-VIDIOC_DQBUF: 將輸出佇列中的資料 buffer取出。

在 driver 內部管理著兩個 buffer queues ,一個輸入佇列,一個輸出佇列。對於 capture device 來說,當輸入佇列中的 buffer 被塞滿資料以後會自動變為輸出佇列,等待呼叫 VIDIOC_DQBUF 將資料進行處理以後重新呼叫 VIDIOC_QBUF 將 buffer 重新放進輸入佇列.

用法:
ioctl--VIDIOC_QBUF:
int ioctl(int fd, int request, struct v4l2_buffer* argp);
引數一:open()所產生的控制代碼。
引數二:VIDIOC_QBUF
引數三:v4l2_buffer 結構體。(IN/OUT引數)

引數三是IN/OUT 引數。需要填充
enum v4l2_buf_type type; //V4L2_BUF_TYPE_VIDEO_CAPTURE
__u32 index;  // 這裡需要解釋一下,因為在呼叫ioctl-VIDIOC_REQBUFS時,建立了count個Buffer。所以,這裡index的有效範圍是:0到count-1. 
memory: V4L2_MEMORY_MMAP.

則這個結構體指明的buffer被送入輸出佇列,表明此Buffer可以被device 填充資料。

用法:
ioctl--VIDIOC_DQBUF:
int ioctl(int fd, int request, struct v4l2_buffer* argp);
引數一:open()所產生的控制代碼。
引數二:VIDIOC_DQBUF
引數三:v4l2_buffer 結構體。(IN/OUT引數)

從輸出佇列中取出一個有資料的Buffer。這個Buffer中的資料被處理後,此Buffer可以通過ioctl-VIDIOC_QBUF再次放入輸入佇列中去。