CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2018/03/08/Linux摄像头驱动1——vivid/#more

Linux摄像头驱动学习第一篇,对虚拟视频驱动Virtual Video Driver(vivid)进行测试、分析、编写。

V4L2(Video for Linux two)是Linux内核中关于视频设备的内核驱动框架,为上层的访问底层的视频设备提供了统一的接口。

V4L2可以支持多种设备,它可以有以下几种接口:

  1. Video capture interface(视频采集接口):从摄像头等设备上获取视频数据,是V4L2设计最初功能;
  1. Video output interface(视频输出接口):驱动计算机的外围视频、图像显示设备;
  2. Video overlay interface(直接传输视频接口):把从视频采集设备采集过来的信号直接输出到输出设备之上,而不用经过CPU;
  3. Video output overlay device(视频输出覆盖设备):也被称为OSD(On-Screen Display),即在显示画面上叠加一层显示,比如菜单设置界面;
  4. VBI interface(视频间隔消隐信号接口):提供对VBI(Vertical Blanking Interval)数据的控制,它可以使应用可以访问传输消隐期的视频信号;
  5. Radio interface(收音机接口):处理从AM或FM高频头设备接收来的音频流;

1.V4L2框架分析

2.测试vivid

这里目的先加载vivid驱动,然后运行应用程序调用vivid驱动,初步体验效果。

2.1加载驱动

先在Ubuntu16.04上输入uname -a,可以得到当前Ubuntu内核版本号:

Linux ubuntu 4.4.0-116-generic #140-Ubuntu SMP Mon Feb 12 21:23:04 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

当前内核版本是4.4.0-116-generic,然后去Linux内核官网下载对应的内核,提取出其中的linux-4.13.9/drivers/media/文件夹。

修改media/platform/vivid/下的Makefile:

KERN_DIR = /usr/src/linux-headers-4.4.0-116-genericvivid-objs := vivid-core.o vivid-ctrls.o vivid-vid-common.o vivid-vbi-gen.o \vivid-vid-cap.o vivid-vid-out.o vivid-kthread-cap.o vivid-kthread-out.o \vivid-radio-rx.o vivid-radio-tx.o vivid-radio-common.o \vivid-rds-gen.o vivid-sdr-cap.o vivid-vbi-cap.o vivid-vbi-out.o \vivid-osd.o vivid-tpg.o vivid-tpg-colors.oall:make -C $(KERN_DIR) M=`pwd` modules clean:make -C $(KERN_DIR) M=`pwd` modules cleanrm -rf modules.orderobj-$(CONFIG_VIDEO_VIVID) += vivid.o

然后执行make编译,获得vivid.ko
此时加载模块sudo insmod vivid.ko,发现报错如下:

insmod: ERROR: could not insert module vivid.ko: Unknown symbol in module

原因是模块中的一些依赖函数的模块,没有加载,通过dmesg命令,可以看到很多函数:

[  488.786285] vivid: Unknown symbol vb2_queue_init (err 0)
[  488.786295] vivid: Unknown symbol v4l2_ctrl_poll (err 0)
[  488.786304] vivid: Unknown symbol v4l2_enum_dv_timings_cap (err 0)
[  488.786314] vivid: Unknown symbol video_ioctl2 (err 0)
[  488.786364] vivid: Unknown symbol v4l2_get_timestamp (err 0)
[  488.786389] vivid: Unknown symbol v4l2_device_put (err 0)
[  488.786418] vivid: Unknown symbol vb2_ioctl_streamoff (err 0)
…………

需要先加载这些函数所在的模块才行。
这里有两个方法:
一是找到函数对应的文件,修改Makefile,编译出来,先加载。
二是找到函数对应的文件,其实模块都已经编译好了,路径在/lib/modules/4.4.0-116-generic/kernel/drivers/media/v4l2-core/里面,直接加载即可。

这两种方式都需要慢慢找对应的文件,比较麻烦,直接:

sudo modprobe vivid     //安装自带vivid及依赖
sudo rmmod vivid        //卸载自带的vivid
sudo insmod ./vivid.ko  //安装自己编译的vivid.ko

这里先使用modprobr加载vivid,会将其依赖一并加载,然后再卸载vivid,最后加载上我们编译的vivid.ko

这里为什么使用自己编译的vivid.ko,而不使用自带的?
因为后面修改vivid源码后,重新加载修改后的驱动,才知道修改后的效果。

2.2应用程序

Linux摄像头测试软件webcam、spcaview、luvcview、xawtv等,经测试,luvcviewxawtv比较靠谱。

  • luvcview
luvcview -h             //帮助信息
luvcview -d /dev/videoX //指定设备
luvcview -L             //查询当前摄像头所支持的所有数据格式以及分辨率信息
luvcview                //运行摄像头
  • xawtv
