重头写一个v4l2的虚拟驱动_1

简介

  因为在qcom平台上和linux原生都是用的v4l2框架作为camera的驱动框架,所以本着学习记录的笔记,做了如下文档记录。
该文档是学习《卫东山老师视频教程第三期》的个人学习笔记,非常感谢老师的资料。该记录仅供学习交流,如有侵犯到大家利益,还望海涵,请联系博主删除。

注册video_device

代码演示

  首先是驱动程序的入口、出口以及license,然后第一步就要分配,设置和注册一个video_device结构(基于内核版本:linux-3.13.1)。
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/pci.h>
#include <linux/random.h>
#include <linux/version.h>
#include <linux/mutex.h>
#include <linux/videodev2.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/kthread.h>
#include <linux/highmem.h>
#include <linux/freezer.h>
#include <media/videobuf-vmalloc.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>

#define DRIVER_NAME "myvivi_video"

static struct video_device *myvivi_device;
struct v4l2_device	myvivi_v4l2_dev;

static void myvivi_release(struct video_device *vdev)
{
}

static const struct v4l2_file_operations myvivi_fops = {
	.owner      = THIS_MODULE,
};

static int myvivi_init(void)
{
    int error;

    /* 1. 分配一个video_device结构体 */
    myvivi_device = video_device_alloc();

    /* 2. 设置 */
    strlcpy(myvivi_v4l2_dev.name, DRIVER_NAME, sizeof(myvivi_v4l2_dev.name));
    error = v4l2_device_register(NULL, &myvivi_v4l2_dev);
    myvivi_device->v4l2_dev = &myvivi_v4l2_dev;
    myvivi_device->release = myvivi_release;
	myvivi_device->fops    = &myvivi_fops;
    /* 2.1 */

    error = video_register_device(myvivi_device, VFL_TYPE_GRABBER, -1);

    return error;
}

static void myvivi_exit(void)
{
    video_unregister_device(myvivi_device);
    video_device_release(myvivi_device);
}

module_init(myvivi_init);
module_exit(myvivi_exit);

MODULE_LICENSE("GPL");

代码讲解

  首先是分配一个video_device_alloc来分配一个v4l2_device结构,接着填充设置这个结构的v4l2_dev参数、操作函数fops以及释放函数release,
在代码中为了简洁,所有fops和release函数都设置为了空函数。
  接着注册了该v4l2设备,注意这里的video_register_device函数,第一个参数表示我们需要注册的video_device 结构,VFL_TYPE_GRABBER表示是图像采集设备
,最后的-1参数表示,注册时候使用第一个空闲的设备名,如果/dev/video0已经被使用了,那么这次注册就是作为/dev/video1.如果参数不是-1,比如是5,那么注
册的设备文件为:/dev/video5.
  最后是驱动的出口,对应的操作也就是注销掉之前注册的v4l2设备,然后销毁该设备结构。
  如此,一个最简单的v4l2设备就完成了。

注意

  我使用的内核版本是linux-3.13.1,在该版本中,必须填充了v4l2_device结构的v4l2_dev参数、fops操作函数以及释放函数release之后,
调用video_register_device函数进行注册才能注册成功,否则驱动程序,会报出空指针或者参数之类的错误。
  同时在加载该驱动之前,需要保证内核中依赖的v4l2驱动模块已经是加载好了的,v4l2一般对应有如下几个模块:
videodev.ko videobuf-vmalloc.ko videobuf-core.ko  v4l2-common.ko
  驱动依赖哪些模块,可以使用sudo modinfo myvivi.ko 来查看到。

效果演示

  最后insmod加载该驱动模块之后,会生成video*的设备节点,效果如下:

  接着安装工具:sudo apt-get install xawtv
  然后使用该工具后会提示说:
Failed to query video capabilities: Inappropriate ioctl for device
libv4l2: error getting capabilities: Inappropriate ioctl for device
Failed to query video capabilities: Inappropriate ioctl for device
libv4l2: error getting capabilities: Inappropriate ioctl for device
vid-open-auto: failed to open a capture device at /dev/video0
vid-open: could not find a suitable videodev
no video grabber device available

表示为video设备

  在前面使用xawtv的时候,提示说没有找到适合的video设备,所以接着这一步就是将我们之前写的v4l2注册驱动表示为video设备。

代码讲解

添加代码如下:
...............
static const struct v4l2_file_operations myvivi_fops = {
    .owner      = THIS_MODULE,
    .ioctl      = video_ioctl2, /* V4L2 ioctl handler */
};

static int myvivi_vidioc_querycap(struct file *file, void  *priv,
        struct v4l2_capability *cap){

    strcpy(cap->driver, "myvivi");
    strcpy(cap->card, "myvivi");
    cap->version = 0x0001;
    cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;   /*表明是个视频捕捉设备和通过ioctl来访问*/
    return 0;
}

static const struct v4l2_ioctl_ops myvivi_ioctl_ops = {
    // 表示它是一个摄像头设备
    .vidioc_querycap      = myvivi_vidioc_querycap,
};                                                                                                                                   

static int myvivi_init(void)
{
    int error;

    /* 1. 分配一个video_device结构体 */
    myvivi_device = video_device_alloc();

    /* 2. 设置 */
    strlcpy(myvivi_v4l2_dev.name, DRIVER_NAME, sizeof(myvivi_v4l2_dev.name));
    error = v4l2_device_register(NULL, &myvivi_v4l2_dev);
    myvivi_device->v4l2_dev = &myvivi_v4l2_dev;
    myvivi_device->release = myvivi_release;
    myvivi_device->fops    = &myvivi_fops;
    myvivi_device->ioctl_ops = &myvivi_ioctl_ops;
    ..............
}
..............
  新加入初始化了一个myvivi_ioctl_ops操作函数结构,在其中填充了函数myvivi_vidioc_querycap,在应用程序中,会调用该函数来确实该驱动设备类型。
在该函数中V4L2_CAP_VIDEO_CAPTURE表示该驱动设备是一个视频获取设备,V4L2_CAP_STREAMING表示是通过ioctl来进行获取数据。
  同时在调用myvivi_vidioc_querycap函数的时候还需要调用到myvivi_fops中的ioctl函数,所以还需要填充该ioctl。这里使用v4l2提供的ioctl标准操作
函数video_ioctl2。

效果演示

  接着继续用xawtv工具来测试该驱动。效果如下:

  这里标明xawtv找到了/dev/video0是一个video设备。

设置支持的video格式

  前面一步中,xawtv已经识别到了该设备驱动是video设备,接着这一步在驱动中设置该驱动支持的video格式。

代码讲解

static struct v4l2_format myvivi_format;
/* 列举支持哪种格式 */
static int myvivi_vidioc_enum_fmt_vid_cap(struct file *file, void  *priv,
		struct v4l2_fmtdesc *f){

	if (f->index >= 1) /*我们这里设置为只支持一种格式,所以设置f->index如果大于等于1,就返回错误*/
		return -EINVAL;

	strcpy(f->description, "4:2:2, packed, YUYV");
	f->pixelformat = V4L2_PIX_FMT_YUYV;
	return 0;
}

/* 返回当前所使用的格式 */
static int myvivi_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
		struct v4l2_format *f){
	memcpy(f, &myvivi_format, sizeof(myvivi_format));
	return (0);
}

/* 测试驱动程序是否支持某种格式 */
static int myvivi_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
		struct v4l2_format *f){
	unsigned int maxw, maxh;

	if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV)
		return -EINVAL;

	/*该设备支持的最大宽度和高度*/
	maxw  = 1024;
	maxh  = 768;

	/* 调整format的最大最小width, height,和对齐方式
	 * 计算bytesperline, sizeimage
	 */
	v4l_bound_align_image(&f->fmt.pix.width, 48, maxw, 2,
			&f->fmt.pix.height, 32, maxh, 0, 0);

	f->fmt.pix.bytesperline =
		(f->fmt.pix.width * 16) >> 3;  /*设置颜色深度为16*/
	f->fmt.pix.sizeimage =
		f->fmt.pix.height * f->fmt.pix.bytesperline; /*设置图像size*/

	return 0;
}

static int myvivi_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
		struct v4l2_format *f){
	int ret = myvivi_vidioc_try_fmt_vid_cap(file, NULL, f);
	if (ret < 0)
		return ret;

	memcpy(&myvivi_format, f, sizeof(myvivi_format));

	return ret;
}

static const struct v4l2_ioctl_ops myvivi_ioctl_ops = {
	// 表示它是一个摄像头设备
	.vidioc_querycap      = myvivi_vidioc_querycap,

	/* 用于列举、获得、测试、设置摄像头的数据的格式 */
	.vidioc_enum_fmt_vid_cap  = myvivi_vidioc_enum_fmt_vid_cap,
	.vidioc_g_fmt_vid_cap     = myvivi_vidioc_g_fmt_vid_cap,
	.vidioc_try_fmt_vid_cap   = myvivi_vidioc_try_fmt_vid_cap,
	.vidioc_s_fmt_vid_cap     = myvivi_vidioc_s_fmt_vid_cap,

};
  如上所示,一共增加了4个函数。
  myvivi_vidioc_enum_fmt_vid_cap:用来设置支持的格式,这里设置为只支持一种格式 V4L2_PIX_FMT_YUYV。
  myvivi_vidioc_g_fmt_vid_cap:用来获得支持的video格式信息,格式信息保存在结构myvivi_format中。
  myvivi_vidioc_try_fmt_vid_cap:用来测试传入的某个video格式是否被该设备驱动支持,同时设置了该格式下的video的width、height和image size等信息。
  myvivi_vidioc_s_fmt_vid_cap:首先用myvivi_vidioc_try_fmt_vid_cap,判断传入的某种格式是否支持,如果支持就保存到myvivi_format结构中。

