5.2 应用程序和驱动程序中buffer的传输流程

i.MX6Q---IPU总结笔记 同时被 2 个专栏收录
35 篇文章 5 订阅
35 篇文章 48 订阅

摄像头采集的数据,是通过buffer的形式传到驱动程序中,然后驱动程序使能CSI设备来采集数据。之后将采集到的数据再次通过buffer的形式传递给应用程序中。这个过程中使用了VIDIOC_REQBUFSVIDIOC_QUERYBUFVIDIOC_QBUFVIDIOC_STREAMONVIDIOC_DQBUF这些ioctl调用。下面就来具体分析这个流程。


1. 内核中的数据结构

内核中有关V4L2的头文件是/include/uapi/linux/videodev2.h文件。


1.1 v4l2_requestbuffers结构体:

enum v4l2_memory { 
	V4L2_MEMORY_MMAP             = 1,    //内核空间开辟缓冲区
	V4L2_MEMORY_USERPTR          = 2,    //用户空间的应用中开辟缓冲区
	V4L2_MEMORY_OVERLAY          = 3, 
	V4L2_MEMORY_DMABUF           = 4, 
};
enum v4l2_buf_type { 
	V4L2_BUF_TYPE_VIDEO_CAPTURE        = 1, 
	V4L2_BUF_TYPE_VIDEO_OUTPUT         = 2, 
	V4L2_BUF_TYPE_VIDEO_OVERLAY        = 3, 
	V4L2_BUF_TYPE_VBI_CAPTURE          = 4, 
	V4L2_BUF_TYPE_VBI_OUTPUT           = 5, 
	V4L2_BUF_TYPE_SLICED_VBI_CAPTURE   = 6, 
	V4L2_BUF_TYPE_SLICED_VBI_OUTPUT    = 7, 
	/* Experimental */ 
	V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8, 
	V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE = 9, 
	V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE  = 10, 
	/* Deprecated, do not use */ 
	V4L2_BUF_TYPE_PRIVATE              = 0x80, 
};

struct v4l2_requestbuffers { 
	__u32			count; 
	__u32			type;		/* enum v4l2_buf_type */ 
	__u32			memory;		/* enum v4l2_memory */ 
	__u32			reserved[2]; 
};

这个v4l2_requestbuffers结构体用在VIDIOC_REQBUFSioctl中,它代表想要申请内存的属性。

count代表想要申请的buffers的个数;

type代表buffer的类型,对于capture设备来说,一般都是V4L2_BUF_TYPE_VIDEO_CAPTURE

memory关于这个memory的类型,是传输视频帧的方法,主要有两种方法,readwrite方法和流I/O的方法。常用的是流I/O的方法,应用程序通过mmap系统调用映射到用户地址空间,从而使得用户和内核只需要交换缓冲区指针即可,不需要拷贝帧。

比如在应用程序中:

	struct v4l2_requestbuffers req; 
      memset(&req, 0, sizeof (req)); 
      req.count = TEST_BUFFER_NUM; 
      req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
      req.memory = V4L2_MEMORY_MMAP;
	ioctl(fd_v4l, VIDIOC_REQBUFS, &req)


1.2 v4l2_buffer结构体:

首先介绍一下v4l2_plane结构体,它如下所示,包含在v4l2_buffer结构体中:

/** 
 * struct v4l2_plane - plane info for multi-planar buffers 
 * @bytesused:		number of bytes occupied by data in the plane (payload) 
 * @length:		size of this plane (NOT the payload) in bytes 
 * @mem_offset:		when memory in the associated struct v4l2_buffer is 
 *			V4L2_MEMORY_MMAP, equals the offset from the start of 
 *			the device memory for this plane (or is a "cookie" that 
 *			should be passed to mmap() called on the video node) 
 * @userptr:		when memory is V4L2_MEMORY_USERPTR, a userspace pointer 
 *			pointing to this plane 
 * @fd:			when memory is V4L2_MEMORY_DMABUF, a userspace file 
 *			descriptor associated with this plane 
 * @data_offset:	offset in the plane to the start of data; usually 0, 
 *			unless there is a header in front of the data 
 * 
 * Multi-planar buffers consist of one or more planes, e.g. an YCbCr buffer 
 * with two planes can have one plane for Y, and another for interleaved CbCr 
 * components. Each plane can reside in a separate memory buffer, or even in 
 * a completely separate memory node (e.g. in embedded devices). 
 */ 
struct v4l2_plane { 
	__u32			bytesused; 
	__u32			length; 
	union { 
		__u32		mem_offset; 
		unsigned long	userptr; 
		__s32		fd; 
	} m; 
	__u32			data_offset; 
	__u32			reserved[11]; 
}; 

这个plane我翻译成位面,这个结构体是用在多位面的情况下每个位面的一些信息。

bytesused表示已经使用的字节数(载重的大小)。

length表示这个位面所拥有的字节数(不是载重)。

m.offset表示从设备内存基址开始到这个位面的偏移值。

m.userptr表示用户空间的一个指针指向这个位面。

m.fd表示用户空间与此位面相关连的一个文件描述符。

data_offset表示这个位面中数据开始的偏移值,一般为0.