xawtv -h                //帮助信息
xawtv -c /dev/videoX    //指定设备
xawtv -noalsa           //关闭声卡启动
xawtv                   //运行摄像头

这里加载vivid驱动后,运行xawtv效果如下:

3.分析vivid

第一次接触V4L2,直接对内核提供的Virtual Video Driver(vivid)进行分析,只要熟悉了vivid,后续再对摄像头再进行分析,就会轻松很多。

vivid是内核提供的一个虚拟机的视频设备驱动,内核提供的vivid源码在linux-4.13.9/drivers/media/platform/vivid/

3.1 初始化、注册分析

vivid_init()里分别注册了vivid_pdevvivid_pdrv,注册后,由于两者name一致,则会调用probe()。在probe()里面主要进行初始化、注册等相关流程。

可以看到,在probe()里,会调用vivid_create_instance(),让后在里面先分配一个video_device,然后设置video_device,包括操作函数opsioctl操作函数,设备等。
然后对ctrl属性进行详细的设置,最后注册video_device,和进行常规的字符设备注册。

因此,写摄像头驱动程序的流程如下:

  1. 分配video_device:video_device_alloc()kzalloc()
  1. 设置video_device:.fops.ioctl_opsdev
  2. 注册video_device: video_register_device()

3.2 操作函数分析

再来看看操作函数是如何调用的:

当应用层open()/read()/write()操作/dev/videox时,先找到v4l2_fops
然后调用v4l2_open/v4l2_read/v4l2_write(drivers/media/v4l2-core/v4l2-dev.c),
再通过video_devdata根据次设备号从数组中得到video_device,再找到vivid_fops里对应的操作函数。

ioctl的前面流程类似,后面通过video_usercopy()获取传入的ioctl类型,找到对应ioctl_ops,调用不同的ioctl

3.3 ioctl_ops分析

摄像头驱动有众多的ioctl,这些ioctl实现了对设备的控制:

static const struct v4l2_ioctl_ops vivid_ioctl_ops = {/* 表示它是一个摄像头设备 */.vidioc_querycap     = vidioc_querycap, /* 摄像头数据格式的操作 */.vidioc_enum_fmt_vid_cap    = vidioc_enum_fmt_vid,     //列举格式.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,    //设置格式/* 支持multi-planar */.vidioc_enum_fmt_vid_cap_mplane = vidioc_enum_fmt_vid_mplane,.vidioc_g_fmt_vid_cap_mplane    = vidioc_g_fmt_vid_cap_mplane,.vidioc_try_fmt_vid_cap_mplane   = vidioc_try_fmt_vid_cap_mplane,.vidioc_s_fmt_vid_cap_mplane   = vidioc_s_fmt_vid_cap_mplane,/* 数据输出操作 */.vidioc_enum_fmt_vid_out = vidioc_enum_fmt_vid,     //枚举输出格式.vidioc_g_fmt_vid_out       = vidioc_g_fmt_vid_out,    //获取输出格式.vidioc_try_fmt_vid_out     = vidioc_try_fmt_vid_out,  //测试输出格式.vidioc_s_fmt_vid_out       = vidioc_s_fmt_vid_out,    //设置输出格式/* 支持multi-planar */.vidioc_enum_fmt_vid_out_mplane = vidioc_enum_fmt_vid_mplane,.vidioc_g_fmt_vid_out_mplane  = vidioc_g_fmt_vid_out_mplane,.vidioc_try_fmt_vid_out_mplane   = vidioc_try_fmt_vid_out_mplane,.vidioc_s_fmt_vid_out_mplane   = vidioc_s_fmt_vid_out_mplane,.vidioc_g_selection      = vidioc_g_selection,          //获取选择矩形.vidioc_s_selection     = vidioc_s_selection,          //设置选择矩形.vidioc_cropcap         = vidioc_cropcap,              //查询裁剪限制.vidioc_g_fmt_vbi_cap       = vidioc_g_fmt_vbi_cap,        //获取指向原始数据VBI的指针.vidioc_try_fmt_vbi_cap     = vidioc_g_fmt_vbi_cap,.vidioc_s_fmt_vbi_cap       = vidioc_s_fmt_vbi_cap,.vidioc_g_fmt_sliced_vbi_cap    = vidioc_g_fmt_sliced_vbi_cap,.vidioc_try_fmt_sliced_vbi_cap  = vidioc_try_fmt_sliced_vbi_cap,.vidioc_s_fmt_sliced_vbi_cap    = vidioc_s_fmt_sliced_vbi_cap,.vidioc_g_sliced_vbi_cap = vidioc_g_sliced_vbi_cap,.vidioc_g_fmt_vbi_out        = vidioc_g_fmt_vbi_out,.vidioc_try_fmt_vbi_out     = vidioc_g_fmt_vbi_out,.vidioc_s_fmt_vbi_out       = vidioc_s_fmt_vbi_out,.vidioc_g_fmt_sliced_vbi_out    = vidioc_g_fmt_sliced_vbi_out,.vidioc_try_fmt_sliced_vbi_out  = vidioc_try_fmt_sliced_vbi_out,.vidioc_s_fmt_sliced_vbi_out    = vidioc_s_fmt_sliced_vbi_out,.vidioc_enum_fmt_sdr_cap = vidioc_enum_fmt_sdr_cap,.vidioc_g_fmt_sdr_cap        = vidioc_g_fmt_sdr_cap,.vidioc_try_fmt_sdr_cap     = vidioc_try_fmt_sdr_cap,.vidioc_s_fmt_sdr_cap     = vidioc_s_fmt_sdr_cap,.vidioc_overlay         = vidioc_overlay,.vidioc_enum_framesizes       = vidioc_enum_framesizes,.vidioc_enum_frameintervals   = vidioc_enum_frameintervals,.vidioc_g_parm            = vidioc_g_parm,.vidioc_s_parm         = vidioc_s_parm,.vidioc_enum_fmt_vid_overlay   = vidioc_enum_fmt_vid_overlay,.vidioc_g_fmt_vid_overlay    = vidioc_g_fmt_vid_overlay,.vidioc_try_fmt_vid_overlay = vidioc_try_fmt_vid_overlay,.vidioc_s_fmt_vid_overlay = vidioc_s_fmt_vid_overlay,.vidioc_g_fmt_vid_out_overlay   = vidioc_g_fmt_vid_out_overlay,.vidioc_try_fmt_vid_out_overlay = vidioc_try_fmt_vid_out_overlay,.vidioc_s_fmt_vid_out_overlay = vidioc_s_fmt_vid_out_overlay,.vidioc_g_fbuf          = vidioc_g_fbuf,.vidioc_s_fbuf         = vidioc_s_fbuf,/* 缓冲区操作 */.vidioc_reqbufs         = vb2_ioctl_reqbufs,          //申请.vidioc_create_bufs      = vb2_ioctl_create_bufs,      //创建.vidioc_prepare_buf      = vb2_ioctl_prepare_buf,      //准备.vidioc_querybuf     = vb2_ioctl_querybuf,         //查询.vidioc_qbuf         = vb2_ioctl_qbuf,             //放入.vidioc_dqbuf            = vb2_ioctl_dqbuf,            //取出.vidioc_expbuf           = vb2_ioctl_expbuf,           //导出.vidioc_streamon     = vb2_ioctl_streamon,         //启动.vidioc_streamoff        = vb2_ioctl_streamoff,        //停止/* 输入源操作 */                                     .vidioc_enum_input       = vidioc_enum_input,          //枚举输入源.vidioc_g_input           = vidioc_g_input,             //获取输入源.vidioc_s_input           = vidioc_s_input,             //设置输入源.vidioc_s_audio           = vidioc_s_audio,             //设置音频.vidioc_g_audio            = vidioc_g_audio,             //获取音频.vidioc_enumaudio      = vidioc_enumaudio,           //枚举音频.vidioc_s_frequency        = vidioc_s_frequency,         //设置频率.vidioc_g_frequency        = vidioc_g_frequency,             //获取输入源.vidioc_s_tuner           = vidioc_s_tuner,             //设置调谐器.vidioc_g_tuner           = vidioc_g_tuner,             //获取调谐器.vidioc_s_modulator       = vidioc_s_modulator,         //设置调制器.vidioc_g_modulator       = vidioc_g_modulator,             //获取调制器.vidioc_s_hw_freq_seek        = vidioc_s_hw_freq_seek,           //硬件频率搜索.vidioc_enum_freq_bands     = vidioc_enum_freq_bands,          //枚举调谐器或调制器支持的频段/* 输出端操作 */.vidioc_enum_output      = vidioc_enum_output,          //枚举视频输出端.vidioc_g_output       = vidioc_g_output,             //获取视频输出.vidioc_s_output        = vidioc_s_output,             //设置视频输出.vidioc_s_audout        = vidioc_s_audout,             //设置音频输出.vidioc_g_audout        = vidioc_g_audout,             //获取音频输出.vidioc_enumaudout      = vidioc_enumaudout,           //枚举视频输出端/* 制式操作 */.vidioc_querystd     = vidioc_querystd,             //查询制式.vidioc_g_std         = vidioc_g_std,                //获取制式.vidioc_s_std         = vidioc_s_std,                //设置制式.vidioc_s_dv_timings      = vidioc_s_dv_timings,         //设置DV时序.vidioc_g_dv_timings        = vidioc_g_dv_timings,         //获取DV时序.vidioc_query_dv_timings    = vidioc_query_dv_timings,     //查询DV时序.vidioc_enum_dv_timings     = vidioc_enum_dv_timings,      //枚举DV时序.vidioc_dv_timings_cap      = vidioc_dv_timings_cap,       //查询DV应用程序功能.vidioc_g_edid          = vidioc_g_edid,               //获取EDID数据块.vidioc_s_edid           = vidioc_s_edid,               //设置EDID数据块/* 调试操作 */.vidioc_log_status     = vidioc_log_status,           //输出设备状态到内核日志.vidioc_subscribe_event        = vidioc_subscribe_event,      //订阅V4L2事件.vidioc_unsubscribe_event = v4l2_event_unsubscribe,      //取消订阅V4L2事件
};