效果演示

  同样是用xawtv工具来演示下效果,不过这里看不出太明显。

  这里可以看到,xawtv有读取到了一些格式相关信息。
时间: 2024-10-07 06:00:15

重头写一个v4l2的虚拟驱动_1的相关文章

重头写一个v4l2的虚拟驱动_2

简介 因为在qcom平台上和linux原生都是用的v4l2框架作为camera的驱动框架,所以本着学习记录的笔记,做了如下文档记录. 该文档是学习<卫东山老师视频教程第三期>的个人学习笔记,非常感谢老师的资料.该记录仅供学习交流,如有侵犯到大家利益,还望海涵,请联系博主删除. buffer队列操作 首先是填充了队列相关的4个函数: static int myvivi_vidioc_reqbufs(struct file *file, void *priv, struct v4l2_reques

重头写一个v4l2的虚拟驱动_3

简介 因为在qcom平台上和linux原生都是用的v4l2框架作为camera的驱动框架,所以本着学习记录的笔记,做了如下文档记录. 该文档是学习<卫东山老师视频教程第三期>的个人学习笔记,非常感谢老师的资料.该记录仅供学习交流,如有侵犯到大家利益,还望海涵,请联系博主删除. poll/select 在前一篇中我们说到,应用程序和驱动通过select/poll机制来进行交互,从而做到不停地qbuffer和dequebuffer. 所以驱动中还需要实现它的poll函数: static unsig

v4l2虚拟驱动的应用测试程序讲解

简介 在前面我们已经完成了myvivi这个虚拟的v4l2摄像头驱动程序的编写.这里继续编写一个该驱动的应用测试程序来加深一下该驱动的工作原理. 具体代码 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <assert.h> #include <getopt.h> #include <fcntl.h> #include <unistd.h>

linux设备驱动第三篇:写一个简单的字符设备驱动

在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存. 下面就开始学习如何写一个简单的字符设备驱动.首先我们来分解一下字符设备驱动都有那些结构或者方法组成,也就是说实现一个可以使用的字符设备驱动我们必须做些什么工作. 1.主设备号和次设备号 对于字符设备的访问是通过文件系统中的设备名称进行的.他们通常位于/dev目录下.如下: [plain] vie

用WinForm写一个虚拟WiFi助手玩玩(附源码)

这早不是什么新鲜的东西了,同类软件已经有很多,但不是收费就是有广告,在学校的时候就想自已写一个了,但那时候啥也没学,对C的掌握程度也就是定义几个变量,打印一行“Hello,world”这样,为了写这破玩意,还特意跑图书馆看了几天的VB,然后网上拷了些代码,用调cmd的方式实现了基本功能.到现在也做了1年的.Net码农了,打算重新撸一个,windows应该会开放这方面的api,估计也简单. 在开始之前我搜了一下,貌似没有用.Net写的,应该早有人写过只是没发出来吧.唯一找到的就是在codeplex

linux设备驱动第三篇:如何写一个简单的字符设备驱动?

在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存. 下面就开始学习如何写一个简单的字符设备驱动.首先我们来分解一下字符设备驱动都有那些结构或者方法组成,也就是说实现一个可以使用的字符设备驱动我们必须做些什么工作. 1.主设备号和次设备号 对于字符设备的访问是通过文件系统中的设备名称进行的.他们通常位于/dev目录下.如下: [email prot

【转】写一个块设备驱动(1)

原文地址:写一个块设备驱动 一直对块设备驱动似懂非懂,这次发现了这个介绍块设备驱动很好的系列,打算把这套东西弄懂,一起跟着作者学习一遍 作者写这个系列的初衷如下,我觉得很好,网上搜到的大部分都是介绍一些玄乎的东西,看完似懂非懂的~ 在这套教程中,我们通过写一个建立在内存中的块设备驱动,来学习linux内核和相关设备驱动知识. 选择写块设备驱动的原因是: 1:容易上手 2:可以牵连出更多的内核知识 3:像本文这样的块设备驱动教程不多,所以需要一个 概述 在开始赵磊的教程之前,先对块IO子系统进行一

写一个块设备驱动5,6

http://blogold.chinaunix.net/u3/108239/showart.php?id=2144628 第5章 +---------------------------------------------------+|                 写一个块设备驱动                  |+---------------------------------------------------+| 作者:赵磊                          

写一个块设备驱动13,14

http://blogold.chinaunix.net/u3/108239/showart.php?id=2144636 第13章 +---------------------------------------------------+|                 写一个块设备驱动                  |+---------------------------------------------------+| 作者:赵磊