(1)打開視屏設備
在V4L2中,視頻設備被看做一個文件。使用open()函數打開設備。算法
1.用非阻塞模式打開攝像頭設備,其形式入下:編程
int camerafd; cameraFd = open("/dev/video0", O_RDWR | O_NONBLOCK, 0);
2.使用阻塞模打開攝像頭設備,其形式以下:緩存
cameraFd = open("/dev/video0", O_RDWR, 0);
應用程序可以使用阻塞和非阻塞模式打開視頻設備。若是使用非阻塞模式調用視頻設備,即便還沒有捕獲到信息,驅動依舊會把緩存(DQBUFF)中的數據返回給應用程序。
(2)設備屬性及採集方式
打開視頻設備後,能夠設置該視頻設備的屬性,如進行裁剪、縮放等。這一步是可選的。在Linux編程中,ioctl是input/output control 的縮寫。ioctl()函數的功能是經過打開的文件描述符對各類文件尤爲是字符設備文件進行控制,完成特定的I/O操做。通常使用ioctl()函數來對設備的I/O通道進行管理,其格式以下:ide
int ioctl(int fd, unsigned long int request, .../*args*/);
在進行V4L2開發時,經常使用的命令標識符以下(部分是可選的)。函數
VIDIOC_REQBUFS:分配內存
VIDIOC_QUERYBUF:把VIDIOC_REQBUF中分配的數據緩存轉換成物理地址
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_QUERYSTD:檢查當前視頻設備支持的標準
VIDIOC_STREAMON:開始視頻顯示函數
VIDIOC_STREAMOFF:結束視頻顯示函數
1.檢查當前視頻設備支持的標準
在亞洲,通常使用PAL(720 * 576)制式的攝像頭,而歐洲通常使用NTSC(720*480),使用VIDIOC_QUERYSTD來檢測:spa
v4l2_std_id std; do { ret = ioctl(fd, VIDIO_QUERYSTD, &std); }while(ret==-1 && errno==EAGAIN); switch(std) { case V4L2_STD_NTSC: . . . case V4L2_STD_PAL: ... }
2.設置視頻捕獲格式
當檢測完視頻設備支持的標準後,還須要設定視頻捕獲的格式,結構以下:操作系統
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; // 視頻數據存儲類型,如YUV(4:2:2)求RGB enum v4l2_field field; __u32 bytesperline; __u32 sizeimage; enum v4l2_colorspace colorspace; __u32 priv; };
3.分配內存
接下來能夠爲視頻捕獲分配內存code
struct v4l2_requestbuffers req; if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) { return -1; }
v4l2_requestbuffers結構以下:orm
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]; };
4.獲取並記錄緩存的物理空間
使用VIDIOC_REQBUFS可得到req.count個緩存。下一步經過調用VIDICO_QUERYBUF來獲取這些緩存的地址(內核地址),而後使用mmap()函數將其轉換成應用程序中的絕對地址(用戶地址),以下圖因此,最後把這段緩存放入緩存隊列:
經過VIDIOC_QUERYBUF將內核地址轉換爲用戶地址
typedef struct VideoBuffer { void *start; size_t length; }; 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, PORT_READ|PORT_WRITE, MAP_SHARED, fd, buf.m.offset); if (buffers[numBufs].start == MAP_FAILED) { return -1; } //放入緩存隊列 if (ioctl(fd, VIDICO_QBUF,&buf) == -1) { return -1; } }
5.視頻採集方式
操做系統通常把系統使用的內存劃分紅用戶空間和內核空間,分別由應用程序管理和操做系統共管理。應用程序能夠直接訪問內存的地址,而內核空間存放的是供內核訪問的代碼和數據,用戶不能直接訪問。V4L2捕獲的數據最初存放在內核空間,這意味着用戶不能直接訪問該段內存,必須經過某些手段來轉換地址。
共有三種視頻採集方式:使用read/write方式、內存映射方式和用戶指針模式。
6.處理採集數據
V4L2有一個數據緩存,req.count數量的緩存數據。數據緩存採用FIFO的方式,當應用程序調用緩存數據時,緩存隊列將最早採集到的視頻數據緩存送出,並從新採集一段視頻數據。這個過程要用到兩個ioctl命令,VIDICO_DQBUF和VIDICO_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, VIDICO_DQBUF, &buf) == -1) { return -1; } //視頻處理算法 //從新放入緩存隊列 if (ioctl(camerafd, VIDICO_QBUF, &buf) == -1) { return -1; }
(3)關閉視頻設備
使用close()函數關閉視頻設備:
close(cameraFd);
大致流程: