一文带你了解V4L2
发布日期:2021-06-30 18:43:34 浏览次数:3 分类:技术文章

本文共 10785 字,大约阅读时间需要 35 分钟。

1、什么是v4l2

V4L2(Video4Linux的缩写)是Linux下关于视频采集相关设备的驱动框架,为驱动和应用程序提供了一套统一的接口规范。

V4L2支持的设备十分广泛,但是其中只有很少一部分在本质上是真正的视频设备:

•Video capture device :从摄像头等设备上获取视频数据。对很多人来讲,video capture是V4L2的基本应用。设备名称为/dev/video,主设备号81,子设备号0~63•Video output device :将视频数据编码为模拟信号输出。与video capture设备名相同。•Video overlay device :将同步锁相视频数据(如TV)转换为VGA信号,或者将抓取的视频数据直接存放到视频卡的显存中。•Video output overlay device :也被称为OSD(On-Screen Display)•VBI device :提供对VBI(Vertical Blanking Interval)数据的控制,发送VBI数据或抓取VBI数据。设备名/dev/vbi0~vbi31,主设备号81,子设备号224~255•Radio device :FM/AM发送和接收设备。设备名 /dev/radio0~radio63,主设备号81,子设备号64~127

V4L2在Linux系统中的结构图如下:

2、从应用层看V4L2

从V4L2简单框图可以看出,V4L2是一个字符设备,而V4L2的大部分功能都是通过设备文件的ioctl导出的。

可以将这些ioctl分类如下:

•Query Capability:查询设备支持的功能,只有VIDIOC_QUERY_CAP一个。•优先级相关:包括VIDIOC_G_PRIORITY,VIDIOC_S_PRIORITY,设置优先级。•capture相关:视频捕获相关Ioctl。

capture ioctl list ID 描述

VIDIOC_ENUM_FMT    枚举设备所支持的所有数据格式VIDIOC_S_FMT    设置数据格式VIDIOC_G_FMT    获取数据格式VIDIOC_TRY_FMT    与VIDIOC_S_FMT一样,但不会改变设备的状态VIDIOC_REQBUFS    向设备请求视频缓冲区,即初始化视频缓冲区VIDIOC_QUERYBUF    查询缓冲区的状态VIDIOC_QBUF    从设备获取一帧视频数据VIDIOC_DQBUF    将视频缓冲区归回给设备,VIDIOC_OVERLAY    开始或者停止overlayVIDIOC_G_FBUF    获取video overlay设备或OSD设备的framebuffer参数VIDIOC_S_FBUF    设置framebuffer参数VIDIOC_STREAMON    开始流I/O操作,capture or output deviceVIDIOC_STREAMOFF    关闭流I/O操作

•TV视频标准:

TV Standard ID 描述 VIDIOC_ENUMSTD 枚举设备支持的所有标准 VIDIOC_G_STD 获取当前正在使用的标准 VIDIOC_S_STD 设置视频标准 VIDIOC_QUERYSTD 有的设备支持自动侦测输入源的视频标准,此时使用此ioctl查询侦测到的视频标准

•input/output:

Input / Output ID 描述

VIDIOC_ENUMINPUT    枚举所有input端口VIDIOC_G_INPUT    获取当前正在使用的input端口VIDIOC_S_INPUT    设置将要使用的input端口VIDIOC_ENUMOUTPUT    枚举所有output端口VIDIOC_G_OUTPUT    获取当前正在使用的output端口VIDIOC_S_OUTPUT    设置将要使用的output端口VIDIOC_ENUMAUDIO    枚举所有audio input端口VIDIOC_G_AUDIO    获取当前正在使用的audio input端口VIDIOC_S_AUDIO    设置将要使用的audio input端口VIDIOC_ENUMAUDOUT    枚举所有audio output端口VIDIOC_G_AUDOUT    获取当前正在使用的audio output端口VIDIOC_S_AUDOUT    设置将要使用的audio output端口

