开始学习在Linux下视频源捕获驱动框架,也就是V4L2(video4linux),本次关于v4l2的知识准备主要在于其的官方例程,理解官方例程也就差不多掌握了v4l2的基本内容。例程在:http://blog.chinaunix.net/uid-23983143-id-3351976.html

知识准备:

v42视频编程的流程和对文件操作并没有什么本质的不同,大概的流程如下

1.打开视频设备(通常是/dev/video0)

2.获得设备信息。

3.根据需要更改设备的相关设置。

4.获得采集到的图像数据(在这里v4l提供了两种方式,直接通过打开的设备读取数据,使用mmap内存映射的方式获取数据)。

5.对采集到的数据进行操作(如显示到屏幕,图像处理,存储成图片文件)。

6.关闭视频设备。

知道了流程之后,我们就需要根据流程完成相应的函数。

第一步:

那么我们首先完成第1步打开视频设备,需要完成int v4l_open(char *, v4l_device *);

具体的函数如下

#define DEFAULT_DEVICE “/dev/video0”

int v4l_open(char *dev , v4l_device *vd)

{

if(!dev)dev= DEFAULT_DEVICE;

if((vd-fd=open(dev,O_RDWR))<0){perror(“v4l_open:”);return -1;}

if(v4l_get_capability(vd))return -1;

if(v4l_get_picture(vd))return -1;//这两个函数就是即将要完成的获取设备信息的函数

return 0

}

同样对于第6步也十分简单,就是int v4l_close(v4l_device *);的作用。

函数如下:

int v4l_close(v4l_device *vd)

{close(vd->fd);return 0;}

第二步:

现在我们完成第2步中获得设备信息的任务,下面先给出函数在对函数作出相应的说明。

int v4l_get_capability(v4l_device *vd)

{

if (ioctl(vd->fd, VIDIOCGCAP, &(vd->capability)) < 0) {

perror("v4l_get_capability:");

return -1;

}

return 0;

}

int v4l_get_picture(v4l_device *vd)

{

if (ioctl(vd->fd, VIDIOCGPICT, &(vd->picture)) < 0) {

perror("v4l_get_picture:");

return -1;

}

return 0;

}

对于以上两个函数我们不熟悉的地方可有vd->capability和vd->picture两个结构体,和这两个函数中最主要的语句ioctl。对于ioctl的行为它是由驱动程序提供和定义的,在这里当然是由v4l所定义的,其中宏VIDIOCGCAP和VIDIOCGPICT的分别表示获得视频设备的capability和picture。对于其他的宏功能定义可以在你的Linux系统中的/usr/include/linux/videodev.h中找到,这个头文件也包含了capability和picture的定义。例如:

struct video_capability

{

char name[32];

int type;

int channels;   /* Num channels */

int audios;      /* Num audio devices */

int maxwidth; /* Supported width *

int maxheight; /* And height */

int minwidth;  /* Supported width */

int minheight; /* And height */

};capability结构它包括了视频设备的名称,频道数,音频设备数,支持的最大最小宽度和高度等信息。

struct video_picture

{

__u16     brightness;

__u16     hue;

__u16     colour;

__u16     contrast;

__u16     whiteness;       /* Black and white only */

__u16     depth;            /* Capture depth */

__u16   palette;    /* Palette in use */

}picture结构包括了亮度,对比度,色深,调色板等等信息。头文件里还列出了palette相关的值,这里并没有给出。

了解了以上也就了解了这两个简单函数的作用,现在我们已经获取到了相关视频设备的capabilty和picture属性。

这里直接给出另外一个函数

int v4l_get_mbuf(v4l_device *vd)

{

if (ioctl(vd->fd, VIDIOCGMBUG ,&(vd->mbuf)) < 0) {

perror("v4l_get_mbuf:");

return -1;

}

return 0;

}

对于结构体video_mbuf在v4l中的定义如下,video_mbuf结构体是为了服务使用mmap内存映射来获取图像的方法而设置的结构体,通过这个结构体可以获得摄像头设备存储图像的内存大小。具体的定义如下,各变量的使用也会在下文详细说明。