这个结构体中m那个联合中的参数与enumv4l2_memory结构体中的数据是息息相关的。这个v4l2_plane一般嵌入在v4l2_buffer结构体中,同时v4l2_buffer结构体中也有一个memory字段,即enumv4l2_memory

memory== V4L2_MEMORY_MMAP的时候,则m.memoffset设置。

memory== V4L2_MEMORY_USERPTR的时候,则m.userptr设置。

memory== V4L2_MEMORY_DMABUF的时候,则m.fd设置。

/** 
 * struct v4l2_buffer - video buffer info 
 * @index:	id number of the buffer 
 * @type:	enum v4l2_buf_type; buffer type (type == *_MPLANE for 
 *		multiplanar buffers); 
 * @bytesused:	number of bytes occupied by data in the buffer (payload); 
 *		unused (set to 0) for multiplanar buffers 
 * @flags:	buffer informational flags 
 * @field:	enum v4l2_field; field order of the image in the buffer 
 * @timestamp:	frame timestamp 
 * @timecode:	frame timecode 
 * @sequence:	sequence count of this frame 
 * @memory:	enum v4l2_memory; the method, in which the actual video data is 
 *		passed 
 * @offset:	for non-multiplanar buffers with memory == V4L2_MEMORY_MMAP; 
 *		offset from the start of the device memory for this plane, 
 *		(or a "cookie" that should be passed to mmap() as offset) 
 * @userptr:	for non-multiplanar buffers with memory == V4L2_MEMORY_USERPTR; 
 *		a userspace pointer pointing to this buffer 
 * @fd:		for non-multiplanar buffers with memory == V4L2_MEMORY_DMABUF; 
 *		a userspace file descriptor associated with this buffer 
 * @planes:	for multiplanar buffers; userspace pointer to the array of plane 
 *		info structs for this buffer 
 * @length:	size in bytes of the buffer (NOT its payload) for single-plane 
 *		buffers (when type != *_MPLANE); number of elements in the 
 *		planes array for multi-plane buffers 
 * @input:	input number from which the video data has has been captured 
 * 
 * Contains data exchanged by application and driver using one of the Streaming 
 * I/O methods. 
 */ 
struct v4l2_buffer { 
	__u32			index; 
	__u32			type; 
	__u32			bytesused; 
	__u32			flags; 
	__u32			field; 
	struct timeval		timestamp; 
	struct v4l2_timecode	timecode; 
	__u32			sequence; 

	/* memory location */ 
	__u32			memory; 
	union { 
		__u32           offset; 
		unsigned long   userptr; 
		struct v4l2_plane *planes; 
		__s32		fd; 
	} m; 
	__u32			length; 
	__u32			reserved2; 
	__u32			reserved; 
};

index鉴别缓冲区的序号。

type缓冲区类型,就是enumv4l2_buf_type里面所包含的类型。

bytesused缓冲区中数据的大小,单位是byte

flags表示当前缓冲区的状态,有以下几种:

#defineV4L2_BUF_FLAG_MAPPED 0x0001 /* Buffer is mapped (flag) */

#defineV4L2_BUF_FLAG_QUEUED 0x0002 /* Buffer is queued for processing */

#defineV4L2_BUF_FLAG_DONE 0x0004 /* Buffer is ready */

#defineV4L2_BUF_FLAG_KEYFRAME 0x0008 /* Image is a keyframe (I-frame) */

#defineV4L2_BUF_FLAG_PFRAME 0x0010 /* Image is a P-frame */

#defineV4L2_BUF_FLAG_BFRAME 0x0020 /* Image is a B-frame */

/*Buffer is ready, but the data contained within is corrupted. */

#defineV4L2_BUF_FLAG_ERROR 0x0040

#defineV4L2_BUF_FLAG_TIMECODE 0x0100 /* timecode field is valid */

#defineV4L2_BUF_FLAG_PREPARED 0x0400 /* Buffer is prepared for queuing */

/*Cache handling flags */

#defineV4L2_BUF_FLAG_NO_CACHE_INVALIDATE 0x0800

#defineV4L2_BUF_FLAG_NO_CACHE_CLEAN 0x1000

/*Timestamp type */

#defineV4L2_BUF_FLAG_TIMESTAMP_MASK 0xe000

#defineV4L2_BUF_FLAG_TIMESTAMP_UNKNOWN 0x0000

#defineV4L2_BUF_FLAG_TIMESTAMP_MONOTONIC 0x2000

#defineV4L2_BUF_FLAG_TIMESTAMP_COPY 0x4000

timestamp时间戳。

timecode时间编码,对于视频类应用有用。

memory即是enumv4l2_memory中的哪一种。

m.offsetmemory==V4L2_MEMORY_MMAP,表示从设备内存基址到当前缓冲区的偏移值。(同时这个值也是传给mmap函数的offset参数)

m.userptrmemory==V4L2_MEMORY_USERPTR,表示用户空间的一个指针指向这个缓冲区。

m.fdmemory==V4L2_MEMORY_DMABUF,表示用户空间与此缓冲区相关联的一个文件描述符。

