zoukankan      html  css  js  c++  java
  • 流媒体:V4L2视频获取

      从深圳回来已经20多天了,除了完善毕业设计程序和论文,其他时间都去玩游戏了。真的是最后的一段时间能够无忧无虑在校园里挥霍自己的青春了。今天完成的答辩,比想象的要简单,一直以来想把我现在的这个流媒体的东西用文字记录下来,但是都去玩了。但是今天开始还是把这些东西都记录下来。其实整个项目最开始接触的是socket编程,用socket写一个很简单的机遇POP3协议的邮件发送程序都觉得沾沾自喜。现在看来但是确实还是很幼稚的。。。

      其实V4L2就是LINUX下的一套API,我刚刚开始接触的时候觉得好难,完全就TMD看不懂啊。。。反正就是各种不靠谱,其实现在看来这些东西不难,其实很简单。只是当时没有决心去做而已。其实大多数的初学者都有我这样的想法,看着这些不熟悉的东西都会很烦躁,沉不住气不想去看。但是事实是多看看多GOOGLE查查基本就能理解了。下面是API的代码解析:

    1)打开一个视频设备:

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

      要从摄像头中回去到图像首先当然要打开一个摄像头,在LINUX中对摄像头的操作是对相应的设备文件进行操作实现的。在LINUX的根文件系统中/dev目录有很多设备文件,其中摄像头对应的是viode0,使用open函数打开,O_RDWR表示读写,O_NONBLOCK表示非阻塞,屏蔽掉表示阻塞方式(使用非阻塞方式调用视频设备,即使尚未捕获到视频信息,驱动依旧会把缓存里面的东西返回给应用程序,如果使用阻塞方式调用视频设备,正好相反,在没捕获到视频设备之前,程序会阻塞在OPEN函数所在的位置)open函数返回一个文件描述符fd,以后的程序中就是fd进行操作。

    2)ioctl()函数

      具体的ioctl()函数是啥玩意儿我也不知道,百度百科里面说是一种获得设备信息和向设备发送控制参数的手段。那么在我们的代码中就是通过这个函数来和摄像头进行“交互”。比如我要知道这个摄像头是什么型号,有多大的视野,我要获取多大的图像。。。等等,具体设置在后文中会有详细解释。ioctl()函数有3个参数,第一个是前面提到的文件描述符fd,就是我们要操作摄像头。第二个参数是命令,第三个参数是一个结构体,根据第二个参数的不同使用不同的结构体。

    a)在这个程序中ioclt函数用到的命令:

    VIDIOC_QUERYCAP  //查询设备功能信息

    VIDIOC_CROPCAP  //查询驱动的修剪能力

    VIDIOC_S_CROP  //设置视频信号的矩形边框                      

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

    VIDIOC_REQBUFS  //分配内存                           

    VIDIOC_QUERYBUF  //把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址

    VIDIOC_QBUF  //把数据从缓存中读取出来                                  

    VIDIOC_STREAMON  //开始视频流的获取

    VIDIOC_STREAMOFF  //结束视频流的获取               

    VIDIOC_DQBUF  //把数据放回缓存队列

    此处可戳这里:http://www.cnblogs.com/xmphoenix/archive/2011/08/20/2147064.html(反正这个V4L2比我写得好)

    b)程序中用到的结构体

    struct v4l2_capability cap  //返回当前视频设备所支持的功能;

    struct v4l2_cropcap cropcap  //设置设备的捕捉能力参数;

    struct v4l2_crop crop  //设置窗口的捕捉能力参数;

    struct v4l2_format fmt  //设置帧的格式,比如宽度,高度等;

    struct v4l2_requestbuffers req  //向驱动申请帧缓冲的请求,里面包含申请的个数;

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

    (以上所有的命令和结构体都是在程序中出现的,但是绝对不是完整的,还有很多没有没有一一列举出来)

    3)V4L2操作流程

    a.打开设备文件:

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

           使用open函数打开"/dev/video0"这个设备文件可以分为阻塞方式和非阻塞方式,如果使用非阻塞方式调用视频设备,即使尚未捕获到视频信息,驱动依旧会把缓存(DQBUFF)里面的东西返回给应用程序,如果使用阻塞方式调用视频设备,正好相反,在没捕获到视频设备之前,程序会阻塞在OPEN函数所在的位置。

    b.查询视频设备支持的功能:

    struct v4l2_capability cap;
    
    ioctl(fd, VIDIOC_QUERYCAP, &cap);

    利用ioctl()函数来对打开的设备文件而获得的句柄fd进行读写,第二个参数VIDIOC_QUERYCAP是查询设备属性命令,获取到的设备信息保存到 struct v4l2_capability声明的结构体中,通过判断结构体中capabilities成员的值来判断设备文件是不是支持视频捕捉(V4L2_CAP_VIDEO_CAPTURE)等操作。

    c.设置设备和窗口参数:

    struct v4l2_cropcap cropcap;
    
    struct v4l2_crop crop;
    
    ioctl(fd, VIDIOC_CROPCAP, &cropcap);
    
    ioctl(fd, VIDIOC_S_CROP, &crop);

    同样利用ioctl()函数,VIDIOC_CROPCAP用于查询设备的窗口属性,包括最大窗口左上角坐标和宽高、默认窗口左上角坐标和宽高等属性,根据查询到的值再使用VIDIOC_S_CROP命令和struct v4l2_crop声明的结构体来设置我们的窗口。

    d.设置获取帧的格式:

    struct v4l2_format fmt;
    
    ioctl(fd, VIDIOC_S_FMT, &fmt);

    先是为声明的结构体变量fmt即帧的属性赋值,包括帧类型、宽、高、帧的数据存储类型(YUVRGB)等,然后是ioctl()函数和对应的命令VIDIOC_S_FMT进行设置。

    e.向驱动申请帧缓冲和地址映射:

    struct v4l2_requestbuffers req;
    
    struct v4l2_buffer buf;
    
    buffers = calloc(req.count, sizeof(*buffers));
    
    ioctl(fd, VIDIOC_REQBUFS, &req);
    
     ioctl(fd, VIDIOC_QUERYBUF, &buf);
    
    mmap(NULL, // start anywhere
    
                         buf.length,
    
                         PROT_READ | PROT_WRITE,
    
                         MAP_SHARED,
    
                         fd, buf.m.offset);
    
     ioctl(fd, VIDIOC_QBUF, &buf);

    首先是用VIDIOC_REQBUFS命令向驱动申请帧(一般不超过5个),在结构体struct v4l2_requestbuffers有我们需要设置的参数,然后在我们的计算机内存中定义帧空间,用VIDIOC_QUERYBUF命令查询设备中的帧信息,并把查询到的信息保存到struct v4l2_buffer定义的结构体中,使用mmap()函数将设备中的帧的缓存地址一一映射成计算机内存中的绝对地址,然后使用VIDIOC_QBUF命令把这些帧放到缓存中,这样我们就可以方便对这些帧进行读取。

     f.采集和处理数据:

    enum v4l2_buf_type type;
    
    struct v4l2_buffer queue_buf;
    
    ioctl(fd, VIDIOC_STREAMON, &type);
    
    ioctl(fd, VIDIOC_DQBUF, &queue_buf);
    
    ioctl(fd, VIDIOC_QBUF, &queue_buf);

    V4L2有一个数据缓存,存放了前面已经申请数量的缓存数据。数据缓存采用FIFO的方式,当应用程序调用缓存数据时,缓存队列将最先采集到的视频数据送出,并重新回到缓存队列中等待接收数据。这个过程中需要用到两个命令VIDIOC_DQBUF和VIDIOC_QBUF来送去和放入缓存。当然在这之前我们需要使用VIDIOC_STREAMON来打开视频流。

    全部代码如下,我现在发现我封装得特别傻逼,但是还是写下来,有时间再改改,文件是video.c

    #include "video.h"
    
    
    static struct buffer * buffers = NULL;
    static unsigned int n_buffers = 0;
    
    extern findex;
    extern fd;
    
    int open_device()
    {
           fd = open("/dev/video0", O_RDWR/*|O_NONBLOCK*/, 0);
        if(-1 == fd)
        {
            printf("open error
    ");
            return -1;
        }
        return 0;
    }
    
    int close_device()
    {
        if(-1 == close(fd))
        {
            printf("close error
    ");
            return -1;
        }
        return 0;
    }
    
    int init_device()
    {
        struct v4l2_capability cap;
        struct v4l2_cropcap cropcap;
        struct v4l2_crop crop;
        struct v4l2_format fmt;
    
        if(-1 == ioctl(fd, VIDIOC_QUERYCAP, &cap))
        {
            printf("querycap error
    ");
            return -1;
        }
        printf("**************************************************
    ");
        printf("Driver Name:%s
    Card Name:%s
    Bus info:%s
    Driver Version:%u.%u.%u
    Capabilities:%u    
    ",cap.driver,cap.card,cap.bus_info,(cap.version>>16)&0xff, (cap.version>>8)&0xff,cap.version&0xff,cap.capabilities);
        printf("**************************************************
    ");
    
        if(!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
        {
            printf("Capture error
    ");
            return -1;
        }
    
        if(!(cap.capabilities & V4L2_CAP_STREAMING))
        {
            printf("Streaming error
    ");
            return -1;
        }
    
        CLEAR(cropcap);
    
        cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    
        if(0 == ioctl(fd, VIDIOC_CROPCAP, &cropcap))
        {
            
            CLEAR(crop);
            crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            crop.c = cropcap.defrect;
            if(-1 == ioctl(fd, VIDIOC_S_CROP, &crop))
            {
                switch (errno)
                {
                    case EINVAL:
                        printf("not support crop
    ");
                }
                printf("can't set VIDIOC_S_CROP
    ");
                //return -1;
            }
        }
        CLEAR(fmt);
    
        fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        fmt.fmt.pix.width = 640;
        fmt.fmt.pix.height = 480;
        fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;
        fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
    
        if(-1 == ioctl(fd, VIDIOC_S_FMT, &fmt))
        {
            printf("VIDIOC_S_FMT error
    ");
            return -1;
        }
        return 0;
    }
    
    int init_mmap()
    {
        struct v4l2_requestbuffers req;
        CLEAR(req);
    
        req.count = 4;
        req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        req.memory = V4L2_MEMORY_MMAP;
    
        if(-1 == ioctl(fd, VIDIOC_REQBUFS, &req))
        {     
                printf("VIDIOC_REQBUFS
    ");
                return -1;    
        }
    
        if(req.count < 2)
        {
            printf("Insufficient buffer memory
    ");
            return -1;
        }
    
        buffers = calloc(req.count, sizeof(*buffers));
    
        if(!buffers)
        {
            printf("out of memory
    ");
            return -1;
        }
    
        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(fd, VIDIOC_QUERYBUF, &buf))
            {
                printf("VIDIOC_QUERYBUF
    ");
                return -1;
            }
    
            buffers[n_buffers].length = buf.length;
            buffers[n_buffers].start =
                    mmap(NULL, // start anywhere
                         buf.length,
                         PROT_READ | PROT_WRITE,
                         MAP_SHARED,
                         fd, buf.m.offset);
    
            if(MAP_FAILED == buffers[n_buffers].start)
            {
                   printf("mmap error
    ");
                return -1;
            }
        }
        return 0;
    
    }
    
    int start_capturing()
    {
        unsigned int i;
        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;
    //        fprintf(stderr, "n_buffers: %d
    ", i);
    
            if(-1 == ioctl(fd, VIDIOC_QBUF, &buf))
            {
                printf("VIDIOC_QBUF error
    ");
                return -1;
            }
        }
    
        enum v4l2_buf_type type;
        type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    
        if(-1 == ioctl(fd, VIDIOC_STREAMON, &type))
        {
            printf("VIDIOC_STREAMON error
    ");
            return -1;
        }
        return 0;
    }
    
    int stop_capturing()
    {
        enum v4l2_buf_type type;
        type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    
        if(-1 == ioctl(fd, VIDIOC_STREAMOFF, &type))
        {
            printf(" VIDIOC_STREAMOFF error
    ");
            return -1;
        }
        return 0;
    }
    
    int uninit_mmap()
    {
        unsigned int i;
        for(i = 0; i < n_buffers; ++i)
        {
            if(-1 == munmap(buffers[i].start, buffers[i].length))
            {
                printf("munmap error
    ");
                return -1;
            }
    
        }
        free(buffers);
        return 0;
    }
    
    int get_frame(void **frame_buf, size_t* len)
    {
        struct v4l2_buffer queue_buf;
        CLEAR(queue_buf);
    
        queue_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        queue_buf.memory = V4L2_MEMORY_MMAP;
    
        if(-1 == ioctl(fd, VIDIOC_DQBUF, &queue_buf))
        {
            printf("VIDIOC_DQBUF error
    ");
            return -1;
        }
        printf("run to here
    ");
        *frame_buf = buffers[queue_buf.index].start;
        *len = buffers[queue_buf.index].length;
        findex = queue_buf.index;
        return 0;
    
    }
    
    int unget_frame()
    {
        if(findex != -1)
        {
            struct v4l2_buffer queue_buf;
            CLEAR(queue_buf);
    
            queue_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            queue_buf.memory = V4L2_MEMORY_MMAP;
            queue_buf.index = findex;
    
            if(-1 == ioctl(fd, VIDIOC_QBUF, &queue_buf))
            {
                printf("VIDIOC_QBUF error
    ");
                return -1;
            }
            return 0;
        }
        return -1;
    }

    这些代码我忘记当初是在那抄的了!!!!

    总结:get_frame的第一个参数是一个双重指针是由于C语言的值传递,在这个函数中获取到了每一帧的开始地址和每一帧的大小,那么就是获取到一帧图像啦!

     

  • 相关阅读:
    C#深入浅出 修饰符(二)
    HDU 5785 Interesting
    HDU 5783 Divide the Sequence
    HDU 5781 ATM Mechine
    UVA 714 Copying Books
    uva 1471 Defense Lines
    UVA 11134 Fabled Rooks
    UVA 11572 Unique Snowflakes
    UVA 11093 Just Finish it up
    UVA 10954 Add All
  • 原文地址:https://www.cnblogs.com/dszhazha/p/V4L2.html
Copyright © 2011-2022 走看看