V4L2驱动与应用

参考韦东山驱动大全

一、V4L2应用程序开发

1. 数据采集流程

  • Video for Linux two(Video4Linux2)简称V4L2,是V4L的改进版。
  • V4L2支持三种方式来采集图像:内存映射方式(mmap)、直接读取方式(read)和用户指针。
    • 内存映射的方式采集速度较快,一般用于连续视频数据的采集,实际工作中的应用概率更高;
    • 直接读取的方式相对速度慢一些,所以常用于静态图片数据的采集;
    • 用户指针使用较少。

1.1 buffer的管理

  • 使用摄像头时,核心是"获得数据"。所以先讲如何获取数据,即如何得到buffer。

  • 摄像头采集数据时,是一帧又一帧地连续采集。所以需要申请若干个buffer,驱动程序把数据放入buffer,APP从buffer得到数据。这些buffer可以使用链表来管理。

  • 由于摄像头在驱动编写后不一定会调用,如果在驱动中申请buffer,那么可能会导致一部分的内存空间被浪费。因此buffer的申请是在应用程序中进行的。

驱动程序周而复始地做如下事情:

  • 从硬件采集到数据
  • 把"空闲链表"取出buffer,把数据存入buffer
  • 把含有数据的buffer放入"完成链表"

APP也会周而复始地做如下事情:

  • 监测"完成链表",等待它含有buffer
  • 从"完成链表"中取出buffer
  • 处理数据
  • 把buffer放入"空闲链表"

链表操作示意图如下:
在这里插入图片描述

1.2 完整的使用流程

参考mjpg-streamer和video2lcd,总结了摄像头的使用流程,如下:

  • open:打开设备节点/dev/videoX
  • ioctl VIDIOC_QUERYCAP:Query Capbility,查询能力,比如
    • 确认它是否是"捕获设备",因为有些节点是输出设备
    • 确认它是否支持mmap操作,还是仅支持read/write操作
  • ioctl VIDIOC_ENUM_FMT:枚举它支持的格式
  • ioctl VIDIOC_S_FMT:在上面枚举出来的格式里,选择一个来设置格式
  • ioctl VIDIOC_REQBUFS:申请buffer,APP可以申请很多个buffer,但是驱动程序不一定能申请到
  • ioctl VIDIOC_QUERYBUF和mmap:查询buffer信息(长度)、映射
    • 如果申请到了N个buffer,这个ioctl就应该执行N次(for循环)
    • 执行mmap后,APP就可以直接读写这些buffer
  • ioctl VIDIOC_QBUF:把buffer放入"空闲链表"
    • 如果申请到了N个buffer,这个ioctl就应该执行N次
  • ioctl VIDIOC_STREAMON:启动摄像头
  • 这里是一个循环:使用poll/select监测buffer,然后从"完成链表"中取出buffer,处理后再放入"空闲链表"
    • poll/select,阻塞等待数据
    • ioctl VIDIOC_DQBUF:从"完成链表"中取出buffer
    • 处理:前面使用mmap映射了每个buffer的地址,处理时就可以直接使用地址来访问buffer
    • ioclt VIDIOC_QBUF:把buffer放入"空闲链表"
  • ioctl VIDIOC_STREAMOFF:停止摄像头

2. 控制流程

使用摄像头时,我们可以调整很多参数,比如:

  • 对于视频流本身:

    • 设置格式:比如V4L2_PIX_FMT_YUYV、V4L2_PIX_FMT_MJPEG、V4L2_PIX_FMT_RGB565
    • 设置分辨率:1024*768等
  • 对于控制部分:

    • 调节亮度
    • 调节对比度
    • 调节色度

2.1 APP接口

就APP而言,对于这些参数有3套接口:查询或枚举(Query/Enum)、获得(Get)、设置(Set)。

2.1.1 数据格式

以设置数据格式为例,可以先枚举:

struct v4l2_fmtdesc fmtdesc;
fmtdesc.index = 0;  // 比如从0开始
fmtdesc.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;  // 指定type为"捕获"
ioctl(vd->fd, VIDIOC_ENUM_FMT, &fmtdesc);

#if 0
/*
 *	F O R M A T   E N U M E R A T I O N
 */
struct v4l2_fmtdesc {
	__u32		    index;             /* Format number      */
	__u32		    type;              /* enum v4l2_buf_type */
	__u32               flags;
	__u8		    description[32];   /* Description string */
	__u32		    pixelformat;       /* Format fourcc      */
	__u32		    reserved[4];
};
#endif

还可以获得当前的格式:

struct v4l2_format currentFormat;
memset(&currentFormat, 0, sizeof(struct v4l2_format));
currentFormat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
ioctl(vd->fd, VIDIOC_G_FMT, &currentFormat);

