Linux摄像头驱动1——vivid

版权声明:本文采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可,欢迎转载,但转载请注明来自hceng blog(www.hceng.cn),并保持转载后文章内容的完整。本人保留所有版权相关权利。 https://blog.csdn.net/hceng_linux/article/details/89874036

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-generic

vivid-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.o
		
all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-$(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 -h             //帮助信息
luvcview -d /dev/videoX //指定设备
luvcview -L             //查询当前摄像头所支持的所有数据格式以及分辨率信息 
luvcview                //运行摄像头
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_mmap
		vb2_fop_mmap
			videobuf_mmap_mapper
				__videobuf_mmap_mapper
					mem->vaddr = vmalloc_user(pages); //这里分配buf

3.把缓冲区放入队列

app:ioctl(fd, VIDIOC_QBUF, xx)
	videobuf_qbuf
		q->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_poll
		vdev->fops->poll();
			vivi_poll 
				videobuf_poll_stream
					buf = list_entry();  //从队列的头部获得buf
					poll_wait();         //如果没有数据则休眠
			
	vivid_thread_vid_cap                 //内核进程唤醒
		vivid_thread_vid_cap_tick
			vivid_fillbuff				 //构造数据
			vb2_buffer_done 	 
				wake_up();  			 //唤醒进程

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

app:ioctl(fd, VIDIOC_DQBUF, xx)   //使用VIDIOC_DQBUF获取buf状态
	videobuf_dqbuf
		stream_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 [email protected]”);
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

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

猜你喜欢

转载自blog.csdn.net/hceng_linux/article/details/89874036