•controls:设备特定的控制,例如设置对比度,亮度•controls ID 描述*

VIDIOC_QUERYCTRL    查询指定control的详细信息VIDIOC_G_CTRL    获取指定control的值VIDIOC_S_CTRL    设置指定control的值VIDIOC_G_EXT_CTRLS    获取多个control的值VIDIOC_S_EXT_CTRLS    设置多个control的值VIDIOC_TRY_EXT_CTRLS    与VIDIOC_S_EXT_CTRLS相同,但是不改变设备状态VIDIOC_QUERYMENU    查询menu

•其他杂项:•controls ID 描述*

VIDIOC_G_MODULATOR     VIDIOC_S_MODULATOR     VIDIOC_G_CROP     VIDIOC_S_CROP     VIDIOC_G_SELECTION     VIDIOC_S_SELECTION     VIDIOC_CROPCAP     VIDIOC_G_ENC_INDEX     VIDIOC_ENCODER_CMD     VIDIOC_TRY_ENCODER_CMD     VIDIOC_DECODER_CMD     VIDIOC_TRY_DECODER_CMD     VIDIOC_G_PARM     VIDIOC_S_PARM     VIDIOC_G_TUNER     VIDIOC_S_TUNER     VIDIOC_G_FREQUENCY     VIDIOC_S_FREQUENCY     VIDIOC_G_SLICED_VBI_CAP     VIDIOC_LOG_STATUS     VIDIOC_DBG_G_CHIP_IDENT     VIDIOC_S_HW_FREQ_SEEK     VIDIOC_ENUM_FRAMESIZES     VIDIOC_ENUM_FRAMEINTERVALS     VIDIOC_ENUM_DV_PRESETS     VIDIOC_S_DV_PRESET     VIDIOC_G_DV_PRESET     VIDIOC_QUERY_DV_PRESET     VIDIOC_S_DV_TIMINGS     VIDIOC_G_DV_TIMINGS     VIDIOC_DQEVENT     VIDIOC_SUBSCRIBE_EVENT     VIDIOC_UNSUBSCRIBE_EVENT     VIDIOC_CREATE_BUFS     VIDIOC_PREPARE_BUF

v4l2设备的基本操作流程如下

1、打开设备,例如 fd = open("/dev/video0",0)

2、查询设备能力. 例如:

struct capability cap;ioctl(fd,VIDIOC_QUERYCAP,&cap)
3、设置优先级(可选)

4、配置设备。

包括:

•视频输入源的视频标准,VIDIOC_*_STD•视频数据的格式 , VIDIOC_*_FMT•视频输入端口, VIDIOC_*_INPUT•视频输出端口,VIDIOC_*_OUTPUT

5、启动设备开始I/O操作。V4L2支持一下三种I/O方式:

•Read/Write:通过调用设备节点文件的Read/Write函数,与设备交互数据。打开设备后,默认使用的是此方法。•Stream I/O:流操作,只传递数据缓冲区指针,不拷贝数据。使用此方法,需要调用VIDIOC_REQBUFS ioctl来通知设备。流操作I/O有两种方式memory map和user buffer。(具体区别后面章节介绍)•overlay :也可以理解为memory to memory 传输。将数据从内存拷贝到显存中。overlay设备独有的。

对于Capture device可以以如下方式启动设备:

•调用VIDIOC_REQBUFS ioctl来分配缓冲区队列;•调用VIDIOC_STREAMON ioctl通知设备开始stream IO•调用VIDIOC_QBUF ioctl从设备获取一帧视频数据;•使用完数据后,调用VIDIOC_DQBUF将缓冲区还给设备,以便设备填充下一帧数据。

6、释放资源并关闭设备。

1.3、从驱动层看V4L2

在驱动层,V4L2为驱动编写者做了很多工作。只需要实现硬件相关的代码,并且注册相关设备即可。

