V4L2介绍

一、 V4L2概述

V4L2(video 4 linux 2)是针对uvc免驱usb设备的编程框架,主要用于采集usb摄像头等。不同kernel版本可能会有差异,下面介绍基于kernel 2.6.39版本。

V4L2有以下几种接口:

1. 视频采集接口(video capture interface):这种应用的设备可以是高频头或者摄像头.V4L2的最初设计就是应用于这种功能的

2. 视频输出接口(video output interface):可以驱动计算机的外围视频图像设备--像可以输出电视信号格式的设备

3. 直接传输视频接口(video overlay interface):它的主要工作是把从视频采集设备采集过来的信号直接输出到输出设备之上,而不用经过系统的CPU

4. 视频间隔消隐信号接口(VBI interface):它可以使应用可以访问传输消隐期的视频信号

5. 收音机接口(radio interface):可用来处理从AMFM高频头设备接收来的音频流

 

本文只介绍与摄像头(video capture interface)相关内容。

二、 摄像头应用

(一)一般操作流程:

1.打开设备文件。 int fd=open(”/dev/video0”,O_RDWR /*|O_NONBLOCK*/);
2.取得设备的capability,看看设备具有什么功能,比如是否具有视频输入,或者音频输入输出等。VIDIOC_QUERYCAP,struct v4l2_capability
3.设置视频帧格式,帧的格式包括宽度和高度等。VIDIOC_S_FMT, struct v4l2_format
4.向驱动申请帧缓冲,一般不超过5个。struct v4l2_requestbuffers
5.将申请到的帧缓冲映射到用户空间,这样就可以直接操作采集到的帧了,而不必去复制。mmap
6.将申请到的帧缓冲全部入队列,以便存放采集到的数据.VIDIOC_QBUF,struct v4l2_buffer
7.开始视频的采集。VIDIOC_STREAMON
8.出队列以取得已采集数据的帧缓冲,取得原始采集数据。VIDIOC_DQBUF
9.将缓冲重新入队列尾,这样可以循环采集。VIDIOC_QBUF
10.停止视频的采集。VIDIOC_STREAMOFF
11.关闭视频设备。close(fd);

Demo见附录1

(二)摄像头常用ioctl命令(videodev2.h)

VIDIOC_REQBUFS:分配内存 

struct v4l2_requestbuffers {

__u32 count;

enum v4l2_buf_type      type;

enum v4l2_memory        memory;

__u32 reserved[2];

};

requestbuffers.count = 5; /*根据需要可调整。命令返回时count的值为实际分配到buffer个数*/

requestbuffers.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

requestbuffers.memory = V4L2_MEMORY_MMAP; /*UVC驱动只支持这种memory*/

ioctl(fd, VIDIOC_REQBUFS, & requestbuffers);

VIDIOC_QUERYBUF:把VIDIOC_REQBUFS中分配的数据缓存映射到本进程地址空间 

buf.index = i; /*i:0~n-1n为实际驱动分配的buffer个数*/

buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

buf.memory = V4L2_MEMORY_MMAP;

ioctl(fd, VIDIOC_QUERYBUF, &buf)

mmap(0 /* start anywhere */ , 

buf.length,

 PROT_READ, MAP_SHARED, fd,

 buf.m.offset);

VIDIOC_QUERYCAP:查询驱动功能              

ioctl(fd, VIDIOC_QUERYCAP, &cap)

cap.driver:驱动名称

cap.card:设备名称

cap.bus_info:总线信息

cap.version:版本信息

cap.capabilities:设备能力

如:driver:uvcvideo,card:UVC Camera (046d:0825),bus_info:usb-0000:02:03.0-1,version:0x100,capabilities:0x4000001

#define V4L2_CAP_VIDEO_CAPTURE0x00000001  /* Is a video capture device */

#define V4L2_CAP_VIDEO_OUTPUT 0x00000002  /* Is a video output device */

#define V4L2_CAP_VIDEO_OVERLAY 0x00000004  /* Can do video overlay */

#define V4L2_CAP_VBI_CAPTURE 0x00000010  /* Is a raw VBI capture device */

……………….

#define V4L2_CAP_STREAMING              0x04000000  /* streaming I/O ioctls */

VIDIOC_ENUM_FMT获取当前驱动支持的视频格式 

fmt.index = 0;/*index递增可查询所有支持格式*/

fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

ioctl(fd, VIDIOC_ENUM_FMT, &fmt)

命令返回信息:

pixelformat:像素格式,如MJPG0x47504A4D)、YUYV0x56595559

description:格式描述,如MJPEG、YUV 4:2:2 (YUYV)

VIDIOC_ENUM_FRAMESIZES:获取视频帧大小

fsize.index = 0; /*index递增可查询某种pixel_format支持的所有视频帧大小*/

fsize.pixel_format = pixfmt; /* VIDIOC_ENUM_FMT 命令返回的pixelformat */

ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fsize)

命令返回信息:

fsize.type = V4L2_FRMSIZE_TYPE_DISCRETE; /* UVC驱动固定返回这种类型 */

fsize.discrete.width = frame->wWidth;

fsize.discrete.height = frame->wHeight;

VIDIOC_ENUM_FRAMEINTERVALS:获取帧率

fival.index = 0; /*index递增可查询某种帧格式支持的所有帧率*/

fival.pixel_format = pixfmt;

fival.width = width;

fival.height = height;

ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &fival)

命令返回信息:

fival.discrete.numerator

fival.discrete.denominator

denominator/ numerator即fps

VIDIOC_S_FMT设置当前驱动的频捕获格式 

    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    fmt.fmt.pix.width = width;

    fmt.fmt.pix.height = height;

    fmt.fmt.pix.pixelformat = formatIn; /* VIDIOC_ENUM_FMT 命令返回的pixelformat 之一*/

fmt.fmt.pix.field = V4L2_FIELD_ANY;

    ioctl(fd, VIDIOC_S_FMT, & fmt);

VIDIOC_G_FMT读取当前驱动的频捕获格式 

    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

ioctl(fd, VIDIOC_G_FMT, & fmt);

命令返回信息:

fmt.pix.pixelformat  /*像素格式,如V4L2_PIX_FMT_MJPEG,V4L2_PIX_FMT_YUYV等*/

fmt.pix.width /*帧宽度*/

fmt.pix.height  /*帧高度*/

fmt.pix.field = V4L2_FIELD_NONE;/* 摄像头设备一般不会做数据交错*/

fmt.pix.bytesperline /*UVC驱动返回为0,不知道原因*/

fmt.pix.sizeimage /*图像大小,以字节为单位*/

fmt.pix.colorspace /*enum v4l2_colorspace */

VIDIOC_TRY_FMT验证当前驱动的显示格式 

该命令和VIDIOC_S_FMT类似,不同之处在于该命令不会真正改变显示格式,多用来协商显示参数。

VIDIOC_S_PARM:设置视频帧率

    setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    setfps.parm.capture.timeperframe.numerator=1;

    setfps.parm.capture.timeperframe.denominator= fps;

    ioctl(fd, VIDIOC_S_PARM, setfps);

VIDIOC_DQBUF把数据从缓存中读取出来

    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    buf.memory = V4L2_MEMORY_MMAP;

ret = ioctl(fd, VIDIOC_DQBUF, &buf);

命令返回信息:

buf. index  /*取出的是哪块内存*/

fival. bytesused  /*读出的数据大小*/

 

VIDIOC_QBUF把数据放回缓存队列 

buf.index = i;

buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

buf.memory = V4L2_MEMORY_MMAP;

ioctl(fd, VIDIOC_QBUF, &buf);

VIDIOC_STREAMON开始视频捕获 

int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

ioctl(fd, VIDIOC_STREAMON, &type);

VIDIOC_STREAMOFF结束视频捕获 

int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    ioctl(fd, VIDIOC_STREAMOFF, &type);

三、 V4L2框架结构

1V4L2在内核中结构

 

V4L2Linux系统中的结构图

Linux系统中视频输入设备主要包括以下四个部分:

字符设备驱动程序核心:V4L2本身就是一个字符设备,具有字符设备所有的特性,暴露接口给用户空间;

V4L2驱动核心:主要是构建一个内核中标准视频设备驱动的框架,为视频操作提供统一的接口函数;

平台V4L2设备驱动:在V4L2框架下,根据平台自身的特性实现与平台相关的V4L2驱动部分,包括注册video_devicev4l2_dev

具体的sensor驱动:主要上电、提供工作时钟、视频图像裁剪、流IO开启等,实现各种设备控制方法供上层调用并注册v4l2_subdev

 

