转自:https://blog.csdn.net/Guet_Kite/article/details/78574781

权声明:本文为 风筝 博主原创文章,未经博主允许不得转载!!!!!!谢谢合作 https://blog.csdn.net/Guet_Kite/article/details/78574781

你好!这里是风筝的博客,

欢迎和我一起交流。

上一章写了V4L2框架:嵌入式Linux驱动笔记(十七)——详解V4L2框架(UVC驱动)
现在来写V4L2的重点,他的用户空间操作函数集合:

const struct v4l2_file_operations uvc_fops = {
.owner = THIS_MODULE,
.open = uvc_v4l2_open,
.release = uvc_v4l2_release,
.unlocked_ioctl = video_ioctl2,
#ifdef CONFIG_COMPAT
.compat_ioctl32 = uvc_v4l2_compat_ioctl32,
#endif
.read = uvc_v4l2_read,
.mmap = uvc_v4l2_mmap,
.poll = uvc_v4l2_poll,
#ifndef CONFIG_MMU
.get_unmapped_area = uvc_v4l2_get_unmapped_area,
#endif
};

看下open函数:

static int uvc_v4l2_open(struct file *file)
{
/*部分函数省略*/
struct uvc_streaming *stream;
struct uvc_fh *handle; stream = video_drvdata(file);//获取uvc视频流
ret = usb_autopm_get_interface(stream->dev->intf);//唤醒设备
handle = kzalloc(sizeof *handle, GFP_KERNEL);//创建uvc句柄 if (stream->dev->users == 0) {//第一次时
ret = uvc_status_start(stream->dev, GFP_KERNEL);//uvc状态开始,里面提交urb
}
stream->dev->users++; v4l2_fh_init(&handle->vfh, &stream->vdev);
v4l2_fh_add(&handle->vfh);
handle->chain = stream->chain;//捆绑uvc句柄和uvc视频链
handle->stream = stream;//捆绑uvc句柄和uvc视频流
handle->state = UVC_HANDLE_PASSIVE;//设置uvc状态为未激活
file->private_data = handle;//将uvc句柄作为文件的私有数据
return 0;
}

open函数不是我们这章的重点,用户空间对V4L2设备的操作基本都是ioctl来实现的,我们看下ioctl函数:

long video_ioctl2(struct file *file,
unsigned int cmd, unsigned long arg)
{
return video_usercopy(file, cmd, arg, __video_do_ioctl);
}

可以看出video_usercopy函数就是从user空间copy复制ioctl的cmd和arg参数,然后进入__video_do_ioctl函数:

static long __video_do_ioctl(struct file *file,
unsigned int cmd, void *arg)
{
/*部分内容省略*/
struct video_device *vfd = video_devdata(file);
const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops;
const struct v4l2_ioctl_info *info; if (v4l2_is_known_ioctl(cmd)) {
info = &v4l2_ioctls[_IOC_NR(cmd)];//判断是INFO_FL_STD还是INFO_FL_FUNC
}
if (info->flags & INFO_FL_STD) {//如果是INFO_FL_STD
typedef int (*vidioc_op)(struct file *file, void *fh, void *p);
const void *p = vfd->ioctl_ops;//调用到ioctl_ops真正的ioctrl操作集
const vidioc_op *vidioc = p + info->u.offset;//通过偏移值找到要执行函数的地址 ret = (*vidioc)(file, fh, arg);//直接调用到视频设备驱动中video_device->ioctl_ops
} else if (info->flags & INFO_FL_FUNC) {//如果是INFO_FL_FUNC
ret = info->u.func(ops, file, fh, arg);//调用到v4l2自己实现的标准回调函数
}
}

我们可以看出,如果info->flags是INFO_FL_FUNC,会调用vfd->ioctl_ops函数集合里的某个函数(通过info->u.offset偏移值确定),那vfd->ioctl_ops是什么呢?其实就是上一章说的,uvc_register_video函数里的vdev->ioctl_ops = &uvc_ioctl_ops了。
如果info->flags是INFO_FL_FUNC,直接调用info->u.func(ops, file, fh, arg)函数
那info又是怎么确定的呢?当然是:info = &v4l2_ioctls[_IOC_NR(cmd)];

