V4L2驅動的移植與應用(二)
二、V4L2的應用下麵簡單介紹一下V4L2驅動的應用流程。
1、 視頻采集的基本流程
一般的,視頻采集都有如下流程:
2、 打開視頻設備
在V4L2中,視頻設備被看做一個文件。使用open函數打開這個設備:
// 用非阻塞模式打開攝像頭設備
int cameraFd;
cameraFd = open("/dev/video0", O_RDWR | O_NONBLOCK, 0);
// 如果用阻塞模式打開攝像頭設備,上述代碼變為:
//cameraFd = open("/dev/video0", O_RDWR, 0);
關於阻塞模式和非阻塞模式:應用程序能夠使用阻塞模式或非阻塞模式打開視頻設備,如果使用非阻塞模式調用視頻設備,即使尚未捕獲到信息,驅動依舊會把緩存(DQBUFF)裏的東西返回給應用程序。
3、 設定屬性及采集方式
打開視頻設備後,可以設置該視頻設備的屬性,例如裁剪、縮放等。這一步是可選的。在Linux編程中,一般使用ioctl函數來對設備的I/O通道進行管理:
extern int ioctl (int __fd, unsigned long int __request, ...) __THROW;
__fd:設備的ID,例如剛才用open函數打開視頻通道後返回的cameraFd;
__request:具體的命令標誌符。
在進行V4L2開發中,一般會用到以下的命令標誌符:
VIDIOC_REQBUFS:分配內存
VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的數據緩存轉換成物理地址
VIDIOC_QUERYCAP:查詢驅動功能
VIDIOC_ENUM_FMT:獲取當前驅動支持的視頻格式
VIDIOC_S_FMT:設置當前驅動的頻捕獲格式
VIDIOC_G_FMT:讀取當前驅動的頻捕獲格式
VIDIOC_TRY_FMT:驗證當前驅動的顯示格式
VIDIOC_CROPCAP:查詢驅動的修剪能力
VIDIOC_S_CROP:設置視頻信號的邊框
VIDIOC_G_CROP:讀取視頻信號的邊框
VIDIOC_QBUF:把數據從緩存中讀取出來
VIDIOC_DQBUF:把數據放回緩存隊列
VIDIOC_STREAMON:開始視頻顯示函數
VIDIOC_STREAMOFF:結束視頻顯示函數
VIDIOC_QUERYSTD:檢查當前視頻設備支持的標準,例如PAL或NTSC。
這些IO調用,有些是必須的,有些是可選擇的。
4、 檢查當前視頻設備支持的標準
在亞洲,一般使用PAL(720X576)製式的攝像頭,而歐洲一般使用NTSC(720X480),使用VIDIOC_QUERYSTD來檢測:
v4l2_std_id std;
do {
ret = ioctl(fd, VIDIOC_QUERYSTD, &std);
} while (ret == -1 && errno == EAGAIN);
switch (std) {
case V4L2_STD_NTSC:
//……
case V4L2_STD_PAL:
//……
}
5、 設置視頻捕獲格式
當檢測完視頻設備支持的標準後,還需要設定視頻捕獲格式:
struct v4l2_format fmt;
memset ( &fmt, 0, sizeof(fmt) );
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 720;
fmt.fmt.pix.height = 576;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {
return -1;
}
v4l2_format結構體定義如下:
struct v4l2_format
{
enum v4l2_buf_type type; // 數據流類型,必須永遠是V4L2_BUF_TYPE_VIDEO_CAPTURE
union
{
struct v4l2_pix_format pix;
struct v4l2_window win;
struct v4l2_vbi_format vbi;
__u8 raw_data[200];
} fmt;
};
struct v4l2_pix_format
{
__u32 width; // 寬,必須是16的倍數
__u32 height; // 高,必須是16的倍數
__u32 pixelformat; // 視頻數據存儲類型,例如是YUV4:2:2還是RGB
enum v4l2_field field;
__u32 bytesperline;
__u32 sizeimage;
enum v4l2_colorspace colorspace;
__u32 priv;
};
6、 分配內存
接下來可以為視頻捕獲分配內存:
struct v4l2_requestbuffers req;
if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
return -1;
}
v4l2_requestbuffers定義如下:
struct v4l2_requestbuffers
{
__u32 count; // 緩存數量,也就是說在緩存隊列裏保持多少張照片
enum v4l2_buf_type type; // 數據流類型,必須永遠是V4L2_BUF_TYPE_VIDEO_CAPTURE
enum v4l2_memory memory; // V4L2_MEMORY_MMAP 或 V4L2_MEMORY_USERPTR
__u32 reserved[2];
};
7、 獲取並記錄緩存的物理空間
使用VIDIOC_REQBUFS,我們獲取了req.count個緩存,下一步通過調用VIDIOC_QUERYBUF命令來獲取這些緩存的地址,然後使用mmap函數轉換成應用程序中的絕對地址,最後把這段緩存放入緩存隊列:
typedef struct VideoBuffer {
void *start;
size_t length;
} VideoBuffer;
VideoBuffer* buffers = calloc( req.count, sizeof(*buffers) );
struct v4l2_buffer buf;
for (numBufs = 0; numBufs < req.count; numBufs++) {
memset( &buf, 0, sizeof(buf) );
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = numBufs;
// 讀取緩存
if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {
return -1;
}
buffers[numBufs].length = buf.length;
// 轉換成相對地址
buffers[numBufs].start = mmap(NULL, buf.length,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd, buf.m.offset);
if (buffers[numBufs].start == MAP_FAILED) {
return -1;
}
// 放入緩存隊列
if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
return -1;
}
}
8、 關於視頻采集方式
操作係統一般把係統使用的內存劃分成用戶空間和內核空間,分別由應用程序管理和操作係統管理。應用程序可以直接訪問內存的地址,而內核空間存放的是 供內核訪問的代碼和數據,用戶不能直接訪問。v4l2捕獲的數據,最初是存放在內核空間的,這意味著用戶不能直接訪問該段內存,必須通過某些手段來轉換地址。
一共有三種視頻采集方式:
1)使用read、write方式:直接使用 read 和 write 函數進行讀寫。這種方式最簡單,但是這種方式會在 用戶空間和內核空間不斷拷貝數據 ,同時在用戶空間和內核空間占用 了 大量內存,效率不高。
2)內存映射方式(mmap):把設備裏的內存映射到應用程序中的內存控件,直接處理設備內存,這是一種有效的方式。上麵的mmap函數就是使用這種方式。
3)用戶指針模式:內存由用戶空間的應用程序分配,並把地址傳遞到內核中的驅動程序,然後由 v4l2 驅動程序直接將數據填充到用戶空間的內存中。這點需要在v4l2_requestbuffers裏將memory字段設置成V4L2_MEMORY_USERPTR。
第一種方式效率是最低的,後麵兩種方法都能提高執行的效率,但是對於mmap 方式,文檔中有這樣一句描述 --Remember the buffers are allocated in physical memory, as opposed to virtual memory which can be swapped out to disk. Applications should free the buffers as soon as possible with the munmap () function .(使用mmap方法的時候,buffers相當於是在內核空間中分配的,這種情況下,這些buffer是不能被交換到虛擬內存中,雖然這種方法不怎麼影響讀寫效率,但是它一直占用著內核空間中的內存,當係統的內存有限的時候,如果同時運行有大量的進程,則對係統的整體性能會有一定的影響。)
所以,對於三種視頻采集方式的選擇,推薦的順序是 userptr 、 mmap 、 read-write 。當使用 mmap 或 userptr 方式的時候,有一個環形緩衝隊列的概念,這個隊列中,有 n 個 buffer ,驅動程序采集到的視頻幀數據,就是存儲在每個 buffer 中。在每次用 VIDIOC_DQBUF 取出一個 buffer ,並且處理完數據後,一定要用 VIDIOC_QBUF 將這個 buffer 再次放回到環形緩衝隊列中。環形緩衝隊列,也使得這兩種視頻采集方式的效率高於直接 read/write 。
9、 處理采集數據
V4L2有一個數據緩存,存放req.count數量的緩存數據。數據緩存采用FIFO的方式,當應用程序調用緩存數據時,緩存隊列將最先采集到的 視頻數據緩存送出,並重新采集一張視頻數據。這個過程需要用到兩個ioctl命令,VIDIOC_DQBUF和VIDIOC_QBUF:
struct v4l2_buffer buf;
memset(&buf,0,sizeof(buf));
buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory=V4L2_MEMORY_MMAP;
buf.index=0;
//讀取緩存
if (ioctl(cameraFd, VIDIOC_DQBUF, &buf) == -1)
{
return -1;
}
//…………視頻處理算法
//重新放入緩存隊列
if (ioctl(cameraFd, VIDIOC_QBUF, &buf) == -1) {
return -1;
}
10、 關閉視頻設備
使用close函數關閉一個視頻設備
close(cameraFd)
最後更新:2017-04-03 16:48:59