大家好,又见面了,我是你们的朋友全栈君。
环境:
OS:Ubuntu 16.04 (Win10 hypev)
Kernel Version:3.13.0-24-generic
这里终极目标是注册一个/dev/video0的设备,再通过一个应用程序去读取它:
#include <linux/module.h>
#include <linux/videodev2.h>
#include <media/videobuf2-vmalloc.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-event.h>
#include <media/v4l2-common.h>
// 声明结构体sv供v4l2_device_register使用
struct sv{
struct v4l2_device v4l2_dev;
struct video_device vdev;
};
static struct sv sdev;
// 查询设备支持的功能
static int sv_querycap(struct file *file, void *priv, struct v4l2_capability *vcap)
{
struct sv *sv = video_drvdata(file);
strlcpy(vcap->driver, sv->vdev.name,sizeof(vcap->driver));
strlcpy(vcap->card, "Stanway test card",sizeof(vcap->card));
strlcpy(vcap->bus_info, "Stanway test bus",sizeof(vcap->bus_info));
vcap->capabilities = V4L2_CAP_VIDEO_CAPTURE|V4L2_CAP_READWRITE; // report capabilities
printk(KERN_INFO "[stanway]%s, %d, \n", __func__,__LINE__);
return 0;
}
/***********************************************
*
*V4L2模块函数
*
**********************************************/
static const struct v4l2_file_operations sv_fops = {
.owner = THIS_MODULE,
.open = v4l2_fh_open, // open /dev/video0
.release = v4l2_fh_release, // close /dev/video
.unlocked_ioctl = video_ioctl2,
};
static const struct v4l2_ioctl_ops sv_ioctl_ops = {
.vidioc_querycap = sv_querycap, // ioctl VIDIOC_QUERYCAP 时会调用sv_querycap函数
};
static int __init vivi_init(void)
{
struct sv *sv;
struct v4l2_device *v4l2_dev;
int ret;
sv = &sdev;
v4l2_dev = &sv->v4l2_dev;
//init v4l2 name, version
strlcpy(v4l2_dev->name, "sv", sizeof(v4l2_dev->name));
v4l2_info(v4l2_dev, "Color SV VGA driver %s\n", "0.0.1"); //output V4l2 info
ret = v4l2_device_register(NULL, v4l2_dev);
if (ret < 0)
{
printk(KERN_INFO "Could not register v4l2_device\n");
return ret;
}
//setup video
strlcpy(sv->vdev.name, "My vivi driver", sizeof(sv->vdev.name));
sv->vdev.v4l2_dev = v4l2_dev; // set v4;2_device address to video_device
sv->vdev.fops = &sv_fops; // v4l2_file_operations
sv->vdev.ioctl_ops = &sv_ioctl_ops; // v4l2_ioctl_ops
sv->vdev.release = video_device_release_empty;
set_bit(V4L2_FL_USES_V4L2_FH, &sv->vdev.flags);
video_set_drvdata(&sv->vdev, sv); //将sv设置为驱动私有数据
if (video_register_device(&sv->vdev, VFL_TYPE_GRABBER, -1) != 0){
printk(KERN_INFO "[stanway%s, %d, video_register_device FAIL\n", __func__,__LINE__);
ret = -ENODEV;
goto out_dev;
}
printk(KERN_INFO "[stanway]%s, %d, module inserted\n", __func__, __LINE__);
return 0;
out_dev:
v4l2_device_unregister(&sv->v4l2_dev);
video_unregister_device(&sv->vdev);
return ret;
}
static void __exit vivi_exit(void)
{
struct sv *sv;
sv = &sdev;
printk(KERN_INFO "[stanway] %s, %d, module remove\n", __func__, __LINE__);
video_unregister_device(&sv->vdev);
v4l2_device_unregister(&sv->v4l2_dev);
}
module_init(vivi_init);
module_exit(vivi_exit);
MODULE_DESCRIPTION("Stanway test module");
MODULE_AUTHOR("Stanway Hu");
MODULE_LICENSE("GPL");
Makefile:
#
#Makefile for kernel test
#
CONFIG_MODULE_SIG=n
PWD := $(shell pwd)
KVERSION := $(shell uname -r)
KERNEL_DIR = /usr/src/linux-headers-$(KVERSION)/
MODULE_NAME = myv4l2
obj-m := $(MODULE_NAME).o
all:
make -C $(KERNEL_DIR) M=$(PWD) modules
clean:
make -C $(KERNEL_DIR) M=$(PWD) clean
编译:
make
加载:
sudo insmod myv412.ko
dmesg报错:
[11095.173012] myv4l2: Unknown symbol video_ioctl2 (err 0)
[11095.173017] myv4l2: Unknown symbol v4l2_fh_open (err 0)
[11095.173020] myv4l2: Unknown symbol video_devdata (err 0)
[11095.173022] myv4l2: Unknown symbol v4l2_fh_release (err 0)
[11095.173026] myv4l2: Unknown symbol video_unregister_device (err 0)
[11095.173029] myv4l2: Unknown symbol v4l2_device_register (err 0)
[11095.173032] myv4l2: Unknown symbol __video_register_device (err 0)
[11095.173034] myv4l2: Unknown symbol v4l2_device_unregister (err 0)
[11095.173037] myv4l2: Unknown symbol video_device_release_empty (err 0)
解决:
1.安装v4l2支持:
sudo apt-get install v4l2loopback-dkms
2.进入目录:/lib/modules/3.13.0-24-generic/kernel/drivers/media/v4l2-core,运行:
sudo insmod videodev.ko
3.再insmod myv4l2.ko即可:lsmod
ls /dev/video
dmesg:
再sudo rmmod myv4l2
现在再来编一个应用程序vtest.c读取/dev/video0:
#include <errno.h>
#include <fcntl.h>
#include <linux/videodev2.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
static int xioctl(int fd,int request, void *arg)
{
int r;
do r=ioctl(fd,request,arg);
while(-1 ==r && EINTR == errno);
return r;
}
int print_caps(int fd)
{
struct v4l2_capability caps={};
perror("print_caps enter");
if(-1 == xioctl(fd,VIDIOC_QUERYCAP,&caps))
{
perror("Querying cap fail");
return 1;
}
printf( "Driver Caps:\n"
" Driver: \"%s\"\n"
" Card: \"%s\"\n"
" Bus: \"%s\"\n"
" Version: %d.%d\n"
" Capabilities: %08x\n",
caps.driver,
caps.card,
caps.bus_info,
(caps.version>>16)&&0xff,
(caps.version>>24)&&0xff,
caps.capabilities);
perror("print_caps enter");
return 0;
}
int main()
{
int fd;
fd=open("/dev/video0",O_RDWR);
if(fd ==-1)
{
perror("opening video device fail");
return 1;
}
perror("opening video device success");
if(print_caps(fd))
{
return 1;
}else{
}
perror("close fd");
close(fd);
return 0;
}
" Driver: \"%s\"\n"
" Card: \"%s\"\n"
" Bus: \"%s\"\n"
" Version: %d.%d\n"
" Capabilities: %08x\n",
caps.driver,
caps.card,
caps.bus_info,
(caps.version>>16)&&0xff,
(caps.version>>24)&&0xff,
caps.capabilities);
perror("print_caps enter");
return 0;
}
int main()
{
int fd;
fd=open("/dev/video0",O_RDWR);
if(fd ==-1)
{
perror("opening video device fail");
return 1;
}
perror("opening video device success");
if(print_caps(fd))
{
return 1;
}else{
}
perror("close fd");
close(fd);
return 0;
}
再gcc vtest.c -o test,再运行./vtest:
V4L2架构概览:
由框架可知,有两种方式编写sensor的驱动程序:
一是直接将sensor作为video device,若采用这种方式需要自己处理内存管理问题,比较复杂。
二是将sensor作为子设备供上一层V4L2核心调用,采用这种方式只需要调用V4L2提供的API即可。
上面例子使用的就是第二种方式。
下面介绍一下V4L2整体情况:
V4L2提供一套数据结构和底层V4L2驱动接口规范供Linux下的视频设备程序使用,主要是一系列回调函数,如设置摄像头频率,帧率,视频压缩格式和图像参数等,还可用于其他多媒体开发,如音频等。
一般采用V4L2驱动的摄像头设备文件是/dev/video0,V4L2支持两种方式采集图像:内存映射方式mmap和直接读取方式read。
V4L2的重要数据结构都存放在/include/linux/videodev2.h文件中,在采集图像过程中,就是通过操作这些数据结构来获得最终图像数据,Linux系统对V4L2的支持是从Linux 2.5.x版本开始的,它可在内核编译阶段配置,或后期安装,默认情况下都有此开发接口。
videodev2.hl类似位置,其内部实际指向另外的.h:
V4L2定义了通用API元素,图像的格式,输入输出方法,以及Linux内核驱动处理视频信息的一系列接口,有主要以下五大接口:
1.视频采集接口(video capture interface)
2.视频输出接口(video output interface)
3.视频覆盖预览接口 (video overlay interface) — 此功能会将采集到的画面保存在内存中,同时不需要经过其他处理直接将画面显示在屏幕上
4.视频输出覆盖接口 (video output overlay interface)— 此功能打开,第3个功能需要关闭
5.编解码接口 (codec interface)
V4L2结构体:
1.常用结构体在文件./include/uapi/linux/videodev2.h下定义:
struct v4l2_requestbuffers:申请缓冲区,对应命令为VIDIOC_REQBUFS
VIDIOC_REQBUFS命令通过该结构体向驱动发出申请,请求一片连续的内存空间用于缓存视频信息。
cout:申请的缓冲区个数,是根据图像占用空间大小而设定的
type:视频捕获模式,枚举v4l2_buf_type
memory:内存区的使用方式
struct v4l2_capability:视频设备的功能,对应命令为VIDIOC_QUERYCAP
这里就是上例中用到的结构体:
1>.driver[16]:表示驱动名,这个名需要与struct video_device中的name字段匹配
2>.card[32]:表示设备名
3>.bus_info[32]:表示设备在系统中的位置
4>.version:表示驱动版本号
5>.capabilities:表示设备支持的操作,常见值有V4L2_CAP_VIDEO_CAPTURE|V4L2_CAP_STREAMING表示的是一个视频捕捉设备且具有数据流控制模式,支持的模式可见如下枚举,它是后面v4l2_format结构体type字段的值:
常见捕获模式为视频捕获模式V4L2_BUF_TYPE_VIDEO_CAPTURE,在此模式下采用fmt枚举域:v4l2_pix_format:
其中:
width:视频宽
height:视频高
pixelformat:视频数据格式(常见值有V4L2_PIX_FMT_YUV422P|V4L2_PIX_FMT_RGB565)
field:v4l2_field的枚举量
bytesperline:一行图像占用的字节数
sizeimage:图像占用的总字节数
colorspace:设备的颜色空间
6>.reserved[4]:保留字段
struct v4l2_input:视频输入信息,对应命令VIDIOC_ENUMINPUT
index:表示哪一个输入
name:输入标签
type:输入类型
audioset:关联的音频(位域)
tunner:关联的调谐器(FM/AM?)
std:
status:
reserved:
struct v4l2_standard:视频制式,比如PAL,NTSC,对应命令VIDIOC_ENUMSTD
struct v4l2_format:帧格式,对应命令VIDIOC_G_FMT,VIDIOC_S_FMT等
struct v4l2_buffer:驱动中的一帧图像缓存,对应命令VIDIOC_QUERYBUF
index:缓存编号
type:视频捕获模式
bytesused:缓存已使用空间大小
flags:缓存当前状态,常见值有V4L2_BUF_FLAG_MAPPED|V4L2_BUF_FLAG_QUEUED|V4L2_BUF_FLAG_DONE,分别表示当前缓存已经映射,缓存可以采集数据,缓存可以提取数据。
timestamp:时间戳
sequence:缓存序号
memory:缓存使用方式
offset:当前缓存与内存区起始地址的偏移
length:缓存大小
reserved2:
reserved:一般用于传递物理地址
VIDIOC_QBUF和VIDIOC_DQBUF命令都采用该结构与驱动通信:
VIDIOC_QBUF命令向驱动传递应用程序已经处理完的缓存,即将缓存加入空闲可捕获视频队列,传递的主要参数为index
VIDIOC_DQBUF命令向驱动获取已经存放有视频数据的缓存,该结构体的各个字段几乎都会被更新,但主要的参数也是index,应用程序会根据index确定可用数据的起始地址和范围。
struct v4l2_crop:视频信号矩形边框
2.常用的IOCTL接口命令也在文件./include/uapi/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:检查当前视频设备支持的标准,如PATL或NTSC
调用V4L2的流程:
打开设备 -> 检查和设置设备属性 -> 设置帧格式 -> 设置一种输入输出方法,缓冲区管理 – > 循环获取数据 ->关闭设备
1.打开设备:在V4L2中,视频设备被看作一个文件,使用open函数即可打开该设备,有两种模式:
一是非阻塞模式打开设备,这种模式下即使尚未捕获到数据,驱动依旧会把缓冲DQBUFF内的数据返回给应用层:
int fd = open(“/dev/video0”, O_RDWR|O_NONBLOCK);
二是阻塞模式打开设备,这种模式下必须捕获到数据才返回,否则一直等待:
int fd = open(“/dev/video0”, O_RDWR);
2.获取设备特性:查看设备都支持什么功能,比如是否具有视频输入功能
struct v4l2_capability caps={};
int ret = ioctl(fd, VIDIOC_QUERYCAP, &caps);
//获取成功,检查是否有视频捕获功能
if (!(caps.capabilities & V4L2_CAP_VIDEO_CAPTURE)){
…
}
//是否具有流控制功能
if (!(caps.capabilities & V4L2_CAP_STREAMING)){
…
}
3.选择视频输入:
struct v4l2_input input;
…….这里初始化input?
int ret = ioctl(fd, VIDIOC_QUERYCAP, &input);
一个视频设备可以有多个视频输入,若只有一路输入,这个功能可省去。
VIDIOC_G_INPUT和VIDIOC_S_INPUT用于查询和选择当前的input,一个video设备节点可能对应多个视频源,比如saf7113可最多支持四路cvbs输入,若上层想要在四个cvbs视频输入间切换,则就要调用ioctl(fd, VIDIOC_S_INPUT, &input)来切换。
VIDIOC_G_INPUT和VIDIOC_G_OUTPUT返回当前的video input和output的index值。
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.设置视频捕获格式:v4l2_format结构体用以设置摄像头的视频制式,帧格式等,在设置这个参数时应先填好v4l2_format的各个域,如type传输流类型,fmt.pix.width宽,fmt.pix.height高,fmt.pix.pixelformat采样类型,如YUV4:2:2,后通过VIDIOC_S_FMT命令设置视频捕捉格式。
struct v4l2_format fmt;
memset(&fmt, 0, sizeof(fmt));
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
fmt.fmt.pix.width = g_display_width;
fmt.fmt.pix.heigth = g_display_height;
fmt.fmt.pix.pixelformat = g_fmt;
fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
//设置设备捕获视频的格式
if (ioctl(dev->fd, VIDIOC_S_FMT, &fmt) < 0)
{
…
}
若该视频设备驱动不支持所设定的图像格式,视频驱动会重新修改struct v4l2_format结构体变量的值为该设备所支持的图像格式,因此在程序设计时,设定完所有的视频格式后,要获取实际的视频格式,需要重新读取struct v4l2_format结构体变量。
6.向驱动申请帧缓存:一般不超过5个,CAP_BUF_NUM = 4
struct v4l2_requestbuffers req;/* 申请设备的缓存区 */
memset(&req, 0, sizeof(req));
req.count = CAP_BUF_NUM; //申请一个拥有四个缓冲帧的缓冲区
req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
req.memory = V4L2_MEMORY_MMAP;
if (ioctl(dev->fd, VIDIOC_REQBUFS, &req) < 0)
{
if (EINVAL == errno)
{
printf(stderr, "%s does not support "
"memory mapping\n", dev->dev);
return TFAIL;
}
else
{
printf(stderr, "%s does not support "
"memory mapping, unknow error\n", dev->dev);
return TFAIL;
}
}
if (req.count < 2)
{
printf(stderr, "Insufficient buffer memory on %s\n",
dev->dev);
return TFAIL;
}
v4l2_requestbuffers结构体定义了缓存的数量,驱动会根据此申请对应数量的视频缓存,多个缓存可用于建立FIFO,来提高视频采集的效率,控制命令为VIDIOC_REQBUFS
主要功能:请求V4L2驱动分配视频缓冲区,也就是申请V4L2视频驱动分配内存,V4L2是视频设备的驱动层,它位于内核空间,因此通过VIDIOC_REQBUFS控制命令申请的内存空间位于内核空间中,应用程序不能直接访问,需要调用mmap内存映射函数把内核空间的内存映射到用户空间后,应用程序通过访问用户空间地址来访问内核空间。若成功,则会在V4L2驱动层分配好视频缓冲区。
7.获取每个缓存的信息,并mmap到用户空间
应用层和设备有三种交换数据的方法,直接read/write,内存映射mmap和用户指针,这里只说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;
}
mmap语法:
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
参数:
addr:映射的起始地址,一般为NULL,让内核自动选择
length:被映射内存块的长度
prot:标志映射后能否被读写,其值为PROT_EXEC,PROT_READ,PROT_WRITE,PROT_NONE
flags:确定此内存映射能否被其他进程共享:MAP_SHARED,MAP_PRIVATE
fd:返回成功映射后的地址,不成功返回MAP_FAILED((void)-1)
offset:被映射对象内容的起点
munmap语法:
int munmap( void * addr, size_t len ) ;
执行成功返回0,失败返回-1。该调用在进程地址空间中解除一个映射关系
addr:为调用mmap()时返回的地址
len:为映射区的大小
详细参考网址
8.开始采集视频,也就是在缓冲区处理好之后就可获得视频了:在开始之前,还需要把缓冲帧放入缓冲队列中。
//把四个缓冲帧放入队列
for (i = 0; i < CAPBUFNUM; i++)
{
memset(&buf, 0, sizeof(buf));
buf.type = V4L2BUFTYPE_VIDEO_CAPTURE;
buf.memory = V4L2_MEMORY_MMAP;
buf.index = i;
buf.m.offset = dev->buffer[i].offset;
/ 将空闲的内存加入可捕获视频的队列 /
if(ioctl(dev->fd, VIDIOC_QBUF, &buf) < 0)
{
printf(“ERROR: VIDIOC_QBUF[%s], FUNC[%s], LINE[%d]\n”, dev->dev, __FUNCTION, __LINE);
return TFAIL;
}
}
type = V4L2BUFTYPEVIDEOCAPTURE;
/ 打开设备视频流 /
if(ioctl(dev->fd, VIDIOC_STREAMON, &type) < 0)
{
printf(“ERROR: VIDIOC_STREAMON[%s], FUNC[%s], LINE[%d]\n”, dev->dev, __FUNCTION, __LINE);
return TFAIL;
}
在前期初始化完成之后,只是解决了一帧视频数据的格式和大小问题,而连续视频帧数据的采集需要用帧缓冲区队列的方式来解决,也就是要通过驱动程序在内存中申请多个缓冲区来存放视频数据。以下三点比较重要:
第1点.应用程序通过API提供的方法VIDIOC_REQBUFS申请若干个视频数据的帧缓冲区,申请帧缓冲区数量不低于3个,每个帧缓冲区存放一帧视频数据,这些帧缓存区在内核空间。
第2点.应用程序通过API提供的查询方法VIDIOC_QUERYBUF查询到帧缓冲区在内核空间的长度和偏移量地址
第3点.应用程序再通过mmap,将申请到的内核空间帧缓冲区的地址映射到用户空间地址,这样就可以直接处理帧缓冲区的数据
执行步骤,以下三步:
第一步:将帧缓冲区排队在视频输入队列中,并启动视频采集
在驱动程序处理视频的过程中,会定义两个队列:视频采集输入队列incoming queues和视频采集输出队列outgoing queues,前者等待驱动放入视频数据的队列,后者是驱动程序已经放入视频数据的队列,将申请到的帧缓冲区在视频采集输入队列排队,并启动视频采集。
第二步:循环往复,采集连续的视频数据
一是启动视频采集后,驱动程序开始采集一帧数据,把采集的数据放入视频采集输入队列的第一个帧缓冲区,一帧数据采集完成后,也就是第一个帧缓冲区存满一帧数据后,驱动程序将该帧缓冲区移到视频采集输出队列,等待应用程序从输出队列取出,驱动程序接下来采集下一帧数据,放入第二个帧缓冲区,同样在第二个帧缓冲区存满下一帧数据后,也会被放入视频采集输出队列中。
二是应用程序从视频采集输出队列中取出含有视频数据的帧缓冲区后,会处理帧缓冲区中的视频数据,比如存储或压缩这些数据。
9.取出FIFO缓冲中已经采样的帧缓存:
struct v4l2_buffer capture_buf;
memset(&capture_buf, 0, sizeof(capture_buf));
capture_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
capture_buf.memory = V4L2_MEMORY_MMAP;
/* 将已经捕获好视频的内存拉出已捕获视频队列 */
if (ioctl(dev.fd, VIDIOC_DQBUF, &capture_buf) < 0)
{
printf("ERROR: VIDIOC_DQBUF[%s], FUNC[%s], LINE[%d]\n", dev, __FUNCTION__, __LINE__);
return TFAIL;
}
}
image_data_handle(buffer[capture_buf.index].start, capture_buf.bytesused);
10.将刚刚处理完的缓重新入队列尾,实现循环采集
if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
return -1;
}
以上第8,9,10三步可用以下图示描述:
11.停止视频采集,解除映射
int ret = ioctl(fd, VIDIOC_STREAMOFF, &buf_type);
munmap(buffer[j].start, buffer[j].length);
12.关闭视频设备
close(fd);
简洁版流程描述:
(1)打开视频设备文件。int fd=open(“/dev/video0”,O_RDWR);
(2)查询视频设备的能力,比如是否具有视频输入,或者音频输入输出等。ioctl(fd_v4l, VIDIOC_QUERYCAP, &cap)
(3)设置视频采集的参数
设置视频的制式,制式包括PAL/NTSC,使用ioctl(fd_v4l, VIDIOC_S_STD, &std_id)
设置视频图像的采集窗口的大小,使用ioctl(fd_v4l, VIDIOC_S_CROP, &crop)
设置视频帧格式,包括帧的点阵格式,宽度和高度等,使用ioctl(fd_v4l, VIDIOC_S_FMT, &fmt)
设置视频的帧率,使用ioctl(fd_v4l, VIDIOC_S_PARM, &parm)
设置视频的旋转方式,使用ioctl(fd_v4l, VIDIOC_S_CTRL, &ctrl)
(4)向驱动申请视频流数据的帧缓冲区
请求/申请若干个帧缓冲区,一般为不少于3个,使用ioctl(fd_v4l, VIDIOC_REQBUFS, &req)
查询帧缓冲区在内核空间中的长度和偏移量 ioctl(fd_v4l, VIDIOC_QUERYBUF, &buf)
(5)应用程序通过内存映射,将帧缓冲区的地址映射到用户空间,这样就可以直接操作采集到的帧了,而不必去复制。
buffers[i].start = mmap (NULL, buffers[i].length, PROT_READ | PROT_WRITE, MAP_SHARED, fd_v4l, buffers[i].offset);
(6)将申请到的帧缓冲全部放入视频采集输出队列,以便存放采集的数据。ioctl (fd_v4l, VIDIOC_QBUF, &buf)
(7)开始视频流数据的采集。 ioctl (fd_v4l, VIDIOC_STREAMON, &type)
(8) 驱动将采集到的一帧视频数据存入输入队列第一个帧缓冲区,存完后将该帧缓冲区移至视频采集输出队列。
(9)应用程序从视频采集输出队列中取出已含有采集数据的帧缓冲区。ioctl (fd_v4l, VIDIOC_DQBUF, &buf) ,应用程序处理该帧缓冲区的原始视频数据。
(10)处理完后,应用程序的将该帧缓冲区重新排入输入队列,这样便可以循环采集数据。ioctl (fd_v4l, VIDIOC_QBUF, &buf)
重复上述步骤8到10,直到停止采集数据。
(11)停止视频的采集。ioctl (fd_v4l, VIDIOC_STREAMOFF, &type)
(12)释放申请的视频帧缓冲区unmap,关闭视频设备文件close(fd_v4l)。
以上的程序流程,包含了视频设备采集连续的视频数据的逻辑关系。而在实际运用中,往往还要加入对视频数据进行处理(如压缩编码)的工作,否则,视频流数据量相当大,需要很大的存储空间和传输带宽。
详细参考网址1
详细参考网址2
详细参考网址3
详细参考网址4
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/128904.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...