static struct v4l2_ioctl_info v4l2_ioctls[] = {//.ioctl, .u.func, .debug, .flags
IOCTL_INFO_FNC(VIDIOC_QUERYCAP, v4l_querycap, v4l_print_querycap, 0),//列举性能
IOCTL_INFO_FNC(VIDIOC_ENUM_FMT, v4l_enum_fmt, v4l_print_fmtdesc, INFO_FL_CLEAR(v4l2_fmtdesc, type)),//列举格式
IOCTL_INFO_FNC(VIDIOC_G_FMT, v4l_g_fmt, v4l_print_format, 0),
IOCTL_INFO_FNC(VIDIOC_S_FMT, v4l_s_fmt, v4l_print_format, INFO_FL_PRIO),//设置摄像头使用某种格式
IOCTL_INFO_FNC(VIDIOC_REQBUFS, v4l_reqbufs, v4l_print_requestbuffers, INFO_FL_PRIO | INFO_FL_QUEUE),//请求系统分配缓冲区
IOCTL_INFO_FNC(VIDIOC_QUERYBUF, v4l_querybuf, v4l_print_buffer, INFO_FL_QUEUE | INFO_FL_CLEAR(v4l2_buffer, length)),//查询所分配的缓冲区
/*太长了,省略后续*/
}

其实,虽然是调用info->u.func(ops, file, fh, arg)函数,但是ops是vfd->ioctl_ops;,以v4l2_ioctls[]数组里的v4l_querycap函数(就是u.func字段)为例,里面也会调用:ops->vidioc_querycap(file, fh, cap);
所以,最终还是会回到uvc_ioctl_ops函数集合里。其实和info->flags 是 INFO_FL_STD的情况没什么大的差别。

我们看下uvc_ioctl_ops 这个真正的ioctl操作函数集合:

const struct v4l2_ioctl_ops uvc_ioctl_ops = {
.vidioc_querycap = uvc_ioctl_querycap,
.vidioc_enum_fmt_vid_cap = uvc_ioctl_enum_fmt_vid_cap,//列举支持哪种格式
.vidioc_enum_fmt_vid_out = uvc_ioctl_enum_fmt_vid_out,
.vidioc_g_fmt_vid_cap = uvc_ioctl_g_fmt_vid_cap,//获取格式、分辨率
.vidioc_g_fmt_vid_out = uvc_ioctl_g_fmt_vid_out,
.vidioc_s_fmt_vid_cap = uvc_ioctl_s_fmt_vid_cap,//先try测试,然后把要设置的格式/分辨率存起来
.vidioc_s_fmt_vid_out = uvc_ioctl_s_fmt_vid_out,
.vidioc_try_fmt_vid_cap = uvc_ioctl_try_fmt_vid_cap,//检测是否支持用户输入的格式
.vidioc_try_fmt_vid_out = uvc_ioctl_try_fmt_vid_out,
.vidioc_reqbufs = uvc_ioctl_reqbufs,//请求分配缓存,APP将从这些缓存中读到视频数据
.vidioc_querybuf = uvc_ioctl_querybuf,//查询缓存状态, 比如地址信息(APP可以用mmap进行映射)
.vidioc_qbuf = uvc_ioctl_qbuf,//把缓冲区放入队列,底层的硬件操作函数将会把数据放入这个队列的缓存
.vidioc_expbuf = uvc_ioctl_expbuf,
.vidioc_dqbuf = uvc_ioctl_dqbuf,//APP通过poll/select确定有数据后,把缓存从队列中取出来
.vidioc_create_bufs = uvc_ioctl_create_bufs,
.vidioc_streamon = uvc_ioctl_streamon,//启动视频传输
.vidioc_streamoff = uvc_ioctl_streamoff,
.vidioc_enum_input = uvc_ioctl_enum_input,
.vidioc_g_input = uvc_ioctl_g_input,
.vidioc_s_input = uvc_ioctl_s_input,
/*太长了,后续省略......*/
};

