二、V4L2的应用

下面简单介绍一下V4L2驱动的应用流程。

1、  视频采集的基本流程

一般的,视频采集都有如下流程:

2、  打开视频设备

在V4L2中,视频设备被看做一个文件。使用open函数打开这个设备:

// 用非阻塞模式打开摄像头设备

int cameraFd;

cameraFd = open("/dev/video0", O_RDWR | O_NONBLOCK, 0);

// 如果用阻塞模式打开摄像头设备,上述代码变为:

//cameraFd = open("/dev/video0", O_RDWR, 0);

关于阻塞模式和非阻塞模式:应用程序能够使用阻塞模式或非阻塞模式打开视频设备,如果使用非阻塞模式调用视频设备,即使尚未捕获到信息,驱动依旧会把缓存(DQBUFF)里的东西返回给应用程序。

3、  设定属性及采集方式

打开视频设备后,可以设置该视频设备的属性,例如裁剪、缩放等。这一步是可选的。在Linux编程中,一般使用ioctl函数来对设备的I/O通道进行管理:

extern int ioctl (int __fd, unsigned long int __request, ...) __THROW;

__fd:设备的ID,例如刚才用open函数打开视频通道后返回的cameraFd;

__request:具体的命令标志符。

在进行V4L2开发中,一般会用到以下的命令标志符:

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。

这些IO调用,有些是必须的,有些是可选择的。

4、  检查当前视频设备支持的标准

在亚洲,一般使用PAL(720X576)制式的摄像头,而欧洲一般使用NTSC(720X480),使用VIDIOC_QUERYSTD来检测:

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;

memset ( &fmt, 0, sizeof(fmt) );

fmt.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;

fmt.fmt.pix.width       = 720;

fmt.fmt.pix.height      = 576;

fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;

fmt.fmt.pix.field       = V4L2_FIELD_INTERLACED;

if (ioctl(fd, VIDIOC_S_FMT, &fmt) == -1) {

  return -1;

}

v4l2_format结构体定义如下:

struct v4l2_format

{

    enum v4l2_buf_type type;    // 数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE

    union

    {

        struct v4l2_pix_format    pix; 

        struct v4l2_window        win; 

        struct v4l2_vbi_format    vbi; 

        __u8    raw_data[200];         

    } fmt;

};

struct v4l2_pix_format

{

    __u32                   width;         // 宽,必须是16的倍数

    __u32                   height;        // 高,必须是16的倍数

    __u32                   pixelformat;   // 视频数据存储类型,例如是YUV4:2:2还是RGB

    enum v4l2_field         field;

    __u32                   bytesperline;   

    __u32                   sizeimage;

    enum v4l2_colorspace    colorspace;

    __u32                   priv;      

};

6、  分配内存

接下来可以为视频捕获分配内存:

struct v4l2_requestbuffers  req;

if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {

  return -1;

}

v4l2_requestbuffers定义如下:

struct v4l2_requestbuffers

{

    __u32               count;  // 缓存数量,也就是说在缓存队列里保持多少张照片

    enum v4l2_buf_type  type;   // 数据流类型,必须永远是V4L2_BUF_TYPE_VIDEO_CAPTURE

    enum v4l2_memory    memory; // V4L2_MEMORY_MMAP 或 V4L2_MEMORY_USERPTR

    __u32               reserved[2];

};

7、  获取并记录缓存的物理空间

使用VIDIOC_REQBUFS,我们获取了req.count个缓存,下一步通过调用VIDIOC_QUERYBUF命令来获取这些缓存的地址,然后使用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) {

        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;

    }

    // 放入缓存队列

    if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {

        return -1;

    }

}

8、  关于视频采集方式

操作系统一般把系统使用的内存划分成用户空间和内核空间,分别由应用程序管理和操作系统管理。应用程序可以直接访问内存的地址,而内核空间存放的是 供内核访问的代码和数据,用户不能直接访问。v4l2捕获的数据,最初是存放在内核空间的,这意味着用户不能直接访问该段内存,必须通过某些手段来转换地址。

