概述
上一节我们了解了v4l2 api的使用方法,这一节我们来看下相关框架。不过这里先不介绍media framework,media的相关内容后面的文章再讲。
框架图
这个框架图分为4个部分:用户空间、v4l2核心、平台驱动、寄存器。这里的平台驱动指的是camera interface(CAMIF)驱动,例如三星的fimc驱动,下面我会以fimc驱动为例子讲述这里的平台驱动。上一节我们知道了获取摄像头图像是一般流程:
1、以O_RDWR | O_NONBLOCK的方式open设备节点,为啥是O_NONBLOCK,因为在VIDIOC_DQBUF的时候如果在输出队列中没有数据,那默认是阻塞的。
2、通过VIDIOC_QUERYCAP获取摄像头的相关信息。
3、通过VIDIOC_S_FMT设置摄像头格式
4、通过VIDIOC_REQBUFS请求buffer,一般是4个buffer。
5、通过VIDIOC_QUERYBUF查询buffer,如果buffer已请求成功,则调用mmap映射驱动分配的buffer到应用层
6、通过VIDIOC_QBUF把buffer放到传入队列
7、通过VIDIOC_STREAMON启动流传输
8、在文件描述符上poll或者select等待数据
9、被唤醒后通过VIDIOC_DQBUF从传出队列中取出数据,同时会告诉你数据放在哪个buffer。
10、处理图像、显示图像,然后回到6操作继续。
这个框架图以buffer为核心描述了VIDIOC_REQBUFS、VIDIOC_QBUF、VIDIOC_STREAMON和VIDIOC_DQBUF涉及到的操作。我们以这几个ioctl宏来讲下v4l2框架。
VIDIOC_REQBUFS
fimc驱动实现了v4l2_ioctl_ops的成员函数vidioc_reqbufs。通过对/dev/videox节点的VIDIOC_REQBUFS操作会调用到vb2_ioctl_reqbufs,来看下vb2_ioctl_reqbufs里面的操作。
//注:以缩进的形式或者"---->"来表示函数调用。
int vb2_ioctl_reqbufs(struct file *file, void *priv,struct v4l2_requestbuffers *p){struct video_device *vdev = video_devdata(file);.....res = vb2_core_reqbufs(vdev->queue, p->memory, &p->count);---->---> {__vb2_queue_alloc(q, memory, num_buffers, num_planes, plane_sizes) --->-->{for (buffer = 0; buffer < num_buffers; ++buffer) {//用户层指定的buffer数量struct vb2_buffer *vb;vb = kzalloc(q->buf_struct_size, GFP_KERNEL);vb->vb2_queue = q;..... //fill vbif (memory == VB2_MEMORY_MMAP) { //上层指定stream I/O方式为mmapret = __vb2_buf_mem_alloc(vb);mem_priv = call_ptr_memop(vb, alloc,.....);struct vb2_dc_buf *buf;buf = kzalloc(sizeof *buf, GFP_KERNEL);buf->cookie = dma_alloc_attrs(dev, size, &buf->dma_addr,GFP_KERNEL | gfp_flags, buf->attrs);buf->dma_dir = dma_dir;return buf;vb->planes[plane].mem_priv = mem_priv;}}}}
}
在这里,我们可以清晰的看到请求的buffer实际上就是vb2_buffer,我们应用层通过struct v4l2_requestbuffers的count成员来指定申请的buffer数量,指定多少个buffer就分配多少个vb2_buffer。再往__vb2_buf_mem_alloc分析代码,可以发现里面还为分配的buffer申请了dma相关资源。这些资源会在VIDIOC_QBUF和mmap的时候用到,一个是把dma输出地址写入cameif dma输出寄存器,另一个是映射一个一致性dma内存到应用空间。
mmap
在mmap buffer的时候会利用到前面申请的dma资源,下面的dma_mmap_attrs会映射一个一致性dma内存到应用空间。
//注:以缩进的形式或者"---->"来表示函数调用。
mmap vb2_dc_mmap(void *buf_priv, struct vm_area_struct *vma) //mmap刚才分配的dma地址到虚拟内存,即到应用层ret = dma_mmap_attrs(buf->dev, vma, buf->cookie,buf->dma_addr, buf->size, buf->attrs)
VIDIOC_QBUF
这个操作会把前面分配的buffer放入传入队列中,也就是放入vb2_queue的queued_list中。前面的请求buffer操作申请了v4l2_requestbuffers.count个vb2_buffer,并且做了初始化的操作,但是我们应用空间也有一个类似的v4l2_buffer,那这两个buffer有什么关系呢?在我看来这两个buffer是一个映射关系,在QBUF的时候会把v4l2_buffer的部分数据信息拷贝到vb2_buffer中。具体的拷贝动作在vb2_fill_vb2_v4l2_buffer中,它会把v4l2_buffer的planes等信息拷贝给vb2_buffer。
//注:以缩进的形式或者"---->"来表示函数调用。
vb2_qbufvb2_queue_or_prepare_bufvb2_fill_vb2_v4l2_buffer(vb, b) //(usrspace data)v4l2_buffer data to vb2_buffervb2_core_qbuf ---->--->{/* Fill buffer information for the userspace */if (pb) {call_void_bufop(q, copy_timestamp, vb, pb);call_void_bufop(q, fill_user_buffer, vb, pb);}if (!vb->prepared) { //这里还没有准备,所以要prepareret = __buf_prepare(vb); ---->---->{switch (q->memory) {case VB2_MEMORY_MMAP: //ret = __prepare_mmap(vb);---->---->{ //struct vb2_buffer *vb//fill planesret = call_bufop(vb->vb2_queue, fill_vb2_buffer, vb, vb->planes); return ret ? ret : call_vb_qop(vb, buf_prepare, vb);}}vb->prepared = true;}}//添加到queued buffers list中,这个buffer会在dqbuf dequeued的时候被取出list_add_tail(&vb->queued_entry, &q->queued_list);__enqueue_in_driver(vb);call_void_vb_qop(vb, buf_queue, vb);---->----->void buffer_queue(struct vb2_buffer *vb) {//in fimc_capture.c//设置dma地址到fimc 寄存器中}}
VIDIOC_STREAMON
fimc驱动实现了v4l2_ioctl_ops的成员函数vidioc_streamon,这个函数会调用到vb2_ioctl_streamon,来看下vb2_ioctl_streamon会去做什么操作。
//注:以缩进的形式或者"---->"来表示函数调用。
vb2_ioctl_streamonvb2_streamonvb2_core_streamonvb2_start_streamingret = call_qop(q, start_streaming, q,atomic_read(&q->owned_by_drv_count));start_streaming //(struct vb2_ops fimc_capture_qops)fimc_capture_hw_init //写寄存器fimc_hw_set_out_dmafimc_activate_capture //写寄存器
其实streamon的主要操作是调用平台驱动往cameraif的流传输寄存器写入数据,一般就是写入输出dma的地址,然后启动摄像头流模式传输。
中断
当cameraif接受到图像数据的时候,会产生硬件中断。在4412的fimc驱动中会这样处理:
//注:以缩进的形式或者"---->"来表示函数调用。
fimc_capture_irq_handler vb2_buffer_done(&v_buf->vb.vb2_buf, VB2_BUF_STATE_DONE);list_add_tail(&vb->done_entry, &q->done_list);//把完成数据填充的buffer放入vb2_queue的done_list中done_listwake_up(&q->done_wq); //唤醒在等待队列
VIDIOC_DQBUF
这个操作会调用到vb2_dqbuf,这个函数会从done_list中取出vb2_buffer,然后从vb2_buffer提取出相关信息保存给应用层的v4l2_buffer。当函数返回后,就可以在应用拿到对应的buffer数据了。
//注:以缩进的形式或者"---->"来表示函数调用。
vb2_dqbufvb2_core_dqbuf__vb2_get_done_vb(....,struct vb2_buffer **vb,....)__vb2_wait_for_done_vb //wait for a buffer to become available for dequeuing wait_event_interruptible(q->done_wq,.....)*vb = list_first_entry(&q->done_list, struct vb2_buffer, done_entry)call_void_vb_qop(vb, buf_finish, vb); vb->prepared = false;if (pb) //userspace 有提供v4l2_buffer,所以会进入这里call_void_bufop(q, fill_user_buffer, vb, pb); --->--->__fill_v4l2_buffer(struct vb2_buffer *vb, void *pb){ //fill in a struct v4l2_buffer with information to be returned to userspacefor (plane = 0; plane < vb->num_planes; ++plane) {struct v4l2_plane *pdst = &b->m.planes[plane];struct vb2_plane *psrc = &vb->planes[plane];pdst->bytesused = psrc->bytesused;pdst->length = psrc->length;if (q->memory == VB2_MEMORY_MMAP) //走这里,pdst->m.mem_offset = psrc->m.offset;else if (q->memory == VB2_MEMORY_USERPTR)pdst->m.userptr = psrc->m.userptr;else if (q->memory == VB2_MEMORY_DMABUF)pdst->m.fd = psrc->m.fd;pdst->data_offset = psrc->data_offset;memset(pdst->reserved, 0, sizeof(pdst->reserved));}}list_del(&vb->queued_entry);
到了这里,我们已经整体了领悟v4l2的框架…