m.plane指向某一个structv4l2_plane结构体,在multiplanar情况下,用户指向位面数组的指针。

length缓冲区的大小non_multiplanar情况下);位面的个数(multiplanar情况下)。



2.VIDIOC_REQBUFS

应用程序中:

	  struct v4l2_requestbuffers req; 
        memset(&req, 0, sizeof (req)); 
        req.count = TEST_BUFFER_NUM; 
        req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
        req.memory = V4L2_MEMORY_MMAP; 

        if (ioctl(fd_v4l, VIDIOC_REQBUFS, &req) < 0) 
        { 
                printf("v4l_capture_setup: VIDIOC_REQBUFS failed\n"); 
                return 0; 
        }

其中

#defineTEST_BUFFER_NUM 3

通过这个VIDIOC_REQBUFSioctl调用来申请内存空间,这里的关键结构体就是structv4l2_requestbuffers结构体,对应设置req中的几个参数,然后就会调用到内核驱动中。


驱动中:

mxc_allocate_frame_buf(cam, req->count);

static int mxc_allocate_frame_buf(cam_data *cam, int count) 
for (i = 0; i < count; i++) 
{ 
	cam->frame[i].vaddress = dma_alloc_coherent(0, 
				       PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage), 
				       &cam->frame[i].paddress, GFP_DMA | GFP_KERNEL);

	cam->frame[i].buffer.index = i; 
	cam->frame[i].buffer.flags = V4L2_BUF_FLAG_MAPPED; 
	cam->frame[i].buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
	cam->frame[i].buffer.length = PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage); 
	cam->frame[i].buffer.memory = V4L2_MEMORY_MMAP; 
	cam->frame[i].buffer.m.offset = cam->frame[i].paddress; 
	cam->frame[i].index = i;
}

首先在内核的cam_data结构体中有一个重要的成员:structmxc_v4l_frame frame[FRAME_NUM];

这个成员structmxc_v4l_frame如下所示:

struct mxc_v4l_frame { 
    u32 paddress; 
    void *vaddress; 
    int count; 
    int width; 
    int height; 

    struct v4l2_buffer buffer; 
    struct list_head queue; 
    int index; 
    union { 
            int ipu_buf_num; 
            int csi_buf_num; 
    }; 
};

它包含一个很重要的结构体:structv4l2_buffer buffer,这个结构体正是我们前面所讲过的。它是本文要讨论的核心。


在这个函数中,通过dma_alloc_coherent函数申请req->count个缓冲区,之后设置cam->frame[i]中各个成员的值,其中最重要的是为structv4l2_buffer buffer赋值。

cam->frame[i].vaddress= dma_alloc_coherent(0,

PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage),

&cam->frame[i].paddress,GFP_DMA | GFP_KERNEL);

这个dma_alloc_coherent函数,会生成一个虚拟地址,保存在cam->frame[i].vaddress,同时会生成对应的物理地址,保存在&cam->frame[i].paddress

然后将通过cam->frame[i].buffer.m.offset= cam->frame[i].paddress;将这个物理地址同样保存在cam->frame[i].buffer.m.offset

那么在应用程序中#defineTEST_BUFFER_NUM3,而在驱动程序中,通过一个for循环,所以会申请成功个缓冲区,每个buffer的物理地址都保存在cam->frame[i].buffer.m.offset中,虚拟地址都保存在cam->frame[i].vaddress



3.VIDIOC_QUERYBUF


在应用程序中:

	struct v4l2_buffer buf;

	for (i = 0; i < TEST_BUFFER_NUM; i++) 
        { 
                memset(&buf, 0, sizeof (buf)); 
                buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
                buf.memory = V4L2_MEMORY_MMAP; 
	          buf.index = i; 
                if (ioctl(fd_v4l, VIDIOC_QUERYBUF, &buf) < 0) 
                { 
                        printf("VIDIOC_QUERYBUF error\n"); 
                        return -1; 
                } 

                buffers[i].length = buf.length; 
                buffers[i].offset = (size_t) buf.m.offset; 
                buffers[i].start = mmap (NULL, buffers[i].length, 
                    PROT_READ | PROT_WRITE, MAP_SHARED, 
                    fd_v4l, buffers[i].offset); 
				memset(buffers[i].start, 0xFF, buffers[i].length); 
        }

应用程序中首先声明了一个structv4l2_buffer buf;这个buf是个structv4l2_buffer类型的,其中在前面的分析中,可以看到,这个structv4l2_buffer类型包含在structmxc_v4l_frame,即cam->frame[i]中。


驱动程序中:

mxc_v4l2_buffer_status(cam, buf);
static int mxc_v4l2_buffer_status(cam_data *cam, struct v4l2_buffer *buf) 
	<span style="color:#FF0000;">memcpy(buf, &(cam->frame[buf->index].buffer), sizeof(*buf)</span>); 

最终会调用到mxc_v4l2_buffer_status函数中,这个函数的核心就是一个memcpy函数,他将

cam->frame[buf->index].buffer里面的内容拷贝到buf中。

其中这个cam->frame[buf->index].buffer是在上面2中已经申请配置的。

同时,在应用程序中,是一个for循环,会将cam->frame[buf->index].buffer中申请的每个buffer都拷贝到buf中。


