三、 V4L2 API 及數據結構
V4L2 是 V4L 的升級版本,爲 linux 下視頻設備程序提供了一套接口規範。包括一套數據結構和底層 V4L2 驅動接口。
1 、常用的結構體在內核目錄 include/linux/videodev2.h 中定義
struct v4l2_requestbuffers // 申請幀緩衝,對應命令 VIDIOC_REQBUFS
struct v4l2_capability // 視頻設備的功能,對應命令 VIDIOC_QUERYCAP
struct v4l2_input // 視頻輸入信息,對應命令 VIDIOC_ENUMINPUT
struct v4l2_standard // 視頻的制式,比如PAL ,NTSC ,對應命令 VIDIOC_ENUMSTD
struct v4l2_format // 幀的格式,對應命令VIDIOC_G_FMT 、VIDIOC_S_FMT 等
struct v4l2_buffer // 驅動 中的一幀圖像緩存,對應命令VIDIOC_QUERYBUF
struct v4l2_crop // 視頻信號矩形邊框
v4l2_std_id // 視頻制式
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 )取得設備的 capability
struct v4l2_capability capability ;
int ret = ioctl(fd, VIDIOC_QUERYCAP, &capability);
看看設備具有什麼功能,比如是否具有視頻輸入特性。
( 3 )選擇視頻輸入
struct v4l2_input input ;
……初始化 input
int ret = ioctl(fd, VIDIOC_QUERYCAP, &input);
一個視頻設備可以有多個視頻輸入。如果只有一路輸入,這個功能可以沒有。
( 4 )檢測視頻支持的制式
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;
fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
fmt.fmt.pix.height = height;
fmt.fmt.pix.width = width;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
ret = ioctl(fd, VIDIOC_S_FMT, &fmt);
if(ret) {
perror("VIDIOC_S_FMT/n");
close(fd);
return -1;
}
( 6 )向驅動申請幀緩存
struct v4l2_requestbuffers req;
if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
return -1;
}
v4l2_requestbuffers 結構中定義了緩存的數量,驅動會據此申請對應數量的視頻緩存。多個緩存可以用於建立 FIFO ,來提高視頻採集的效率。
( 7 )獲取每個緩存的信息,並 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;
}
( 8 )開始採集視頻
int buf_type= V4L2_BUF_TYPE_VIDEO_CAPTURE ;
int ret = ioctl(fd, VIDIOC_STREAMON, &buf_type);
( 9 )取出 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 映射好的緩存,取出視頻數據。
( 10 )將剛剛處理完的緩衝重新入隊列尾,這樣可以循環採集
if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
return -1;
}
( 11 )停止視頻的採集
int ret = ioctl(fd, VIDIOC_STREAMOFF, &buf_type);
( 12 )關閉視頻設備
close(fd);