提取出11个必须的ioctl:

 /* 表示它是一个摄像头设备 */.vidioc_querycap       = vidioc_querycap, /* 摄像头数据格式的操作 */.vidioc_enum_fmt_vid_cap    = vidioc_enum_fmt_vid,     //列举格式.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            = vb2_ioctl_reqbufs,          //申请.vidioc_querybuf     = vb2_ioctl_querybuf,         //查询.vidioc_qbuf         = vb2_ioctl_qbuf,             //放入.vidioc_dqbuf            = vb2_ioctl_dqbuf,            //取出.vidioc_streamon     = vb2_ioctl_streamon,         //启动.vidioc_streamoff        = vb2_ioctl_streamoff,        //停止

3.4 数据获取过程分析

1.请求分配缓冲区

app:ioctl(fd, VIDIOC_REQBUFS, xx)videobuf_reqbufs(vdev->queue, p->memory, &p->count);//没分配真正buf

2.查询映射缓冲区

app:ioctl(fd, VIDIOC_QUERYBUF, xx)videobuf_querybuf //获得buf的数据格式、大小、每一行长度、高度v4l2_mmapvb2_fop_mmapvideobuf_mmap_mapper__videobuf_mmap_mappermem->vaddr = vmalloc_user(pages); //这里分配buf

3.把缓冲区放入队列

app:ioctl(fd, VIDIOC_QBUF, xx)videobuf_qbufq->ops->buf_prepare(q, buf, field);      //调用驱动程序提供的函数做预处理list_add_tail(&buf->stream, &q->stream); //把缓冲区放入队列的尾部q->ops->buf_queue(q, buf);               //用驱动程序提供的"入队列函数"

**4.启动摄像头 **

app:ioctl(fd, VIDIOC_STREAMON, xx)videobuf_streamon   q->streaming = 1;

5.用select查询是否有数据

 v4l2_pollvdev->fops->poll();vivi_poll videobuf_poll_streambuf = list_entry();  //从队列的头部获得bufpoll_wait();         //如果没有数据则休眠vivid_thread_vid_cap                 //内核进程唤醒vivid_thread_vid_cap_tickvivid_fillbuff              //构造数据vb2_buffer_done   wake_up();              //唤醒进程

6.有数据后,从队列取出缓冲区

app:ioctl(fd, VIDIOC_DQBUF, xx)   //使用VIDIOC_DQBUF获取buf状态videobuf_dqbufstream_next_buffer  //在队列里获得有数据的缓冲区list_del            //把它从队列中删掉videobuf_status     //把这个缓冲区的状态返回给APP

7.读取对应地址缓冲区
应用程序根据VIDIOC_DQBUF所得到缓冲区状态,知道是哪一个缓冲区有数据,就去读对应的地址(该地址来自前面的mmap)

调试技巧:

1.得到xawtv进行了哪些系统调用:
sudo strace -o xawtv.log xawtv
2.在串口终端下,修改打印等级:
sudo echo "8 4 1 7" >/proc/sys/kernel/printk
3.当无串口,即前面方法无效时,在SSH登陆时:
tail -f /var/log/kern.log &
可实现内核打印增量显示。

4.编写vivid

根据前面的分析,简单记录下虚拟视频驱动的编写流程:

1.注册平台设备和驱动;
2.probe()函数:
  a.分配video_device
  b.设置video_device,包括:releasefopsioctl_opsv4l2_dev
  c.注册设置video_device
  d.其它:定义/初始化自旋锁/定时器;
3.填充操作函数v4l2_file_operations:
  a.open():初始buf化队列和设置定时器;
  b.close():删除定时器和释放buf队列;
  c.mmap():调用videobuf_mmap_mapper开辟虚拟内存;
  d.poll():调用videobuf_poll_stream实现poll机制非阻塞访问;
