xawtv涉及的vivid系统调用分析

news/2024/10/20 11:25:18/

xawtv涉及的vivid系统调用分析


文章目录

  • xawtv涉及的vivid系统调用分析
  • 调用过程分析
  • 摄像头驱动程序必需的11个ioctl
    • 非必须
    • 必须
  • 分析数据的获取过程
    • 1.请求分配缓冲区: ioctl(4, VIDIOC_REQBUFS // 请求系统分配缓冲区
    • 2.查询映射缓冲区:
    • 3.把缓冲区放入队列:
    • 4.启动摄像头
    • 5.用select查询是否有数据
    • 6.有数据后从队列里取出缓冲区
    • 7.应用程序根据VIDIOC_DQBUF所得到缓冲区状态,知道是哪一个缓冲区有数据就去读对应的地址(该地址来自前面的mmap)


调用过程分析

/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的几大函数:

  1. v4l2_open
  2. v4l2_read_attr/v4l2_write_attr
  3. v4l2_start_streaming
  4. 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 heis 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和planeunsigned int buffer = 0, plane = 0;// 定义一个int类型的变量retint ret;// 定义一个unsigned long类型的变量length// 判断内存类型是否为V4L2_MEMORY_MMAPif (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);// 调用内存操作函数mmapret = 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_pollvdev->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);// 如果锁定失败,返回POLLERRif (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;// 如果有挂起的事件,则返回POLLPRIif (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->waitpoll_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);

7.应用程序根据VIDIOC_DQBUF所得到缓冲区状态,知道是哪一个缓冲区有数据就去读对应的地址(该地址来自前面的mmap)


http://www.ppmy.cn/news/61383.html

相关文章

代码随想录算法训练营第三十天 | 航班问题、二维回溯

回溯法小结 本周小结&#xff01;&#xff08;回溯算法系列三&#xff09; | 代码随想录 (programmercarl.com) 性能分析 子集问题分析&#xff1a; 时间复杂度&#xff1a;O(n 2n)&#xff0c;因为每一个元素的状态无外乎取与不取&#xff0c;所以时间复杂度为O(2n)&…

登山 最长上升子序列问题 线性DP

&#x1f351; 算法题解专栏 &#x1f351; 洛谷 登山 登山 题目描述 五一到了&#xff0c;ACM队组织大家去登山观光&#xff0c;队员们发现山上一个有N个景点&#xff0c;并且决定按照顺序来浏览这些景点&#xff0c;即每次所浏览景点的编号都要大于前一个浏览景点的编号。…

用户界面对象的线程亲缘性第二篇: 设备上下文

在上一篇文章中&#xff0c;我们简单地介绍了控制窗口句柄的线程亲缘性规则。 今天&#xff0c;我们来讲讲设备上下文(Device Context, 简称 DC) 。 设备上下文也有一定程度的线程亲缘性。调用 DC 相关函数&#xff0c;例如 GetDC 的线程&#xff0c;必须在同一个线程中调用其…

PID整定二:基于Ziegler-Nichols的频域响应

PID整定二&#xff1a;基于Ziegler-Nichols的频域响应 1参考2连续Ziegler-Nichols方法的PID整定2.1整定方法2.2仿真示例 1参考 1.1根轨迹图的绘制及分析 1.2计算机控制技术01-3.4离散系统的根轨迹分析法 1.3PID控制算法学习笔记 2连续Ziegler-Nichols方法的PID整定 2.1整定…

leetcode:234.回文链表(详解)

前言&#xff1a;内容包括-题目&#xff0c;代码实现&#xff0c;大致思路&#xff0c;代码解读 题目&#xff1a; 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&…

Java日志详解

文章目录 1.日志的概述1.1 日志文件1.1.1 调试日志1.1.2 系统日志 1.2 JAVA日志框架1.2.1 为什么要用日志框架1.2.2 日志框架和日志门面 2.JUL2.1 JUL简介2.2 JUL组件介绍2.3 JUL的基本使用2.3.1 日志输出的级别2.3.2 日志的输出方式2.3.3 自定义日志的级别2.3.4 将日志输出到具…

【产品方案】后台管理系统设计思路

第一章 前言 相比前端设计&#xff0c;我更喜欢设计后台管理系统。如果说前端设计考验的是共情能力&#xff0c;那后台管理系统设计考研的就是逻辑能力&#xff0c;前者需要站在用户的角度&#xff0c;后者是站在管理者的角度思考。 有幸参与了公司不少业务系统从“0-1”的设计…

Lerna

Lerna Lerna是一个优化基于gitnpm的多pagkage项目的管理工具 解决的痛点 痛点一:重复操作 多Package本地link多Package依赖安装多Package单元测试多Package代码提交多Package代码发布 痛点二:版本一致性 发布时版本一 致性发布后相互依赖版本升级 package越多&#xff0c;管…