可以看到非常的多,带_cap的是捕获设备,带_out的是输出设备。
虽然这些ioctl非常多,但是在韦东山第三期视频里说道,简化的摄像头驱动程序至少需要11个ioctl,我能力不大,所以也就按照这个来分析:

.vidioc_querycap = uvc_ioctl_querycap,
.vidioc_enum_fmt_vid_cap = uvc_ioctl_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap = uvc_ioctl_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap = uvc_ioctl_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap = uvc_ioctl_s_fmt_vid_cap,
.vidioc_reqbufs = uvc_ioctl_reqbufs,
.vidioc_querybuf = uvc_ioctl_querybuf,
.vidioc_qbuf = uvc_ioctl_qbuf,
.vidioc_dqbuf = uvc_ioctl_dqbuf,
.vidioc_streamon = uvc_ioctl_streamon,
.vidioc_streamoff = uvc_ioctl_streamoff,

我们先看第【1】个ioctl:.vidioc_querycap = uvc_ioctl_querycap函数:

static int uvc_ioctl_querycap(struct file *file, void *fh,
struct v4l2_capability *cap)
{
struct video_device *vdev = video_devdata(file);
struct uvc_fh *handle = file->private_data;
struct uvc_video_chain *chain = handle->chain;
struct uvc_streaming *stream = handle->stream; strlcpy(cap->driver, "uvcvideo", sizeof(cap->driver));
strlcpy(cap->card, vdev->name, sizeof(cap->card));
usb_make_path(stream->dev->udev, cap->bus_info, sizeof(cap->bus_info));
cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING
| chain->caps;//获取这是一个什么设备:
if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)//如果是视频捕获设备
cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
else
cap->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING; return 0;
}

很简单的函数,.vidioc_querycap 里的回调函数主要就是说明这个是什么设备而已,输入设备?输出设备?

接下来看第【2】个ioctl:.vidioc_enum_fmt_vid_cap = uvc_ioctl_enum_fmt_vid_cap函数:
uvc_ioctl_enum_fmt_vid_cap函数里又会调用uvc_ioctl_enum_fmt函数:

static int uvc_ioctl_enum_fmt(struct uvc_streaming *stream,struct v4l2_fmtdesc *fmt)
{
/*省略部分内容*/
struct uvc_format *format; format = &stream->format[fmt->index];//从uvc_fmts找出支持的格式
strlcpy(fmt->description, format->name, sizeof(fmt->description));//存放到description
fmt->description[sizeof(fmt->description) - 1] = 0;
fmt->pixelformat = format->fcc;
}

.vidioc_enum_fmt_vid_cap 里的回调函数主要就是列举出支持的格式,把他放到fmt->description描述符中,然后返回给用户空间传进来的fmt。

继续看第【3】个ioctl:.vidioc_g_fmt_vid_cap = uvc_ioctl_g_fmt_vid_cap函数:
uvc_ioctl_g_fmt_vid_cap函数里又会调用uvc_v4l2_get_format函数:

static int uvc_v4l2_get_format(struct uvc_streaming *stream,struct v4l2_format *fmt)
{
/*部分内容省略*/
format = stream->cur_format;//从stream获取当前格式
frame = stream->cur_frame;//从stream获取当前分辨率 fmt->fmt.pix.pixelformat = format->fcc;
fmt->fmt.pix.width = frame->wWidth;
fmt->fmt.pix.height = frame->wHeight;
fmt->fmt.pix.field = V4L2_FIELD_NONE;
fmt->fmt.pix.bytesperline = uvc_v4l2_get_bytesperline(format, frame);
fmt->fmt.pix.sizeimage = stream->ctrl.dwMaxVideoFrameSize;
fmt->fmt.pix.colorspace = format->colorspace;
fmt->fmt.pix.priv = 0;
}