struct video_mbuf

{

int   size;        可映射的摄像头内存大小

int   frames;    摄像头可同时存储的帧数

int   offsets[VIDEO_MAX_FRAME];每一帧图像的偏移量

};

第三步:

下面完成第3步按照需要更改设备的相应设置,事实上可以更改的设置很多,本文以更改picture属性为例说明更改属性的一般方法。

那么我们就完成extern int v4l_set_picture(v4l_device *, int, int, int, int, int,);这个函数吧

int v4l_set_picture(v4l_device *vd,int br,int hue,int col,int cont,int white)

{

if(br) vd->picture.brightnesss=br;

if(hue) vd->picture.hue=hue;

if(col) vd->picture.color=col;

if(cont) vd->picture.contrast=cont;

if(white) vd->picture.whiteness=white;

if(ioctl(vd->fd,VIDIOCSPICT,&(vd->picture))<0)

{perror("v4l_set_picture: ");return -1;}

return 0;

}

上述函数就是更改picture相关属性的例子,其核心还是v4l给我们提供的ioctl的相关调用,通过这个函数可以修改如亮度,对比度等相关的值。

第4步获得采集到的图像数据。

这一步是使用v4l比较重要的一步,涉及到几个函数的编写。当然使用v4l就是为了要获得图像,所以这一步很关键,但是当你获得了图像数据后,还需要根据你想要达到的目的和具体情况做进一步的处理,也就是第5步所做的事情,这些内容将在后面第三部分提到。这里讲如何获得采集到的数据。

如前所述获得图像的方式有两种,分别是直接读取设备和使用mmap内存映射,而通常大家使用的方法都是后者。

1).直接读取设备

直接读设备的方式就是使用read()函数,我们先前定义的

extern int v4l_grab_picture(v4l_device *, unsigned int);函数就是完成这个工作的,它的实现也很简单。

int v4l_grab_picture(v4l_device *vd, unsighed int size)

{

if(read(vd-fd,&(vd->map),size)==0)return -1;

return 0;

}

该函数的使用也很简单,就是给出图像数据的大小,vd->map所指向的数据就是图像数据。而图像数据的大小你要根据设备的属性自己计算获得。

2).使用mmap内存映射来获取图像

在这部分涉及到下面几个函数,它们配合来完成最终图像采集的功能。

extern int v4l_mmap_init(v4l_device *);该函数把摄像头图像数据映射到进程内存中,也就是只要使用vd->map指针就可以使用采集到的图像数据(下文详细说明)

extern int v4l_grab_init(v4l_device *, int, int);该函数完成图像采集前的初始化工作。

extern int v4l_grab_frame(v4l_device *, int);该函数是真正完成图像采集的一步,在本文使用了一个通常都会使用的一个小技巧,可以在处理一帧数据时同时采集下一帧的数据,因为通常我们使用的摄像头都可以至少存储两帧的数据。

extern int v4l_grab_sync(v4l_device *);该函数用来完成截取图像的同步工作,在截取一帧图像后调用,返回表明一帧截取结束。

下面分别介绍这几个函数。

mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必在调用read(),write()等操作。两个不同进程A、B共享内存的意思是,同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时访问进程B对共享内存中数据的更新,反之亦然。

采用共享内存通信的一个显而易见的好处是减少I/O操作提高读取效率,因为使用mmap后进程可以直接读取内存而不需要任何数据的拷贝。

mmap的函数原型如下

void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )

addr:共享内存的起始地址,一般设为0,表示由系统分配。

len:指定映射内存的大小。在我们这里,该值为摄像头mbuf结构体的size值,即图像数据的总大小。

port:指定共享内存的访问权限 PROT_READ(可读),PROT_WRITE(可写)

flags:一般设置为MAP_SHARED

fd:同享文件的文件描述符。

介绍完了mmap的使用,就可以介绍上文中定义的函数extern int v4l_mmap_init(v4l_device *);了。先给出这个函数的代码,再做说明。