4.填充操作函数v4l2_ioctl_ops
  前面介绍的11个必须ioctl,几乎都是调用内核提供的API;
5.填充操作函数videobuf_queue_ops
  对buf进行一些操作;
6.填充数据:
  利用定时器,不断产生数据并唤醒进程,实现获取到图像采集数据;

{% codeblock lang:c [my_vivid.c] https://github.com/hceng/learn/blob/master/tiny4412/07_vivid/my_vivid/my_vivid.c %}
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/font.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/videodev2.h>
#include <linux/v4l2-dv-timings.h>
#include <media/videobuf2-vmalloc.h>
#include <media/videobuf2-dma-contig.h>
#include <media/v4l2-dv-timings.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-event.h>
#include <media/v4l2-device.h>
#include <media/videobuf-core.h>
#include <media/videobuf-vmalloc.h>

#include “fillbuf.c”

/* 队列操作a: 定义自旋锁、定时器、buf队列 */
static spinlock_t my_vivid_queue_slock;
static struct timer_list my_vivid_timer;
static struct videobuf_queue my_vivid_vb_vidqueue;
static struct list_head my_vivid_vb_local_queue;

static void my_vivid_timer_function(unsigned long data)
{
struct videobuf_buffer *vb;
void *vbuf;
struct timeval ts;

printk("enter %s\n", __func__);/* 1. 构造数据: 从队列头部取出第1个videobuf, 填充数据 *//* 1.1 从本地队列取出第1个videobuf */
if (list_empty(&my_vivid_vb_local_queue))
{goto out;
}vb = list_entry(my_vivid_vb_local_queue.next,struct videobuf_buffer, queue);/* Nobody is waiting on this buffer, return */
if (!waitqueue_active(&vb->done))goto out;/* 1.2 填充数据 */
vbuf = videobuf_to_vmalloc(vb);
//memset(vbuf, 0xFF, vb->size);my_vivid_fillbuff(vb);vb->field_count++;
do_gettimeofday(&ts);
vb->ts = ts;
vb->state = VIDEOBUF_DONE;/* 1.3 把videobuf从本地队列中删除 */
list_del(&vb->queue);/* 2. 唤醒进程: 唤醒videobuf->done上的进程 */
wake_up(&vb->done);

out:
/* 3. 修改timer的超时时间 : 30fps, 1秒里有30帧数据
* 每1/30 秒产生一帧数据
*/
mod_timer(&my_vivid_timer, jiffies + HZ / 30);
}

/* 参考documentations/video4linux/v4l2-framework.txt:
drivers\media\video\videobuf-core.c
ops->buf_setup - calculates the size of the video buffers and avoid they to waste more than some maximum limit of RAM;
ops->buf_prepare - fills the video buffer structs and calls videobuf_iolock() to alloc and prepare mmaped memory;
ops->buf_queue - advices the driver that another buffer were requested (by read() or by QBUF);
ops->buf_release - frees any buffer that were allocated.
*/

/* videobuf operations */
//APP调用ioctl VIDIOC_REQBUFS时会导致此函数被调用,它重新调整count和size
static int my_vivid_buffer_setup(struct videobuf_queue *vq, unsigned int *count, unsigned int *size)
{
printk(“enter %s\n”, func);

*size = my_vivid_format.fmt.pix.sizeimage;if (0 == *count)*count = 32;return 0;

}

//APP调用ioctlVIDIOC_QBUF时导致此函数被调用,它会填充video_buffer结构体并调用videobuf_iolock来分配内存
static int my_vivid_buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb,
enum v4l2_field field)
{
printk(“enter %s\n”, func);

/* 1. 设置videobuf */
vb->size         = my_vivid_format.fmt.pix.sizeimage;
vb->bytesperline = my_vivid_format.fmt.pix.bytesperline;
vb->width        = my_vivid_format.fmt.pix.width;
vb->height       = my_vivid_format.fmt.pix.height;
vb->field        = field;/* 2. 做些准备工作 */
my_vivid_precalculate_bars(0);/* 3. 设置状态 */
vb->state = VIDEOBUF_PREPARED;return 0;

}

/* APP调用ioctl VIDIOC_QBUF时:

    1. 先调用buf_prepare进行一些准备工作
    1. 把buf放入stream队列
    1. 调用buf_queue(起通知、记录作用)
      */
      static void my_vivid_buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb)
      {
      printk(“enter %s\n”, func);

    vb->state = VIDEOBUF_QUEUED;

    /* 把videobuf放入本地一个队列尾部

    • 定时器处理函数就可以从本地队列取出videobuf
      */
      list_add_tail(&vb->queue, &my_vivid_vb_local_queue);
      }