.vidioc_g_fmt_vid_cap里的回调函数主要是获取格式、分辨率,把他存放在fmt->fmt.pix里。

第【4】个ioctl:.vidioc_try_fmt_vid_cap = uvc_ioctl_try_fmt_vid_cap函数:
uvc_ioctl_try_fmt_vid_cap函数里面又调用uvc_v4l2_try_format函数:

static int uvc_v4l2_try_format(struct uvc_streaming *stream,
struct v4l2_format *fmt, struct uvc_streaming_control *probe,
struct uvc_format **uvc_format, struct uvc_frame **uvc_frame)
{
/*部分内容省略*/
for (i = 0; i < stream->nformats; ++i) {//在format查找支持的格式是否有请求的格式
format = &stream->format[i];
if (format->fcc == fmt->fmt.pix.pixelformat)
break;
}
if (i == stream->nformats) {//如果没有
format = stream->def_format;//使用默认的格式
fmt->fmt.pix.pixelformat = format->fcc;
}
rw = fmt->fmt.pix.width;
rh = fmt->fmt.pix.height;
maxd = (unsigned int)-1; for (i = 0; i < format->nframes; ++i) {//查找最接近的图像分辨率
__u16 w = format->frame[i].wWidth;
__u16 h = format->frame[i].wHeight;
d = min(w, rw) * min(h, rh);
d = w*h + rw*rh - 2*d;
if (d < maxd) {
maxd = d;
frame = &format->frame[i];
}
if (maxd == 0)
break;
} interval = frame->dwDefaultFrameInterval;//每一帧的间隙
probe->bmHint = 1; /* dwFrameInterval */
probe->bFormatIndex = format->index;
probe->bFrameIndex = frame->bFrameIndex;
probe->dwFrameInterval = uvc_try_frame_interval(frame, interval);
ret = uvc_probe_video(stream, probe);//里面调用uvc_set_video_ctrl fmt->fmt.pix.width = frame->wWidth;//设置具体的参数
fmt->fmt.pix.height = frame->wHeight;//分辨率
fmt->fmt.pix.field = V4L2_FIELD_NONE;
fmt->fmt.pix.bytesperline = uvc_v4l2_get_bytesperline(format, frame);
fmt->fmt.pix.sizeimage = probe->dwMaxVideoFrameSize;//一桢大小
fmt->fmt.pix.colorspace = format->colorspace;
fmt->fmt.pix.priv = 0;
}

我们看下.vidioc_try_fmt_vid_cap的回调函数做了什么:
1.检测硬件是否支持请求的格式,否则使用默认格式
2.然后在format->frame[i]查找最近的分辨率来使用
3.设置每一帧的时间间隙,也就是一秒多少帧
4.填充probe,然后在uvc_probe_video里设置video控制
5.最后把尝试好的数据填充到fmt->fmt.pix

所以.vidioc_try_fmt_vid_cap的回调函数主要是起一个测试作用。

第【5】个ioctl:.vidioc_s_fmt_vid_cap = uvc_ioctl_s_fmt_vid_cap函数:
uvc_ioctl_s_fmt_vid_cap函数里调用uvc_v4l2_set_format函数:

static int uvc_v4l2_set_format(struct uvc_streaming *stream,
struct v4l2_format *fmt)
{
/*部分内容省略*/
ret = uvc_v4l2_try_format(stream, fmt, &probe, &format, &frame);//测试好参数 /*把参数保存在stream,供uvc_v4l2_get_format函数调用*/
stream->ctrl = probe;
stream->cur_format = format;//把格式foramt保存起来
stream->cur_frame = frame;//把分辨率frame保存起来
}

其实我感觉这个函数都没啥啥,大部分都在uvc_v4l2_try_format函数里做了,最后就是把参数存起来到stream里而已。

