V4L2学习笔记

Posted 请给我倒杯茶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了V4L2学习笔记相关的知识,希望对你有一定的参考价值。

本文转载自:http://www.cnblogs.com/silence-hust/p/4464291.html

  v4l2,一开始听到这个名词的时候,以为又是一个很难很难的模块,涉及到视频的处理,后来在网上各种找资料后,才发现其实v4l2已经分装好了驱动程序,只要我们根据需要调用相应的接口和函数,从而实现视频的获取和处理。只要认真的看几篇文章就对v4l2有一定的了解了,由于是第一次接触,网上的资料良莠不齐,难得可以找到几篇自己感觉很不错的。记录下来:(没必要看太多,很多都是一样的意思)

http://www.embedu.org/Column/Column320.htm   这篇是不错的介绍,很讨厌有弹窗

http://www.cnblogs.com/emouse/archive/2013/03/04/2943243.html  这个可以作为第一篇来看,博主整理的不错

http://blog.chinaunix.net/uid-11765716-id-2855735.html    这篇也比较详细

http://blog.csdn.net/ddddwant/article/details/8475211   这篇提到的问题和我遇到的一样,花屏了,内存没有读取好

http://my.oschina.net/u/1024767/blog/210801#OSC_h2_14    对capture.c文件的解读

http://blog.csdn.net/g_salamander/article/details/8107692    对各个结构体有比较好的说明

 

一、Video for Linux two

  v4l2为linux下视频设备程序提供了一套接口规范。包括一套数据结构和底层V4L2驱动接口。只能在linux下使用。它使程序有发现设备和操作设备的能力。它主要是用一系列的回调函数来实现这些功能。像设置摄像头的频率、帧频、视频压缩格式和图像参数等等。当然也可以用于其他多媒体的开发,如音频等。

  在Linux下,所有外设都被看成一种特殊的文件,成为“设备文件”,可以象访问普通文件一样对其进行读写。一般来说,采用V4L2驱动的摄像头设备文是/dev/v4l/video0。为了通用,可以建立一个到/dev/video0的链接。V4L2支持两种方式来采集图像:内存映射方式(mmap)和直接读取方式(read)。V4L2在include/linux/videodev.h文件中定义了一些重要的数据结构,在采集图像的过程中,就是通过对这些数据的操作来获得最终的图像数据。Linux系统V4L2的能力可在Linux内核编译阶段配置,默认情况下都有此开发接口。V4L2从Linux 2.5.x版本的内核中开始出现。

  V4L2规范中不仅定义了通用API元素(Common API Elements),图像的格式(Image Formats),输入/输出方法(Input/Output),还定义了Linux内核驱动处理视频信息的一系列接口(Interfaces),这些接口主要有:

  视频采集接口——Video Capture Interface;

  视频输出接口—— Video Output Interface;

  视频覆盖/预览接口——Video Overlay Interface;

  视频输出覆盖接口——Video Output Overlay Interface;

  编解码接口——Codec Interface。

二、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        //视频制式

常用结构体的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
struct v4l2_capability
 
{
 
u8 driver[16]; // 驱动名字
 
u8 card[32]; // 设备名字
 
u8 bus_info[32]; // 设备在系统中的位置
 
u32 version; // 驱动版本号
 
u32 capabilities; // 设备支持的操作
 
u32 reserved[4]; // 保留字段
 
};

  其中域 capabilities 代表设备支持的操作模式,常见的值有 V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING 表示是一个视频捕捉设备并且具有数据流控制模式;另外 driver 域需要和 struct video_device 中的 name 匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
struct v4l2_format { 
    enum v4l2_buf_type type; 
    union 
        struct v4l2_pix_format         pix;     /* V4L2_BUF_TYPE_VIDEO_CAPTURE */ 
        struct v4l2_window             win;     /* V4L2_BUF_TYPE_VIDEO_OVERLAY */ 
        struct v4l2_vbi_format         vbi;     /* V4L2_BUF_TYPE_VBI_CAPTURE */ 
        struct v4l2_sliced_vbi_format  sliced;  /* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */ 
        __u8   raw_data[200];                   /* user-defined */ 
    } fmt; 
}; 
 