/* APP不再使用队列时, 用它来释放内存 */
static void my_vivid_buffer_release(struct videobuf_queue *vq,
struct videobuf_buffer *vb)
{
printk(“enter %s\n”, func);

videobuf_vmalloc_free(vb);vb->state = VIDEOBUF_NEEDS_INIT;

}

static struct videobuf_queue_ops my_vivid_video_qops =
{
.buf_setup = my_vivid_buffer_setup, /* 计算大小以免浪费 */
.buf_prepare = my_vivid_buffer_prepare,
.buf_queue = my_vivid_buffer_queue,
.buf_release = my_vivid_buffer_release,
};

/* v4l2_file_operations */
static int my_vivid_open(struct file *file)
{
printk(“enter %s\n”, func);

//队列操作c:初始化
videobuf_queue_vmalloc_init(&my_vivid_vb_vidqueue, &my_vivid_video_qops,NULL, &my_vivid_queue_slock, V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_INTERLACED,sizeof(struct videobuf_buffer), NULL, NULL); /* 倒数第3个参数是buffer的头部大小 */my_vivid_timer.expires = jiffies + 1;
add_timer(&my_vivid_timer);return 0;

}

static int my_vivid_close(struct file *file)
{
printk(“enter %s\n”, func);

del_timer(&my_vivid_timer);
videobuf_stop(&my_vivid_vb_vidqueue);
videobuf_mmap_free(&my_vivid_vb_vidqueue);return 0;

}

static int my_vivid_mmap(struct file *file, struct vm_area_struct *vma)
{
printk(“enter %s\n”, func);

return videobuf_mmap_mapper(&my_vivid_vb_vidqueue, vma);

}

static unsigned int my_vivid_poll(struct file *file, struct poll_table_struct *wait)
{
printk(“enter %s\n”, func);

return videobuf_poll_stream(file, &my_vivid_vb_vidqueue, wait);

}

/* v4l2_ioctl_ops */
static int my_vivid_vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *cap)
{
printk(“enter %s\n”, func);

strcpy(cap->driver, "my_vivid");
strcpy(cap->card, "my_vivid");
cap->version = 0x0001;cap->device_caps  = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_DEVICE_CAPS;
cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | V4L2_CAP_DEVICE_CAPS;return 0;

}

static int my_vivid_vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_fmtdesc *f)
{
printk(“enter %s\n”, func);

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

}

static int my_vivid_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
printk(“enter %s\n”, func);

memcpy(f, &my_vivid_format, sizeof(my_vivid_format));return 0;

}

static int my_vivid_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
unsigned int maxw, maxh;
enum v4l2_field field;

printk("enter %s\n", __func__);if (f->fmt.pix.pixelformat != V4L2_PIX_FMT_YUYV)return -EINVAL;field = f->fmt.pix.field;if (field == V4L2_FIELD_ANY)
{field = V4L2_FIELD_INTERLACED;
}
else if (V4L2_FIELD_INTERLACED != field)
{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;
f->fmt.pix.sizeimage =f->fmt.pix.height * f->fmt.pix.bytesperline;return 0;

}

static int my_vivid_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
struct v4l2_format *f)
{
int ret;

printk("enter %s\n", __func__);ret = my_vivid_vidioc_try_fmt_vid_cap(file, NULL, f);if (ret < 0)return ret;memcpy(&my_vivid_format, f, sizeof(my_vivid_format));return ret;

}

static int my_vivid_vidioc_reqbufs(struct file *file, void *priv,
struct v4l2_requestbuffers *p)
{
printk(“enter %s\n”, func);

return (videobuf_reqbufs(&my_vivid_vb_vidqueue, p));

}

static int my_vivid_vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
printk(“enter %s\n”, func);

return (videobuf_querybuf(&my_vivid_vb_vidqueue, p));

}

static int my_vivid_vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
printk(“enter %s\n”, func);

return (videobuf_qbuf(&my_vivid_vb_vidqueue, p));

}

static int my_vivid_vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p)
{
printk(“enter %s\n”, func);

return (videobuf_dqbuf(&my_vivid_vb_vidqueue, p, file->f_flags & O_NONBLOCK));

}

static int my_vivid_vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i)
{
printk(“enter %s\n”, func);

return videobuf_streamon(&my_vivid_vb_vidqueue);

}

static int my_vivid_vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i)
{
printk(“enter %s\n”, func);

videobuf_streamoff(&my_vivid_vb_vidqueue);return 0;

}

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