V4L2的核心源码位于drivers/media/video,源码以实现的功能可以划分为四类:

核心模块实现:由v4l2-dev.c实现,主要作用申请字符主设备号、注册class和提供video device注册注销等相关函数;

V4L2框架:由v4l2-device.cv4l2-subdev.cv4l2-fh.cv4l2-ctrls.c等文件实现,构建V4L2框架;

Videobuf管理:由videobuf2-core.cvideobuf2-dma-contig.cvideobuf2-dma-sg.cvideobuf2-memops.cvideobuf2-vmalloc.cv4l2-mem2mem.c等文件实现,完成videobuffer的分配、管理和注销。

Ioctl框架:由v4l2-ioctl.c文件实现,构建V4L2ioctl的框架。

2UVCV4L2

 

 

a)V4L2驱动注册

const struct v4l2_file_operations uvc_fops = {

.owner= THIS_MODULE,

.open= uvc_v4l2_open,

.release= uvc_v4l2_release,

.unlocked_ioctl= uvc_v4l2_ioctl,

.read= uvc_v4l2_read,

.mmap= uvc_v4l2_mmap,

.poll= uvc_v4l2_poll,

};

static int uvc_register_video(struct uvc_device *dev,

struct uvc_streaming *stream)

{

struct video_device *vdev;

int ret;

 

/* Initialize the streaming interface with default streaming

 * parameters.

 */

ret = uvc_video_init(stream);

if (ret < 0) {

uvc_printk(KERN_ERR, "Failed to initialize the device "

"(%d).\n", ret);

return ret;

}

 

/* Register the device with V4L. */

vdev = video_device_alloc();

if (vdev == NULL) {

uvc_printk(KERN_ERR, "Failed to allocate video device (%d).\n",

   ret);

return -ENOMEM;

}

 

/* We already hold a reference to dev->udev. The video device will be

 * unregistered before the reference is released, so we don't need to

 * get another one.

 */

vdev->parent = &dev->intf->dev;

vdev->fops = &uvc_fops;

vdev->release = uvc_release;

strlcpy(vdev->name, dev->name, sizeof vdev->name);

 

/* Set the driver data before calling video_register_device, otherwise

 * uvc_v4l2_open might race us.

 */

stream->vdev = vdev;

video_set_drvdata(vdev, stream);

 

ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);

if (ret < 0) {

uvc_printk(KERN_ERR, "Failed to register video device (%d).\n",

   ret);

stream->vdev = NULL;

video_device_release(vdev);

return ret;

}

 

atomic_inc(&dev->nstreams);

return 0;

}

b)V4L2文件操作(v4l2_file_operations)

应用层对/dev/video0设备文件的操作,通过系统调用直接作用于V4L2注册cdev时的v4l2_fops上,然后v4l2_fops会调用video_register_device传入的video_device. fops,uvc_fops。

由此可见,V4L2并不是摄像头驱动,只是一个框架而已。它向上提供字符设备节点、标准化ioctl命令,方便应用开发视频类应用程序;向下提供V4L2设备注册接口,方便开发硬件驱动。

3帧缓冲区管理

启动视频采集后,驱动程序开始采集一帧数据,把采集的数据放入视频采集输入队列的第一个帧缓冲区,一帧数据采集完成,也就是第一个帧缓冲区存满一帧数据后,驱动程序将该帧缓冲区移至视频采集输出队列,等待应用程序从输出队列取出。驱动程序接下来采集下一帧数据,放入第二个帧缓冲区,同样帧缓冲区存满下一帧数据后,被放入视频采集输出队列。

  应用程序从视频采集输出队列中取出含有视频数据的帧缓冲区,处理帧缓冲区中的视频数据,如存储或压缩。

  最后,应用程序将处理完数据的帧缓冲区重新放入视频采集输入队列,这样可以循环采集,如下图所示。

四、 UVCUSB Video Class)简介

Linux UVC driver(uvc) 该驱动适用于符合USB视频类(USB Video Class)规范的摄像头设备,它包括V4L2内核设备驱动和用户空间工具补丁。

USB摄像头大体上可以分为UVC camerasnon-UVC camerasUVC是一个开放的标准,拥有维护良好的驱动,它属于内核代码的一部分2.6.26版本kernel以后)。插入摄像头后就可以工作,而无须编译或安装额外的驱动。