第【6】个ioctl:.vidioc_reqbufs = uvc_ioctl_reqbufs函数:
调用关系:

uvc_ioctl_reqbufs
uvc_request_buffers
vb2_reqbufs
vb2_core_reqbufs
int vb2_core_reqbufs(struct vb2_queue *q, enum vb2_memory memory,unsigned int *count)
{
/*部分内容省略*/
__vb2_queue_cancel(q);//清理任何缓冲的准备或排队队列状态
memset(q->alloc_devs, 0, sizeof(q->alloc_devs));//初始化清零
q->memory = memory;//指向内存
ret = call_qop(q, queue_setup, q, &num_buffers, &num_planes,
plane_sizes, q->alloc_devs);//查询需要多少缓冲区buffers,设置缓存区队列
allocated_buffers =
__vb2_queue_alloc(q, memory, num_buffers, num_planes, plane_sizes);//申请缓存区
}

其中call_qop(q, queue_setup, q, &num_buffers, &num_planes,plane_sizes, q->alloc_devs)里面其实就是调用:(q)->ops->queue_setup(q, &num_buffers, &num_planes,plane_sizes, q->alloc_devs)
那这个(q)->ops又是在哪设置呢?
其实就是我们上一章里的uvc_queue_init函数里:
queue->queue.ops = &uvc_queue_qops;

static struct vb2_ops uvc_queue_qops = {
.queue_setup = uvc_queue_setup,
.buf_prepare = uvc_buffer_prepare,
.buf_queue = uvc_buffer_queue,
.buf_finish = uvc_buffer_finish,
.wait_prepare = vb2_ops_wait_prepare,
.wait_finish = vb2_ops_wait_finish,
.start_streaming = uvc_start_streaming,
.stop_streaming = uvc_stop_streaming,
};

这个结构体很重要,之后我们还会调用到。
知道需要多少缓存之后,就会调用__vb2_queue_alloc函数申请空间:

static int __vb2_queue_alloc(struct vb2_queue *q, enum vb2_memory memory,
unsigned int num_buffers, unsigned int num_planes,
const unsigned plane_sizes[VB2_MAX_PLANES])
{
/*部分内容省略*/
unsigned int buffer, plane;
struct vb2_buffer *vb; for (buffer = 0; buffer < num_buffers; ++buffer) {
vb = kzalloc(q->buf_struct_size, GFP_KERNEL);//分配空间
vb->state = VB2_BUF_STATE_DEQUEUED;//设置状态
vb->vb2_queue = q;
vb->num_planes = num_planes;
vb->index = q->num_buffers + buffer;
vb->type = q->type;//设置类型,比如捕获之类的
vb->memory = memory;//设置内存
for (plane = 0; plane < num_planes; ++plane) {
vb->planes[plane].length = plane_sizes[plane];
vb->planes[plane].min_length = plane_sizes[plane];
}
q->bufs[vb->index] = vb;//申请的空间装入bufs if (memory == VB2_MEMORY_MMAP) {//如果使用的是VB2_MEMORY_MMAP类型
ret = __vb2_buf_mem_alloc(vb);//mmap出来
if (ret) {
dprintk(1, "failed allocating memory for "
"buffer %d\n", buffer);
q->bufs[vb->index] = NULL;
kfree(vb);
break;
}
__setup_offsets(vb);//设置偏移量
ret = call_vb_qop(vb, buf_init, vb);//buf初始化
}
}
}

这里面就是申请num_buffers个缓存了,设置好然后放到q->bufs[]里,再mmap到用户空间。
为什么mmap呢?
read和write,是基本帧IO访问方式,每一帧都要通过IO操作,通过read读取每一帧数据,数据需要在内核和用户之间拷贝,这种方式访问速度可能会非常慢;
但是通过mmap在内核空间开辟缓冲区,这些缓冲区可以是大而连续DMA缓冲区、通过vmalloc()创建的虚拟缓冲区,或者直接在设备的IO内存中开辟的缓冲区(如果硬件支持);是流IO访问方式,不需要内存拷贝,访问速度比较快。