#if 0
struct v4l2_format {
	__u32	 type;
	union {
		struct v4l2_pix_format		pix;     /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
		struct v4l2_pix_format_mplane	pix_mp;  /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */
		struct v4l2_window		win;     /* V4L2_BUF_TYPE_VIDEO_OVERLAY */
		struct v4l2_vbi_format		vbi;     /* V4L2_BUF_TYPE_VBI_CAPTURE */
		struct v4l2_sliced_vbi_format	sliced;  /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
		struct v4l2_sdr_format		sdr;     /* V4L2_BUF_TYPE_SDR_CAPTURE */
		__u8	raw_data[200];                   /* user-defined */
	} fmt;
};

/*
 *	V I D E O   I M A G E   F O R M A T
 */
struct v4l2_pix_format {v4l2_format
	__u32         		width;
	__u32			height;
	__u32			pixelformat;
	__u32			field;		/* enum v4l2_field */
	__u32            	bytesperline;	/* for padding, zero if unused */
	__u32          		sizeimage;
	__u32			colorspace;	/* enum v4l2_colorspace */
	__u32			priv;		/* private data, depends on pixelformat */
	__u32			flags;		/* format flags (V4L2_PIX_FMT_FLAG_*) */
	__u32			ycbcr_enc;	/* enum v4l2_ycbcr_encoding */
	__u32			quantization;	/* enum v4l2_quantization */
	__u32			xfer_func;	/* enum v4l2_xfer_func */
};
#endif

也可以设置当前的格式:

struct v4l2_format fmt;
memset(&fmt, 0, sizeof(struct v4l2_format));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = 1024;
fmt.fmt.pix.height = 768;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
fmt.fmt.pix.field = V4L2_FIELD_ANY;
int ret = ioctl(vd->fd, VIDIOC_S_FMT, &fmt);
2.1.2 选择输入源

可以获得当期输入源、设置当前输入源:

int value;
ioctl(h->fd,VIDIOC_G_INPUT,&value);  // 读到的value从0开始, 0表示第1个input源

int value = 0;  // 0表示第1个input源
ioctl(h->fd,VIDIOC_S_INPUT,&value)
2.1.3 其他参数

如果每一参数都提供一系列的ioctl cmd,那使用起来很不方便。

对于这些参数,APP使用对应ID来选中它,然后使用VIDIOC_QUERYCTRL(查询控制)、VIDIOC_G_CTRL(获得当前设置控制)、VIDIOC_S_CTRL(设置控制)来操作它。

不同参数的ID值不同。

以亮度Brightness为例,有如下调用方法:

  • 查询:
struct v4l2_queryctrl   qctrl;
memset(&qctrl, 0, sizeof(qctrl));
qctrl.id = V4L2_CID_BRIGHTNESS; // V4L2_CID_BASE+0;
ioctl(fd, VIDIOC_QUERYCTRL, &qctrl);

/*  Used in the VIDIOC_QUERYCTRL ioctl for querying controls */
struct v4l2_queryctrl {
	__u32		     id;
	__u32		     type;	/* enum v4l2_ctrl_type */
	__u8		     name[32];	/* Whatever */
	__s32		     minimum;	/* Note signedness */
	__s32		     maximum;
	__s32		     step;
	__s32		     default_value;
	__u32                flags;
	__u32		     reserved[2];
};

  • 获得当前值
struct v4l2_control c;
c.id = V4L2_CID_BRIGHTNESS; // V4L2_CID_BASE+0;
ioctl(h->fd, VIDIOC_G_CTRL, &c);


/*
 *	C O N T R O L S
 */
struct v4l2_control {
	__u32		     id;
	__s32		     value;
};
  • 设置
struct v4l2_control c;
c.id = V4L2_CID_BRIGHTNESS; // V4L2_CID_BASE+0;
c.value = 99;
ioctl(h->fd, VIDIOC_S_CTRL, &c);

2.2 理解接口

2.2.1 概念

以USB摄像头为例,它的内部结构如下:

在这里插入图片描述

  • 一个USB摄像头必定有一个VideoControl接口,用于控制。
  • 有0个或多个VideoStreaming接口,用于传输视频。

摄像头使用流程中的ioctl VIDIOC_STREAMON:启动摄像头ioctl VIDIOC_STREAMOFF:关闭摄像头都是对VideoStreaming Interface的操控

