流媒体:V4L2视频获取

转载: https://www.cnblogs.com/dszhazha/p/V4L2.html


从深圳回来已经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即帧的属性赋值,包括帧类型、宽、高、帧的数据存储类型(YUV\RGB)等,然后是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\n");
        return -1;
    }
    return 0;
}

int close_device()
{
    if(-1 == close(fd))
    {
        printf("close error\n");
        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\n");
        return -1;
    }
    printf("**************************************************\n");
    printf("Driver Name:%s\nCard Name:%s\nBus info:%s\nDriver Version:%u.%u.%u\nCapabilities:%u    \n",cap.driver,cap.card,cap.bus_info,(cap.version>>16)&0xff, (cap.version>>8)&0xff,cap.version&0xff,cap.capabilities);
    printf("**************************************************\n");

    if(!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
    {
        printf("Capture error\n");
        return -1;
    }

    if(!(cap.capabilities & V4L2_CAP_STREAMING))
    {
        printf("Streaming error\n");
        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\n");
            }
            printf("can't set VIDIOC_S_CROP\n");
            //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\n");
        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\n");
            return -1;    
    }

    if(req.count < 2)
    {
        printf("Insufficient buffer memory\n");
        return -1;
    }

    buffers = calloc(req.count, sizeof(*buffers));

    if(!buffers)
    {
        printf("out of memory\n");
        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\n");
            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\n");
            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\n", i);

        if(-1 == ioctl(fd, VIDIOC_QBUF, &buf))
        {
            printf("VIDIOC_QBUF error\n");
            return -1;
        }
    }

    enum v4l2_buf_type type;
    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    if(-1 == ioctl(fd, VIDIOC_STREAMON, &type))
    {
        printf("VIDIOC_STREAMON error\n");
        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\n");
        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\n");
            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\n");
        return -1;
    }
    printf("run to here\n");
    *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\n");
            return -1;
        }
        return 0;
    }
    return -1;
}

复制代码

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

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


猜你喜欢

转载自blog.csdn.net/zhenguo26/article/details/80722852