一共有三种视频采集方式:

1)使用read、write方式:直接使用 read 和 write 函数进行读写。这种方式最简单,但是这种方式会在 用户空间和内核空间不断拷贝数据 ,同时在用户空间和内核空间占用 了 大量内存,效率不高。

2)内存映射方式(mmap):把设备里的内存映射到应用程序中的内存控件,直接处理设备内存,这是一种有效的方式。上面的mmap函数就是使用这种方式。

3)用户指针模式:内存由用户空间的应用程序分配,并把地址传递到内核中的驱动程序,然后由 v4l2 驱动程序直接将数据填充到用户空间的内存中。这点需要在v4l2_requestbuffers里将memory字段设置成V4L2_MEMORY_USERPTR。

第一种方式效率是最低的,后面两种方法都能提高执行的效率,但是对于mmap 方式,文档中有这样一句描述 --Remember the buffers are allocated in physical memory, as opposed to virtual memory which can be swapped out to disk. Applications should free the buffers as soon as possible with
the munmap () function .(使用mmap方法的时候,buffers相当于是在内核空间中分配的,这种情况下,这些buffer是不能被交换到虚拟内存中,虽然这种方法不怎么影响读写效率,但是它一直占用着内核空间中的内存,当系统的内存有限的时候,如果同时运行有大量的进程,则对系统的整体性能会有一定的影响。)

所以,对于三种视频采集方式的选择,推荐的顺序是 userptr 、 mmap 、 read-write 。当使用 mmap 或 userptr 方式的时候,有一个环形缓冲队列的概念,这个队列中,有 n 个 buffer ,驱动程序采集到的视频帧数据,就是存储在每个 buffer 中。在每次用 VIDIOC_DQBUF 取出一个 buffer ,并且处理完数据后,一定要用 VIDIOC_QBUF 将这个 buffer 再次放回到环形缓冲队列中。环形缓冲队列,也使得这两种视频采集方式的效率高于直接
read/write 。

9、  处理采集数据

V4L2有一个数据缓存,存放req.count数量的缓存数据。数据缓存采用FIFO的方式,当应用程序调用缓存数据时,缓存队列将最先采集到的 视频数据缓存送出,并重新采集一张视频数据。这个过程需要用到两个ioctl命令,VIDIOC_DQBUF和VIDIOC_QBUF:

struct v4l2_buffer buf;

memset(&buf,0,sizeof(buf));

buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;

buf.memory=V4L2_MEMORY_MMAP;

buf.index=0;

//读取缓存

if (ioctl(cameraFd, VIDIOC_DQBUF, &buf) == -1)

{

    return -1;

}

//…………视频处理算法

//重新放入缓存队列

if (ioctl(cameraFd, VIDIOC_QBUF, &buf) == -1) {

    return -1;

}

10、              关闭视频设备

使用close函数关闭一个视频设备

close(cameraFd)