/* 用于列举、获得、测试、设置摄像头的数据的格式 */
.vidioc_enum_fmt_vid_cap  = my_vivid_vidioc_enum_fmt_vid_cap,
.vidioc_g_fmt_vid_cap     = my_vivid_vidioc_g_fmt_vid_cap,
.vidioc_try_fmt_vid_cap   = my_vivid_vidioc_try_fmt_vid_cap,
.vidioc_s_fmt_vid_cap     = my_vivid_vidioc_s_fmt_vid_cap,/* 缓冲区操作: 申请/查询/放入队列/取出队列 */
.vidioc_reqbufs       = my_vivid_vidioc_reqbufs,
.vidioc_querybuf      = my_vivid_vidioc_querybuf,
.vidioc_qbuf          = my_vivid_vidioc_qbuf,
.vidioc_dqbuf         = my_vivid_vidioc_dqbuf,// 启动/停止
.vidioc_streamon      = my_vivid_vidioc_streamon,
.vidioc_streamoff     = my_vivid_vidioc_streamoff,

};

static const struct v4l2_file_operations my_vivid_fops =
{
.owner = THIS_MODULE,
.open = my_vivid_open,
.release = my_vivid_close,
.mmap = my_vivid_mmap,
.unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
.poll = my_vivid_poll,
};

static struct video_device *my_vivid_dev;
static struct v4l2_device v4l2_dev;

static void my_vivid_dev_release(struct video_device *vdev)
{
printk(“enter %s\n”, func);
}

static int my_vivid_probe(struct platform_device *pdev)
{
int ret;
printk(“enter %s\n”, func);

/* 1.分配一个video_device结构体 */
my_vivid_dev = video_device_alloc();
if (NULL == my_vivid_dev)
{printk("Failed to alloc video device (%d)\n", ret);return -ENOMEM;
}/* 2.设置 */
my_vivid_dev->release   = my_vivid_dev_release;
my_vivid_dev->fops      = &my_vivid_fops;
my_vivid_dev->ioctl_ops = &my_vivid_ioctl_ops;
my_vivid_dev->v4l2_dev  = &v4l2_dev;//队列操作b:初始化自旋锁
spin_lock_init(&my_vivid_queue_slock);/* 3.注册 */
ret = video_register_device(my_vivid_dev, VFL_TYPE_GRABBER, -1);
if (ret)
{printk("Failed to register as video device (%d)\n", ret);goto err_register_dev;
}//用定时器产生数据并唤醒进程
init_timer(&my_vivid_timer);
my_vivid_timer.function  = my_vivid_timer_function;
INIT_LIST_HEAD(&my_vivid_vb_local_queue);return 0;

err_register_dev:
video_device_release(my_vivid_dev);
return -ENODEV;
}

static int my_vivid_remove(struct platform_device *pdev)
{
printk(“enter %s\n”, func);

v4l2_device_unregister(my_vivid_dev->v4l2_dev);
video_device_release(my_vivid_dev);return 0;

}

static void my_vivid_pdev_release(struct device *dev)
{
printk(“enter %s\n”, func);
}

static struct platform_device my_vivid_pdev =
{
.name = “my_vivid”,
.dev.release = my_vivid_pdev_release,
};

static struct platform_driver my_vivid_pdrv =
{
.probe = my_vivid_probe,
.remove = my_vivid_remove,
.driver = {
.name = “my_vivid”,
},
};

static int my_vivid_init(void)
{
int ret;

printk("enter %s\n", __func__);ret = platform_device_register(&my_vivid_pdev);
if (ret)return ret;ret = platform_driver_register(&my_vivid_pdrv);
if (ret)platform_device_unregister(&my_vivid_pdev);return ret;

}

static void my_vivid_exit(void)
{
printk(“enter %s\n”, func);

platform_driver_unregister(&my_vivid_pdrv);
platform_device_unregister(&my_vivid_pdev);

}

module_init(my_vivid_init);
module_exit(my_vivid_exit);

MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“hceng huangcheng.job@foxmail.com”);
MODULE_DESCRIPTION(“A Virtual Video Test Code For Learn.”);
MODULE_ALIAS(“My vivid”);
MODULE_VERSION(“V1.0”);
{% endcodeblock %}

5.测试效果

重新编译驱动,加载新驱动:

sudo modprobe vivid
sudo rmmod vivid
sudo insmod my_vivid.ko

实测还差两个驱动依赖:

sudo insmod /lib/modules/4.4.0-116-generic/kernel/drivers/media/v4l2-core/videobuf-core.ko
sudo insmod /lib/modules/4.4.0-116-generic/kernel/drivers/media/v4l2-core/videobuf-vmalloc.ko

运行xawtv

参考资料:
韦东山第三期项目视频_摄像头