2.6.26版本以前的kernel上没有UVC驱动。在这种低版本的kernel上要支持UVC cameras则需要额外编译UVC驱动并加载之。

 

 

 

 

 

五、 V4L2相关Kernel配置

2.5.X内核开始包含V4L22.6.26 kernel开始包含了UVC

 

Device Drivers  --->           

<*> Multimedia support  --->           

<*>Video For Linux  

[*] Enable Video For Linux API 1 (DEPRECATED) 

[*]   Video capture adapters  ---> 

[*]   V4L USB devices  --->  

<*>   USB Video Class (UVC)   

[*]     UVC input events device support  

 

如果这些驱动没有编译到内核中,只是以驱动模块形式存在,加载顺序如下:

insmod v4l2-common.ko 

insmod compat_ioctl32.ko  

insmod v4l1-compat.ko 

insmod videodev.ko

insmod uvcvideo.ko

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

附录1  

/********************

** cam_demo.c

********************/

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <assert.h>

#include <getopt.h>            

#include <fcntl.h>             

#include <unistd.h>

#include <errno.h>

#include <malloc.h>

#include <sys/stat.h>

#include <sys/types.h>

#include <sys/time.h>

#include <sys/mman.h>

#include <sys/ioctl.h>

#include <asm/types.h>         

#include <linux/videodev2.h>

 

#define CLEAR(x) memset (&(x), 0, sizeof (x))

 

struct buffer {

        void *                  start;

        size_t                  length;

};

static char *           dev_name        = "/dev/video0";//摄像头设备名

static int              cam_fd              = -1;

struct buffer *         buffers         = NULL;

static unsigned int     n_buffers       = 0;

FILE *file_fd;

//static unsigned long file_length;

 

//

//获取一帧数据

//

static int read_frame (void)

{

    struct v4l2_buffer buf;

    unsigned int i;

 

    CLEAR (buf);

    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    buf.memory = V4L2_MEMORY_MMAP;

    int ff = ioctl (cam_fd, VIDIOC_DQBUF, &buf);

    if(ff<0)

        printf("failture\n"); //出列采集的帧缓冲

 

    assert (buf.index < n_buffers);

 

    printf ("buf.index dq is %d,\n",buf.index);    

    fwrite(buffers[buf.index].start, buffers[buf.index].length, 1, file_fd); //将其写入文件中

    ff=ioctl (cam_fd, VIDIOC_QBUF, &buf); //再将其入列

    if(ff<0)

        printf("failture VIDIOC_QBUF\n"); 

 

    return 1;

}

 

int main (int argc,char ** argv)

