• 流媒体: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语言的值传递,在这个函数中获取到了每一帧的开始地址和每一帧的大小,那么就是获取到一帧图像啦!

     

  • 相关阅读:
    优化Hibernate所鼓励的7大措施:
    Java未赋值变量的默认初始值
    年轻代
    JVM介绍
    Java锁的种类以及辨析
    java语言复制数组的四种方法
    static和final
    抽象函数抽象类
    try catch finally
    九大内置对象
  • 原文地址:https://www.cnblogs.com/dszhazha/p/V4L2.html
Copyright © 2020-2023  润新知