Linux摄像头驱动1——vivid相关推荐

  1. Linux摄像头驱动1——vivid(转)

    转载自 hceng blog: https://hceng.cn/2018/03/08/Linux%E6%91%84%E5%83%8F%E5%A4%B4%E9%A9%B1%E5%8A%A81%E2%8 ...

  2. linux v4l2系统详解,Linux摄像头驱动学习之:(一)V4L2_框架分析

    这段时间开始搞安卓camera底层驱动了,把以前的的Linux视频驱动回顾一下,本篇主要概述一下vfl2(video for linux 2). 一. V4L2框架: video for linux ...

  3. linux cmos摄像头,Linux摄像头驱动4——CMOS摄像头

    Linux摄像头驱动学习第四篇,对CMOS摄像头进行学习,实现在Tiny4412上使用CMOS摄像头采集图像,在LCD上显示图像. 坚持到了Linux摄像头学习的最后一部分--CMOS摄像头. 写完本 ...

  4. linux摄像头V4L2 subdev,linux 摄像头驱动 详解linux 摄像头驱动编写

    想了解详解linux 摄像头驱动编写的相关内容吗,feixiaoxing在本文为您仔细讲解linux 摄像头驱动的相关知识和一些Code实例,欢迎阅读和指正,我们先划重点:linux,摄像头驱动,下面 ...

  5. linux摄像头教程,linux 摄像头驱动编写方法教程

    从某个角度来说,摄像头是各个厂家主打的应用功能.那么,linux是如何支持摄像头的,我们可以来看一下?本篇文章主要介绍了详解linux 摄像头驱动编写,小编觉得挺不错的,现在分享给大家,也给大家做个参 ...

  6. Linux摄像头驱动第一篇之虚拟摄像头驱动vivi.c

    本文学习自韦东山老师的摄像头驱动模块 目录 一 摄像头驱动程序学习切入点以及V4L2模型概览 二 简析虚拟视频驱动 VIVI.C 2.1 初始化.设置.注册过程 2.2 简析vivi.c的open,r ...

  7. linux驱动双摄像头,详解linux 摄像头驱动编写

    对于现代嵌入式设备,特别是手机来说,摄像头是很重要的一个设备.很多同学买手机,一看颜值,第二就看摄像头拍照如何.所以,从某个角度来说,摄像头是各个厂家主打的应用功能.那么,linux是如何支持摄像头的 ...

  8. 【Java常用工具类汇总 2,linux摄像头驱动架构

    System.out.println(Strings.padStart("zs", 2, '*'));//zs System.out.println(Strings.padStar ...

  9. 通过虚拟驱动vivi分析摄像头驱动

    Linux摄像头驱动学习之:(二)通过虚拟驱动vivi分析摄像头驱动 一.通过指令 "strace -o xawtv.log xawtv" 得到以下调用信息: // 1~7都是在v ...

最新文章

  1. redis java切片_jedis 单点配置
  2. 疯狂python讲义视频 百度云-疯狂Python讲义 PDF 含源码工具版
  3. 移动端banner css3(@keyframes )实现
  4. IDEA连接数据库自动生成实体类
  5. 转载的ActiveRecord Sql Server中文问题2
  6. 99%的程序员都在用Lombok,原理竟然这么简单?我也手撸了一个!
  7. 再说共识性算法Raft
  8. wince系统_汽车操作系统分类
  9. 获取虚拟账号列表失败啥意思_「图」Windows 10 Build 18963发布:可显GPU温度 支持重命名虚拟桌面...
  10. 用python海龟制图画花瓣_Python教程:使用Turtles画出带有花瓣的花
  11. Selenium Grid跨浏览器-兼容性测试
  12. 360 android 应用市场,360应用市场
  13. 网易考拉向欧洲市场投入220亿,能掀起多大波澜?
  14. 储油罐的变位识别与罐容表标定
  15. 克利夫顿优势识别器 Clifton Strengths
  16. python 换行打印
  17. ORA-01790: expression must have same datatype as corresponding expression
  18. 贪吃的九头龙(tyvj P1523)
  19. 使用大白菜U盘重装系统
  20. 华硕天选2 WLAN不可用解决办法

热门文章

  1. 谈谈判别式模型与生成式模型
  2. 前端js校验常用的60余种工具方法
  3. [转]2008年最牛语录
  4. 多旋翼无人机APM(PIX)飞控ardupilot missionplanner扩展调参、基本调参(PID调参)
  5. Python中的构造函数
  6. UML建模与软件开发设计(四)——包图设计
  7. CSS 画圆 三角形箭头
  8. 调用腾讯QQ天气预报的JS代码
  9. web安全入门(基础篇)---小迪视频笔记
  10. 世界前沿的六大“数据可视化”创业公司