调用过程分析
/libng/plugins/drv0-v4l2.c
- 1.open
/libng/plugins/drv0-v4l2.c
v4l2_open - 2.ioctl(4, VIDIOC_QUERYCAP
log不需要
3.ioctl(4, VIDIOC_G_FMT获取摄像头提供的数据格式
4.for(){ioctl(4, VIDIOC_ENUM_FMT列举出摄像头支持的格式}
5.ioctl(4, VIDIOC_QUERYCAP列举
6.ioctl(4, VIDIOC_G_INPUT获得当前使用的输入源
7.ioctl(4, VIDIOC_ENUMINPUT列举输入源
8.ioctl(4, VIDIOC_QUERYCTRL查询属性,如亮度,对比度等
9.ioctl(4, VIDIOC_QUERYCA
10.ioctl(4, VIDIOC_ENUMINPUT
11-15都是在get_device_capabilities()中调用
/libng/plugins/drv0-v4l2.c
v4l2_open
get_device_capabilities
- 11.for()ioctl(4, VIDIOC_ENUMINPUT列举输入源
- 12.for()ioctl(4, VIDIOC_ENUMSTD列举标准(制式)
- 13.for()ioctl(4, VIDIOC_ENUM_FMT列举出摄像头支持的格式
- 14ioctl(4, VIDIOC_G_PARM
- 15.for()ioctl(4, VIDIOC_QUERYCTRL查询属性,如亮度最大,最小值,默认值,对比度等
16-18都是通过v4l2_read_attr调用的
/libng/plugins/drv0-v4l2.c
v4l2_read_attr
- 16ioctl(4, VIDIOC_G_STD获得当前使用的标准或制式
- 17.ioctl(4, VIDIOC_G_INPUT
- 18.ioctl(4, VIDIOC_G_CTRL获得当前属性,比如亮度等
/libng/plugins/drv0-v4l2.c
v4l2_overlay
- 19.ioctl(4, VIDIOC_TRY_FMT试试能否支持某种格式
- 20.ioctl(4, VIDIOC_S_FMT设置摄像头使用某种格式
21-24通过v4l2_start_streaming调用的
- 21.ioctl(4, VIDIOC_REQBUFS请求系统分配缓冲区
- 22.for(){ioctl(4, VIDIOC_QUERYBUF查询所分配的缓冲区
mmap} - 23.for()ioctl(4, VIDIOC_QBUF把缓冲区放入队列
- 24.ioctl(4, VIDIOC_STREAMON启动摄像头
25中都是通过v4l2_write_attr调用
- 25.for()ioctl(4, MATROXFB_S_TVOCTRL or VIDIOC_S_CTRL设置属性
ioctl(4, VIDIOC_S_INPUT设置输入源
ioctl(4, VIDIOC_S_STD设置制式
v4l2_nextframe->v4l2_waiton
- 26.v4l2_queue_all
v4l2_waiton
for(){
select(5, [4], NULL, NULL, {5, 0})
ioctl(4, VIDIOC_DQBUF把缓冲区从队列中取出
。。。。处理,之前通过mmap获得了缓冲区的地址, 就可以直接访问地址来访问数据
ioctl(4, VIDIOC_QBUF把缓冲区放入队列
}
xawtv的几大函数:
- v4l2_open
- v4l2_read_attr/v4l2_write_attr
- v4l2_start_streaming
- v4l2_nextframe/v4l2_waiton
摄像头驱动程序必需的11个ioctl
完整的ioctl内容
vivid_ioctl_ops static const struct v4l2_ioctl_ops vivid_ioctl_ops = { .vidioc_querycap = vidioc_querycap, /* 用于列举,获得,测试,设置摄像头所提供的数据格式 */ //.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid, .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, //.vidioc_enum_fmt_vid_cap_mplane = vidioc_enum_fmt_vid_mplane, //.vidioc_g_fmt_vid_cap_mplane = vidioc_g_fmt_vid_cap_mplane, //.vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt_vid_cap_mplane, //.vidioc_s_fmt_vid_cap_mplane = vidioc_s_fmt_vid_cap_mplane, /* .vidioc_enum_fmt_vid_out = vidioc_enum_fmt_vid, .vidioc_g_fmt_vid_out = vidioc_g_fmt_vid_out, .vidioc_try_fmt_vid_out = vidioc_try_fmt_vid_out, .vidioc_s_fmt_vid_out = vidioc_s_fmt_vid_out, .vidioc_enum_fmt_vid_out_mplane = vidioc_enum_fmt_vid_mplane, .vidioc_g_fmt_vid_out_mplane = vidioc_g_fmt_vid_out_mplane, .vidioc_try_fmt_vid_out_mplane = vidioc_try_fmt_vid_out_mplane, .vidioc_s_fmt_vid_out_mplane = vidioc_s_fmt_vid_out_mplane, */ /* .vidioc_g_selection = vidioc_g_selection, .vidioc_s_selection = vidioc_s_selection, .vidioc_cropcap = vidioc_cropcap, */ /* .vidioc_g_fmt_vbi_cap = vidioc_g_fmt_vbi_cap, .vidioc_try_fmt_vbi_cap = vidioc_g_fmt_vbi_cap, .vidioc_s_fmt_vbi_cap = vidioc_s_fmt_vbi_cap, */ /* .vidioc_g_fmt_sliced_vbi_cap = vidioc_g_fmt_sliced_vbi_cap, .vidioc_try_fmt_sliced_vbi_cap = vidioc_try_fmt_sliced_vbi_cap, .vidioc_s_fmt_sliced_vbi_cap = vidioc_s_fmt_sliced_vbi_cap, .vidioc_g_sliced_vbi_cap = vidioc_g_sliced_vbi_cap, */ /* .vidioc_g_fmt_vbi_out = vidioc_g_fmt_vbi_out, .vidioc_try_fmt_vbi_out = vidioc_g_fmt_vbi_out, .vidioc_s_fmt_vbi_out = vidioc_s_fmt_vbi_out, */ /* .vidioc_g_fmt_sliced_vbi_out = vidioc_g_fmt_sliced_vbi_out, .vidioc_try_fmt_sliced_vbi_out = vidioc_try_fmt_sliced_vbi_out, .vidioc_s_fmt_sliced_vbi_out = vidioc_s_fmt_sliced_vbi_out, */ /* .vidioc_enum_fmt_sdr_cap = vidioc_enum_fmt_sdr_cap, .vidioc_g_fmt_sdr_cap = vidioc_g_fmt_sdr_cap, .vidioc_try_fmt_sdr_cap = vidioc_try_fmt_sdr_cap, .vidioc_s_fmt_sdr_cap = vidioc_s_fmt_sdr_cap, */ /* .vidioc_overlay = vidioc_overlay, .vidioc_enum_framesizes = vidioc_enum_framesizes, .vidioc_enum_frameintervals = vidioc_enum_frameintervals, .vidioc_g_parm = vidioc_g_parm, .vidioc_s_parm = vidioc_s_parm, */ //.vidioc_enum_fmt_vid_overlay = vidioc_enum_fmt_vid_overlay, //.vidioc_g_fmt_vid_overlay = vidioc_g_fmt_vid_overlay, //.vidioc_try_fmt_vid_overlay = vidioc_try_fmt_vid_overlay, //.vidioc_s_fmt_vid_overlay = vidioc_s_fmt_vid_overlay, //.vidioc_g_fmt_vid_out_overlay = vidioc_g_fmt_vid_out_overlay, //.vidioc_try_fmt_vid_out_overlay = vidioc_try_fmt_vid_out_overlay, //.vidioc_s_fmt_vid_out_overlay = vidioc_s_fmt_vid_out_overlay, .vidioc_g_fbuf = vidioc_g_fbuf, .vidioc_s_fbuf = vidioc_s_fbuf, /* 缓冲区操作:申请/查询/放入队列/取出队列 */ .vidioc_reqbufs = vb2_ioctl_reqbufs, //.vidioc_create_bufs = vb2_ioctl_create_bufs, //.vidioc_prepare_buf = vb2_ioctl_prepare_buf, .vidioc_querybuf = vb2_ioctl_querybuf, .vidioc_qbuf = vb2_ioctl_qbuf, .vidioc_dqbuf = vb2_ioctl_dqbuf, //.vidioc_expbuf = vb2_ioctl_expbuf, .vidioc_streamon = vb2_ioctl_streamon, .vidioc_streamoff = vb2_ioctl_streamoff, /* 用于选择输入源,再xawtv中就是video source*/ //.vidioc_enum_input = vidioc_enum_input, //.vidioc_g_input = vidioc_g_input, //.vidioc_s_input = vidioc_s_input, /* .vidioc_s_audio = vidioc_s_audio, .vidioc_g_audio = vidioc_g_audio, .vidioc_enumaudio = vidioc_enumaudio, .vidioc_s_frequency = vidioc_s_frequency, .vidioc_g_frequency = vidioc_g_frequency, .vidioc_s_tuner = vidioc_s_tuner, .vidioc_g_tuner = vidioc_g_tuner, .vidioc_s_modulator = vidioc_s_modulator, .vidioc_g_modulator = vidioc_g_modulator, .vidioc_s_hw_freq_seek = vidioc_s_hw_freq_seek, .vidioc_enum_freq_bands = vidioc_enum_freq_bands, */ /* .vidioc_enum_output = vidioc_enum_output, .vidioc_g_output = vidioc_g_output, .vidioc_s_output = vidioc_s_output, .vidioc_s_audout = vidioc_s_audout, .vidioc_g_audout = vidioc_g_audout, .vidioc_enumaudout = vidioc_enumaudout, */ /* 用于列举,设置,获得tv制式 */ //.vidioc_querystd = vidioc_querystd, //.vidioc_g_std = vidioc_g_std, //.vidioc_s_std = vidioc_s_std, /* .vidioc_s_dv_timings = vidioc_s_dv_timings, .vidioc_g_dv_timings = vidioc_g_dv_timings, .vidioc_query_dv_timings = vidioc_query_dv_timings, .vidioc_enum_dv_timings = vidioc_enum_dv_timings, .vidioc_dv_timings_cap = vidioc_dv_timings_cap, .vidioc_g_edid = vidioc_g_edid, .vidioc_s_edid = vidioc_s_edid, */ /* .vidioc_log_status = vidioc_log_status, .vidioc_subscribe_event = vidioc_subscribe_event, .vidioc_unsubscribe_event = v4l2_event_unsubscribe, */ };
非必须
vivid
输入源原来有四个,如果输入源只有一个,vidioc_enum_input是否还需要?
注释掉
vidioc_enum_input
vivid-core.c
重新编译vivid,再运行xawtv
故vidioc_enum_input是非必须的,注释掉vivid中输入源部分
删除后测试
非必须的如下
/* 用于选择输入源,再xawtv中就是video source*/ //.vidioc_enum_input = vidioc_enum_input, //.vidioc_g_input = vidioc_g_input, //.vidioc_s_input = vidioc_s_input,
非必须
/* 用于列举,设置,获得tv制式 */ //.vidioc_querystd = vidioc_querystd, //.vidioc_g_std = vidioc_g_std, //.vidioc_s_std = vidioc_s_std,
/* 用于列举,获得,测试,设置摄像头所提供的数据格式 */
//.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid,
.vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap,
.vidioc_enum_fmt_vid_cap_mplane = vidioc_enum_fmt_vid_mplane,
.vidioc_g_fmt_vid_cap_mplane = vidioc_g_fmt_vid_cap_mplane,
.vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt_vid_cap_mplane,
.vidioc_s_fmt_vid_cap_mplane = vidioc_s_fmt_vid_cap_mplane,
必须
// 表示它是一个摄像头设备 .vidioc_querycap = vidioc_querycap, /* 用于列举、获得、测试、设置摄像头的数据的格式 */ .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, /* 缓冲区操作: 申请/查询/放入队列/取出队列 */ .vidioc_reqbufs = vidioc_reqbufs, .vidioc_querybuf = vidioc_querybuf, .vidioc_qbuf = vidioc_qbuf, .vidioc_dqbuf = vidioc_dqbuf, // 启动/停止 .vidioc_streamon = vidioc_streamon, .vidioc_streamoff = vidioc_streamoff,
分析数据的获取过程
1.请求分配缓冲区: ioctl(4, VIDIOC_REQBUFS // 请求系统分配缓冲区
/driver/media/v4l2-core/v4l2-ioctl.c
static struct v4l2_ioctl_info v4l2_ioctls[]
IOCTL_INFO_FNC(VIDIOC_REQBUFS, v4l_reqbufs, v4l_print_requestbuffers, INFO_FL_PRIO | INFO_FL_QUEUE),
static int v4l_reqbufs(const struct v4l2_ioctl_ops *ops, struct file *file, void *fh, void *arg) { struct v4l2_requestbuffers *p = arg; int ret = check_fmt(file, p->type); if (ret) return ret; CLEAR_AFTER_FIELD(p, memory); return ops->vidioc_reqbufs(file, fh, p); }
最终调用到vidioc_reqbufs
/* vb2 ioctl helpers */ int vb2_ioctl_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *p) { struct video_device *vdev = video_devdata(file); int res = __verify_memory_type(vdev->queue, p->memory, p->type); if (res) return res; if (vb2_queue_is_busy(vdev, file)) return -EBUSY; res = __reqbufs(vdev->queue, p); /* If count == 0, then the owner has released all buffers and he is no longer owner of the queue. Otherwise we have a new owner. */ if (res == 0) vdev->queue->owner = p->count ? file->private_data : NULL; return res; }
队列在open函数用v4l2_fh_init初始化
xawtv/libng/plugins/v4l2_start_streaming
应用程序中没有相关的大小信息,驱动程序中,内用一般用到的时候再进行分配。
注意:这个IOCTL只是分配缓冲区的头部信息,真正的缓存还没有分配呢
vivid_fops
static const struct v4l2_file_operations vivid_fops = { .owner = THIS_MODULE, .open = v4l2_fh_open, .release = vivid_fop_release, .read = vb2_fop_read, .write = vb2_fop_write, .poll = vb2_fop_poll, .unlocked_ioctl = video_ioctl2, .mmap = vb2_fop_mmap, };
v4l2_fh_open
int v4l2_fh_open(struct file *filp) { struct video_device *vdev = video_devdata(filp); struct v4l2_fh *fh = kzalloc(sizeof(*fh), GFP_KERNEL); filp->private_data = fh; if (fh == NULL) return -ENOMEM; v4l2_fh_init(fh, vdev); v4l2_fh_add(fh); return 0; }
v4l2_fh_init
void v4l2_fh_init(struct v4l2_fh *fh, struct video_device *vdev) { fh->vdev = vdev; /* Inherit from video_device. May be overridden by the driver. */ fh->ctrl_handler = vdev->ctrl_handler; INIT_LIST_HEAD(&fh->list); set_bit(V4L2_FL_USES_V4L2_FH, &fh->vdev->flags); /* * determine_valid_ioctls() does not know if struct v4l2_fh * is used by this driver, but here we do. So enable the * prio ioctls here. */ set_bit(_IOC_NR(VIDIOC_G_PRIORITY), vdev->valid_ioctls); set_bit(_IOC_NR(VIDIOC_S_PRIORITY), vdev->valid_ioctls); fh->prio = V4L2_PRIORITY_UNSET; init_waitqueue_head(&fh->wait); INIT_LIST_HEAD(&fh->available); INIT_LIST_HEAD(&fh->subscribed); fh->sequence = -1;
}
2.查询映射缓冲区:
ioctl(4, VIDIOC_QUERYBUF // 查询所分配的缓冲区
vb2_querybuf // 获得缓冲区的数据格式、大小、每一行长度、高度
.vidioc_querybuf = vb2_ioctl_querybuf,
vb2_ioctl_querybuf
vb2_ioctl_querybuf
vb2_querybuf
int vb2_querybuf(struct vb2_queue *q, struct v4l2_buffer *b) { struct vb2_buffer *vb; int ret; if (b->type != q->type) { dprintk(1, "wrong buffer type\n"); return -EINVAL; } if (b->index >= q->num_buffers) { dprintk(1, "buffer index out of range\n"); return -EINVAL; } vb = q->bufs[b->index]; ret = __verify_planes_array(vb, b); if (!ret) __fill_v4l2_buffer(vb, b); return re
t;
}
应用程序中
xawtv/libng/plugins/v4l2_start_streaming
mmap(参数里有"大小") // 在这里才分配缓存
mmap->vivid_fops.vb2_fop_mmap->vb2_fop_mmap->vb2_mmap
int vb2_mmap(struct vb2_queue *q, struct vm_area_struct *vma) { // 将虚拟内存地址转换为物理内存地址 unsigned long off = vma->vm_pgoff << PAGE_SHIFT; // 定义一个vb2_buffer结构体指针 struct vb2_buffer *vb; // 定义一个unsigned int类型的变量buffer和plane unsigned int buffer = 0, plane = 0; // 定义一个int类型的变量ret int ret; // 定义一个unsigned long类型的变量length // 判断内存类型是否为V4L2_MEMORY_MMAP if (q->memory != V4L2_MEMORY_MMAP) { dprintk(1, "queue is not currently set up for mmap\n"); return -EINVAL; } /* * 检查内存区域访问模式。 */ if (!(vma->vm_flags & VM_SHARED)) { dprintk(1, "invalid vma flags, VM_SHARED needed\n"); return -EINVAL; } if (V4L2_TYPE_IS_OUTPUT(q->type)) { if (!(vma->vm_flags & VM_WRITE)) { dprintk(1, "invalid vma flags, VM_WRITE needed\n"); return -EINVAL; } } else { if (!(vma->vm_flags & VM_READ)) { dprintk(1, "invalid vma flags, VM_READ needed\n"); return -EINVAL; } } if (vb2_fileio_is_active(q)) { dprintk(1, "mmap: file io in progress\n"); return -EBUSY; } /* * Find the plane corresponding to the offset passed by userspace. */ // 找到与用户空间传递的偏移量对应的平面 ret = __find_plane_by_offset(q, off, &buffer, &plane); if (ret) return ret; vb = q->bufs[buffer]; /* * MMAP requires page_aligned buffers. * The buffer length was page_aligned at __vb2_buf_mem_alloc(), * so, we need to do the same here. */ // MMAP需要页面对齐的缓冲区。 // 缓冲区长度在__vb2_buf_mem_alloc()中进行了页面对齐, // 因此我们需要在此处执行相同的操作。 length = PAGE_ALIGN(vb->v4l2_planes[plane].length); if (length < (vma->vm_end - vma->vm_start)) { dprintk(1, "MMAP invalid, as it would overflow buffer length\n"); return -EINVAL; } mutex_lock(&q->mmap_lock); // 调用内存操作函数mmap ret = call_memop(vb, mmap, vb->planes[plane].mem_priv, vma); mutex_unlock(&q->mmap_lock); if (ret) return ret; dprintk(3, "buffer %d, plane %d successfully mapped\n", buffer, plane); return 0; }
vb2_mmap->call_memop
#define call_memop(vb, op, args...) \ ((vb)->vb2_queue->mem_ops->op ? \ (vb)->vb2_queue->mem_ops->op(args) : 0)
或
#define call_memop(vb, op, args...) \ ({ \ struct vb2_queue *_q = (vb)->vb2_queue; \ int err; \ \ log_memop(vb, op); \ err = _q->mem_ops->op ? _q->mem_ops->op(args) : 0; \ if (!err) \ (vb)->cnt_mem_ ## op++; \ err; \ })
3.把缓冲区放入队列:
ioctl(4, VIDIOC_QBUF // 把缓冲区放入队列
.vidioc_qbuf = vb2_ioctl_qbuf,
vb2_ioctl_qbuf
int vb2_ioctl_qbuf(struct file *file, void *priv, struct v4l2_buffer *p) { struct video_device *vdev = video_devdata(file); if (vb2_queue_is_busy(vdev, file)) return -EBUSY; return vb2_qbuf(vdev->queue, p); }
vb2_ioctl_qbuf->vb2_dqbuf
int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking) { if (vb2_fileio_is_active(q)) { dprintk(1, "file io in progress\n"); return -EBUSY; } return vb2_internal_dqbuf(q, b, nonblocking); }
vb2_ioctl_qbuf->vb2_dqbuf->vb2_internal_dqbuf
/* * 从队列中取出一个缓冲区,将其状态设置为已出队列状态 * @q: videobuf2队列 * @b: 从用户空间传递给驱动程序的缓冲区结构 * @nonblocking: 如果为true,则此调用不会等待缓冲区,如果没有准备好的缓冲区等待出队列。通常,驱动程序将传递(file->f_flags&O_NONBLOCK)。 * * 应该从驱动程序的vidioc_dqbuf ioctl处理程序中调用此函数。 * 此函数: * 1)验证传递的缓冲区, * 2)在驱动程序中调用buf_finish回调(如果提供),其中驱动程序可以在将缓冲区返回到用户空间之前执行任何其他操作,例如缓存同步, * 3)填充缓冲区结构成员与用户空间相关的信息。 * * 此函数的返回值旨在直接从驱动程序中的vidioc_dqbuf处理程序返回。 */ static int vb2_internal_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking) { struct vb2_buffer *vb = NULL; int ret; if (b->type != q->type) { // 如果缓冲区类型与队列类型不匹配 dprintk(1, "invalid buffer type\n"); // 打印错误信息 return -EINVAL; // 返回无效参数错误 } ret = __vb2_get_done_vb(q, &vb, b, nonblocking); // 获取已完成的缓冲区 if (ret < 0) // 如果获取失败 return ret; // 返回获取失败的错误码 switch (vb->state) { // 根据缓冲区状态进行不同的操作 case VB2_BUF_STATE_DONE: // 如果缓冲区状态为已完成 dprintk(3, "returning done buffer\n"); // 打印信息 break; case VB2_BUF_STATE_ERROR: // 如果缓冲区状态为错误 dprintk(3, "returning done buffer with errors\n"); // 打印信息 break; default: // 如果缓冲区状态无效 dprintk(1, "invalid buffer state\n"); // 打印错误信息 return -EINVAL; // 返回无效参数错误 } call_void_vb_qop(vb, buf_finish, vb); // 调用buf_finish回调 /* 填充缓冲区信息以供用户空间使用 */ __fill_v4l2_buffer(vb, b); /* 从videobuf队列中删除 */ list_del(&vb->queued_entry); q->queued_count--; /* 回到已出队列状态 */ __vb2_dqbuf(vb); dprintk(1, "dqbuf of buffer %d, with state %d\n", vb->v4l2_buf.index, vb->state); // 打印信息 return 0; // 返回成功 }
调用驱动程序提供的函数做些预处理
int ret = vb2_queue_or_prepare_buf(q, b, "qbuf");
把缓冲区放入队列的尾部
list_add_tail(&vb->queued_entry, &q->queued_list);
调用驱动程序提供的"入队列函数"
__fill_v4l2_buffer(vb, b);
4.启动摄像头
ioctl(4, VIDIOC_STREAMON
.vidioc_streamon = vb2_ioctl_streamon,
int vb2_ioctl_streamon(struct file *file, void *priv, enum v4l2_buf_type i) { struct video_device *vdev = video_devdata(file); if (vb2_queue_is_busy(vdev, file)) return -EBUSY; return vb2_streamon(vdev->queue, i); }
vb2_ioctl_streamon->vb2_streamon
int vb2_streamon(struct vb2_queue *q, enum v4l2_buf_type type) { if (vb2_fileio_is_active(q)) { dprintk(1, "file io in progress\n"); return -EBUSY; } return vb2_internal_streamon(q, type); }
vb2_ioctl_streamon->vb2_streamon->vb2_internal_streamon
static int vb2_internal_streamon(struct vb2_queue *q, enum v4l2_buf_type type) { int ret; if (type != q->type) { dprintk(1, "invalid stream type\n"); return -EINVAL; } if (q->streaming) { dprintk(3, "already streaming\n"); return 0; } if (!q->num_buffers) { dprintk(1, "no buffers have been allocated\n"); return -EINVAL; } if (q->num_buffers < q->min_buffers_needed) { dprintk(1, "need at least %u allocated buffers\n", q->min_buffers_needed); return -EINVAL; } /* * Tell driver to start streaming provided sufficient buffers * are available. */ if (q->queued_count >= q->min_buffers_needed) { ret = vb2_start_streaming(q); if (ret) { __vb2_queue_cancel(q); return ret; } } q->streaming = 1; dprintk(3, "successful\n"); return 0; }
q->streaming = 1;
5.用select查询是否有数据
// 驱动程序里必定有: 产生数据、唤醒进程 v4l2_poll vdev->fops->poll(filp, poll) vivi_poll vb2_fop_poll // 获取用户请求的事件 unsigned long req_events = poll_requested_events(wait); // 如果没有数据则休眠 poll_wait(file, &buf->done, wait);
v4l2_poll
// v4l2_poll函数的实现 static unsigned int v4l2_poll(struct file *filp, struct poll_table_struct *poll) { // 获取video_device结构体 struct video_device *vdev = video_devdata(filp); // 初始化返回值 unsigned int res = POLLERR | POLLHUP; // 检查是否实现了poll函数 if (!vdev->fops->poll) return DEFAULT_POLLMASK; // 调用驱动程序的poll函数 if (video_is_registered(vdev)) res = vdev->fops->poll(filp, poll); // 打印调试信息 if (vdev->dev_debug & V4L2_DEV_DEBUG_POLL) printk(KERN_DEBUG "%s: poll: %08x\n", video_device_node_name(vdev), res); return res; }
v4l2_poll->vb2_fop_poll
unsigned int vb2_fop_poll(struct file *file, poll_table *wait) { // 获取video_device结构体 struct video_device *vdev = video_devdata(file); // 获取vb2_queue结构体 struct vb2_queue *q = vdev->queue; // 获取锁 struct mutex *lock = q->lock ? q->lock : vdev->lock; unsigned res; void *fileio; /* * 如果这个helper不知道如何锁定,那么你不应该使用它,而应该编写自己的helper。 */ WARN_ON(!lock); // 如果锁定失败,返回POLLERR if (lock && mutex_lock_interruptible(lock)) return POLLERR; fileio = q->fileio; // 调用vb2_poll函数 res = vb2_poll(vdev->queue, file, wait); /* 如果fileio已经启动,则我们有一个新的队列所有者。 */ if (!fileio && q->fileio) q->owner = file->private_data; if (lock) mutex_unlock(lock); return res; }
v4l2_poll->vb2_fop_poll->vb2_poll
unsigned int vb2_poll(struct vb2_queue *q, struct file *file, poll_table *wait) { // 获取文件描述符对应的video_device结构体 struct video_device *vfd = video_devdata(file); // 获取用户请求的事件 unsigned long req_events = poll_requested_events(wait); // 初始化vb指针 struct vb2_buffer *vb = NULL; // 初始化返回值 unsigned int res = 0; // 初始化标志位 unsigned long flags; // 如果驱动程序使用struct v4l2_fh结构体,则检查是否有挂起的事件 if (test_bit(V4L2_FL_USES_V4L2_FH, &vfd->flags)) { struct v4l2_fh *fh = file->private_data; // 如果有挂起的事件,则返回POLLPRI if (v4l2_event_pending(fh)) res = POLLPRI; // 如果用户请求的事件中包含POLLPRI,则等待事件 else if (req_events & POLLPRI) poll_wait(file, &fh->wait, wait); } if (!V4L2_TYPE_IS_OUTPUT(q->type) && !(req_events & (POLLIN | POLLRDNORM))) return res; if (V4L2_TYPE_IS_OUTPUT(q->type) && !(req_events & (POLLOUT | POLLWRNORM))) return res; /* * 如果队列中没有缓冲区,且文件I/O模拟器未启动,则启动文件I/O模拟器。 */ if (q->num_buffers == 0 && !vb2_fileio_is_active(q)) { /* * 如果队列类型不是输出类型,且I/O模式为读,且用户请求的事件中包含POLLIN或POLLRDNORM,则启动文件I/O模拟器。 */ if (!V4L2_TYPE_IS_OUTPUT(q->type) && (q->io_modes & VB2_READ) && (req_events & (POLLIN | POLLRDNORM))) { /* * 如果文件I/O模拟器初始化失败,则返回POLLERR。 */ if (__vb2_init_fileio(q, 1)) return res | POLLERR; } /* * 如果队列类型是输出类型,且I/O模式为写,且用户请求的事件中包含POLLOUT或POLLWRNORM,则启动文件I/O模拟器。 */ if (V4L2_TYPE_IS_OUTPUT(q->type) && (q->io_modes & VB2_WRITE) && (req_events & (POLLOUT | POLLWRNORM))) { /* * 如果文件I/O模拟器初始化失败,则返回POLLERR。 */ if (__vb2_init_fileio(q, 0)) return res | POLLERR; /* * 对于输出队列,可以立即进行写操作。 */ return res | POLLOUT | POLLWRNORM; } } /* * 如果队列不在流式传输状态,或者错误标志被设置,则没有等待的内容。 */ if (!vb2_is_streaming(q) || q->error) return res | POLLERR; /* * 为了与vb1兼容:如果尚未调用QBUF,则返回POLLERR。这仅影响捕获队列,输出队列将始终将waiting_for_buffers初始化为false。 */ if (q->waiting_for_buffers) return res | POLLERR; /* * 对于输出流,只要排队的缓冲区少于可用的缓冲区,就可以写入。 */ if (V4L2_TYPE_IS_OUTPUT(q->type) && q->queued_count < q->num_buffers) return res | POLLOUT | POLLWRNORM; // 如果done_list为空,则等待 if (list_empty(&q->done_list)) poll_wait(file, &q->done_wq, wait); /* * 取出第一个可用于出队的缓冲区。 */ spin_lock_irqsave(&q->done_lock, flags); if (!list_empty(&q->done_list)) vb = list_first_entry(&q->done_list, struct vb2_buffer, done_entry); spin_unlock_irqrestore(&q->done_lock, flags); /* * 如果缓冲区存在且状态为VB2_BUF_STATE_DONE或VB2_BUF_STATE_ERROR,则返回POLLOUT | POLLWRNORM或POLLIN | POLLRDNORM。 */ if (vb && (vb->state == VB2_BUF_STATE_DONE || vb->state == VB2_BUF_STATE_ERROR)) { return (V4L2_TYPE_IS_OUTPUT(q->type)) ? res | POLLOUT | POLLWRNORM : res | POLLIN | POLLRDNORM; } return res; }
获取用户请求的事件
unsigned long req_events = poll_requested_events(wait);
如果用户请求的事件中包含POLLPRI,则等待事件
poll_wait(file, &fh->wait, wait);
谁来产生数据、谁来唤醒它?
&fh->wait poll_wait(file, &fh->wait, wait); res = vb2_poll(vdev->queue, file, wait);
内核线程每隔一定时间执行一次,构造数据, 唤醒进程
int vb2_thread_start(struct vb2_queue *q, vb2_thread_fnc fnc, void *priv, const char *thread_name) { struct vb2_threadio_data *threadio; int ret = 0; if (q->threadio) return -EBUSY; if (vb2_is_busy(q)) return -EBUSY; if (WARN_ON(q->fileio)) return -EBUSY; threadio = kzalloc(sizeof(*threadio), GFP_KERNEL); if (threadio == NULL) return -ENOMEM; threadio->fnc = fnc; threadio->priv = priv; ret = __vb2_init_fileio(q, !V4L2_TYPE_IS_OUTPUT(q->type)); dprintk(3, "file io: vb2_init_fileio result: %d\n", ret); if (ret) goto nomem; q->threadio = threadio; threadio->thread = kthread_run(vb2_thread, q, "vb2-%s", thread_name); if (IS_ERR(threadio->thread)) { ret = PTR_ERR(threadio->thread); threadio->thread = NULL; goto nothread; } return 0; nothread: __vb2_cleanup_fileio(q); nomem: kfree(threadio); return ret; }
vb2_thread_start->vb2_thread
static int vb2_thread(void *data) { struct vb2_queue *q = data; struct vb2_threadio_data *threadio = q->threadio; struct vb2_fileio_data *fileio = q->fileio; bool set_timestamp = false; int prequeue = 0; int index = 0; int ret = 0; if (V4L2_TYPE_IS_OUTPUT(q->type)) { prequeue = q->num_buffers; set_timestamp = (q->timestamp_flags & V4L2_BUF_FLAG_TIMESTAMP_MASK) == V4L2_BUF_FLAG_TIMESTAMP_COPY; } set_freezable(); for (;;) { struct vb2_buffer *vb; /* * Call vb2_dqbuf to get buffer back. */ memset(&fileio->b, 0, sizeof(fileio->b)); fileio->b.type = q->type; fileio->b.memory = q->memory; if (prequeue) { fileio->b.index = index++; prequeue--; } else { call_void_qop(q, wait_finish, q); if (!threadio->stop) ret = vb2_internal_dqbuf(q, &fileio->b, 0); call_void_qop(q, wait_prepare, q); dprintk(5, "file io: vb2_dqbuf result: %d\n", ret); } if (ret || threadio->stop) break; try_to_freeze(); vb = q->bufs[fileio->b.index]; if (!(fileio->b.flags & V4L2_BUF_FLAG_ERROR)) if (threadio->fnc(vb, threadio->priv)) break; call_void_qop(q, wait_finish, q); if (set_timestamp) v4l2_get_timestamp(&fileio->b.timestamp); if (!threadio->stop) ret = vb2_internal_qbuf(q, &fileio->b); call_void_qop(q, wait_prepare, q); if (ret || threadio->stop) break; } /* Hmm, linux becomes *very* unhappy without this ... */ while (!kthread_should_stop()) { set_current_state(TASK_INTERRUPTIBLE); schedule(); } return 0; }
6.有数据后从队列里取出缓冲区
// 有那么多缓冲区,APP如何知道哪一个缓冲区有数据?调用VIDIOC_DQBUF
ioctl(4, VIDIOC_DQBUF
.vidioc_dqbuf = vb2_ioctl_dqbuf,
vb2_ioctl_dqbuf
int vb2_ioctl_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p) { struct video_device *vdev = video_devdata(file); if (vb2_queue_is_busy(vdev, file)) return -EBUSY; return vb2_dqbuf(vdev->queue, p, file->f_flags & O_NONBLOCK); }
vb2_ioctl_dqbuf->vb2_dqbuf
int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking) { if (vb2_fileio_is_active(q)) { dprintk(1, "file io in progress\n"); return -EBUSY; } return vb2_internal_dqbuf(q, b, nonblocking); }
vb2_ioctl_dqbuf->vb2_dqbuf->vb2_internal_dqbuf
/* * 从队列中取出一个缓冲区,将其状态设置为已出队列状态 * @q: videobuf2队列 * @b: 从用户空间传递给驱动程序的缓冲区结构 * @nonblocking: 如果为true,则此调用不会等待缓冲区,如果没有准备好的缓冲区等待出队列。通常,驱动程序将传递(file->f_flags&O_NONBLOCK)。 * * 应该从驱动程序的vidioc_dqbuf ioctl处理程序中调用此函数。 * 此函数: * 1)验证传递的缓冲区, * 2)在驱动程序中调用buf_finish回调(如果提供),其中驱动程序可以在将缓冲区返回到用户空间之前执行任何其他操作,例如缓存同步, * 3)填充缓冲区结构成员与用户空间相关的信息。 * * 此函数的返回值旨在直接从驱动程序中的vidioc_dqbuf处理程序返回。 */ static int vb2_internal_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking) { struct vb2_buffer *vb = NULL; int ret; if (b->type != q->type) { // 如果缓冲区类型与队列类型不匹配 dprintk(1, "invalid buffer type\n"); // 打印错误信息 return -EINVAL; // 返回无效参数错误 } ret = __vb2_get_done_vb(q, &vb, b, nonblocking); // 获取已完成的缓冲区 if (ret < 0) // 如果获取失败 return ret; // 返回获取失败的错误码 switch (vb->state) { // 根据缓冲区状态进行不同的操作 case VB2_BUF_STATE_DONE: // 如果缓冲区状态为已完成 dprintk(3, "returning done buffer\n"); // 打印信息 break; case VB2_BUF_STATE_ERROR: // 如果缓冲区状态为错误 dprintk(3, "returning done buffer with errors\n"); // 打印信息 break; default: // 如果缓冲区状态无效 dprintk(1, "invalid buffer state\n"); // 打印错误信息 return -EINVAL; // 返回无效参数错误 } call_void_vb_qop(vb, buf_finish, vb); // 调用buf_finish回调 /* 填充缓冲区信息以供用户空间使用 */ __fill_v4l2_buffer(vb, b); /* 从videobuf队列中删除 */ list_del(&vb->queued_entry); q->queued_count--; /* 回到已出队列状态 */ __vb2_dqbuf(vb); dprintk(1, "dqbuf of buffer %d, with state %d\n", vb->v4l2_buf.index, vb->state); // 打印信息 return 0; // 返回成功 }
// 在队列里获得有数据的缓冲区 ret = __vb2_get_done_vb(q, &vb, b, nonblocking);
// 获取已完成的缓冲区 // 把它从队列中删掉 list_del(&vb->queued_entry);
// 把这个缓冲区的状态返回给APP __fill_v4l2_buffer(vb, b);