enum v4l2_buf_type { 
    V4L2_BUF_TYPE_VIDEO_CAPTURE        = 1, 
    V4L2_BUF_TYPE_VIDEO_OUTPUT         = 2, 
    V4L2_BUF_TYPE_VIDEO_OVERLAY        = 3, 
    ... 
    V4L2_BUF_TYPE_PRIVATE              = 0x80, 
}; 
   
struct v4l2_pix_format { 
    __u32                   width; 
    __u32                   height; 
    __u32                   pixelformat; 
    enum v4l2_field         field; 
    __u32                   bytesperline;   /* for padding, zero if unused */ 
    __u32                   sizeimage; 
    enum v4l2_colorspace    colorspace; 
    __u32                   priv;           /* private data, depends on pixelformat */ 
};

  常见的捕获模式为 V4L2_BUF_TYPE_VIDEO_CAPTURE 即视频捕捉模式,在此模式下 fmt 联合体采用域 v4l2_pix_format:其中 width 为视频的宽、height 为视频的高、pixelformat 为视频数据格式(常见的值有 V4L2_PIX_FMT_YUV422P | V4L2_PIX_FMT_RGB565)、bytesperline 为一行图像占用的字节数、sizeimage 则为图像占用的总字节数、colorspace 指定设备的颜色空间。

 

struct v4l2_requestbuffers 与 VIDIOC_REQBUFS ,VIDIOC_REQBUFS 命令通过结构 v4l2_requestbuffers 请求驱动申请一片连续的内存用于缓存视频信息:

1
2
3
4
5
6
7
8
9
10
11
struct v4l2_requestbuffers {
    __u32                   count;
    enum v4l2_buf_type      type;
    enum v4l2_memory        memory;
    __u32                   reserved[2];
};
enum v4l2_memory {
    V4L2_MEMORY_MMAP             = 1,
    V4L2_MEMORY_USERPTR          = 2,
    V4L2_MEMORY_OVERLAY          = 3,
};

count 指定根据图像占用空间大小申请的缓存区个数,type 为视频捕获模式,memory 为内存区的使用方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct v4l2_buffer {
    __u32   index;
    enum v4l2_buf_type    type;
    __u32    bytesused;
    __u32    flags;
    enum v4l2_field  field;
    struct timeval    timestamp;
    struct v4l2_timecode   timecode;
    __u32     sequence;
  
    /* memory location */
    enum v4l2_memory    memory;
    union {
            __u32   offset;
            unsigned long   userptr;
    } m;
    __u32    length;
    __u32    input;
    __u32    reserved;
};

  index 为缓存编号

  type 为视频捕获模式

  bytesused 为缓存已使用空间大小

  flags 为缓存当前状态(常见值有 V4L2_BUF_FLAG_MAPPED | V4L2_BUF_FLAG_QUEUED | V4L2_BUF_FLAG_DONE,分别代表当前缓存已经映射、缓存可以采集数据、缓存可以提取数据)

  timestamp 为时间戳

  sequence为缓存序号

  memory 为缓存使用方式

  offset 为当前缓存与内存区起始地址的偏移

  length 为缓存大小

  reserved 一般用于传递物理地址值。

  另外 VIDIOC_QBUF 和 VIDIOC_DQBUF 命令都采用结构 v4l2_buffer 与驱动通信:VIDIOC_QBUF 命令向驱动传递应用程序已经处理完的缓存,即将缓存加入空闲可捕获视频的队列,传递的主要参数为 index;VIDIOC_DQBUF 命令向驱动获取已经存放有视频数据的缓存,v4l2_buffer 的各个域几乎都会被更新,但主要的参数也是 index,应用程序会根据 index 确定可用数据的起始地址和范围。

 

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。

三、调用v4l2的工作流程

  打开设备-> 检查和设置设备属性-> 设置帧格式-> 设置一种输入输出方法(缓冲 区管理)-> 循环获取数据-> 关闭设备。

 

