static int video_open(const char *devname) { struct v4l2_capability cap; int dev, ret; dev = open(devname, O_RDWR); if (dev < 0) { TestAp_Printf(TESTAP_DBG_ERR, "Error opening device %s: %d.\n", devname, errno); return dev; } memset(&cap, 0, sizeof cap); ret = ioctl(dev, VIDIOC_QUERYCAP, &cap); if (ret < 0) { TestAp_Printf(TESTAP_DBG_ERR, "Error opening device %s: unable to query device.\n", devname); close(dev); return ret; } }
static int video_set_format(int dev, unsigned int w, unsigned int h, unsigned int format) { struct v4l2_format fmt; int ret; memset(&fmt, 0, sizeof fmt); fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; fmt.fmt.pix.width = w; fmt.fmt.pix.height = h; fmt.fmt.pix.pixelformat = format; fmt.fmt.pix.field = V4L2_FIELD_ANY; ret = ioctl(dev, VIDIOC_S_FMT, &fmt); if (ret < 0) { TestAp_Printf(TESTAP_DBG_ERR, "Unable to set format: %d.\n", errno); return ret; } TestAp_Printf(TESTAP_DBG_FLOW, "Video format set: width: %u height: %u buffer size: %u\n", fmt.fmt.pix.width, fmt.fmt.pix.height, fmt.fmt.pix.sizeimage); return 0; }
static int video_set_framerate(int dev, int framerate) { struct v4l2_streamparm parm; int ret; memset(&parm, 0, sizeof parm); parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; ret = ioctl(dev, VIDIOC_G_PARM, &parm); if (ret < 0) { TestAp_Printf(TESTAP_DBG_ERR, "Unable to get frame rate: %d.\n", errno); return ret; } TestAp_Printf(TESTAP_DBG_FLOW, "Current frame rate: %u/%u\n", parm.parm.capture.timeperframe.numerator, parm.parm.capture.timeperframe.denominator); parm.parm.capture.timeperframe.numerator = 1; parm.parm.capture.timeperframe.denominator = framerate; ret = ioctl(dev, VIDIOC_S_PARM, &parm); if (ret < 0) { TestAp_Printf(TESTAP_DBG_ERR, "Unable to set frame rate: %d.\n", errno); return ret; } ret = ioctl(dev, VIDIOC_G_PARM, &parm); if (ret < 0) { TestAp_Printf(TESTAP_DBG_ERR, "Unable to get frame rate: %d.\n", errno); return ret; } TestAp_Printf(TESTAP_DBG_FLOW, "Frame rate set: %u/%u\n", parm.parm.capture.timeperframe.numerator, parm.parm.capture.timeperframe.denominator); return 0; }
static int video_reqbufs(int dev, int nbufs) { struct v4l2_requestbuffers rb; int ret; memset(&rb, 0, sizeof rb); rb.count = nbufs; rb.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; rb.memory = V4L2_MEMORY_MMAP; ret = ioctl(dev, VIDIOC_REQBUFS, &rb); if (ret < 0) { TestAp_Printf(TESTAP_DBG_ERR, "Unable to allocate buffers: %d.\n", errno); return ret; } TestAp_Printf(TESTAP_DBG_FLOW, "%u buffers allocated.\n", rb.count); return rb.count; }
对应的内核处理流程如下:
主要的工作,就是通过vb2_vmalloc_alloc函数中的vmalloc_user函数分配内核虚拟地址连续的内存(虚拟地址空间属于(VMALLOC_START, VMALLOC_END)空间,并建立
相应的内核映射),每个这样的内存块(请求中,总共有nbufs个视频buf),都通过struct vb2_buffer *vb结构体来表示,并将该分配的虚拟内存块的开始地址和长度分别存储在如下位置:
vb->planes[plane].mem_priv = mem_priv;//mem_priv即为vb2_vmalloc_alloc函数返回的struct vb2_vmalloc_buf *buf结构体,其中buf->vaddr指向分配的vamlloc内存的开始地址。
vb->v4l2_planes[plane].length = q->plane_sizes[plane];//
vb初始化完成后,会放入的buf队列的如下位置:
最后,会设置每个vb所对应的视频buf帧所对应的偏移地址:q->bufs[q->num_buffers + buffer] = vb;
vb->v4l2_planes[plane].length = q->plane_sizes[plane];
vb->v4l2_planes[plane].m.mem_offset = off;
off += vb->v4l2_planes[plane].length;
for (i = 0; i < nbufs; ++i) { memset(&buf0, 0, sizeof buf0); buf0.index = i; buf0.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf0.memory = V4L2_MEMORY_MMAP; ret = ioctl(dev, VIDIOC_QUERYBUF, &buf0); if (ret < 0) { TestAp_Printf(TESTAP_DBG_ERR, "Unable to query buffer %u (%d).\n", i, errno); close(dev); return 1; } TestAp_Printf(TESTAP_DBG_FLOW, "length: %u offset: %10u -- ", buf0.length, buf0.m.offset); mem0[i] = mmap(0, buf0.length, PROT_READ, MAP_SHARED, dev, buf0.m.offset); if (mem0[i] == MAP_FAILED) { TestAp_Printf(TESTAP_DBG_ERR, "Unable to map buffer %u (%d)\n", i, errno); close(dev); return 1; } TestAp_Printf(TESTAP_DBG_FLOW, "Buffer %u mapped at address %p.\n", i, mem0[i]); }
VIDIOC_QUERYBUF的内核流程如下:
主要工作:通过用户传递进来的buf index(struct v4l2_buffer *b),从buf队列上来找到对应的struct vb2_buffer *vb;
vb = q->bufs[b->index];
然后在__fill_v4l2_buffer函数中,利用vb中的信息来填充struct v4l2_buffer *b结构,主要包括如下信息:
buf的长度、和存储数据的长度和buf对应的偏移
struct v4l2_buffer *b->length = vb->v4l2_planes[0].length;
b->bytesused = vb->v4l2_planes[0].bytesused;
b->m.offset = vb->v4l2_planes[0].m.mem_offset;
在mmap之前,执行一个vidioc_querybuf的目的就是为了获取buf对应的偏移量:b->m.offset
video mmap的内核过程如下:
其中最主要的工作:就是将之前内核分配的视频buf映射到用户空间,这样用户空间就可以直接读取内核扑获的视频数据。
其中最重要的函数则是:vb2_mmap和vb2_vmalloc_mmap
关于vb2_mmap函数的描述,内核注释有如下的表述:
/**
* vb2_mmap() - map video buffers into application address space
* @q: videobuf2 queue
* @vma: vma passed to the mmap file operation handler in the driver
*
* Should be called from mmap file operation handler of a driver.
* This function maps one plane of one of the available video buffers to
* userspace. To map whole video memory allocated on reqbufs, this function
* has to be called once per each plane per each buffer previously allocated.
*
* When the userspace application calls mmap, it passes to it an offset returned
* to it earlier by the means of vidioc_querybuf handler. That offset acts as
* a "cookie", which is then used to identify the plane to be mapped.
* This function finds a plane with a matching offset and a mapping is performed
* by the means of a provided memory operation.
*
* The return values from this function are intended to be directly returned
* from the mmap handler in driver.
*/
上面的注释中,提到的最重要的一条就是:在执行mmap系统调用的时候,传递进去的一个offset,是作为一个cookie来使用的,表示当前是要对哪个video buffer进行映射操作。具体到vb2_mmap函数中,应用层传递进来的offset(buf0.m.offset)是存储在vma->vm_pgoff变量中。
__find_plane_by_offset函数负责根据用户空间提供的offset来找到对应的struct vb2_buffer *vb,然后直接调用remap_vmalloc_range函数,将vmalloc空间的内存直接映射到用户空间中去,用户空间的地址范围在(vma->vm_start,vma->vm_end)。
for (i = 0; i < nbufs; ++i) { memset(&buf0, 0, sizeof buf0); buf0.index = i; buf0.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf0.memory = V4L2_MEMORY_MMAP; ret = ioctl(dev, VIDIOC_QBUF, &buf0); if (ret < 0) { TestAp_Printf(TESTAP_DBG_ERR, "Unable to queue buffer0(%d).\n", errno); close(dev); return 1; } }
static int video_enable(int dev, int enable) { int type = V4L2_BUF_TYPE_VIDEO_CAPTURE; int ret; ret = ioctl(dev, enable ? VIDIOC_STREAMON : VIDIOC_STREAMOFF, &type); if (ret < 0) { TestAp_Printf(TESTAP_DBG_ERR, "Unable to %s capture: %d.\n", enable ? "start" : "stop", errno); return ret; } return 0; }
VIDIOC_STREAMON的内核功能流程:
驱动中,会将之前通过协商设置的参数(图像格式(yuv,mjpeg,h264等),宽,高,帧率等)通过commit命令(VS_COMMIT_CONTROL )发送到设备,让其生效,并开始工作。在分配和初始bulk/iso 的urb后(包括设置urb的完成回调函数),最后调用usb_submit_urb函数,将这个urb提交到usb hcd驱动核心的urb传输队列中。这样urb就可以开始收usb控制器接收到的视频数据料。
for (i = 0; i < nframes; ++i) { /* Dequeue a buffer. */ memset(&buf0, 0, sizeof buf0); buf0.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; buf0.memory = V4L2_MEMORY_MMAP; ret = ioctl(dev, VIDIOC_DQBUF, &buf0); if (ret < 0) { TestAp_Printf(TESTAP_DBG_ERR, "Unable to dequeue buffer0 (%d).\n", errno); close(dev); return 1; }/*save image picture captured*/ if(rec_fp1 == NULL) rec_fp1 = fopen(rec_filename, "a+b"); if(rec_fp1 != NULL) { fwrite(mem0[buf0.index], buf0.bytesused, 1, rec_fp1); } /* Requeue the buffer. */ if (delay > 0) usleep(delay * 1000); ret = ioctl(dev, VIDIOC_QBUF, &buf0); if (ret < 0) { TestAp_Printf(TESTAP_DBG_ERR, "Unable to requeue buffer0 (%d).\n", errno); close(dev); return 1; } fflush(stdout); }
struct vb2_queue *q有两个队列:
q->done_list: 用来连接那些已经保存了不久前扑获的视频数据帧的struct vb2_buffer;
q->queued_list:用来链接那些已经空闲的struct vb2_buffer内存块,当视频数据到来时,驱动会从这个队列中,取出一个struct vb2_buffe来存储新到来的视频数据,待储存一个完整的视频帧后,就将该struct vb2_buffe在连接到q->queued_list 队列的同时又添加到q->done_list 队列上,并唤醒等待队列:q->done_wq。
针对uvc驱动,一个struct vb2_buffer视频帧,在VIDIOC_QBUF时,他首先是放在struct vb2_queue *q->queued_list 队列上,然后再挂在struct uvc_video_queue *queue->irqqueue队列上,然后供uvc_video_complete函数来使用。
struct vb2_buffer 与 struct vb2_vmalloc_buf联系
vb2_buffer.mem 和vb2_vmalloc_buf.vaddr 都指向vmalloc内存块的开始地址
vb2_buffer.length 和vb2_vmalloc_buf.size 都指向vmalloc内存的长度
vb2_buffer.bytesused
struct vb2_vmalloc_buf主要被memops操作时使用。
而vb2_buffer主要被用来抽象vb2_vmalloc_buf所表示的vmalloc物理内存。
VIDIOC_QBUF的内核流程:
大概的过程:
uvc_buffer_queue函数负责将struct vb2_buffer内存块挂在struct uvc_video_queue *queue->irqqueue队列上,
在这之前vb2_qbuf函数将struct vb2_buffer内存块先是挂在struct vb2_queue *q-〉queued_list队列上。然后就
可以供uvc的驱动在urb的完成函数中(uvc_video_complete)来使用这个队列中的buf来填充扑获的视频数据了。
uvc_video_complete的流程如下:
步骤1:从struct uvc_video_queue *queue->irqqueue队列上,取出一个空闲的内存块,来存储从urb拷贝过来的数据。
步骤3,9,10开始对视频帧进行译码
步骤4:从struct uvc_video_queue *queue->irqqueue上取下一个空闲的内存块,并将前一块已经存满一帧视频的内存块从queue->irqqueue上删除(list_del(&buf->queue);)
并将该内存块添加到struct vb2_queue *q->done_list列表中(步骤7),并唤醒等待视频帧的用户(wake_up(&q->done_wq);//步骤8)
VIDIOC_DQBUF的流程:
步骤5:检查struct vb2_queue *q->done_list列表是否为空,如果为空则睡眠等待,不为空则从struct vb2_queue *q->done_list列表中取下一个视频buf块,并将他从q->done_list列表删除(步骤6)
步骤9:最后将视频buf块同时也从struct vb2_queue *q->queued_list列表中删除,并设置vb->state的状态为:VB2_BUF_STATE_DEQUEUED
video_enable (struct vdIn *vd) { int type = V4L2_BUF_TYPE_VIDEO_CAPTURE; int ret; ret = ioctl (vd->fd, VIDIOC_STREAMON, &type); if (ret < 0) { TestAp_Printf(TESTAP_DBG_ERR, "Unable to %s capture: %d.\n", "start", errno); return ret; } vd->isstreaming = 1; return 0; }
close(dev);