看应用程序中的for循环,它在调用VIDIOC_QUERYBUF之后,同时就会将这个buf中的值拷贝到buffers[i],而这个buffers[i]是应用程序中的一个全局变量,如下所示:

struct testbuffer buffers[TEST_BUFFER_NUM]; 

struct testbuffer 
{ 
        unsigned char *start; 
        size_t offset; 
        unsigned int length; 
};

注意这个赋值语句:buffers[i].offset= (size_t) buf.m.offset;这个buf.m.offsetVIDIOC_QUERYBUF里面,通过memcpy函数,将cam->frame[i]中的内容拷贝过来的,buf.m.offset保存的是申请内存的物理地址。参见上面标红的语句

这时候,应用程序这个structtestbuffer 结构体中各个元素的含义就清楚了

start保存的是mmap函数映射内存后的首地址。

offset保存的是VIDIOC_QUERYBUFbuf.m.offset的值,同样是cam->frame[i].buffer.m.offset,即申请内存的物理地址。

length保存的是申请内存的大小


到这以后,总结一下:

首先通过dma_alloc_coherent函数申请内存,将物理地址,虚拟地址都保存在cam->frame[i]中,同时为cam->frame[i].buffer赋值。

然后就会调用VIDIOC_QUERYBUF,来将cam->frame[i]里面的buffer都取出来,通过buf这个中间变量赋值给应用程序中的buffers[i]中。

这时候,申请的内存的物理地址,虚拟地址,大小等等信息保存在驱动程序中的cam->frame[i]中和应用程序中的buffers[i]中。



4.VIDIOC_QBUF

应用程序中:

for (i = 0; i < TEST_BUFFER_NUM; i++) 
        { 
                memset(&buf, 0, sizeof (buf)); 
                buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
                buf.memory = V4L2_MEMORY_MMAP; 
                buf.index = i; 
		    buf.m.offset = buffers[i].offset; 

                if (ioctl (fd_v4l, VIDIOC_QBUF, &buf) < 0) { 
                        printf("VIDIOC_QBUF error\n"); 
                        return -1; 
                } 
        }

应用程序中,通过这个for循环,依次从应用程序中的buffer[i]中取出buffer,保存在buf这个中间变量中,然后调用VIDIOC_QBUFioctl


驱动中:

struct v4l2_buffer *buf = arg;
int index = buf->index;
if ((cam->frame[index].buffer.flags & 0x7) == V4L2_BUF_FLAG_MAPPED) 
{ 
	cam->frame[index].buffer.flags |= V4L2_BUF_FLAG_QUEUED; 
	list_add_tail(&cam->frame[index].queue, &cam->ready_q); 

	buf->flags = cam->frame[index].buffer.flags;
}


驱动程序中会为cam->frame[index].buffer.flags添加上V4L2_BUF_FLAG_QUEUED属性,同时将这个cam->frame[index].buffer添加到cam->ready_q队列中,然后将cam->frame[index].buffer.flags复制给bufflags属性


由于应用程序中是个for循环,最终会把应用程序中buffers[i]数组中的所有成员都通过这个VIDIOC_QBUFioctl调用来添加flags属性以及添加到cam->ready_q队列中。同时,可以发现,在驱动程序中,也同时更新了cam->frame[index]



5. VIDIOC_STREAMON


5.1 驱动程序中调用到mxc_streamon函数,在里面:

cam->enc_enable(cam);
cam->dummy_frame.vaddress = dma_alloc_coherent(0, 
			       PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage), 
			       &cam->dummy_frame.paddress, GFP_DMA | GFP_KERNEL);
cam->dummy_frame.buffer.type = V4L2_BUF_TYPE_PRIVATE; 
cam->dummy_frame.buffer.length = PAGE_ALIGN(cam->v2f.fmt.pix.sizeimage); 
cam->dummy_frame.buffer.m.offset = cam->dummy_frame.paddress;

cam_data结构体中,还有一个structmxc_v4l_frame dummy_frame;开始一直不理解这个成员的意义(一会再讲)。看上面的操作,在VIDIOC_REQBUFSioctl函数中,相似的操作已经做过一遍了。

需要注意的是,cam->dummy_frame.vaddress保存的是所申请内存的虚拟地址,&cam->dummy_frame.paddress保存的是所申请内存的物理地址,同时将这个物理地址赋给了cam->dummy_frame.buffer.m.offset


之后继续调用到:

prp_enc_setup(cam)

dma_addr_t dummy = cam->dummy_frame.buffer.m.offset;

ipu_init_channel_buffer(cam->ipu, CSI_PRP_ENC_MEM, 
					    IPU_OUTPUT_BUFFER, 
					    enc.csi_prp_enc_mem.out_pixel_fmt, 
					    enc.csi_prp_enc_mem.out_width, 
					    enc.csi_prp_enc_mem.out_height, 
					    cam->v2f.fmt.pix.bytesperline / 
					    bytes_per_pixel(enc.csi_prp_enc_mem. 
							    out_pixel_fmt), 
					    cam->rotation, 
					    dummy, dummy, 0, 
					    cam->offset.u_offset, 
					    cam->offset.v_offset);

