系列文章目录
【V4L2】V4L2框架简述
【V4L2】V4L2框架之驱动结构体
【V4L2】V4L2子设备
【V4L2】V4L2框架-media device
【V4L2】V4L2框架-videobuf2
文章目录
- 系列文章目录
- 用户空间的操作
- /dev/video 节点与 videobuf2 联系
- 编程注意事项
用户空间的操作
- 用户空间 stream 操作 ioctl 调用流程:
-
VIDIOC_S_PARAM
::参数为 structv4l2_streamparm
,主要设置帧率,type 与 capturemode/outputmode 等。内核的 ioctl 需要保存 capturemode(mode需自定义)帧率等数据,可以保存到自定义的结构体里面。也可以在内核里面通过 v4l2_subdev_call 来调用 -
VIDIOC_S_FMT
::参数为 structv4l2_format
,主要设置长宽,数据格式等。内核的 ioctl 需要保存长宽,像素格式,所在的 field 等。必要时需要调用相关的子设备的结构体进行子设备的格式设置。 -
VIDIOC_REQBUFS
::参数为 struct v4l2_requestbuffers,设置 count,type 与 memory 请求数据,在 queue_setup 回调函数里面需要设置一些东西。参见上面关于 queue_setup 的描述。 -
VIDIOC_QUERYBUF
::参数为 struct v4l2_buffer,需设置 index,type,memory,length(plane length) 等参数 -
VIDIOC_QBUF
,该操作与上一个不断循环,直到获取所有的 buf 并将 buf 入队到内核驱动的的 buf 管理列表中 -
VIDIOC_STREAMON
::参数为 enum v4l2_buf_type,开启指定类型的 stream。 -
select 等待 buf 可读。在内核驱动里面需要获取 plane 的地址,vb2_plane_vaddr,在数据填充完毕之后需要调用 vb2_buffer_done 来标注该 buffer 已经成功填充完毕,以便 v4l2 把数据放入 done_list,以待用户空间进行读取。
-
VIDIOC_DQBUF
,该操作与上一个操作不断循环,直到停止数据采集 -
VIDIOC_STREAMOFF
,结束数据采集工作。
/dev/video 节点与 videobuf2 联系
首先 video_device 需要有自己的 open,release,unlocked_ioctl 等等,一个常见的初始化操作如下「此处参照了 omap3isp 里面的代码」:
static struct v4l2_file_operations isp_video_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = video_ioctl2,
.open = isp_video_open,
.release = isp_video_release,
.poll = isp_video_poll,
.mmap = isp_video_mmap,
};
其中那几个函数几乎都是与 videobuf2 是挂钩的,如果去看下内核里面的代码就可以了然了,其中实用频率最高的就是
.unlocked_ioctl = video_ioctl2, 这个回调函数了,这个回调函数是用户空间通过
/dev/videoX 节点通往 videobuf2 的不二路径,所以干脆直接就把这个成员赋值为 video_ioctl2
了,这个是 videobuf2 为我们提供的操作函数,那就拿起来并使用它吧。从用户空间的角度看,只有一个 ioctl 的入口,但是我们穿过重重关卡,来到内核之后,再往前走两步,就可谓之「初极狭,才通人。复行数十步,豁然开朗」。里面还有一大坨分门别类的 ioctl 回调函数,截取如下:
static const struct v4l2_ioctl_ops isp_video_ioctl_ops = {
.vidioc_querycap = isp_video_querycap,
.vidioc_g_fmt_vid_cap = isp_video_get_format,
.vidioc_s_fmt_vid_cap = isp_video_set_format,
.vidioc_try_fmt_vid_cap = isp_video_try_format,
.vidioc_g_fmt_vid_out = isp_video_get_format,
.vidioc_s_fmt_vid_out = isp_video_set_format,
.vidioc_try_fmt_vid_out = isp_video_try_format,
.vidioc_cropcap = isp_video_cropcap,
.vidioc_g_crop = isp_video_get_crop,
.vidioc_s_crop = isp_video_set_crop,
.vidioc_g_parm = isp_video_get_param,
.vidioc_s_parm = isp_video_set_param,
.vidioc_reqbufs = isp_video_reqbufs,
.vidioc_querybuf = isp_video_querybuf,
.vidioc_qbuf = isp_video_qbuf,
.vidioc_dqbuf = isp_video_dqbuf,
.vidioc_streamon = isp_video_streamon,
.vidioc_streamoff = isp_video_streamoff,
.vidioc_enum_input = isp_video_enum_input,
.vidioc_g_input = isp_video_g_input,
.vidioc_s_input = isp_video_s_input,
};
有了上面两个,我们只需要在初始化 video_device
的时候做以下的操作,此时便可以把整个串联起来了:
video->video.fops = &isp_video_fops; /* 重点 */
video->video.vfl_type = VFL_TYPE_GRABBER;
video->video.release = video_device_release_empty; /* 重点 */
video->video.ioctl_ops = &isp_video_ioctl_ops; /* 重点 */
video->pipe.stream_state = ISP_PIPELINE_STREAM_STOPPED;
看到这里是不是就把 /dev/videoX
,video_device
,vb2_queue
,videobuf2
这几个全部贯通起来了.
重新梳理一遍:首先用户 open 一个 /dev/videoX
,获取其句柄,同时触发内核的 open 函数内部对 videobuf2
的 vb2_queue 进行初始化;然后进行一系列的 ioctl 操作,入口是 isp_video_fops->unlocked_ioctl 成员,再往后会细分为 isp_video_ioctl_ops
里面的一个个回调,这一个个回调与 vb2 众多的 ops 深度结合起来共同完成了数据流的管理工作。
编程注意事项
- 要特别注意 plane 与 buffer 索引的区别,一个 buffer 下面有多个 plane,应该是这样的循环方式:
for (buffer) {
for (plane) {
... ...
}
}
- 像下面的 ioctl 尽量可以使用 vb2 提供的回调函数,如果需要自己实现的话也需要显式调用 vb2 提供的回调函数:
V4L24L2 ioctls
VIDIOC_REQBUFS
VIDIOC_QUERYBUF
VIDIOC_QBUF
VIDIOC_DQBUF
VB2 ioctls
.start_streaming
.stop_streaming
- 需要自己实现一个自旋锁用于 buffer 队列管理,关于为什么用自旋锁,原因是当驱动要获取数据的时候,可以加锁,然后从该队列删除,释放锁,填充数据可以等到释放锁之后进行,所以没必要用别的,自旋锁轻量,在这种情况下自旋锁的使用成本较低。通常在驱动里面维护一个 list 队列,存放激活的 buffer 数据,使用自旋锁自行管理,在 queue 的时候入队,数据填充完毕之后出队,调用
vb2_buffer_done
来放入 vb2 的 buffer 队列等待用户拿取。在vb2_queue
初始化的时候可以设置buf_struct_size
成员为自定义大小的帧结构体(自定义的结构体要把vb2_v4l2_buffer
放在第一个成员的位置,在里面可以添加帧的属性等等),要用到的时候就使用container_of
来获取自定义的帧结构体。 - 使用wait队列来定时产生数据:
init_waitqueue_head
kernel thread
set_freezable
DECLARE_WAITQUEUE
add_wait_queue
generate datas ...
schedule_timeout_interruptible
remove_wait_queue
try_to_freeze
wake_up_interruptible
-
在 streamoff 的时候需要调用
vb2_buffer_done
(, ERR) 来归还所有的 buffer 到 vb2 里面。只要调用vb2_buffer_done
,该 buffer 就会被放入到 vb2 的各种队列里面,剩下的事情就是调用vb2_ioctl_streamoff
让 vb2 来完成数据的释放啦。 -
如何获取 vb2_buffer 的虚拟地址与物理地址
dma-contig, scatter/gather-dma,vmalloc(只有虚拟地址)
虚拟地址:vb2_plane_vaddr()
物理地址:
dma-contig:vb2_dma_contig_plane_dma_addr
scatter/gather-dma:vb2_dma_sg_plane_desc
- 编程步骤
-
自定义一个 video 设备结构体,最简单的里面需要包含,
video_device
、vb2_queue
以及自定义的 buffer 帧结构体描述。 -
对上面提到的两个结构体
video_device
、vb2_queue
进行初始化。 -
实现
vb2_ops
结构体,具体的实现以及注意事项参见上面的描述。