背景:
V4L2是V4L的升級版本,為linux下視頻設備程序提供了一套接口規范。包括一套數據結構和底層V4L2驅動接口。
一般操作流程(視頻設備):
1.打開設備文件。
int fd=open("/dev/video0",O_RDWR);
2. 取得設備的capability,看看設備具有什么功能,比如是否具有視頻輸入,或者音頻輸入輸出等。
VIDIOC_QUERYCAP,struct v4l2_capability(可選)
3. 選擇視頻輸入,一個視頻設備可以有多個視頻輸入。
VIDIOC_S_INPUT,struct v4l2_input
4. 設置視頻的制式和幀格式,制式包括PAL,NTSC,幀的格式個包括寬度和高度等。
VIDIOC_S_STD,VIDIOC_S_FMT,struct v4l2_std_id,struct v4l2_format
5. 向驅動申請幀緩沖,一般不超過5個。
struct v4l2_requestbuffers
6. 將申請到的幀緩沖映射到用戶空間,這樣就可以直接操作采集到的幀了,而不必去復制。
7. 將申請到的幀緩沖全部入隊列,以便存放采集到的數據.
VIDIOC_QBUF,struct v4l2_buffer
8. 開始視頻的采集。
VIDIOC_STREAMON
9. 出隊列以取得已采集數據的幀緩沖,取得原始采集數據。
VIDIOC_DQBUF
10. 將緩沖重新入隊列尾,這樣可以循環采集。
VIDIOC_QBUF
11. 停止視頻的采集。
VIDIOC_STREAMOFF
12. 關閉視頻設備。
close(fd);
V4L2 API及數據結構
1、常用的結構體在內核目錄include/linux/videodev2.h中定義
struct v4l2_requestbuffers reqbufs;//向驅動申請幀緩沖的請求,里面包含申請的個數 struct v4l2_capability cap;//這個設備的功能,比如是否是視頻輸入設備 struct v4l2_input input; //視頻輸入 struct v4l2_standard std;//視頻的制式,比如PAL,NTSC struct v4l2_format fmt;//幀的格式,比如寬度,高度等 struct v4l2_buffer buf;//代表驅動中的一幀 v4l2_std_id stdid;//視頻制式,例如:V4L2_STD_PAL_B struct v4l2_queryctrl query;//某一類型的控制 struct v4l2_control control;//具體控制的值
2、常用的IOCTL接口命令也在include/linux/videodev2.h中定義
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。
3、操作流程
V4L2提供了很多訪問接口,你可以根據具體需要選擇操作方法。需要注意的是,很少有驅動完全實現了所有的接口功能。所以在使用時需要參考驅動源碼,或仔細閱讀驅動提供者的使用說明。
下面列舉出一種操作的流程,供參考。
(1)打開設備文件
int fd = open(Devicename,mode); Devicename:/dev/video0、/dev/video1 …… Mode:O_RDWR [| O_NONBLOCK] 如果使用非阻塞模式調用視頻設備,則當沒有可用的視頻數據時,不會阻塞,而立刻返回。
(2)選擇視頻輸入
struct v4l2_input input; input.index = 0; int ret = ioctl(fd, VIDIOC_S_INPUT, &input); 一個視頻設備可以有多個視頻輸入。如果只有一路輸入,這個功能可以沒有。
(3)設置視頻捕獲格式
struct v4l2_format fmt; fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32; fmt.fmt.pix.height = height; fmt.fmt.pix.width = width; ret = ioctl(fd, VIDIOC_S_FMT, &fmt); if(ret) { perror("VIDIOC_S_FMT/n"); close(fd); return -1; }
(4)向驅動申請幀緩存
struct v4l2_requestbuffers req; if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) { return -1; }
v4l2_requestbuffers結構中定義了緩存的數量,驅動會據此申請對應數量的視頻緩存。多個緩存可以用於建立FIFO,來提高視頻采集的效率。
( 5 )獲取每個緩存的信息,並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) {//獲取到對應index的緩存信息,此處主要利用length信息及offset信息來完成后面的mmap操作。 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; }
(6)開始采集視頻
int buf_type= V4L2_BUF_TYPE_VIDEO_CAPTURE; int ret = ioctl(fd, VIDIOC_STREAMON, &buf_type);
(7)取出FIFO緩存中已經采樣的幀緩存
struct v4l2_buffer buf; memset(&buf,0,sizeof(buf)); buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory=V4L2_MEMORY_MMAP; buf.index=0;//此值由下面的ioctl返回 if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1) { return -1; } 根據返回的buf.index找到對應的mmap映射好的緩存,取出視頻數據。
(8)將剛剛處理完的緩沖重新入隊列尾,這樣可以循環采集
if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) { return -1; }
(9)停止視頻的采集
int ret = ioctl(fd, VIDIOC_STREAMOFF, &buf_type);
(10)關閉視頻設備
close(fd);