在VideoControl内部,有多个实体Entity,即Unit或Terminal,CT、IT、SU、PU、OT称为Unit。上一个Unit或Terminal的数据,流向下一个Unit或Terminal,多个Unit或Terminal组成一个完整的UVC功能设备。

  • 只有一个输出引脚
  • 可以Fan-out,不能Fan-in
    在这里插入图片描述
  • Terminal:位于边界,用于联通外界。有:IT(Input Terminal)、OT(Output Terminal)、CT(Camera Terminal)。模型如下,有一个输出引脚:
    IT不关心后续输出,OT不关心前面输入
    在这里插入图片描述

  • Unit:位于VideoControl内部,用来进行各种控制

    • SU:Selector Unit(选择单元),从多路输入中选择一路,比如设备支持多种输入源,可以通过SU进行选择切换。模型如下
      在这里插入图片描述

    • PU:Porocessing Unit(处理单元),用于调整亮度、对比度、色度等,有如下控制功能:

      • User Controls
        • Brightness 背光
        • Hue 色度
        • Saturation 饱和度
        • Sharpness 锐度
        • Gamma 伽马
        • Digital Multiplier (Zoom) 数字放大
      • Auto Controls
        • White Balance Temperature 白平衡色温
        • White Balance Component 白平衡组件
        • Backlight Compensation 背光补偿
        • Contrast 对比度
      • Other
        • Gain 增益
        • Power Line Frequency 电源线频率
        • Analog Video Standard 模拟视频标准
        • Analog Video Lock Status 模拟视频锁状态
      • 模型如下
        在这里插入图片描述
    • EU:Encoding Unit(编码单元),对采集所得的数据进行个性化处理的功能。编码单元控制编码器的属性,该编码器对通过它流式传输的视频进行编码。它具有如下功能:
      在这里插入图片描述

    • 模型如下
      在这里插入图片描述

  • XU:Extension Unit(扩展单元),厂家可以在XU上提供自定义的操作,模型如下:
    在这里插入图片描述

2.2.2 操作方法

我们使用ioctl操作设备节点"/dev/video0"时,不同的ioctl操作的可能是VideoControl接口,或者VideoStreaming接口。

跟视频流相关的操作,比如:VIDIOC_ENUM_FMT、VIDIOC_G_FMT、VIDIOC_S_FMT、VIDIOC_STREAMON、VIDIOC_STREAMOFF,是操作VideoStreaming接口。

其他ioctl,大多都是操作VideoControl接口。

从底层驱动和硬件角度看,要操作VideoControl接口,需要指明:

  • entity:你要操作哪个Terminal或Unit,比如PU
  • Control Selector:你要操作entity里面的哪个控制项?比如亮度PU_BRIGHTNESS_CONTROL
  • 控制项里哪些位:比如CT(Camera Terminal)里的CT_PANTILT_RELATIVE_CONTROL控制项对应32位的数据,其中前16位对应PAN控制(左右转动),后16位对应TILE控制(上下转动)

但是APP不关注这些细节,使用一个ID来指定entity、Control Selector、哪些位:

/*
 *	C O N T R O L S
 */
struct v4l2_control {
	__u32		     id;
	__s32		     value;
};

驱动程序里,会解析APP传入的ID,找到entity、Control Selector、哪些位。

自己扩展Unit:
添加Entity-->添加Control Selector-->不同位置的控制信息

在这里插入图片描述

3. 应用程序编写

用的USB摄像头


二、V4L2驱动

在任何APP中都是使用open/read/write,而在驱动中就提供相应的drv_open/read/write,以面向对象的思想来看,将drv_open/read/write集合到file_operations中并且集合成一个接口文件,根据次设备号找到连接对应的硬件相关层
在这里插入图片描述

1. 整体框架

  • 字符设备驱动程序的核心是:file_operations结构体
  • V4L2设备驱动程序的核心是:video_device结构体,它里面有2大成员
    • v4l2_file_operations结构体:实现具体的open/read/write/ioctl/mmap操作
    • v4l2_ioctl_ops结构体:v4l2_file_operations结构体一般使用video_ioctl2函数,它要调用v4l2_ioctl_ops结构体
      在这里插入图片描述

1.1注册流程

  1. 初始化一个video_device结构体,并设置release、fops、ioctl_fops
  2. 分配video_device结构体
  3. 初始化一个v4l2_device结构体,并注册
  4. 建立video_device和v4l2_device的联系
  5. 注册video_device结构体

参考drivers\media\usb\airspy\airspy.c

static struct video_device airspy_template = {
	.name                     = "AirSpy SDR",
	.release                  = video_device_release_empty,
	.fops                     = &airspy_fops,
	.ioctl_ops                = &airspy_ioctl_ops,
};

// 分配/设置video_device结构体
s->vdev = airspy_template;

// 初始化一个v4l2_device结构体(起辅助作用)
/* Register the v4l2_device structure */
s->v4l2_dev.release = airspy_video_release;
ret = v4l2_device_register(&intf->dev, &s->v4l2_dev);

// video_device和4l2_device建立联系
s->vdev.v4l2_dev = &s->v4l2_dev;

// 注册video_device结构体
ret = video_register_device(&s->vdev, VFL_TYPE_SDR, -1);
		__video_register_device
			// 根据次设备号把video_device结构体放入数组
			video_device[vdev->minor] = vdev;
			
			// 注册字符设备驱动程序
			vdev->cdev->ops = &v4l2_fops;
			vdev->cdev->owner = owner;
			ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);

1.2 ioctl调用流程分析

1.2.1 两类ioctl

底层驱动程序提供了很多ioctl的处理函数,比如:

  • 19
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值