设置好偏移值就对buf进行初始化了。
所以.vidioc_reqbufs的回调函数主要是请求分配缓存。

第【7】个.ioctl:.vidioc_querybuf = uvc_ioctl_querybuf函数:
调用关系:

uvc_query_buffer
vb2_querybuf
vb2_core_querybuf
call_void_bufop(q, fill_user_buffer, q->bufs[index], pb);
  • 1
  • 2
  • 3
  • 4

可以看出,这里根据传进来的参数,查找到使用到的缓存,会调用fill_user_buffer返回bufs[]给用户空间。
所以.vidioc_querybuf 的回调函数主要是查询缓存状态,把他返回给用户空间。

第【8】个.vidioc_qbuf = uvc_ioctl_qbuf函数:
调用关系为:

uvc_ioctl_qbuf
uvc_queue_buffer
vb2_qbuf
vb2_core_qbuf
  • 1
  • 2
  • 3
  • 4
int vb2_core_qbuf(struct vb2_queue *q, unsigned int index, void *pb)
{
/*部分内容省略*/
struct vb2_buffer *vb; vb = q->bufs[index];
list_add_tail(&vb->queued_entry, &q->queued_list);//添加到queued_list链表
q->queued_count++;//队列数目加一
q->waiting_for_buffers = false;
vb->state = VB2_BUF_STATE_QUEUED;//标记已入队列 if (q->start_streaming_called)//如果调用过vb2_start_streaming
__enqueue_in_driver(vb);//将vb->planes[plane].mem_priv(即是我们申请的mmap)调入我们写的驱动中
if (pb)
call_void_bufop(q, fill_user_buffer, vb, pb);//填充buffers到用户空间
if (q->streaming && !q->start_streaming_called &&
q->queued_count >= q->min_buffers_needed) {
ret = vb2_start_streaming(q);
}
}

这里面,将对应的vb2_buffer 添加到 q->queued_list 链表中,并改变state状态。
然后调用__enqueue_in_driver把缓冲区放入队列。
然后就是ret = vb2_start_streaming(q)函数,这个函数在uvc_ioctl_streamon函数里也会调用有,所以放在后面写吧,这里虽然也会调用,但是是有条件的:
只有start_streaming没被调用且到达最小需要的buffers数目,才尝试启动

第【9】个.vidioc_dqbuf = uvc_ioctl_dqbuf,函数:
调用关系为:

uvc_ioctl_dqbuf
uvc_dequeue_buffer
vb2_dqbuf
vb2_core_dqbuf
int vb2_core_dqbuf(struct vb2_queue *q, unsigned int *pindex, void *pb,
bool nonblocking)
{
/*部分内容省略*/
struct vb2_buffer *vb = NULL; ret = __vb2_get_done_vb(q, &vb, pb, nonblocking);
call_void_vb_qop(vb, buf_finish, vb);
call_void_bufop(q, fill_user_buffer, vb, pb);
list_del(&vb->queued_entry);
q->queued_count--;
}

这里面,就是在__vb2_get_done_vb函数里,将q->done_list 中的vb2_buffer中提出来,然后 将vb2_buffer中的v4l2_buffer信息返回,并将其从q->done_list 中删除。
然后就调用(q)->vb2_queue->ops->buf_finish代表buf操作完成。
接着调用fill_user_buffer函数把把数据返回给用户空间,最后list_del(&vb->queued_entry)把他移除掉。

第【10】个.vidioc_streamon = uvc_ioctl_streamon,,函数:
调用关系为:

uvc_ioctl_streamon
uvc_queue_streamon
vb2_streamon
vb2_core_streamon
vb2_start_streaming
static int vb2_start_streaming(struct vb2_queue *q)
{
/*部分内容省略*/
struct vb2_buffer *vb;
q->start_streaming_called = 1;//标志以使用streaming
ret = call_qop(q, start_streaming, q,
atomic_read(&q->owned_by_drv_count));//调用q->ops->start_streaming
q->start_streaming_called = 0; for (i = 0; i < q->num_buffers; ++i) {
vb = q->bufs[i];
if (vb->state == VB2_BUF_STATE_ACTIVE)
vb2_buffer_done(vb, VB2_BUF_STATE_QUEUED);//数据完成后
}
}

函数里面q->ops->start_streaming,也就是queue->queue.ops = &uvc_queue_qops里的函数:uvc_start_streaming
但是uvc_start_streaming函数里又会调用uvc_video_enable(stream, 1);
uvc_video_enable函数里面就有这两句:

ret = uvc_commit_video(stream, &stream->ctrl);//uvc提交视频参数
ret = uvc_init_video(stream, GFP_KERNEL);//uvc初始化视频,同时调用uvc_init_video_isoc分配urb

这样就成功的启动视频传输了。
然后数据完成后就是调用vb2_buffer_done函数:函数里面就是:

    list_add_tail(&vb->done_entry, &q->done_list);//将数据放入q->done_list中
vb->state = state;
wake_up(&q->done_wq);//唤醒poll休眠的进程

数据完成就要把buffers放到done_list这个完成的链表中,然后改变状态,最后就唤醒poll函数里的休眠进程。

第【11】个.vidioc_streamoff = uvc_ioctl_streamoff,,函数:
其实就和uvc_ioctl_streamoff是相反的,uvc_ioctl_streamoff是调用uvc_video_enable(stream, 1);
uvc_ioctl_streamoff就是调用uvc_video_enable(stream, 0)了,进行关闭视频传输。

好了,十一个ioctl就马马虎虎的讲完了。还有一些其他的ioctl,比如设置亮度之类的,就自己去看了,是在太多了。
其实一路写来,我对这个buffers这块输出还是很模糊的,不知道怎么写,,,,,,,,,还在研究中。不过大致的流程还是了解了一点。

最后,附上一位大佬的blog:http://www.cnblogs.com/surpassal/archive/2012/12/22/zed_webcam_lab2.html

嵌入式Linux驱动笔记(十八)------浅析V4L2框架之ioctl【转】的更多相关文章

  1. LINUX驱动笔记 目录

    笔记参考了宋宝华老师的<Linux设备驱动开发详解:基于最新的Linux 4.0内核>以及韦东山老师的嵌入式驱动教程 笔记开发环境: 单板:第一章到第八章使用TINY4412-1611:第 ...

  2. 嵌入式linux驱动开发之给linux系统添加温度传感器模块

    忙了几天,终于可以让ds18b20在自己的开发板的linux系统上跑了!虽然ds18b20不是什么新鲜玩意,但是想想知己可以给linux系统添加模块了还是有点小鸡冻呢! 虽然说现在硬件的资源非常丰富而 ...

  3. 嵌入式Linux驱动开发日记

    嵌入式Linux驱动开发日记 主机硬件环境 开发机:虚拟机Ubuntu12.04 内存: 1G 硬盘:80GB 目标板硬件环境 CPU: SP5V210 (开发板:QT210) SDRAM: 512M ...

  4. Linux开源模块迁移概述暨交叉编译跨平台移植总结--从《嵌入式Linux驱动模板简洁和工程实践》

    本文摘录<嵌入式Linux驱动模板简洁和工程实践>一本书"开发和调试技术". Linux强大的是,有那么多的开源项目可以使用.通常非常需要可以通过寻找相关的源模块被定义 ...

  5. python3.4学习笔记(十八) pycharm 安装使用、注册码、显示行号和字体大小等常用设置

    python3.4学习笔记(十八) pycharm 安装使用.注册码.显示行号和字体大小等常用设置Download JetBrains Python IDE :: PyCharmhttp://www. ...

  6. linux驱动开发重点关注内容--摘自《嵌入式Linux驱动模板精讲与项目实践》

    本文摘自本人拙著 <嵌入式Linux驱动模板精讲与项目实践> 初步看起来Linux设备驱动开发涉及内容非常多,而须要实现驱动的设备千差万别.事实上做一段时间驱动之后回首看来主要就是下面几点 ...

  7. 嵌入式Linux驱动案例之中的一个

    前几天解决一个嵌入式Linux驱动问题,做为一个案例进行记录. 本案例是一个CPU通过LocalBus总线訪问外围一个设备,详细设备是一个DSP器件.在实际应用中,性能要求非常高,对数据訪问速度提出比 ...

  8. 嵌入式Linux学习笔记(三) 字符型设备驱动--LED的驱动开发

    在成功构建了一个能够运行在开发板平台的系统后,下一步就要正式开始应用的开发(这里前提是有一定的C语言基础,对ARM体系的软/硬件,这部分有疑问可能要参考其它教程),根据需求仔细分解任务,可以发现包含的 ...

  9. 韦东山 嵌入式linux教程 笔记

    @ 目录 资源链接 一.常用命令 二.shell 三.如何更改PATH? 四.路径 五.vi编辑器 六.进阶命令 七.NAT配置网络 (第2篇-P34) 八.开发板挂载 Ubuntu 的 NFS 目录 ...