{

    struct v4l2_capability cap; 

    struct v4l2_format fmt;

    unsigned int i;

    enum v4l2_buf_type type;

 

    file_fd = fopen("test-mmap.jpg", "w");//图片文件名

    if(file_fd < 0)

    {

        return -1;

    }

    cam_fd = open (dev_name, O_RDWR /* required */ | O_NONBLOCK, 0);//打开设备

    if(cam_fd < 0)

    {

        return -1;

    }

    int ff=ioctl (cam_fd, VIDIOC_QUERYCAP, &cap);//获取摄像头参数

    if(ff<0)

        printf("failture VIDIOC_QUERYCAP\n");

 

    struct v4l2_fmtdesc fmt1;

    int ret;

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

    fmt1.index = 0;

    fmt1.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    while ((ret = ioctl(cam_fd, VIDIOC_ENUM_FMT, &fmt1)) == 0) 

    {

        fmt1.index++;

        printf("{ pixelformat = '%c%c%c%c', description = '%s' }\n",

                fmt1.pixelformat & 0xFF, (fmt1.pixelformat >> 8) & 0xFF,

                (fmt1.pixelformat >> 16) & 0xFF, (fmt1.pixelformat >> 24) & 0xFF,

                fmt1.description);             

 

    }

 

    CLEAR (fmt);

    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    fmt.fmt.pix.width = 640 / 2;  

    fmt.fmt.pix.height = 480 / 2;

  

    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;//V4L2_PIX_FMT_JPEG;//V4L2_PIX_FMT_YUYV;//V4L2_PIX_FMT_YVU420;//V4L2_PIX_FMT_YUYV;

    fmt.fmt.pix.field       = V4L2_FIELD_ANY;//V4L2_FIELD_INTERLACED;

    ff = ioctl (cam_fd, VIDIOC_S_FMT, &fmt); //设置图像格式

    if(ff<0)

        printf("failture VIDIOC_S_FMT\n");

 

//    file_length = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height; //计算图片大小

    struct v4l2_streamparm setfps;

 

    memset (&setfps, 0, sizeof (struct v4l2_streamparm));

    setfps.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    setfps.parm.capture.timeperframe.numerator = 1;

    setfps.parm.capture.timeperframe.denominator = 30;

    ff = ioctl (cam_fd, VIDIOC_S_PARM, &setfps);  

    if (ff < 0)

        printf ("failture VIDIOC_S_PARM\n");

 

    struct v4l2_requestbuffers req;

    CLEAR (req);

    req.count               = 1;

    req.type                = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    req.memory              = V4L2_MEMORY_MMAP;

 

    ioctl (cam_fd, VIDIOC_REQBUFS, &req); //申请缓冲,count是申请的数量

    if(ff<0)

        printf("failture VIDIOC_REQBUFS\n");

 

    if (req.count < 1)

       printf("Insufficient buffer memory\n");

 

     

    buffers = calloc (req.count, sizeof (*buffers));//内存中建立对应空间

    for (n_buffers = 0; n_buffers < req.count; ++n_buffers) 

    {

        struct v4l2_buffer buf;   //驱动中的一帧

        CLEAR (buf);

        buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;

        buf.memory      = V4L2_MEMORY_MMAP;

        buf.index       = n_buffers;

        if (-1 == ioctl (cam_fd, VIDIOC_QUERYBUF, &buf)) //映射用户空间

            printf ("VIDIOC_QUERYBUF error\n");

 

        buffers[n_buffers].length = buf.length;

        buffers[n_buffers].start = mmap (NULL /* start anywhere */,    //通过mmap建立映射关系

                                                            buf.length,

                                                            PROT_READ | PROT_WRITE /* required */,

                                                            MAP_SHARED /* recommended */,

                                                            cam_fd, buf.m.offset);

        if (MAP_FAILED == buffers[n_buffers].start)

            printf ("mmap failed\n");

    }

 

    for (i = 0; i < n_buffers; ++i) 

    {

        struct v4l2_buffer buf;

        CLEAR (buf);

        buf.type        = V4L2_BUF_TYPE_VIDEO_CAPTURE;

        buf.memory      = V4L2_MEMORY_MMAP;

        buf.index       = i;

        if (-1 == ioctl (cam_fd, VIDIOC_QBUF, &buf))//申请到的缓冲进入列队

            printf ("VIDIOC_QBUF failed\n");

    }

 

    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    if (-1 == ioctl (cam_fd, VIDIOC_STREAMON, &type)) //开始捕捉图像数据

        printf ("VIDIOC_STREAMON failed\n");

 

    for (;;) //这一段涉及到异步IO

    {

        fd_set fds;

        struct timeval tv;

        int r;

 

        FD_ZERO (&fds);//将指定的文件描述符集清空

        FD_SET (cam_fd, &fds);//在文件描述符集合中增加一个新的文件描述符

 

        /* Timeout. */

        tv.tv_sec = 2;

        tv.tv_usec = 0;

        r = select (cam_fd + 1, &fds, NULL, NULL, &tv);//判断是否可读(即摄像头是否准备好),tv是定时

        if (-1 == r) {

            if (EINTR == errno)

            continue;

            printf ("select err\n");

        }

 

        if (0 == r) {

            fprintf (stderr, "select timeout\n");

            exit (EXIT_FAILURE);

        }

 

        if (read_frame ())//如果可读,执行read_frame ()函数,并跳出循环

            ; //break;

 

        static int count = 0;

        if (++count > 200)

            break;

    }

 

unmap:

    for (i = 0; i < n_buffers; ++i)

        if (-1 == munmap (buffers[i].start, buffers[i].length))

            printf ("munmap error");

    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;   

    if (-1 == ioctl(cam_fd, VIDIOC_STREAMOFF, &type))   

        printf("VIDIOC_STREAMOFF"); 

 

    close (cam_fd);

    fclose (file_fd);

    exit (EXIT_SUCCESS);

    return 0;

}



Logo

更多推荐