硬件相关代码的编写,除了编写具体硬件的控制代码外,最主要的就是将代码与V4L2框架绑定。绑定主要分为以下两个部分:

•关系绑定:也就是要将我们自己的结构体,与V4L2框架中相关连的结构体绑定在一起。•iocontrol等函数绑定:将V4L2所定义的空的函数指针,与自己的函数绑定在一起。

3.1 关系绑定

提到关系绑定,就必须介绍下V4L2几个重要结构体。

•struct video_device:主要的任务就是负责向内核注册字符设备•struct v4l2_device:一个硬件设备可能包含多个子设备,比如一个电视卡除了有capture设备,可能还有VBI设备或者FM tunner。而v4l2_device就是所有这些设备的根节点,负责管理所有的子设备。•struct v4l2_subdev:子设备,负责实现具体的功能。

v4l2_device,v4l2_subdev可以看作所有设备和子设备的基类。我们在编写自己的驱动时,往往需要继承这些设备基类,添加一些自己的数据成员。例如第三章要讲到的soc_camera_host结构体,就是继承v4l2_device,并添加了互斥锁、子设备列表等成员变量。

绑定的基本流程

根据需要”重载”v4l2_device或v4l2_subdev结构体,添加需要的结构体成员。例如 :

•linux/include/media/soc_camera.h文件中soc_camera_host重载了v4l2_device:

struct soc_camera_host {struct v4l2_device v4l2_dev;struct list_head list;struct mutex host_lock;         /* Protect during probing */unsigned char nr;               /* Host number */void *priv;const char *drv_name;struct soc_camera_host_ops *ops;};

•linux/drivers/media/video/Ml86v7667.c中ml86v7667_priv结构体”重载”了v4l2_subdev:

struct ml86v7667_priv {    struct v4l2_subdev                sd;    struct v4l2_ctrl_handler  hdl;    v4l2_std_id                       std;};
v4l2_device与V4L2框架的绑定:通过调用v4l2_device_register函数实现。例如,上面提到的soc_camera_host的绑定:
int soc_camera_host_register(struct soc_camera_host *ici){      struct soc_camera_host *ix;      int ret;      if (!ici || !ici->ops ||        !ici->ops->try_fmt ||        !ici->ops->set_fmt ||        !ici->ops->set_bus_param ||        !ici->ops->querycap ||        ((!ici->ops->init_videobuf ||        !ici->ops->reqbufs) &&        !ici->ops->init_videobuf2) ||        !ici->ops->add ||        !ici->ops->remove ||        !ici->ops->poll ||        !ici->v4l2_dev.dev)          return -EINVAL;      if (!ici->ops->set_crop)          ici->ops->set_crop = default_s_crop;      if (!ici->ops->get_crop)          ici->ops->get_crop = default_g_crop;      if (!ici->ops->cropcap)          ici->ops->cropcap = default_cropcap;      if (!ici->ops->set_parm)          ici->ops->set_parm = default_s_parm;      if (!ici->ops->get_parm)          ici->ops->get_parm = default_g_parm;      if (!ici->ops->enum_fsizes)          ici->ops->enum_fsizes = default_enum_fsizes;      mutex_lock(&list_lock);      list_for_each_entry(ix, &hosts, list) {          if (ix->nr == ici->nr) {              ret = -EBUSY;              goto edevreg;          }      }      ret = v4l2_device_register(ici->v4l2_dev.dev, &ici->v4l2_dev);      if (ret < 0)          goto edevreg;      list_add_tail(&ici->list, &hosts);      mutex_unlock(&list_lock);      mutex_init(&ici->host_lock);      scan_add_host(ici);      return 0;      edevreg:      mutex_unlock(&list_lock);      return ret;  }

v4l2_subdev与v4l2_device的绑定:通过v4l2_device_register_subdev函数,将subdev注册到根节点上。例如:

static int soc_camera_platform_probe(struct platform_device *pdev){    struct soc_camera_host *ici;    struct soc_camera_platform_priv *priv;    struct soc_camera_platform_info *p = pdev->dev.platform_data;    struct soc_camera_device *icd;    int ret;    if (!p)    return -EINVAL;    if (!p->icd) {        dev_err(&pdev->dev,            "Platform has not set soc_camera_device pointer!\n");        return -EINVAL;    }    priv = kzalloc(sizeof(*priv), GFP_KERNEL);    if (!priv)        return -ENOMEM;    icd = p->icd;    /* soc-camera convention: control's drvdata points to the subdev */    platform_set_drvdata(pdev, &priv->subdev);    /* Set the control device reference */    icd->control = &pdev->dev;    ici = to_soc_camera_host(icd->parent);    v4l2_subdev_init(&priv->subdev, &platform_subdev_ops);    v4l2_set_subdevdata(&priv->subdev, p);    strncpy(priv->subdev.name, dev_name(&pdev->dev), V4L2_SUBDEV_NAME_SIZE);    ret = v4l2_device_register_subdev(&ici->v4l2_dev, &priv->subdev);    if (ret)        goto evdrs;    return ret;  evdrs:    platform_set_drvdata(pdev, NULL);    kfree(priv);    return ret;}

video_device与v4l2_device的绑定:将v4l2_device的地址赋值给video_device的v4l2_dev即可。

3.2 函数绑定

在v4l2 framework 简略版图中,绿色的方框都是需要我们绑定并实现的。

其中v4l2_file_operations和v4l2_ioctl_ops是必须实现的。而v4l2_subdev_ops下的八类ops中,v4l2_subdev_core_ops是必须实现的,其余需要根据设备类型选择实现的。比如video capture类设备需要实现v4l2_subdev_core_ops, v4l2_subdev_video_ops。

•v4l2_file_operations:实现文件类操作,比如open,close,read,write,mmap等。但是ioctl是不需要实现的,一般都是用video_ioctl2代替。例如linux/drivers/media/video/soc_camera.c文件中soc_camera_fops的实现:

static struct v4l2_file_operations soc_camera_fops = {    .owner          = THIS_MODULE,    .open           = soc_camera_open,    .release        = soc_camera_close,    .unlocked_ioctl = video_ioctl2,    .read           = soc_camera_read,    .mmap           = soc_camera_mmap,    .poll           = soc_camera_poll,};

•v4l2_ioctl_ops:V4L2导出给应用层使用的所有ioctl都是在这个地方实现的。但不必全部实现,只实现自己相关的ioctl即可。例如linux/drivers/media/video/soc_camera.c中soc_camera_ioctl_ops的实现:

static const struct v4l2_ioctl_ops soc_camera_ioctl_ops = {  .vidioc_querycap         = soc_camera_querycap,  .vidioc_try_fmt_vid_cap  = soc_camera_try_fmt_vid_cap,  .vidioc_g_fmt_vid_cap    = soc_camera_g_fmt_vid_cap,  .vidioc_s_fmt_vid_cap    = soc_camera_s_fmt_vid_cap,  .vidioc_enum_fmt_vid_cap = soc_camera_enum_fmt_vid_cap,  .vidioc_enum_input       = soc_camera_enum_input,  .vidioc_g_input          = soc_camera_g_input,  .vidioc_s_input          = soc_camera_s_input,  .vidioc_s_std            = soc_camera_s_std,  .vidioc_g_std            = soc_camera_g_std,  .vidioc_enum_framesizes  = soc_camera_enum_fsizes,  .vidioc_reqbufs          = soc_camera_reqbufs,  .vidioc_querybuf         = soc_camera_querybuf,  .vidioc_qbuf             = soc_camera_qbuf,  .vidioc_dqbuf            = soc_camera_dqbuf,  .vidioc_create_bufs      = soc_camera_create_bufs,  .vidioc_prepare_buf      = soc_camera_prepare_buf,  .vidioc_streamon         = soc_camera_streamon,  .vidioc_streamoff        = soc_camera_streamoff,  .vidioc_cropcap          = soc_camera_cropcap,  .vidioc_g_crop           = soc_camera_g_crop,  .vidioc_s_crop           = soc_camera_s_crop,  .vidioc_g_parm           = soc_camera_g_parm,  .vidioc_s_parm           = soc_camera_s_parm,  .vidioc_g_chip_ident     = soc_camera_g_chip_ident,#ifdef CONFIG_VIDEO_ADV_DEBUG  .vidioc_g_register       = soc_camera_g_register,  .vidioc_s_register       = soc_camera_s_register,#endif};

•v4l2_subdev_ops:v4l2_subdev有可能需要实现的ops的总合。分为8类,core,audio,video,vbi,tuner......等。例如, linuxdriversmediavideosoc_camera_platform.c中platform_subdev_ops的实现

static struct v4l2_subdev_video_ops platform_subdev_video_ops = {    .s_stream       = soc_camera_platform_s_stream,    .enum_mbus_fmt  = soc_camera_platform_enum_fmt,    .cropcap        = soc_camera_platform_cropcap,    .g_crop         = soc_camera_platform_g_crop,    .try_mbus_fmt   = soc_camera_platform_fill_fmt,    .g_mbus_fmt     = soc_camera_platform_fill_fmt,    .s_mbus_fmt     = soc_camera_platform_fill_fmt,    .g_mbus_config  = soc_camera_platform_g_mbus_config,};static struct v4l2_subdev_ops platform_subdev_ops = {    .core   = &platform_subdev_core_ops,    .video  = &platform_subdev_video_ops,};

函数绑定只是将驱动所实现的函数赋值给相关的变量即可。


扫码或长按关注

回复「 加群 」进入技术群聊

转载地址:https://linus.blog.csdn.net/article/details/103501939 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Linux 僵尸进程可以被杀死吗?
下一篇:让你不再害怕指针.pdf

发表评论

最新留言

哈哈,博客排版真的漂亮呢~
[***.90.31.176]2024年04月20日 16时39分48秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

【深度学习笔记】用torch.nn.ModuleList搭建神经网络 2019-04-30
【解决错误】AttributeError: module ‘scipy.misc‘ has no attribute ‘imread‘ 2019-04-30
【解决错误】复现RCAN的时候遇到了ImportError: cannot import name ‘_update_worker_pids’ from ‘torch._C’ 2019-04-30
【解决错误】ModuleNotFoundError: No module named ‘skimage‘ 2019-04-30
【深度学习笔记】pytorch的点乘(dot product) 2019-04-30
【深度学习笔记】残差 2019-04-30
【错误解决】cv2.error: OpenCV(4.2.0) C:\projects\opencv-python\opencv\modules\imgproc\sr 2019-04-30
【python学习笔记】读取指定文件夹中的图片,结合边缘保留滤波EPF 2019-04-30
【工具和环境】Linux下安装pycharm 2019-04-30
【Accumulation】The last two sentences of the abstract 2019-04-30
【Accumulation】The definition of SISR 2019-04-30
【工具与环境】Windows下安装Sublime Text 3 2019-04-30
【解决错误】ValueError: some of the strides of a given numpy array are negative. 2019-04-30
【工具与环境】Excel中批量插入行 2019-04-30
【个人实验注意事项】 2019-04-30
【解决错误】ModuleNotFoundError: No module named ‘tqdm‘ 2019-04-30
【解决错误】ModuleNotFoundError: No module named ‘PIL‘ 2019-04-30
【学习笔记】对vanilla的一些个人理解 2019-04-30
【解决错误】json.decoder.JSONDecodeError: Expecting value: line 11 column 14 (char 82) 2019-04-30
【解决错误】The size of tensor a (8) must match the size of tensor b (64) at non-singleton dimension 1 2019-04-30