最近在做视频采集方面的工作, 对V4L2稍微研究了下,写下一些自己的心得和体会,同时做一下总结, 如果有那方面写错的话请多多指教
什么是V4L2(这篇文章只是针对Android设备)
我们都知道硬件千千万万,比如有A厂家的摄像头,B厂家的摄像头,那对应得就有A厂家摄像头驱动,B厂家
摄像头的驱动,那HAL就得通过A厂家驱动获取数据,B厂家驱动获取数据.那有无数厂家了?所以就抽象出
来了一套驱动框架,不管你那个厂家的我摄像头,上面只用调接口,通过驱动框架来获取数据.
官方解释:
下面的是总结的白话:
Video4Linux2是Linux内核中关于视频设备的内核驱动框架,为上层的访问底层的视频设备提供了统一的接口。
凡是内核中的子系统都有抽象底层硬件的差异,为上层提供统一的接口和提取出公共代码避免代码冗余等好处。
就像公司的老板一般都不会直接找底层的员工谈话,而是找部门经理了解情况,一个是因为底层屌丝人数多,
意见各有不同,措辞也不准,部门经理会把情况汇总后再向上汇报;二个是老板时间宝贵。
camera中运用的框架是是这样的: 位于HAL下层, 以后我会分析v4l2在HAL层的运用获取数据
至此我们大概明白了V4L2大概是做什么的.
怎么用呢?
思考:: 假如我不通过V4L2那么我怎么获取camera数据,并且把数据保存下来,视频应该获取不了,因为
视频会在HAL里面把一帧一帧数据传送给codec,然后经过编解码,才生成视频,我们通过V4l2能获取到
一帧一帧数据,并且保存为图片,其实HAL的数据就是通过v4l2拿到原始数据的,然后HAL层在经过算法
等的处理,返回给cameraservice,然后返回给App,App在去保存。所以就生成了图片
一个大致的流程类似这样的
- open device
去open 驱动节点的位置, 因为会v4l2的驱动节点会挂载在/dev/video0下面
fd = open(deviceName, O_RDWR | O_NONBLOCK, 0);
- init_device
struct v4l2_capability cap; //这个设备的功能,比如是否是视频输入设备 struct v4l2_cropcap cropcap; //裁剪能力 struct v4l2_crop crop; // 裁剪 struct v4l2_format fmt; // 设置视频捕获格式 struct v4l2_input input; //视频输入 一些参数的意思 // 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。 //查询能力 if (-1 == px_ioctrl(fd, VIDIOC_QUERYCAP, &cap)){ SKY_LOG("VIDIOC_QUERYCAP ERROR "); } if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)){ SKY_LOG("can't capture for V4L2_CAP_VIDEO_CAPTURE"); } if (!(cap.capabilities & V4L2_CAP_STREAMING)) { SKY_LOG("V4L2_CAP_STREAMING error"); } //把结构体所占的内存全部初始化为0, 方便以后的赋值操作 //这个地方可以定义一个宏函数暂时先这样 memset(&input, 0x00, sizeof(struct v4l2_input)); memset(&cropcap, 0x00, sizeof(struct v4l2_cropcap)); memset(&fmt, 0, sizeof(struct v4l2_format)); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width = cap_rect.width; fmt.fmt.pix.height = cap_rect.height; fmt.fmt.pix.pixelformat = CAPTURE_PIXELFORMAT; fmt.fmt.pix.field = V4L2_FIELD_INTERLACED_BT; //设置图像的格式 int result = ioctrl(fd, VIDIOC_TRY_FMT, &fmt); if (result < 0){ SKY_LOG("VIDIOC_TRY_FMT error"); } result = ioctrl(fd, VIDIOC_S_FMT, &fmt); if (result < 0){ SKY_LOG("VIDIOC_S_FMT error"); } memset(&crop, 0x00, sizeof(struct v4l2_crop)); crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; crop.c.left = cropcap.defrect.left; crop.c.top = cropcap.defrect.top; crop.c.width = cropcap.defrect.width; crop.c.height = cropcap.defrect.height; result = ioctrl(fd, VIDIOC_S_CROP, &crop); if (result < 0){ SKY_LOG("VIDIOC_S_CROP error"); }
- init_userptr
SLOGD("sky init userptr"); int result; struct v4l2_requestbuffers req; memset(&req, 0x00, sizeof(struct v4l2_requestbuffers)); req.count = USE_BUF_COUNT; // buffer size if 4 req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; // video capture type req.memory = V4L2_MEMORY_MMAP; //mmap 方式 result = px_ioctrl(fd, VIDIOC_REQBUFS, &req); //申请空间 if (req.count < 2) { SKY_LOG("insufficient buf memory\n"); //不足的 } if (req.count > USE_BUF_COUNT){ SKY_LOG("request buf memory\n"); //申请error } buffers = (struct buffer*)calloc(req.count, sizeof(*buffers)); for (n_buffers = 0; n_buffers < req.count; n_buffers++) { struct v4l2_buffer buf; //clear buffer memset(&buf, 0x00, sizeof(struct v4l2_buffer)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = n_buffers; //查询分配的buffer信息 int result = px_ioctrl(fd, VIDIOC_QUERYBUF, &buf); if (-1 == result) { SKY_LOG("VIDIOC_QUERYBUF error"); } //void *mmap(void *start, size_t length, int prot, int flags, //int fd, off_t offset); //start:: 映射区的开始地址 //length:: 映射区的长度 //prot :: 期望的内存保护标志,不能与文件的打开模式冲突 //flags:: 指定映射类型 //offset:被映射对象内容的起点。 buffers[n_buffers].length = buf.length; buffers[n_buffers].start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, buf.m.offset); }
- start_capturing VIDIOC_STREAMON 代表开始获取图片流
SLOGD("sky start capturing"); enum v4l2_buf_type type; int result; //n_buffers 在初始化buffer的时候已经自动+1了 for (size_t i = 0; i < n_buffers; i++){ struct v4l2_buffer buf; memset(&buf, 0x00, sizeof(buf)); buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf.memory = V4L2_MEMORY_MMAP; buf.index = i; //VIDIOC_QBUF 把buffer放入放进输入队列,等待填充数据 result = px_ioctrl(fd, VIDIOC_QBUF, &buf); if (-1 == result){ SKY_LOG("VIDIOC_QBUF error"); return; } } type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //开始 result = px_ioctrl(fd, VIDIOC_STREAMON, &type); if (-1 == result){ SKY_LOG("VIDIOC_STREAMON error"); }
-
不断的loop查询 ```java void mainloop() { SLOGD(“sky start main loop”); unsigned int count; count = MAX_FRAME_COUNT; //获取200帧数据
while (count – >0) { for (;;) { fd_set fds; struct timeval tv; int r;
FD_ZERO(&fds); FD_SET(fd, &fds); tv.tv_sec = 2; tv.tv_usec = 0; //检测摄像头是否有数据可以读取 //select 和 poll 来对文件描述符进行监听是否有数据可读。 r = select(fd+1, &fds, NULL, NULL, &tv); if (-1 == r) { if (EINTR == errno) continue; SKY_LOG("system error"); } if (0 == r) { SKY_LOG("select time out"); } read_frame(); }
}
}
6.关掉设备
```java
void close_device()
{
SLOGD("sky close device");
int close_id = close(fd);
if (-1 == close_id){
SKY_LOG("close error");
}
}