camera的测试程序-预览的保存

step1: 打开视频设备

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;
	}
}


step2: Set the video format

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;
}


step3: Set the frame rate

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;
}


step4:Allocate buffers.

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队列的如下位置:

q->bufs[q->num_buffers + buffer] = vb;

最后,会设置每个vb所对应的视频buf帧所对应的偏移地址:

vb->v4l2_planes[plane].length = q->plane_sizes[plane];

vb->v4l2_planes[plane].m.mem_offset = off;

off += vb->v4l2_planes[plane].length;

step5: Map the buffers.

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)。

step6: Queue the buffers.

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;
		}
	}


step7: Start streaming.

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控制器接收到的视频数据料。

step8: 获取视频帧,Dequeue a buffer,并保存到文件里,然后Requeue the buffer.

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

step9: 停止获取视频流

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;
}


step10: 关闭视频设备:

close(dev);


问题1: uvc_video_decode_isoc中的urb->number_of_packets的含义

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值