_ipu_ch_param_init(ipu, dma_chan, pixel_fmt, width, height, stride, u, v, 0, 
			   phyaddr_0, phyaddr_1, phyaddr_2);

最终通过_ipu_ch_param_init函数,将这个dummy的物理地址放在CPMEMword[1]0282958位。


5.2ipu_request_irq函数

err = ipu_request_irq(cam->ipu, IPU_IRQ_PRP_ENC_OUT_EOF, 
				      prp_enc_callback, 0, "Mxc Camera", cam);

之后会调用到ipu_request_irq函数申请中断,中断处理函数是prp_enc_callback,后面需要用到这个中断处理函数的时候再具体分析。


5.3

继续在mxc_streamon函数中执行

frame = list_entry(cam->ready_q.next, struct mxc_v4l_frame, queue); 
list_del(cam->ready_q.next); 
list_add_tail(&frame->queue, &cam->working_q); 
frame->ipu_buf_num = cam->ping_pong_csi; 
err = cam->enc_update_eba(cam, frame->buffer.m.offset);

cam->ready_q队列中取出第一个mxc_v4l_frame实体(在QBUF的时候放进去的),将它放入cam->working_q队列中,然后调用cam->enc_update_eba函数来更新CPMEMword[1]中的0282958位中所存的地址值。


至此,就明白了5.1中所申请的cam->dummy_frame的作用,它先使用这个虚假的buffer的地址来填充CPMEMword[1]中的0282958位,然后调用cam->enc_update_eba函数来不断更新里面保存的buffer的物理地址。但是是怎么不断更新的呢?后面再仔细分析。


6. camera_callback中断处理函数

之后在mxc_streamon函数中就会打开摄像头设备,开始采集数据,很明显的一个问题就是:如果数据填充满一个buffer以后,系统怎么通知驱动程序?然后将这个buffer怎么做?


还记得在5.2中申请的中断不,它一直等待IPU_IRQ_PRP_ENC_OUT_EOF信号,如果超出buffer的范围的话,就会调用中断处理函数,这个中断处理函数是在init_camera_struct函数中通过cam->enc_callback= camera_callback;为它指定的。

在这个camera_callback函数中:

static void camera_callback(u32 mask, void *dev) 
{ 
	struct mxc_v4l_frame *done_frame; 
	struct mxc_v4l_frame *ready_frame; 
	struct timeval cur_time; 

	cam_data *cam = (cam_data *) dev; 
	if (cam == NULL) 
		return; 
 
	pr_debug("In MVC:camera_callback\n"); 

	spin_lock(&cam->queue_int_lock); 
	spin_lock(&cam->dqueue_int_lock); 
	if (!list_empty(&cam->working_q)) { 
		do_gettimeofday(&cur_time);

	done_frame = list_entry(cam->working_q.next, struct mxc_v4l_frame, queue);

	if (done_frame->ipu_buf_num != cam->local_buf_num) 
			goto next;  <span style="color:#FF0000;">//位置1</span>

	done_frame->buffer.timestamp = cur_time;

	if (done_frame->buffer.flags & V4L2_BUF_FLAG_QUEUED) { 
		done_frame->buffer.flags |= V4L2_BUF_FLAG_DONE; 
		done_frame->buffer.flags &= ~V4L2_BUF_FLAG_QUEUED; 

		/* Added to the done queue */ 
		list_del(cam->working_q.next); 
		list_add_tail(&done_frame->queue, &cam->done_q); 

		/* Wake up the queue */ 
		cam->enc_counter++; 
		wake_up_interruptible(&cam->enc_queue); 
		}else 
			pr_err("ERROR: v4l2 capture: camera_callback: " 
				"buffer not queued\n"); 
	}  <span style="color:#FF0000;">//位置2</span>

next: 
	if (!list_empty(&cam->ready_q)) {       //队列不空
		ready_frame = list_entry(cam->ready_q.next, 
					 struct mxc_v4l_frame, 
					 queue); 
		if (cam->enc_update_eba) 
			if (cam->enc_update_eba( 
				cam, 
				ready_frame->buffer.m.offset) == 0) { 
				list_del(cam->ready_q.next); 
				list_add_tail(&ready_frame->queue, 
					      &cam->working_q); 
				ready_frame->ipu_buf_num = cam->local_buf_num; 
			} 
	} else {               //队列为空
		if (cam->enc_update_eba) 
			cam->enc_update_eba( 
				cam, cam->dummy_frame.buffer.m.offset); 
	}

cam->working_q队列中取出structmxc_v4l_frame实体,为done_frame->buffer.flags添加V4L2_BUF_FLAG_DONE属性,同时去掉V4L2_BUF_FLAG_QUEUED属性,然后将这个done_framecam->working_q队列中移除,添加到cam->done_q队列中。


同时,在这个camera_callback函数中,同时会根据done_frame->ipu_buf_num来判断是否跳转到next去执行。


next中:如果队列不空的话,就调用cam->enc_update_eba函数来更新cpmemword[1]中保存的buffer的地址,然后继续通过摄像头采集数据。如果这时候,队列为空了,说明申请的buffer用完了,就把原来申请的虚假的frame的物理地址填进去。


在这个函数中,注意我标红的//位置2处,在这里它没有return语句,也就是说,如果函数在//位置1处没有跳转到next处去执行的话,当程序继续执行到//位置2处时,后面的next语句同样会执行。所以,无论哪种情况下,next语句都会执行!!!


那么什么时候选择是否跳转到next处呢?它是通过:

if (done_frame->ipu_buf_num != cam->local_buf_num) 
			goto next;

这条语句来选择执行的,所以是否跳转到next处,就是这两个值的比较问题了。

我们想知道done_frameipu_buf_num这个值是怎么设置的,同时这个done_frame是从cam->working_q队列中取出来操作的frame,那么它是在什么地方添加到cam->working_q队列中去的呢?答案是mxc_streamon函数中。


再次来反思上面的执行流程,在VIDIOC_QBUF中,会将frame添加到cam->ready_q队列中,在mxc_streamon函数中,会将buffercam->ready_q队列中取出来,然后放到cam->working_q队列中去,注意,这时候在驱动程序中有一个小细节,我将mxc_streamon函数中有关这个小细节的代码复制下来:

spin_lock_irqsave(&cam->queue_int_lock, lock_flags); 
	cam->ping_pong_csi = 0; 
	cam->local_buf_num = 0; 
	if (cam->enc_update_eba) { 
		frame = 
		    list_entry(cam->ready_q.next, struct mxc_v4l_frame, queue); 
		list_del(cam->ready_q.next); 
		list_add_tail(&frame->queue, &cam->working_q); 
		frame->ipu_buf_num = cam->ping_pong_csi; 
		pr_debug("First time : frame->buffer.m.offset = %d. \n", frame->buffer.m.offset); 
		err = cam->enc_update_eba(cam, frame->buffer.m.offset); 

		frame = 
		    list_entry(cam->ready_q.next, struct mxc_v4l_frame, queue); 
		list_del(cam->ready_q.next); 
		list_add_tail(&frame->queue, &cam->working_q); 
		frame->ipu_buf_num = cam->ping_pong_csi; 
		pr_debug("Second time : frame->buffer.m.offset = %d. \n", frame->buffer.m.offset); 
		err |= cam->enc_update_eba(cam, frame->buffer.m.offset); 

		spin_unlock_irqrestore(&cam->queue_int_lock, lock_flags); 
	} else { 
		spin_unlock_irqrestore(&cam->queue_int_lock, lock_flags); 
		return -EINVAL; 
	}

它首先设置了两个变量:cam->ping_pong_csi= 0;和cam->local_buf_num= 0;然后将framecam->ready_q队列中取出来,然后放到cam->working_q队列中去,之后,设置这个

frame->ipu_buf_num= cam->ping_pong_csi;

那么此时,frame->ipu_buf_num= cam->ping_pong_csi == 0

然后调用cam->enc_update_eba函数来将frame中保存的buffer地址写到CPMEMword[1]中的第一个buffer地址处,028位。同时,之前分析过,在这个cam->enc_update_eba函数中,每执行一次,都会去改变一次cam->ping_pong_csi的值,从0变成1,或者从1变成0,而这也就是ping_pong_csi名字的由来。由于之前设置了cam->ping_pong_csi= 0,所以这次cam->ping_pong_csi的值从0变成了1


之后,将同样的代码再次执行一遍:将framecam->ready_q队列中取出来,然后放到cam->working_q队列中去,设置frame->ipu_buf_num= cam->ping_pong_csi;

注意,这时候:frame->ipu_buf_num= cam->ping_pong_csi == 1

然后调用cam->enc_update_eba函数来将frame中保存的buffer地址写到CPMEMword[1]中的第二个buffer地址处,2958位。执行完这次cam->enc_update_eba函数后,cam->ping_pong_csi的值从1又变成了0


在驱动代码中,就只有这个地方更新了两次CPMEM中的buffer地址,所以之后继续更新buffer地址的任务就在camera_callback这个中断处理函数中。

camera_callback函数中,通过

done_frame = list_entry(cam->working_q.next, struct mxc_v4l_frame, queue);

来从cam->working_q队列中取出frame,然后就是比较:

if (done_frame->ipu_buf_num != cam->local_buf_num) 
			goto next;

我们一路跟踪过来,没有地方修改过这个cam->local_buf_num的值,它仍为初始化的值0。而在mxc_streamon函数中,一共向cam->working_q队列中添加了两个frame,同时,这两个frameipu_buf_num依次为01(上面追踪分析了)。所以,从cam->working_q队列中取出第一个frameipu_buf_num0。那么,在这里,这两个值相同,所以就不会直接跳转到next处去执行,就会顺序执行下面的代码。注意,在下面的代码中:

cam->enc_counter++;
wake_up_interruptible(&cam->enc_queue);

来唤醒等等队列,而这个cam->enc_queue队列是在VIDIOC_DQBUF这个ioctl调用中,通过:

wait_event_interruptible_timeout(cam->enc_queue, cam->enc_counter != 0, 10 * HZ)

来等待上面的唤醒条件。

所以,在camera_callback函数中来唤醒VIDIOC_DQBUF这个ioctl调用中这个等待队列。


之后就会执行到next标号处,因为,上面已经唤醒了cam->enc_queue这个队列,在应用程序中通过VIDIOC_DQBUF将第一个bufferDQ出去了,那么,在CPMEM中,只有一个buffer的地址了,在这时候,肯定需要更新buffer地址了。这个next标号后面的内容正是做了这些内容。

next: 
	if (!list_empty(&cam->ready_q)) { 
		ready_frame = list_entry(cam->ready_q.next, 
					 struct mxc_v4l_frame, 
					 queue); 
		if (cam->enc_update_eba) 
			if (cam->enc_update_eba( 
				cam, 
				ready_frame->buffer.m.offset) == 0) { 
				list_del(cam->ready_q.next); 
				list_add_tail(&ready_frame->queue, 
					      &cam->working_q); 
				ready_frame->ipu_buf_num = cam->local_buf_num; 
			} 
	} else { 
		if (cam->enc_update_eba) 
			cam->enc_update_eba( 
				cam, cam->dummy_frame.buffer.m.offset); 
	}

可以看出来,程序中会继续从cam->ready_q队列中去取frame,而这个cam->ready_q队列是在应用程序中一直通过VIDIOC_QBUF这个ioctl调用来不断将所需的buffer添加到队列中去的。然后将取出来的frame,添加到cam->working_q队列中,然后调用cam->enc_update_eba来更新CPMEMword[1]中的地址,同时,在cam->enc_update_eba函数中会继续改变cam->ping_pong_csi的值。


执行完这个流程以后,注意下面一句话:

cam->local_buf_num = (cam->local_buf_num == 0) ? 1 : 0;

终于看到修改cam->local_buf_num值的地方了。那么这个值有什么用呢?肯定与上面那个判断是否跳转到next的语句有关。

比如说现在成功执行完这个camera_callback函数了,等第二个frame也填充满了,又会调用到这个camera_callback中断处理函数,记得在mxc_streamon函数中,设置第二个frame->ipu_buf_num= 1,然后继续执行到camera_callback函数的

if(done_frame->ipu_buf_num != cam->local_buf_num)

gotonext;

判断语句,由于在上一次的camera_callback函数中,已经修改了cam->local_buf_num的值为1,所以这两者依然是相等的关系,也就是说仍然不会直接跳转到next标号处去执行。如此循环下去,这两者会一直相等下去的。。。


于是,从新思考这个问题,为什么要直接跳转到next处呢?是为了跳过//位置1//位置2之间的代码,那么中间这一段代码都做了什么?我们前面分析了,会唤醒VIDIO_DQBUF中的等待队列。如果跳过这一段代码,直接去执行next后面的程序的话,就是为了不去唤醒VIDIO_DQBUF中的等待队列,为什么要这么做呢?

因为在next:处那个判断语句,if(!list_empty(&cam->ready_q)),如果当时,由于某种原因,应用程序中通过VIDIOC_QBUF函数没有成功,或者暂时还没有成功,那么这个cam->ready_q队列中就会为空,然后就会执行到后面的else语句,通过

cam->enc_update_eba( cam, cam->dummy_frame.buffer.m.offset);

来将一个虚假的buffer地址填充到CPMEMwork[1]中,那么,这个虚假的buffer肯定不能被DQBUF出去,如果DQBUF出去的话,图像就会出现错误。所以camera_callback函数中通过

if (done_frame->ipu_buf_num != cam->local_buf_num) 
	goto next;

来避免将填充进去的虚假bufferDQBUF出去。而cam->local_buf_num这个值就是为了表示这个而出现的。

cam->ping_pong_csi这个值在01之间变换,是为了分别向CPMEMword[1]里面的0282929哪几位来填充地址。每次执行一次cam->enc_update_eba函数,这个值都会改变一次。

cam->local_buf_num这个值同样在01之间变换,是为了判断是否有虚假的buffer地址信息填充到CPMEM中,如果有的话,就跳过这个buffer,不让它DQBUF出去,这个值每次执行一次camera_callback函数都会改变一次。


通过上面的分析,就可以看出来,每当一个buffer填充满后,都会触发一次中断,在中断处理函数中,就会去更新CPMEM中的buffer地址,然后一直执行到应用程序停止填充buffer为止。


7. VIDIOC_DQBUF

应用程序中:

while (count-- > 0) 
{ 
	memset(&buf, 0, sizeof (buf)); 
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
	buf.memory = V4L2_MEMORY_MMAP; 
	if (ioctl (fd_v4l, VIDIOC_DQBUF, &buf) < 0)	{ 
          printf("VIDIOC_DQBUF failed.\n"); 
}
fwrite(buffers[buf.index].start, fmt.fmt.pix.sizeimage, 1, fd_y_file);

驱动程序中:

	frame = list_entry(cam->done_q.next, struct mxc_v4l_frame, queue); 
	list_del(cam->done_q.next);
	cam->frame[frame->index].buffer.field = cam->device_type ? 
				V4L2_FIELD_INTERLACED : V4L2_FIELD_NONE; 

	buf->bytesused = cam->v2f.fmt.pix.sizeimage; 
	buf->index = frame->index; 
	buf->flags = frame->buffer.flags; 
	buf->m = cam->frame[frame->index].buffer.m; 
	buf->timestamp = cam->frame[frame->index].buffer.timestamp; 
	buf->field = cam->frame[frame->index].buffer.field;

驱动程序中将cam->done_q.next队列中已经填充好的数据buffer的地址指针从cam->frame[frame->index]中复制给buf这个中间变量,然后这个buf返回给应用程序中,应用程序就获得了这个buffer的地址,然后调用fwrite函数,就可以将数据写到fd_y_file中了。


  • 3
    点赞
  • 3
    评论
  • 3
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

简介 笔者当初为了学习JAVA,收集了很多经典源码,源码难易程度分为初级、级、高级等,详情看源码列表,需要的可以直接下载! 这些源码反映了那时那景笔者对未来的盲目,对代码的热情、执着,对IT的憧憬、向往!此时此景,笔者只专注Android、Iphone等移动平台开发,看着这些源码心有万分感慨,写此文章纪念那时那景! Java 源码包 Applet钢琴模拟程序java源码 2个目标文件,提供基本的音乐编辑功能。编辑音乐软件的朋友,这款实例会对你有所帮助。 Calendar万年历 1个目标文件 EJB 模拟银行ATM流程及操作源代码 6个目标文件,EJB来模拟银行ATM机的流程及操作:获取系统属性,初始化JNDI,取得Home对象的引用,创建EJB对象,并将当前的计数器初始化,调用每一个EJB对象的count()方法,保证Bean正常被激活钝化,EJB对象是用完毕,从内存清除,从账户取出amt,如果amt>账户余额抛出异常,一个实体Bean可以表示不同的数据实例,我们应该通过主键来判断删除哪个数据实例&hellip;&hellip; ejbCreate函数用于初始化一个EJB实例 5个目标文件,演示Address EJB的实现 ,创建一个EJB测试客户端,得到名字上下文,查询jndi名,通过强制转型得到Home接口,getInitialContext()函数返回一个经过初始化的上下文,用client的getHome()函数调用Home接口函数得到远程接口的引用,用远程接口的引用访问EJB。 EJBJNDI的使用源码例子 1个目标文件,JNDI的使用例子,有源代码,可以下载参考,JNDI的使用,初始化Context,它是连接JNDI树的起始点,查找你要的对象,打印找到的对象,关闭Context&hellip;&hellip; ftp文件传输 2个目标文件,FTP的目标是:(1)提高文件的共享性(计算机程序/或数据),(2)鼓励间接地(通过程序)使用远程计算机,(3)保护用户因主机之间的文件存储系统导致的变化,(4)为了可靠高效地传输,虽然用户可以在终端上直接地使用它,但是它的主要作用是供程序使用的。本规范尝试满足大型主机、微型主机、个人工作站、TACs 的不同需求。例如,容易实现协议的设计。 Java EJB有、无状态SessionBean的两个例子 两个例子,无状态SessionBean可会话Bean必须实现SessionBean,获取系统属性,初始化JNDI,取得Home对象的引用,创建EJB对象,计算利息等;在有状态SessionBean,用累加器,以对话状态存储起来,创建EJB对象,并将当前的计数器初始化,调用每一个EJB对象的count()方法,保证Bean正常被激活钝化,EJB对象是用完毕,从内存清除&hellip;&hellip; Java Socket 聊天通信演示代码 2个目标文件,一个服务器,一个客户端。 Java Telnet客户端实例源码 一个目标文件,演示Socket的使用。 Java 组播组发送接受数据实例 3个目标文件。 Java读写文本文件的示例代码 1个目标文件。 java俄罗斯方块 一个目标文件。 Java非对称加密源码实例 1个目标文件 摘要:Java源码,算法相关,非对称加密   Java非对称加密源程序代码实例,本例使用RSA加密技术,定义加密算法可用 DES,DESede,Blowfish等。   设定字符串为“张三,你好,我是李四”   产生张三的密钥对(keyPairZhang)   张三生成公钥(publicKeyZhang)并发送给李四,这里发送的是公钥的数组字节   通过网络或磁盘等方式,把公钥编码传送给李四,李四接收到张三编码后的公钥,将其解码,李四用张三的公钥加密信息,并发送给李四,张三用自己的私钥解密从李四处收到的信息&hellip;&hellip; Java利用DES私钥对称加密代码实例 同上 java聊天室 2个目标文件,简单。 java模拟掷骰子2个 1个目标文件,输出演示。 java凭图游戏 一个目标文件,简单。 java求一个整数的因子 如题。 Java生成密钥的实例 1个目标文件 摘要:Java源码,算法相关,密钥   Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥,通常应对私钥加密后再保存、如何从文件得到公钥编码的字节数组、如何从字节数组解码公钥。 Java数据压缩与传输实例 1个目标文件 摘要:Java源码,文件操作,数据压缩,文件传输   Java数据压缩与传输实例,可以学习一下实例化套按字、得到文件输入流、压缩输入流、文件输出流、实例化缓冲
©️2021 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值