V4L2驱动的移植与应用(二)的更多相关文章

  1. V4L2驱动的移植与应用(二+三)【转】

    转自:http://blog.chinaunix.net/uid-10747583-id-298489.html 原文地址:http://blog.csdn.net/wxzking/archive/2 ...

  2. V4L2驱动的移植与应用(一)

    V4L2(video for linux) 可以支持多种设备,它可以有以下5种接口: 1.视频采集接口(video capture interface):这种应用的设备可以是高频头或者摄像头.V4L2 ...

  3. V4L2驱动的移植与应用(三)

    三.V4L2的demo capture.c是官方示例程序. capture.c 程序中的 process_image 函数: capture.c 程序主要是用来演示怎样使用 v4l2 接口,并没有对采 ...

  4. v4l2驱动编写篇【转】

    转自:http://blog.csdn.net/michaelcao1980/article/details/53008418 大部分所需的信息都在这里.作为一个驱动作者,当挖掘头文件的时候,你可能也 ...

  5. 2.1 摄像头V4L2驱动框架分析

    学习目标:学习V4L2(V4L2:vidio for linux version 2)摄像头驱动框架,分析vivi.c(虚拟视频硬件相关)驱动源码程序,总结V4L2硬件相关的驱动的步骤:  一.V4L ...

  6. 【Linux开发】V4L2驱动框架分析学习

    Author:CJOK Contact:cjok.liao#gmail.com SinaWeibo:@廖野cjok 1.概述 Video4Linux2是Linux内核中关于视频设备的内核驱动框架,为上 ...

  7. WIFI驱动的移植 realtek 8188

    一般我们拿到的android源代码中wifi应用层部分是好的, 主要是wifi芯片的驱动要移植并添加进去. wifi驱动的移植, 以realtek的8188etv为例到官网下载相应的驱动, 解压后可以 ...

  8. linux设备驱动归纳总结(十二):简单的数码相框【转】

    本文转载自:http://blog.chinaunix.net/uid-25014876-id-116926.html linux设备驱动归纳总结(十二):简单的数码相框 xxxxxxxxxxxxxx ...

  9. 【Linux高级驱动】触摸屏驱动的移植

    触摸屏驱动的移植 流程 注意:看框架图 1.添加input.c组件 Device Drivers  ---> Input device support  --->  Generic inp ...

随机推荐

  1. VUE-node.js

    1.什么是node.js 它是可以运行javascript的服务平台        可以把它当做一门后端程序,只是它的开发语言是Javascript2.Python:自己创建的服务       php ...

  2. 使用mybatis从mysql里进行模糊查询的编码问题

    关于这个问题,记录下我的解决方法,希望对有同样困惑的朋友,有所帮助. 问题描述: 我在做mybatis从mysql里模糊查询时,如果模糊的关键词是字母的话,可以查出来.如果模糊的关键词是汉字的话,查不 ...

  3. Java基础教程1:环境配置及第一个HelloWorld.java

    本文主要介绍JDK环境配置.Sublime Text3配置及第一个HelloWorld.Java程序.运行环境为Win10系统,使用JDK1.8版本. 1. JDK下载及环境配置 1.1 JDK下载 ...

  4. 3.C++内联函数,默认参数,占位参数

    本章主要内容: 1)内联函数(替代宏代码段) 2)默认参数 3)占位参数 1.C++的内联函数分析 1.1讲解内联函数之前,首先回忆下之前讲的define宏定义: 之前讲过宏定义会经过预处理器进行文本 ...

  5. ubuntu中gdb调试工具的使用

    首先有一段.c代码 1.可调试gcc编译:gcc -g -o xxx xxx.c 2.启动gdb调试 gdb xxx 3.在main函数处设置断点 break main 4.运行程序 run 5.其他 ...

  6. Vue站点的搭建之旅

      背景       很久没写博客了,这次博客分享一下最近上班空闲时间做的两个业余Demo.分别是V电影App的移动端站点[一直很喜欢用这个APP可是他们没有出对应的mobile端,所以自己开发一个, ...

  7. java泛型类的继承规则

    首先看一看java泛型类的使用: /** * 一个泛型方法:使程序更加安全 * 并且能被更多的使用 * @author 丁** * * @param <T> */ class Pair&l ...

  8. 05-Git

    [Git]   [安装git] $ yum install git  #安装git $ ssh-keygen  #遇到输入符直接回车 $ cat ~/.ssh/id_rsa.pub #将这里的信息添加 ...

  9. 自己制作ssl证书:自己签发免费ssl证书,为nginx生成自签名ssl证书

    这里说下Linux 系统怎么通过openssl命令生成 证书. 首先执行如下命令生成一个key openssl genrsa -des3 -out ssl.key 1024 然后他会要求你输入这个ke ...

  10. word设置每页50行

    1. 显示行号:页面布局-行号-每页重编行号 2. 调整页边距:页面布局-页边距-调整上下边距为2 3. 调整行数:页面布局-文档网格-只指定网格-设置行数