基于V4L2
的视频驱动开发(2
)
华清远见
刘洪涛
三、
V4L2 API
及数据结构
V4L2
是
V4L
的升级版本,为
linux
下视频设备程序提供了一套接口规范。包括一套数据结构和底层
V4L2
驱动接口。
1
、常用的结构体在内核目录
include/linux/videodev2.h
中定义
struct v4l2_requestbuffers //
申请帧缓冲,对应命令
VIDIOC_REQBUFS
struct v4l2_capability //
视频设备的功能,对应命令
VIDIOC_QUERYCAP
struct v4l2_input
//
视频输入信息,对应命令
VIDIOC_ENUMINPUT
struct v4l2_standard //
视频的制式,比如PAL
,NTSC
,对应命令
VIDIOC_ENUMSTD
struct v4l2_format
//
帧的格式,对应命令VIDIOC_G_FMT
、VIDIOC_S_FMT
等
struct v4l2_buffer
//
驱动
中的一帧图像缓存,对应命令VIDIOC_QUERYBUF
struct v4l2_crop
//
视频信号矩形边框
v4l2_std_id
//
视频制式
2
、常用的
IOCTL
接口命令也在
include/linux/videodev2.h
中定义
VIDIOC_REQBUFS //
分配内存
VIDIOC_QUERYBUF //
把
VIDIOC_REQBUFS
中分配的数据缓存转换成物理地址
VIDIOC_QUERYCAP //
查询驱动功能
VIDIOC_ENUM_FMT //
获取当前驱动支持的视频格式
VIDIOC_S_FMT //
设置当前驱动的频捕获格式
VIDIOC_G_FMT //
读取当前驱动的频捕获格式
VIDIOC_TRY_FMT //
验证当前驱动的显示格式
VIDIOC_CROPCAP //
查询驱动的修剪能力
VIDIOC_S_CROP //
设置视频信号的矩形边框
VIDIOC_G_CROP //
读取视频信号的矩形边框
VIDIOC_QBUF //
把数据从缓存中读取出来
VIDIOC_DQBUF //
把数据放回缓存队列
VIDIOC_STREAMON //
开始视频显示函数
VIDIOC_STREAMOFF //
结束视频显示函数
VIDIOC_QUERYSTD //
检查当前视频设备支持的标准,例如
PAL
或
NTSC
。
3
、操作流程
V4L2
提供了很多访问接口,你可以根据具体需要选择操作方法。需要注意的是,很少有驱动完全实现了所有的接口功能。所以在使用时需要参考驱动源码,或仔细阅读驱动提供者的使用说明。
下面列举出一种操作的流程,供参考。
(
1
)打开设备文件
int fd = open(Devicename,mode);
Devicename
:
/dev/video0
、
/dev/video1
……
Mode
:
O_RDWR [| O_NONBLOCK]
如果使用非阻塞模式调用视频设备,则当没有可用的视频数据时,不会阻塞,而立刻返回。
(
2
)取得设备的
capability
struct v4l2_capability capability
;
int ret = ioctl(fd, VIDIOC_QUERYCAP, &capability);
看看设备具有什么功能,比如是否具有视频输入特性。
(
3
)选择视频输入
struct v4l2_input input
;
……初始化
input
int ret = ioctl(fd, VIDIOC_QUERYCAP, &input);
一个视频设备可以有多个视频输入。如果只有一路输入,这个功能可以没有。
(
4
)检测视频支持的制式
v4l2_std_id std;
do {
ret = ioctl(fd, VIDIOC_QUERYSTD, &std);
} while (ret == -1 && errno == EAGAIN);
switch (std) {
case V4L2_STD_NTSC:
//……
case V4L2_STD_PAL:
//……
}
(
5
)设置视频捕获格式
struct v4l2_format fmt;
fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
fmt.fmt.pix.height = height;
fmt.fmt.pix.width = width;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
ret = ioctl(fd, VIDIOC_S_FMT, &fmt);
if(ret) {
perror("VIDIOC_S_FMT/n");
close(fd);
return -1;
}
(
6
)向驱动申请帧缓存
struct v4l2_requestbuffers
req;
if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
return -1;
}
v4l2_requestbuffers
结构中定义了缓存的数量,驱动会据此申请对应数量的视频缓存。多个缓存可以用于建立
FIFO
,来提高视频采集的效率。
(
7
)获取每个缓存的信息,并
mmap
到用户空间
typedef struct VideoBuffer {
void
*start;
size_t
length;
} VideoBuffer;
VideoBuffer*
buffers = calloc( req.count, sizeof(*buffers) );
struct v4l2_buffer
buf;
for (numBufs = 0; numBufs < req.count; numBufs++) {//
映射所有的缓存
memset( &buf, 0, sizeof(buf) );
buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = numBufs;
if (ioctl(
fd, VIDIOC_QUERYBUF, &buf) == -1) {//
获取到对应
index
的缓存信息,此处主要利用
length
信息及
offset
信息来完成后面的
mmap
操作。
return -1;
}
buffers[numBufs].length = buf.length;
//
转换成相对地址
buffers[numBufs].start = mmap(NULL, buf.length,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd, buf.m.offset);
if (buffers[numBufs].start == MAP_FAILED) {
return -1;
}
(
8
)开始采集视频
int buf_type= V4L2_BUF_TYPE_VIDEO_CAPTURE
;
int ret = ioctl(fd, VIDIOC_STREAMON, &buf_type);
(
9
)取出
FIFO
缓存中已经采样的帧缓存
struct v4l2_buffer buf;
memset(&buf,0,sizeof(buf));
buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
buf.memory=V4L2_MEMORY_MMAP;
buf.index=0;//
此值由下面的
ioctl
返回
if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1)
{
return -1;
}
根据返回的
buf.index
找到对应的
mmap
映射好的缓存,取出视频数据。
(
10
)将刚刚处理完的缓冲重新入队列尾,这样可以循环采集
if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
return -1;
}
(
11
)停止视频的采集
int ret = ioctl(fd, VIDIOC_STREAMOFF, &buf_type);
(
12
)关闭视频设备
close(fd);
四、
V4L2
驱动框架
上述流程的各个操作都需要有底层
V4L2
驱动的支持。内核中有一些非常完善的例子。
比如:
linux-2.6.26
内核目录
/drivers/media/video//zc301/zc301_core.c
中的
ZC301
视频驱动代码。上面的
V4L2
操作流程涉及的功能在其中都有实现。
1
、
V4L2
驱动注册、注销函数
Video
核心层(
drivers/media/video/videodev.c
)提供了注册函数
int video_register_device(struct video_device *vfd, int type, int nr)
video_device:
要构建的核心数据结构
Type:
表示设备类型,此设备号的基地址受此变量的影响
Nr:
如果
end-base>nr>0
:次设备号
=base
(基准值,受
type
影响)
+nr
;
否则:系统自动分配合适的次设备号
具体驱动只需要构建
video_device
结构,然后调用注册函数既可。
如:
zc301_core.c
中的
err = video_register_device(cam->v4ldev, VFL_TYPE_GRABBER,
video_nr[dev_nr]);
Video
核心层(
drivers/media/video/videodev.c
)提供了注销函数
void video_unregister_device(struct video_device *vfd)
2
、
struct video_device
的构建
video_device
结构包含了视频设备的属性和操作方法。参见
zc301_core.c
strcpy(cam->v4ldev->name, "ZC0301[P] PC Camera");
cam->v4ldev->owner = THIS_MODULE;
cam->v4ldev->type = VID_TYPE_CAPTURE | VID_TYPE_SCALES;
cam->v4ldev->fops = &zc0301_fops;
cam->v4ldev->minor = video_nr[dev_nr];
cam->v4ldev->release = video_device_release;
video_set_drvdata(cam->v4ldev, cam);
大家发现在这个
zc301
的驱动中并没有实现
struct video_device
中的很多操作函数
,
如
:
vidioc_querycap
、
vidioc_g_fmt_cap
等。主要原因是
struct file_operations zc0301_fops
中的
zc0301_ioctl
实现了前面的所有
ioctl
操作。所以就不需要在
struct video_device
再实现
struct video_device
中的那些操作了。
另一种实现方法如下:
static struct video_device camif_dev =
{
.name
= "s3c2440 camif",
.type
= VID_TYPE_CAPTURE|VID_TYPE_SCALES|VID_TYPE_SUBCAPTURE,
.fops
= &camif_fops,
.minor
= -1,
.release
= camif_dev_release,
.vidioc_querycap
= vidioc_querycap,
.vidioc_enum_fmt_cap
= vidioc_enum_fmt_cap,
.vidioc_g_fmt_cap
= vidioc_g_fmt_cap,
.vidioc_s_fmt_cap
= vidioc_s_fmt_cap,
.vidioc_queryctrl = vidioc_queryctrl,
.vidioc_g_ctrl = vidioc_g_ctrl,
.vidioc_s_ctrl = vidioc_s_ctrl,
};
static struct file_operations camif_fops =
{
.owner
= THIS_MODULE,
.open
= camif_open,
.release
= camif_release,
.read
= camif_read,
.poll
= camif_poll,
.ioctl
= video_ioctl2, /* V4L2 ioctl handler */
.mmap
= camif_mmap,
.llseek
= no_llseek,
};
注意
:
video_ioctl2
是
videodev.c
中是实现的。
video_ioctl2
中会根据
ioctl
不同的
cmd
来
调用
video_device
中的操作方法。
3
、
Video
核心层的实现
参见内核
/drivers/media/videodev.c
(
1
)注册
256
个视频设备
static int __init videodev_init(void)
{
int ret;