随机推荐

  1. 自学Aruba5.2-Aruba安全认证-有PEFNG 许可证环境的角色策略管理

    点击返回:自学Aruba之路 自学Aruba5.2-Aruba安全认证- 有PEFNG 许可证环境的角色策略管理 导入许可后,可以对Role进行配置: 1. 系统自带的Role的可以修改的属性: 2. ...

  2. P4139 上帝与集合的正确用法

    本题是欧拉定理的应用.我这种蒟蒻当然不知道怎么证明啦! 那么我们就不证明了,来直接看结论: ab≡⎧⎩⎨⎪⎪ab%φ(p)abab%φ(p)+φ(p)gcd(a,p)=1gcd(a,p)≠1,b< ...

  3. 洛谷 P1140 相似基因(DP)

    传送门 https://www.cnblogs.com/violet-acmer/p/9852294.html 参考资料: [1]:https://www.cnblogs.com/real-l/p/9 ...

  4. hibernate中复合主键的使用

    转: https://blog.csdn.net/shutingwang/article/details/6627730 https://blog.csdn.net/lmy86263/article/ ...

  5. frp源码剖析-frp中的log模块

    前言&引入 一个好的log模块可以帮助我们排错,分析,统计 一般来说log中需要有时间.栈信息(比如说文件名行号等),这些东西一般某些底层log模块已经帮我们做好了.但在业务中还有很多我们需要 ...

  6. 三种数据库连接池的配置及使用(For JDBC)

    DBCP 一.导包 Apache官网下载DBCP包,导入两个包路径如下: commons-dbcp-1.4-bin\commons-dbcp-1.4\commons-dbcp-1.4.jar:连接池的 ...

  7. 门店评级VS坏客户

    sklearn实战-乳腺癌细胞数据挖掘(博客主亲自录制视频教程) https://study.163.com/course/introduction.htm?courseId=1005269003&a ...

  8. Linux上安装Perl模块的两种方法

    Linux/Unix下安装Perl模块有两种方法:手工安装和自动安装.第一种方法是从CPAN上下载  您需要的模块,手工编译.安装.第二种方法是联上internet,使用一个叫做CPAN的模块自动完 ...

  9. elasticsearch-head安装及启动

    head是用于监控Elasticsearch状态的客户端插件,包括数据可视化,增删改查工具,es语句的可视化等等. 5.0之后的安装方式如下: git clone git://github.com/m ...

  10. CM记录-优化配置解决Reduce卡顿问题

    CDH大数据集群问题问题分析与解决方案 问题描述:Hive提交任务,一直卡在Reduce阶段,进度缓慢. 日志分析:NodeManager节点产生的usercache所在分区空间不足,导致进程异常退出 ...