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可以支持多种设备,它可以有以下几种接口:
- Video capture interface(视频采集接口):从摄像头等设备上获取视频数据,是V4L2设计最初功能;
- Video output interface(视频输出接口):驱动计算机的外围视频、图像显示设备;
- Video overlay interface(直接传输视频接口):把从视频采集设备采集过来的信号直接输出到输出设备之上,而不用经过CPU;
- Video output overlay device(视频输出覆盖设备):也被称为OSD(On-Screen Display),即在显示画面上叠加一层显示,比如菜单设置界面;
- VBI interface(视频间隔消隐信号接口):提供对VBI(Vertical Blanking Interval)数据的控制,它可以使应用可以访问传输消隐期的视频信号;
- 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等,经测试,luvcview
和xawtv
比较靠谱。
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_pdev
和vivid_pdrv
,注册后,由于两者name
一致,则会调用probe()
。在probe()
里面主要进行初始化、注册等相关流程。
可以看到,在probe()
里,会调用vivid_create_instance()
,让后在里面先分配一个video_device
,然后设置video_device
,包括操作函数ops
,ioctl
操作函数,设备等。
然后对ctrl
属性进行详细的设置,最后注册video_device
,和进行常规的字符设备注册。
因此,写摄像头驱动程序的流程如下:
- 分配
video_device
:video_device_alloc()
或kzalloc()
;
- 设置
video_device
:.fops
、.ioctl_ops
、dev
; - 注册
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
,包括:release
、fops
、ioctl_ops
、v4l2_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时:
-
- 先调用buf_prepare进行一些准备工作
-
- 把buf放入stream队列
-
- 调用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);
}
- 调用buf_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
:
参考资料:
韦东山第三期项目视频_摄像头