int v4l_mmap_init(v4l_device *vd)

{

if (v4l_get_mbuf(vd) < 0)

return -1;

if ((vd->map = mmap(0, vd->mbuf.size, PROT_READ|PROT_WRITE, MAP_SHARED, vd->fd, 0)) < 0) {

perror("v4l_mmap_init:mmap");

return -1;

}

return 0;

}

这个函数首先使用v4l_get_mbuf(vd)获得一个摄像头重要的参数,就是需要映射内存的大小,即vd->mbuf.size,然后调用mmap,当我们在编程是调用v4l_mmap_init后,vd.map指针所指向的内存空间即为我们将要采集的图像数据。

获得图像前的初始化工作v4l_grab_init();该函数十分简单直接粘上去,其中将。vd->frame_using[0]和vd->frame_using[1]都设为FALSE,表示两帧的截取都没有开始。

int v4l_grab_init(v4l_device *vd, int width, int height)

{

vd->mmap.width = width;

vd->mmap.height = height;

vd->mmap.format = vd->picture.palette;

vd->frame_current = 0;

vd->frame_using[0] = FALSE;

vd->frame_using[1] = FALSE;

return v4l_grab_frame(vd, 0);

}

真正获得图像的函数extern int v4l_grab_frame(v4l_device *, int);

int v4l_grab_frame(v4l_device *vd, int frame)

{

if (vd->frame_using[frame]) {

fprintf(stderr, "v4l_grab_frame: frame %d is already used./n", frame);

return -1;

}

vd->mmap.frame = frame;

if (ioctl(vd->fd, VIDIOCMCAPTURE, &(vd->mmap)) < 0) {

perror("v4l_grab_frame");

return -1;

}

vd->frame_using[frame] = TRUE;

vd->frame_current = frame;

return 0;

}

读到这里,应该觉得这个函数也是相当的简单。最关键的一步即为调用ioctl(vd->fd, VIDIOCMCAPTURE, &(vd->mmap)),调用后相应的图像就已经获取完毕。其他的代码是为了完成双缓冲就是截取两帧图像用的,可以自己理解下。

在截取图像后还要进行同步操作,就是调用extern int v4l_grab_sync(v4l_device *);函数,该函数如下

int v4l_grab_sync(v4l_device *vd)

{

if (ioctl(vd->fd, VIDIOCSYNC, &(vd->frame_current)) < 0) {

perror("v4l_grab_sync");

}

vd->frame_using[vd->frame_current] = FALSE;

return 0;

}

该函数返回0说明你想要获取的图像帧已经获取完毕。

图像存在了哪里?

最终我们使用v4l的目的是为了获取设备中的图像,那么图像存在哪里?从上面的文章可以知道,vd.map指针所指就是你要获得的第一帧图像。图像的位置,存在vd.map+vd.mbuf.offsets[vd.frame_current]处。其中vd.frame_current=0,即为第一帧的位置,vd.frame_current=1,为第二帧的位置

