1. 程式人生 > >camera的測試程式-預覽的儲存

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的含義