(1)打开设备文件

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

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

  int cameraFd;

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

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

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

  关于阻塞模式和非阻塞模式

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

(2)取得设备的capability

          struct v4l2_capability capability;
          int ret = ioctl(fd, VIDIOC_QUERYCAP, &capability);

  看看设备具有什么功能,比如是否具有视频输入特性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct v4l2_capability cap;
 
memset(&cap, 0, sizeof(cap));
/* 获取设备支持的操作 */
if(ioctl(dev->fd, VIDIOC_QUERYCAP, &cap) < 0){
    if(EINVAL == errno){   /*EINVAL为返回的错误值*/
        printf(stderr,"%s is no V4L2 device\\n", dev->dev);
        return TFAIL;
    }
    else
    {
        printf(stderr,"%s is not V4L2 device,unknow error\\n", dev->dev);
        return TFAIL;
    }
}
//获取成功,检查是否有视频捕获功能
if(!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)){
    printf(stderr, "%s is no video capture device\\n",dev->dev);
    return TFAIL;
}
/* streaming I/O ioctls */
if(!(cap.capabilities & V4L2_CAP_STREAMING)){
    printf(stderr, "%s does not support streaming i/o\\n",dev->dev);
    return TFAIL;
}

(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 and VIDIOC_G_OUTPUT 返回当前的 video input和output的index.

1
2
3
4
5
6
7
8
9
10
struct v4l2_input {
__u32 index; /* Which input *
/__u8 name[32]; /* Label */
__u32 type; /* Type of input */
__u32 audioset; /* Associated audios (bitfield) */
__u32 tuner; /* Associated tuner */
v4l2_std_id std;
__u32 status;
__u32 reserved[4];
};

(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.heigth(高),fmt.pix.field(采样区域,如隔行采样),fmt.pix.pixelformat(采样类型,如 YUV4:2:2),然后通过 VIDIO_S_FMT 操作命令设置视频捕捉格式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.height      = 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)
{
    printf(stderr, "%s iformat not supported \\n",dev->dev);
    close(dev->fd);
    return TFAIL;
}

  注意:如果该视频设备驱动不支持你所设定的图像格式,视频驱动会重新修改struct v4l2_format结构体变量的值为该视频设备所支持的图像格式,所以在程序设计中,设定完所有的视频格式后,要获取实际的视频格式,要重新读取struct v4l2_format结构体变量。 

(6)向驱动申请帧缓存

  一般不超过5个,CAP_BUF_NUM = 4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
    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的申请缓冲区数据结构体类型struct v4l2_requestbuffers  ;

返回值说明: 执行成功时,函数返回值为 0;V4L2驱动层分配好了视频缓冲区;

(7)获取每个缓存的信息,并mmap到用户空间

应用程序和设备有三种交换数据的方法,直接 read/write、内存映射(memory mapping)和用户指针。这里只讨论内存映射(memory mapping)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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;
                          }
//addr 映射起始地址,一般为NULL ,让内核自动选择
//length 被映射内存块的长度
//prot 标志映射后能否被读写,其值为PROT_EXEC,PROT_READ,PROT_WRITE, PROT_NONE
//flags 确定此内存映射能否被其他进程共享,MAP_SHARED,MAP_PRIVATE
//fd,offset, 确定被映射的内存地址 返回成功映射后的地址,不成功返回MAP_FAILED ((void*)-1)
int munmap(void *addr, size_t length);// 断开映射
//addr 为映射后的地址,length 为映射后的内存长度

(8)开始采集视频 (在缓冲区处理好之后就可以获得视频了 )

在开始之前,还应当把缓冲帧放入缓冲队列,应用程序和设备有三种交换数据的方法,直接 read/write内存映射(memory mapping)和用户指针。这里只讨论内存映射(memory mapping)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//把四个缓冲帧放入队列
   for (i = 0; i < CAP_BUF_NUM; i++)
   {

(c)2006-2024 SYSTEM All Rights Reserved IT常识