2019年12月2日Linux开发手记的更多相关文章

  1. 2019年12月4日Linux开发手记

    OK,经过昨天对V4L2工作流程的学习,现在已经大体了解了V4L2的工作原理,现在开始对V4L2的API的学习,目标:1.打开摄像头 2.储存图像 3.关闭摄像头,API网址:Linux Media ...

  2. 2019年12月18日Linux开发手记

    安装idle3: 1.端输入apt install idle3 以安装 2.安装完成后在终端输入idle以启动 配置pip: 1.终端输入apt install python3-pip 使用pip配置 ...

  3. 2019年12月1日Linux开发手记

    配置ubuntu摄像头: 1.设置→添加→usb控制器→兼容usb3.0 2.虚拟机→可移动设备→web camera→连接(断开主机) 3.查看是否配置成功,打开终端,输入: susb ls /de ...

  4. 2019年12月4日 Linux总结

    System V init运行级别 systemd目标名称 作用 0 runlevel0.target,poweroff.target 关机 1 runlevel1.target,poweroff.t ...

  5. 20.Nodejs基础知识(上)——2019年12月16日

    2019年12月16日18:58:55 2019年10月04日12:20:59 1. nodejs简介 Node.js是一个让JavaScript运行在服务器端的开发平台,它让JavaScript的触 ...

  6. 36.React基础介绍——2019年12月24日

    2019年12月24日16:47:12 2019年10月25日11:24:29 主要介绍react入门知识. 1.jsx语法介绍 1.1 介绍 jsx语法是一种类似于html标签的语法,它的作用相当于 ...

  7. 35.ES6语法介绍——2019年12月24日

    2019年12月24日16:22:24 2019年10月09日12:04:44 1. ES6介绍 1.1 新的 Javascript 语法标准 --2015年6月正式发布 --使用babel语法转换器 ...

  8. 22.Express框架——2019年12月19日

    2019年12月19日14:16:36 1. express简介 1.1 介绍 Express框架是后台的Node框架,所以和jQuery.zepto.yui.bootstrap都不一个东西. Exp ...

  9. 19.go语言基础学习(下)——2019年12月16日

    2019年12月16日16:57:04 5.接口 2019年11月01日15:56:09 5.1 duck typing 1. 2. 接口 3.介绍 Go 语言的接口设计是非侵入式的,接口编写者无须知 ...

随机推荐

  1. Tkinter 之Entry输入框标签

    一.参数说明 语法 作用 Entry(root,width=20) 组件的宽度(所占字符个数) Entry(root,fg='blue') 前景字体颜色 Entry(root,bg='blue') 背 ...

  2. laravel6.0控制器-资源控制器

    控制器:控制器用来处理业务的,不应该处理逻辑,如果是小项目可以把逻辑写到控制器里,大点的项目应该抽离出来业务处理层如下:services业务处理层:比如:获取值,验证值,异常捕获命名规则:控制器名:用 ...

  3. SpringBoot系列之YAML配置用法

    1.全局配置 SpringBoot的全局配置文件有两种: application.properties application.yml 配置文件的作用:修改SpringBoot自动配置的默认值,主要是 ...

  4. Web for pentester_writeup之Directory traversal篇

    Web for pentester_writeup之Directory traversal篇 Directory traversal(目录遍历) 目录遍历漏洞,这部分有三个例子,直接查看源代码 Exa ...

  5. PHP函数preg_match()

    部分内容来自:http://www.nowamagic.net/librarys/veda/detail/1054 preg_match — 进行正则表达式匹配. 语法:int preg_match ...

  6. Dijkstra算法 笔记与思路整理

    该文章可能存在硬伤与不妥,不能作为教程阅读.(因为我真的鶸 Dij作为单源最短路算法,需要先确定一个起点.Dij的函数主体为维护每个节点的dis和vis两个变量.dis表示该点距离起点的最短路权值和, ...

  7. CSPS模拟 100

    我又挂分了T_T 这么吉利的数字..本来想考的好一点的 T1 没加当前弧优化(其实也不会),若志了 各种低错连篇而且没想到点不联通..没有奇度点就直接从1开始搜了 于是喜提70(犯了这两个若志错误应该 ...

  8. 阿里巴巴开源项目: 基于mysql数据库binlog的增量订阅&消费

    背景 早期,阿里巴巴B2B公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求.不过早期的数据库同步业务,主要是基于trigger的方式获取增量变更,不过从2010年开始,阿里系公司开始逐步的尝 ...

  9. jmeter-手机号验证注册登录

    1.测试计划->线程组 2.首先获取需要注册的手机号,获取手机号的方式如下 (1)使用配置元件导入需要注册的手机 ·右键线程组-->配置元件-->CSV数据文件设置,如图: (2)使 ...

  10. 浅谈OI中的底层优化!

    众所周知,OI中其实就是算法竞赛,所以时间复杂度非常重要,一个是否优秀的算法或许就决定了人生,而在大多数情况下,我们想出的算法或许并不那么尽如人意,所以这时候就需要一中神奇的的东西,就是底层优化: 其 ...