一、安装摄像头应用程序(初体验vivi)
我们可以在线获取安装包,好像是高版本的ubuntu已经去掉了vivi这个功能。
//在保证自己的ubuntu能够联网的情况下执行这条命令:
1、linux@linux:~$ sudo apt-get install xawtv
//安装成功之后
2、linux@linux:~$ sudo modprobe -i vivi
//执行上面这条命令之后,可以通过ls命令查看dev目录下自动生成了video0这个字符驱动设备
linux@linux:~$ ls /dev/video0 -al
crw-rw----+ 1 root video 81, 0 Dec 10 04:14 /dev/video0
3、linux@linux:~$ xawtv
//执行这个摄像头应用程序xawtv
执行完xawtv摄像头应用程序之后就会出现这样的现象:
二、vivi虚拟摄像头驱动代码分析
如果下载了linux内核源码在内核顶层目录下的驱动文件drivers/media/video/vivi.c,如果你手中没有现成的内核源码,可以通过网页在线浏览,一般浏览器搜索lxr就会出现linux或者android中的,具体网址:https://lxr.missinglinkelectronics.com/linux/
1、分配一个对象
/**通过模式入口函数vivi_create_instance,根据驱动的框架,主要有四步:分配一个对象、
对象初始化、注册对象、注销对象。要想掌握摄像头的驱动框架,就必须确认这四个步骤具体
怎么实现的,然后根据别人的框架来写自己的驱动**/
static int __init vivi_create_instance(int inst)
{
struct vivi_dev *dev; /****这个其实不是分配的对象,而是已经被封
装过的video_device,所以查看必须vivi_dev这个结构体如何实现的****/
struct video_device *vfd;
struct v4l2_ctrl_handler *hdl;
struct vb2_queue *q;
int ret;
dev = kzalloc(sizeof(*dev), GFP_KERNEL); //开辟空间分配内存
if (!dev)
return -ENOMEM;
snprintf(dev->v4l2_dev.name,sizeof(dev->v4l2_dev.name), "%s-%03d", VIVI_MODULE_NAME, inst);
ret = v4l2_device_register(NULL, &dev->v4l2_dev);
/*v4l2_device_registe对video_device进行初始化继承了device对象,
这个就是再/sys下创建文件或者目录供用户查看*/
if (ret)
goto free_dev;
//上边注册函数vivi_create_instance第一个结构体在这儿实现
struct vivi_dev {
struct list_head vivi_devlist;
struct v4l2_device v4l2_dev;
struct v4l2_ctrl_handler ctrl_handler;
/* controls */
struct v4l2_ctrl *brightness; /*这些往下都是亮度等等的一些图像参数*/
struct v4l2_ctrl *contrast;
struct v4l2_ctrl *saturation;
struct v4l2_ctrl *hue;
struct {
/* autogain/gain cluster */
struct v4l2_ctrl *autogain;
struct v4l2_ctrl *gain;
};
struct v4l2_ctrl *volume;
struct v4l2_ctrl *button;
struct v4l2_ctrl *boolean;
struct v4l2_ctrl *int32;
struct v4l2_ctrl *int64;
struct v4l2_ctrl *menu;
struct v4l2_ctrl *string;
struct v4l2_ctrl *bitmask;
struct video_device *vfd; //注册一个对象的函数
vfd =video_device_alloc() //给注册的对象分配内存
*vfd = vivi_template; //初始化对象,直接赋值的vivi_template
}
2、对象初始化
static struct video_device vivi_template = {
.name = "vivi", //摄像头名字比如:vivi或者ov3640
.fops = &vivi_fops, //操作方法
.ioctl_ops = &vivi_ioctl_ops, //ioctl函数的操作方法
.release = video_device_release, //释放资源的函数
.tvnorms = V4L2_STD_525_60,
.current_norm = V4L2_STD_NTSC_M,
};
3、注册对象
video_register_device(vfd, VFL_TYPE_GRABBER, video_nr); /***ideo_register_device这个是注册对象的函数,发现在这个函数里只调用了下面的函数
__video_register_device****/
__video_register_device(vdev, type, nr, 1, vdev->fops->owner); //V4L2-dev.c
{
switch (type) { //type代表本次分配的设备类型
case VFL_TYPE_GRABBER: // VFL_TYPE_GRABBER摄像头设备
name_base = "video";
break;
case VFL_TYPE_VBI: // VFL_TYPE_VBI有线电视设备
name_base = "vbi";
break;
case VFL_TYPE_RADIO: // VFL_TYPE_RADIO声音机的设备
name_base = "radio";
break;
case VFL_TYPE_SUBDEV:
name_base = "v4l-subdev";
break;
default:
printk(KERN_ERR "%s called with unknown type: %d\n",
__func__, type);
return -EINVAL;
}
switch (type) { //用来指定次设备号的
case VFL_TYPE_GRABBER: //0-63
minor_offset = 0;
minor_cnt = 64;
break;
case VFL_TYPE_RADIO: //64-127
minor_offset = 64;
minor_cnt = 64;
break;
case VFL_TYPE_VBI: // 224 - 257
minor_offset = 224;
minor_cnt = 32;
break;
default:
minor_offset = 128; // 128-191
minor_cnt = 64;
break;
}
vdev->cdev = cdev_alloc(); //注册字符设备驱动
vdev->cdev->ops = &v4l2_fops; /*回调vivi_fops,当vivi_fops里的ioctl函数调用的时候回调vivi_ioctl_ops*/
vdev->cdev->owner = owner;
ret = cdev_add(vdev->cdev, MKDEV(VIDEO_MAJOR, vdev->minor), 1);
4、注销对象
//注销的函数为video_unregister_device
void video_unregister_device(struct video_device *vdev)
{
/* Check if vdev was ever registered at all */
if (!vdev || !video_is_registered(vdev))
return;
mutex_lock(&videodev_lock);
/* This must be in a critical section to prevent a race with v4l2_open.
* Once this bit has been cleared video_get may never be called again.
*/
clear_bit(V4L2_FL_REGISTERED, &vdev->flags);
mutex_unlock(&videodev_lock);
device_unregister(&vdev->dev);
}
三、实现摄像头驱动的主要ioctl函数有这些
V4l2_ioctl_ops这个结构体里对这些主要的ioctl函数进行初始化
static const struct v4l2_ioctl_ops vivi_ioctl_ops = {
/* vidioc_querycap就是表明身份,你是谁?比如:我是一个摄像头设备*/
.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,
}
待续……