V4L2开发应用流程的各类超实用VIDIOC命令及其结构体集锦

    本人作为初入音视频领域的新人,观摩了各位大佬关于V4L2详细开发流程的满满干货,特意为这  
 两周的学习做个总结,希望后来者能顺利完成关于V4L2的第一个demo。这期内容主要内容为V4L2的  
 各类实用VIDIOC命令调用,各命令顺序总体上呈现的一个V4L2开发流程。

读前必看:
    1. 在函数原型中出现的 int request , 其中request 参数即所属的VIDIOC命令。
    2. 在例程中出现的函数形参 int fd , fd 是open()函数返回的文件描述符。
    3. 各类VIDIOC命令相关的ioctl接口函数返回值 ret,若小于零,则表示函数调用失败,若等于  
       零,表示成功执行。
    4. 各类VIDIOC命令相关的ioctl接口函数最好有执行失败检测,方便后续调试找问题。
    5. 常用的ioctl接口命令也在include/linux/videodev2.h中定义,则V4L2 必备的头文件:
       #include <linux/videodev2.h>。
    6. 挖掘头文件的时候,也看看 "/include/media/v4l2-dev.h",它定义了很多V4L2 ioctl()  
       将要用到的结构体。

常用的VIDIOC命令:
    1. VIDIOC_QUERYCAP (查询设备属性)
    2. VIDIOC_ENUM_FMT (显示所有支持的格式)
    3. VIDIOC_S_FMT (设置视频捕获格式)
    4. VIDIOC_G_FMT (获取硬件现在的视频捕获格式)
    5. VIDIOC_TRY_FMT (检查是否支持某种帧格式)
    6. VIDIOC_ENUM_FRAMESIZES (枚举设备支持的分辨率信息)
    7. VIDIOC_ENUM_FRAMEINTERVALS (获取设备支持的帧间隔)
    8. VIDIOC_S_PARM && VIDIOC_G_PARM (设置和获取流参数)
    9. VIDIOC_QUERYCAP (查询驱动的修剪能力)
    10. VIDIOC_S_CROP (设置视频信号的边框)
    11. VIDIOC_G_CROP (读取设备信号的边框)
    12. VIDIOC_REQBUFS (向设备申请缓存区)
    13. VIDIOC_QUERYBUF (获取缓存帧的地址、长度)
    14. VIDIOC_QBUF (把帧放入队列)
    15. VIDIOC_DQBUF (从队列中取出帧) 
    16. VIDIOC_STREAMON && VIDIOC_STREAMOFF (启动/停止视频数据流)

一. 视频捕获设备的打开和关闭

 1.打开设备:
  open()
  头文件:
      #include <sys/types.h>
      #include <sys/stat.h>
      #include <fcntl.h>
  函数原型: int fd=open(const char *pathname, int flags);
         or int fd=open(const char *pathname, int flags, mode_t mode);
          ps: 基本上用第一个函数原型

  函数参数:
    1. pathname:打开文件的路径名 ,如 "/dev/video0"  ,也可以是预定义字符串的宏,或  
       char*类型的字符串指针
    2. flags:用来控制打开文件的模式
    3. mode:用来设置创建文件的权限(rwx). 当flags使用 O_CREAT时才有效

  flags参数详解:
    1. O_READONLY: 只读模式
    2. O_WRONLY  :只写模式
    3. O_RDWR:  可读可写模式

  返回值:
     1.调用失败: 返回-1,并修改errno
     2. 调用成功: 返回一个int类型的文件描述符fd 

  应用于V4L2的实例,打开设备节点: /dev/video0
     int fd = open("/dev/video0", O_RDWR);      

    2.关闭设备
    头文件:#include <unistd.h>
    原型:    int close(int fd);
    实例:    close(fd);

二. 查询、设置、获取设备属性(QUERY,ENUM, SET, GET)

    2.1  VIDIOC_QUERYCAP   
     Fun:查询设备属性,即查询你是谁,你能干什么?
     函数原型:int ioctl(int fd, int request, struct v4l2_capability *argp);
        ps: 此处request 即 VIDIOC_QUERYCAP
        //相关结构体:
        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    
     //例程:
    int querycap_camera(int fd)
    {
        //获取驱动信息,VIDIOC_QUERCAP
        struct v4l2_capability cap;
        int ret = 0;
        memset(&cap,0, sizeof(cap));
        ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);
        if(ret < 0)
        {
            printf("VIDIOC_QUERYCAP failed(%d)\n", ret);
            return -1;
         }
        else{
            /*输出 caoability 信息*/
                printf("Capability Informations:\n");
                printf(" driver: %s\n", cap.driver);
                printf(" card: %s\n", cap.card);
                printf(" bus_info: %s\n", cap.bus_info);
                printf(" version: %08X\n", cap.version);
                printf(" capabilities: %08X\n", cap.capabilities);
        }
        printf("--------------------\n");       //可作为分割线。
        return ret;             
    }

  2.2 图像格式四剑客:
   1. VIDIOC_ENUM_FMT (显示所有支持的格式)
   2. VIDIOC_S_FMT(设置视频捕获格式)
   3. VIDIOC_G_FMT(获取硬件现在的视频捕获格式)
   4. VIDIOC_TRY_FMT(检查是否支持某种帧格式)

    1. VIDIOC_ENUM_FMT
    Fun:显示所有支持的格式,即允许应用查询所支持的格式,属于格式协商的部分
    函数原型:   int ioctl(int fd, int request, struct v4l2_fmtdesc *argp);
            此处 request 即 VIDIOC_ENUM_FMT,后续request 都由前文标注的VIDIOC 命令代替
    //相关的结构体:   
    struct v4l2_fmtdesc
    {
        __u32   index;                 // 要查询的格式序号,应用程序设置
        enum    v4l2_buf_type type;    // 帧类型,应用程序设置
        __u32   flags;                 // 是否为压缩格式
        __u8    description[32];       // 格式名称
        __u32   pixelformat;           // 格式
        __u32   reserved[4];           // 保留,不使用设置为0
    };
   参数分析:
    1. index :是用来确定格式的一个简单整型数。与其他V4L2所使用的索引一样,这个也是从0开  
       始递增,至最大允许值为止。应用可以通过一直递增索引值知道返回-EINVAL的方式枚举所有  
       支持的格式。
    2. type:是描述数据流类型的字段。对于视频捕获设备(摄像头或调谐器)来说,就是  
       V4L2_BUF_TYPE_VIDEO_CAPTURE.
    3. flags: 只定义了一个值,即V4L2_FMT_FLAG_COMPRESSED,表示这是一个压缩的视频格式。
    4. description: 是对这个格式的一种简短的字符串描述。
    5. pixelformat: 应是描述视频表现方式的四字符码
    ps: 其中第3、4、5项为就index所支持的某个图像格式,V4L2驱动会自动填写的结构体成员信  
        息。
    //fun: display all supported formats
    void enum_camera_format(int fd)
    {
        struct v4l2_fmtdesc fmtdesc;
        memset(&fmtdesc, 0, sizeof(fmtdesc));
        fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        printf("Supported format:\n");
        while(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) != -1){
            printf("\t%d.%s\n",fmtdesc.index+1, fmtdesc.description);   
            fmtdesc.index++;
        }
    }
    2. VIDIOC_S_FMT 
     Fun:设置视频捕获格式
     函数原型:  int ioctl(int fd, int request, struct v4l2_format* argp);
     //相关的结构体   
     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;  
     }; 
PS: 此处type 用于视频捕获设备来说也就是V4L2_BUF_TYPE_VIDEO_CAPTURE,对应fmt共用体中的  
pix,即pix是重点。
    //其中:
    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;         // 宽,必须是16的倍数
        __u32                 height;        // 高,必须是16的倍数
        __u32                 pixelformat;   // 视频数据存储类型,例如是//YUV4:2:2
        enum v4l2_field       field;  
        __u32                 bytesperline;  // 一行图像占用的字节数   
        __u32                 sizeimage;  
        enum v4l2_colorspace  colorspace;  
        __u32                 priv;    /* private data, depends on pixelformat */  
    };
  描述:
  可用v4l2_format 结构体用来设置摄像头的视频制式、帧格式等,在设置这个参数时应先填好  
v4l2_format 的各个域,如 type(传输流类型),fmt.pix.width(宽)fmt.pix.heigth(高),  
fmt.pix.field(采样区域,如隔行采样),fmt.pix.pixelformat(采样类型,如 YUV4:2:2),  
然后通过 VIDIO_S_FMT 操作命令设置视频捕捉格式。
    //例程:
    int set_camera_fmt_c(int fd, struct v4l2_format *fmt)
    {
        //设置视频格式,格式先在fmt结构体里设置好,再调用VIDIOC_S_FMT,
        int ret = 0;
        memset(fmt, 0, sizeof(*fmt));
        fmt->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        fmt->fmt.pix.width = 1920;
        fmt->fmt.pix.height =1080//获取1080P的视频帧
        fmt->fmt.pix.pixelformat =V4L2_PIX_FMT_YUYV;   //格式设置为YUV格式
        fmt->fmt.pix.field = V4L2_FIELD_ALTERNATE;          
        ret = ioctl(fd, VIDIOC_S_FMT, fmt);
        if(ret < 0){
            printf("VIDIOC_S_FMT failed(%d)\n", ret);
            return -1;
        }
        else{
            printf("VIDIOC_S_FMT succeed(%d)\n", ret);
        }
        return 0;
    }
      3. VIDIOC_G_FMT
         Fun:获取硬件现在的视频捕获格式
         函数原型:  int ioctl(int fd, int request, struct v4l2_format* argp);
         ps: VIDIOC_G_FMT命令与VIDIOC_S_FMT所使用的结构体是相同的,另外需要先有  
         VIDIOC_S_FMT设置图像格式,才能通过VIDIOC_G_FMT获取图像格式,否则会失败。
    //例程:
     int get_camera_fmt(int fd, struct v4l2_format *fmt)
    {
        //获取视频格式
        int ret = 0;
        ret = ioctl(fd, VIDIOC_G_FMT, fmt);
        if(ret < 0)
        {
            printf("VIDIOC_G_FMT failed\n");                
        }
        else{
            printf("VIDIOC_G_FMT succeed\n");
        }
        //printf stream format
        printf("Stream format informations:\n");
        printf(" type: %d\n", fmt->type);           //设置了空格,讲究格式。
         printf(" width: %d\n", fmt->fmt.pix.width);
         printf(" height: %d\n", fmt->fmt.pix.height);
         char fmtstr[8];        //定义一个字符数组,存储格式
         memset(fmtstr, 0, 8);
         memcpy(fmtstr, &fmt->fmt.pix.pixelformat, 8);  
         printf(" pixelformat: %s\n", fmtstr);
         printf(" field: %d\n", fmt->fmt.pix.field);
         printf(" bytesperline: %d\n", fmt->fmt.pix.bytesperline);
         printf(" sizeimage: %d\n", fmt->fmt.pix.sizeimage);
         printf(" colorspace: %d\n", fmt->fmt.pix.colorspace);
         printf(" priv: %d\n", fmt->fmt.pix.priv);
     return 0;
    }
      4. VIDIOC_TRY_FMT
          Fun:检查是否支持某种帧格式
          ps: 用处不多,本质上是不能改变图像格式的
          函数原型: int ioctl(int fd, int request, struct v4l2_format *argp);
          相关的结构体: 和VIDIOC_S_FMT、VIDIOC_G_FMT使用的结构体是同一个类型
    //例程:
    struct v4l2_format fmt;
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32;
    if(ioctl(fd, VIDIOC_TRY_FMT, &fmt)){
        if(errno == EINVAL){
            printf("not support format RGB32\n");
        }
    }

  2.3 枚举 设备支持的分辨率信息
      VIDIOC_ENUM_FRAMESIZES
      fun: 针对VIDIOC_ENUM_FMT所列出的图像格式信息,列举某一格式所对应的分辨率信息
      函数原型: int ioctl( int fd, int request, struct v4l2_frmsizeenum *arg)
      描述: 
        这个ioctl 命令允许应用枚举就VIDIOC_ENUM_FMT获取的设备支持的某一图像格式,视频  
    设备支持的所有的视频帧大小。
    //相关的结构体:
    struct v4l2_frmsizeenum
    {
        __u32 index;            //IN:index of the given frame size in the enumeration
        __u32 pixel_format;     //IN:pixel format for which the frame sizes are enumerated
        __u32 type;             //OUT: frame size type the device supports
        union                   //OUT: frame size with the given index
        {
            struct v4l2_frmsize_discrete  discrete;
            struct v4l2_frmsize_stepwise  stepwise;
        };
        __u32 reserved[2];          
    };

    struct v4l2_frmsize_discrete
    {
        __u32   width;      
        __u32   height;
    };

    struct v4l2_frmsize_stepwise
    {
        __u32   min_width;      //minimum frame width[pixel]
        __u32   max_width;      //maximum frame width[pixel]
        __u32   step_width;     //frame width step size[pixel]
        __u32   min_height;     //minimum frame height[pixel]
        __u32   max_height;     //maximum frame height[pixel]
        __u32   step_height;    //frame height step size[pixel]
    };
    //例程:
    //ps: int resolution [][3][15][2] 采集各设备节点各格式的分辨率
    int enum_camera_fmt_frmsize(int resolution [][3][15][2])
    {
        int camera_node= 0;
        char device_node[20]={};
        int j=sprintf(device_node, CAMERA_DEVICE);
        int k= 0; //分辨率数组的第一维,表示设备节点,dev_node,0-15
        for(camera_node; camera_node<16;camera_node++)
        {
            sprintf(device_node+j, "%d",camera_node);
            printf("camera node: %d\n",camera_node);
            printf("%d.this is %s\n", camera_node, device_node);
            k = camera_node;   //分辨率数组的第一维,表示设备节点,0-15
            //打开设备节点
            int fd = 0;
            fd = open(device_node, O_RDWR,0); 
            if(fd < 0)
            {
                printf("%d.Open %s failed\n", camera_node, device_node);
                //return -1;
                return 0;   //不想在此处结束程序,返回main函数,因为遍历总会遇到不存在的设备节点
            }       
            struct v4l2_fmtdesc fmtdesc;        //  VIDIOC_ENUM_FMT
            struct v4l2_frmsizeenum frmsize;    //VIDIOC_ENUM_FRAMSIZES
            memset(&fmtdesc, 0, sizeof(fmtdesc));
            fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            int x=0,y=0;        //四维数组的第二维,第三维. format, dpi(resolution)
            printf("Supported format:\n");
            while(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) == 0){
              printf("%c%c%c%c\n",fmtdesc.pixelformat &0xff,  
                     (fmtdesc.pixelformat>>8)&0xff,  
                     (fmtdesc.pixelformat>>16)&0xff,
                     (fmtdesc.pixelformat>>24)&0xff);
              printf("\t%d.%s\n",fmtdesc.index+1, fmtdesc.description);   //__u8 string

              // 列举格式后,列举设备的分辨率
              frmsize.pixel_format = fmtdesc.pixelformat;  //VIDIOC_ENUM_FRAMSIZES 初始化
              frmsize.index = 0;                   
              while(ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize)==0)
               {
                   if(frmsize.type == V4L2_FRMSIZE_TYPE_DISCRETE)
                   {
                       resolution[k][x][y][0]=frmsize.discrete.width;
                       resolution[k][x][y][1]=frmsize.discrete.height;
                       printf("DISCRETE: line-%d %d: {%d*%d}\n", __LINE__,
                                   frmsize.index,frmsize.discrete.width,
                                   frmsize.discrete.height);
                   }
                   else if(frmsize.type == V4L2_FRMSIZE_TYPE_STEPWISE)
                   {
                       resolution[k][x][y][0]=frmsize.stepwise.max_width;
                       resolution[k][x][y][1]=frmsize.stepwise.max_height;
                       printf("STEPWISE: line: %d %d: {%d*%d}\n", __LINE__,
                               frmsize.index,frmsize.stepwise.max_width,
                               frmsize.stepwise.max_height);       
                   }
                   //获取帧间隔
                   enum_camera_frmival(fd, &frmsize);
                   frmsize.index++;
                    y = frmsize.index;          //即Y代表某格式的一个分辨率
              }
              fmtdesc.index++;
              x= fmtdesc.index;             //x代表某一格式。
              y= 0;                         //到下一个格式,分辨率清零
          }
        }
       return 0;
    }

    2.4 获取设备支持的帧间隔
     VIDIOC_ENUM_FRAMEINTERVALS
     Fun: enumerate frame intervals  
     函数: int ioctl(int fd, int request, struct v4l2_frmivalenum *argp);
     描述:   
         这个ioctl函数允许应用枚举那些给定像素格式和帧大小的所有间隔。要获取支持的像素  
     格式和帧大小可通过VIDIOC_ENUM_FMT和VIDIOC_ENUM_FRAMESIZES回调函数获取。返回值  
     和 v4l2_frmivalenum.type filed 依赖于该设备支持的帧间隔类型。
    //相关的结构体:
    struct v4l2_frmivalenum
    {
        __u32 index;
        __u32 pixel_format;
        __u32 width;
        __u32 height;
        __u32 type;
        union   
        {
            struct v4l2_fract discrete;
            struct v4l2_frmival stepwise;
        };
       __u32 reserved[2];
    };  

    enum v4l2_frmivaltypes
    {
        V4L2_FRMIVAL_TYPE_DISCRETE = 1,
        V4L2_FRMIVAL_TYPE_CONTINUOUS = 2,
        v4l2_FRMIVAL_TYPE_STEPWISE =3
    };
    //例程:
    /*列举可以有的帧间隔 frame intervals */
        int enum_camera_frmival(int fd, struct v4l2_frmsizeenum* framesize)
        {
            struct v4l2_frmivalenum frmival;
            memset(&frmival,0, sizeof(frmival));
            frmival.pixel_format = framesize ->pixel_format;
            frmival.width = framesize->discrete.width;
            frmival.height = framesize->discrete.height;
            //frmival.type = V4L2_FRMIVAL_TYPE_DISCRETE;
            frmival.index = 0 ; 
             printf("\tthe frame intervals enum\n");

             while(ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival)==0)
             {       
                     //输出分数,即帧间隔
                     printf("%d.frameinterval:%u/%u\n ", frmival.index,
                     frmival.discrete.numerator, frmival.discrete.denominator); 
                     frmival.index++;
             }
            printf(".........................................\n");
            return 0;
        }
    ```


----------


    2.5 设置和获取流参数
        VIDIOC_S_PARM && VIDIOC_G_PARM
        功能: 设置和获取数据流的参数,用于设置帧率等参数
        函数原型: int ioctl( int fd, int request,  v4l2_streamparm *argp);
                Ps: 先设置后获取, 否则先获取会失败。

        描述:
            现行的视频标准决定了每一秒名义上的帧数。如果捕捉和输出的帧数少于这个数量,那么  
        应用将会要求在驱动侧跳帧或者复制。这个在使用read()或write()函数时特别有用,这类函  
        数并不能被时间戳或者序列计数所扩张,并且能避免无效的数据copy。
            进一步的这些ioctl函数能用于决定驱动在I/O模式使用的内在的buffer的数量。这方面  
        看论述read()功能的部分。为了获取/设置流参数,应用分别调用了VIDIOC_G_PARM,  
        VIDIOC_S_PARM ioctl,它们带有指向v4l2_streamparm的指针,结构体里含有 一个存放输  
        入输出设备的各自参数的联合体。

        //结构体1:
        struct v4l2_streamparm
        {
            enum v4l2_buf_type type;         /*The buffer (stream) type, same as struct v4l2_formattype*/
            union 
            {
                struct v4l2_captureparm capture;  /*Parameters for capture devices, used when type is V4L2_BUF_TYPE_VIDEO_CAPTURE.*/
                struct v4l2_outputparm  output;   /*Parameters for output devices, used when type is V4L2_BUF_TYPE_VIDEO_OUTPUT*/
                __u8    raw_data[200];           //A place holder for future extensions.
            }parm;

        };
        //结构体2:
        struct v4l2_captureparm 
        {
            __u32 capability;   
            __u32 capturemode;
            struct v4l2_fract timeperframe;  
            __u32  extendedmode;        
            __u32  readbuffers;
            __u32  reserved[4];
        };
成员分析:
1. capability成员为一组功能标签,目前为止仅有一个被定义:V4L2_CAP_TIMEPERFRAME,它代  
   表可以改变的帧频率。
2. capturemode也只定义了一个标签:V4L2_MODE_HIGHQUALITY,这个标签意在使硬件在适于单  
   帧捕获的高清模式下工作。这种模式为达到设备可处理的最佳图片质量,能做出任何牺牲(包括   
   支持的格式、曝光时间)。
3. timeperframe成员用于指定想要使用的帧率,也是一个结构体,在应用中可设置,为最高的帧  
   率,驱动会自适应。
4. extendedmode在API中没有明确意义,不使用的时候应确保其为0。
5. readbuffers字段是read()操作被调用时内核应为输入帧准备的缓冲区数量。
6. reserved不使用时,永远把reserved 设为0。
        //结构体3:
        struct v4l2_fract           //fraction的简写,分数 
        {
            __u32 numerator;        //分子
            __u32 denominator;      //分母
        };
ps: numerator 和 denominator所描述的系数是成功帧之间的时间间隔

总体:
   设置参数将调用VIDIOC_S_PARM。在这种情况下,驱动将参数设为与应用所提供的参数尽可能  
近的值,并调整v4l2_streamparm结构体以反应当前值。如果应用提供timeperframe为0, 驱  
动应设置为与当前帧视频制式相对应的帧率。若readuffers或writebuffers是0, 驱动应返回  
现行设置而非删除缓存区。
        //例程:
        /*VIDIOC_S_PARM*/
        int main()
        {
            int framerate;   //自定义
            struct v4l2_streamparm  *setfps;
            setfps = (struct v4l2_streamparm*)calloc(1, sizeof(struct v4l2_streamparm)); 
            set_camera_streamparm(fd,setfps,framerate)

        }

        int set_camera_streamparm(int fd, struct v4l2_streamparm *setfps, int framerate)
        {
            memset(setfps,0, sizeof(*setfps));
            setfps->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //1.设置捕捉设备格式
            setfps->parm.capture.timeperframe.numerator =1;//分子
            //分母, 每秒钟通过的帧数
            setfps->parm.capture.timeperframe.denominator = framerate;  
            int ret = ioctl(fd, VIDIOC_S_PARM, setfps);
            if(ret){
                printf("VIDIOC_S_PARM to set fps failed(%d)\n",ret);
                return -1;
            }
            else {
                printf("VIDIOC_S_PARM to set fps succeed(%d)\n",ret);
            }
            return 0;
        }

        /* VIDIOC_G_PARM */
        int get_camera_streamparm(int fd, struct v4l2_streamparm *setfps)
        {
            int ret = ioctl(fd, VIDIOC_G_PARM, setfps);
                if(ret){                        //ret ==0 表示函数成功,
                    printf("VIDIOC_G_PARM failed(%d)\n",ret);
                    return -1;
                }
                else {
                    __u32 x = setfps->parm.capture.timeperframe.numerator;
                    __u32 y = setfps->parm.capture.timeperframe.denominator;
                    printf("this camera is %dfps\n",y/x);
                    printf("VIDIOC_G_PARM succeed(%d)\n",ret);
                }
            return 0;
        }

2.6 VIDIOC_CROP三剑士
  1. VIDIOC_QUERYCAP : 查询驱动的修剪能力
  2. VIDIOC_S_CROP: 设置视频信号的边框
  3. VIDIOC_G_CROP: 读取设备信号的边框

  1.VIDIOC_CROPCAP
    函数:
    int ioctl(int fd,int request, struct _cropcap *argp);
        //相关的结构体:
        struct v4l2_cropcap
        {
            enum v4l2_buf_type type;    // 数据流的类型,应用程序设置
            struct v4l2_rect bounds;    // 这是 camera 的镜头能捕捉到的窗口大小的局限
            struct v4l2_rect defrect;   // 定义默认窗口大小,包括起点位置及长,宽的大小
            struct v4l2_fract pixelaspect;  // 定义了图片的宽高比
        };

        struct v4l2_rect
        {
            __s32 left;
            __s32 top;
            __s32 width;
            __s32 height;
        };

        struct v4l2_fract           //fraction的简写,分数 
        {
            __u32 numerator;        //分子
            __u32 denominator;      //分母
        };
    2. VIDIOC_G_CROP&&VIDIOC_S_CROP函数:
        int ioctl(int fd, int request, struct v4l2_crop *argp);
        int ioctl(int fd, int request, const struct v4l2_crop *argp);
        //相关的结构体:
        struct v4l2_crop
        {
            enum v4l2_buf_type type;// 应用程序设置
            struct v4l2_rect c;
        };
        以下案例是转载:
        Example 1-10. Resetting the cropping parameters

        /* Fun:VIDIOC_S_CROP裁剪 VIDIOC_CROPCAP所得到的默认框的大小 */
        struct v4l2_cropcap cropcap;
        struct v4l2_crop crop;
        memset (&cropcap, 0, sizeof (cropcap));
        cropcap.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        if (-1 == ioctl (fd, VIDIOC_CROPCAP, &cropcap))
         {
            perror ("VIDIOC_CROPCAP");
            exit (EXIT_FAILURE);
        }
        memset (&crop, 0, sizeof (crop));
        crop.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        crop.c = cropcap.defrect;
        /* Ignore if cropping is not supported (EINVAL). */
        if (-1 == ioctl (fd, VIDIOC_S_CROP, &crop)&& errno != EINVAL)
         {
            perror ("VIDIOC_S_CROP");
            exit (EXIT_FAILURE);
        }

   2.7 VIDIOC_STD 四剑客
    ps: 视频标准在摄像头V4L2应用开发中不是很重要,作为补充信息。若不是紧要,可略过
    1. VIDIOC_QUERYSTD: 检查当前视频设备支持的标准,例如PAL或NTSC
    2. VIDIOC_ENUMSTD: 允许应用查询设备实现了哪些标准
    3. VIDIOC_S_STD: 用于应用想要申请某个特定标准
    4. VIDIOC_G_STD: 查询设备当前激活的标准

    1. VIDIOC_QUERYSTD:
        函数原型: int ioctl(int fd, int request, v4l2_std_id *std); 
    2. VIDIOC_ENUMSTD
        函数原型: int ioctl (int fd, int request, struct v4l2_standard  *argp);
    3. VIDIOC_S_STD
        函数原型: int ioctl(int fd, int request, v4l2_std_id std);
    4. VIDIOC_G_STD  
        函数原型: int ioctl(int fd, int request, v4l2_std_id *std);
        相关的数据类型
        1.typedef u64 v4l2_std_id;
        2.struct v4l2_standard 
            {
                U32                  index;
                v4l2_std_id          id;
                u8                   name[24];
                struct v4l2_fract    frameperiod; /* Frames, not fields */
                u32                  framelines;
                U32                  reserved[4];
            };
描述: 
    视频标准通常是由每一个国家的监管部门制定,描述的是视频如何为传输而进行格式化分辨率、  
帧率等。现在世界上使用的标准主要有三个 :
        1. NTSC(北美用 )
        2. PAL(欧洲 、非洲和中国 )
        3. SECAM( 法、俄和非洲部分地区)

    然而这些标准在国家之间都有变化,而且有些设备比其他设备能更加灵活,能与更多的标准变  
 种协同工作。
    V4L2使用  v4l2_std_id来代表视频标准, 它是一个 64 位的掩码 。每个标准变种在掩码  
 中就是一位。所 以 “ 标准 ”NTSC 的定义为 V4L2_STD_NTSC_M,值为 0x1000,而日本的变种  
 就是V4L2_STD_NTSC_M_JP (0x2000)。如果一个设备可以处理所有 NTSC变种,它就可以设为   
 V4L2_STD_NTSC,它将所有相关位置位。对PAL和 SECAM标准,也存在一组类似的位集。

 2.8 VIDIOC_INPUT 三剑师
   ps:选择视频输入
         一个视频设备可以有多个视频输入,如果只有一路输入,这个功能可以没有。
   函数:
    1. VIDIOC_ENUMINPUT
     int ioctl(int fd, int request , struct v4l2_input  *input);
    Ps:视频捕获的应用应首先要通过VIDIOC_ENUMINPUT来枚举所有可用的输入.
    2. VIDIOC_S_INPUT
    int ioctl(int fd, int requeset, unsigned int input.index);
    3. VIDIOC_G_INPUT
    int ioctl(int fd, int requeset, unsigned int *input.index);
        //相关的结构体:
        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];
        };
参数分析:
1. index:应用关注的输入索引号,这是唯一一个用户空间设定的字段。驱动要分配索引号给输入,  
   从0开始,依次增加。想要知道所有可用的输入,应用会调用VIDIOC_ENUMINPUT,索引号会从0  
   开始,并开始递增。一旦驱动返回-EINVAL(超出限制),应用就知道,输入已经枚举完了。另  
   外只要有输入,输入索引号0就一定存在。    
2. name:输入的名称,由驱动设定。可以简单的设为"Camera",诸如此类,如果卡上有多个输入,  
   名称就要与接口的打印信息相符合。     
3. type:输入类型。目前只有两个值可选:V4L2_INPUT_TYPE_TUNER和  
   V4L2_INPUT_TYPE_CAMERA。
4. audiose:描述哪个音频输入可以与哪些视频输入相关联。音频输入与视频输入一样通过索引号  
   枚举,但并非所有的音频与视频的组合都是可用的。这个字段是一个掩码,代表对于当前枚举出  
   的视频而言,哪些音频输入是可以与之关联的。如果没有音频输入可以与之关联,或是只有一个 
   可选,那么就将这个字段置0。
5. tuner: 电子调谐器。如果输入是一个调谐器(type字段是V4L2_INPUT_TYPE_TUNER),这个  
   字段就是会包含一个相应的调谐设备的索引号。
6. v4l2_std_id std:描述设备支持哪个或哪些视频标准。
7. status: 给出输入状态。完整的标识符集合可以在V4L2文档中找到;简要地说,status中设置  
   的每一位都代表一个问题。这些问题包括掉电、无信号、失锁,存在Macrovision模拟防拷贝系  
   统或是其他一些不幸的问题。
8. reserved[4]:保留字段,驱动应设置为0。

   2.9  VIDIOC_OUTPUT三间客
        函数:
        1. VIDIOC_ENUMOUTPUT
        int ioctl(int fd, int request, struct v4l2_output *output);
        2. VIDIOC_S_OUTPUT
        int ioctl(int fd, int request, unsigned int index);
        3. VIDIOC_G_OUTPUT
        int ioctl(int fd, int request, unsigned int *index);
        结构体:
        Ps:VIDIOC_OUTPUT三间客都涉及该结构体,具体形参不同
        struct v4l2_output
        {
            __u32   index;          //输出索引号,工作方式与输入的索引号相同,从0开始递增
            __u8    name[32];       //输出的名称
            __u32   type;           //输出类型
            __u32   audioset;       //能与视频协同工作的音频集
            __u32   modulator;      //与此设备相关的调制器(仅当type==V4L2_OUTPUT_TYPE_MODULATOR)
            v4l2_std_id std;        //输出所支持的视频标准
            __u32   reserved[4];    //保留字段,应设置为0
        }
    __u32 type 支持的输出类型:
        1. V4L2_OUTPUT_TYPE_MODULATOR:用于模拟电视调制器
        2. V4L2_OUTPUT_TYPE_ANALOG:用于基本模拟视频视频输出
        3. V4L2_OUTPUT_TYPE_ANALOGVGAOVERLAY:用于模拟VGA覆盖设备

三. 申请和管理buf, 及内存映射(mmap)

  3.1 VIDIOC_REQBUFS
    Fun: 向设备申请缓存区
    函数原型:int ioctl(int fd, int request, struct v4l2_requestbuffers *argp);
        //相关结构体;
        struct v4l2_requestbuffers
        {
            __u32 count;                // 缓存数量,也就是说在缓存队列里保持多少张照片
            enum v4l2_buf_type type;    // 数据流类型,对视频捕获设备应是V4L2_BUF_TYPE_VIDEO_CAPTURE
            enum v4l2_memory memory;    // V4L2_MEMORY_MMAP 或 V4L2_MEMORY_USERPTR
            __u32 reserved[2];
        };
        //其中:
         enum v4l2_memory {  
                V4L2_MEMORY_MMAP             = 1,  
                V4L2_MEMORY_USERPTR          = 2,  
                V4L2_MEMORY_OVERLAY          = 3,  

         };
         //例程:
        int reqbuf_camera(int fd,struct v4l2_requestbuffers *reqbuf)
        {   
            int ret = 0;
            //请求分配内存    VIDIOC_REQBUFS
            reqbuf->count = BUFFER_COUNT;
            reqbuf->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            reqbuf->memory = V4L2_MEMORY_MMAP;

            ret = ioctl(fd, VIDIOC_REQBUFS, reqbuf);
            if(ret < 0)
            {
                printf("VIDIOC_REQBUFS failed(%d)\n", ret);
                return -1;
            }
            else{
                printf("VIDIOC_REQBUFS succeed(%d)\n", ret);
            }
            return 0;
        }

    3.2 VIDIOC_QUERYBUF
     Fun: 获取缓存帧的地址、长度
     函数原型:int ioctl(int fd, int request, struct v4l2_buffer *argp);
        相关的结构体:
        struct v4l2_buffer
        {
            u32     index;              // buffer 序号
            enum    v4l2_buf_type type; // buffer 类型
            u32     bytesused;          // buffer 中已使用的字节数
            u32     flags;              // 区分是MMAP 还是USERPTR
            enum v4l2_field field;
            struct  timeval timestamp;  // 获取第一个字节时的系统时间
            struct  v4l2_timecode timecode;
            u32     sequence;           // 队列中的序号
            enum v4l2_memory memory;    // IO 方式,被应用程序设置
            union m
            {
                u32 offset;             // 缓冲帧地址,只对MMAP 有效
                unsigned long userptr;
            };
            u32     length;             // 缓冲帧长度
            u32     input;
            u32     reserved;
        };
   补充:      
    1. 同时 VIDIOC_QBUF 和 VIDIOC_DQBUF 命令都采用结构 v4l2_buffer。
    2. VIDIOC_QBUF 命令向驱动传递应用程序已经处理完的缓存,即将缓存加入空闲可捕获视频  
       的队列,传递的主要参数为 index;VIDIOC_DQBUF 命令向驱动获取已经存放有视频数据  
       的缓存,v4l2_buffer 的各个域几乎都会被更新,但主要的参数也是 index,应用程序会  
       根据index 确定可用数据的起始地址和范围。

    3.3 内存映射(Memmory Mapping)
     1. 映射(mmap)
        头文件:#include <sys/mman.h>
        函数:
        void *mmap(void* addr,size_t length,int prot,int flags,int fd,off_t offset)
        参数详解:
    1. addr: 映射起始地址,一般为NULL ,让内核自动选择
    2. length:被映射内存块的长度
    3. prot :标志映射后能否被读写,其值PROT_EXEC,PROT_READ,PROT_WRITE, PROT_NONE
    4. flags: 确定此内存映射能否被其他进程共享,MAP_SHARED, MAP_PRIVATE
    5. fd,offset, 确定被映射的内存地址返回成功映射后的地址,不成功返回  
       MAP_FAILED ((void*)-1);

     2. 断开映射(munmap)
        头文件:#include <unistd.h> , #include <sys/mman.h>
        函数:int munmap(void* addr, size_t length);
        参数: addr 为映射后的地址,length 为映射后的内存长度
        //例程:
        int main()
        {
           VideoBuffer* framebuf = calloc(reqbuf->count,sizeof(VideoBuffer));   
            /*set buf*/
            struct v4l2_buffer buffer,  *buf;
            buf = &buffer;
            querybuf_and_mmap(fd, reqbuf, framebuf, buf);
         }

        int querybuf_and_mmap(int fd, struct v4l2_requestbuffers *reqbuf, VideoBuffer* framebuf, struct v4l2_buffer *buf)
        {
            memset(buf, 0, sizeof(*buf));
            int i=0, ret=0;
            for(i=0; i<reqbuf->count; i++)
            {  //设置buf的index,type,memoy
                buf->index = i;
                buf->type =  V4L2_BUF_TYPE_VIDEO_CAPTURE;
                buf->memory = V4L2_MEMORY_MMAP;
                ret = ioctl(fd, VIDIOC_QUERYBUF, buf);
                if(ret<0)
                {
                    printf("VIDIOC_QUERYBUF failed(%d)\n",ret);
                    return -1;
                }

                //mmap buffer,逐一映射buffer
                framebuf[i].length = buf->length;       //重新获得buf的长度,首地址
                framebuf[i].start = (char *)mmap(0, buf->length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, buf->m.offset);
                if(framebuf[i].start == MAP_FAILED)
                {
                    printf("mmap(%d)failed: %s\n", i,strerror(errno));          
                    return -1;
                }
                //qbuf,放入队列
                queue_buf(fd, buf);

                //显示缓存帧信息
                printf("Frame buffer %d: address=0x%x, length=%d\n", i, (unsigned int)framebuf[i].start, framebuf[i].length);
            }
            return 0;
         }

3.4 VIDIOC_QBUF
    Fun: 把帧放入队列
    函数:int ioctl(int fd, int request, struct v4l2_buffer *argp);
    结构体类型与 VIDIOC_QUERYBUF,VIDIOC_DQBUF相同
        //例程:
        int queue_buf(int fd, struct v4l2_buffer *buf )
        {
            int ret = ioctl(fd, VIDIOC_QBUF, buf);
            if(ret<0)
            {
                printf("VIDIOC_QBUF failed(%d)\n",ret);
                return -1;      
            }
            else{
                printf("VIDIOC_QBUF buf.index:%d\n",buf->index);
                printf("VIDIOC_QBUF succeed(%d)\n",ret);
            }
            return 0;
        }

3.5 VIDIOC_DQBUF 
    Fun: 从队列中取出帧
    函数:int ioctl(int fd, int request, struct v4l2_buffer *argp);
    结构体类型与VIDEOC_QBUF、VIDIOC_QUERYBUF相同
        //例程:
        int dequeue_buf(int fd, struct v4l2_buffer *buf)
        {
            //get frame   VIDIOC_DQBUF
            int ret = ioctl(fd, VIDIOC_DQBUF, buf);
            if(ret <0)
            {
                printf("VIDIOC_DQBUF failed(%d)\n",ret);
                return -1;
            }   
            else{
                printf("VIDIOC_DQBUF buf.index:%d\n",buf->index);
                printf("VIDIOC_DQBUF succeed(%d)\n",ret);
            }
            return 0;
        }

四. 启动/停止视频数据流

    VIDIOC_STREAMON && VIDIOC_STREAMOFF
        接第三部分缓冲区处理后,就可以开始获取数据了   
     函数原型:int ioctl(int fd, int request, const int *argp);

    1. VIDIOC_STREAMON 
       函数原型: int ioctl(int fd, int request, enum v4l2_buf_type *type);
       Fun: 启动数据流
       int ret = ioctl(fd, VIDIOC_STREAMON, &type);

    2. VIDIOC_STREAMOFF 
       函数原型: int ioctl(int fd, int request, enum v4l2_buf_type *type);
       Fun: 关闭数据流
       int ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
        //例程:
        int get_camera_videodata(int fd)
        {   
            //开始录制 VIDIOC_STREAMON
            enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
            int ret = ioctl(fd, VIDIOC_STREAMON, &type);
            if(ret < 0)
            {
                printf("VIDIOC_STREAMON failed(%d)\n", ret);
                return ret;
            }
            else{
                printf("VIDIOC_STREAMON succeed(%d)\n", ret);
            }
            return 0;
        }

        int close_camera(int fd,VideoBuffer* framebuf)
        {
            //关闭设备并解除映射
            close(fd);
            int i=0;
            for(i=0;i<4;i++)
            {
                munmap(framebuf[i].start, framebuf[i].length);
            }
            printf("Camera test Done.\n");
            return 0;
        }

五. 参考文档

  1. 百度V4L2:
    https://baike.baidu.com/item/V4L2
  2. LIinux系统下V4L2简单抓帧
    https://blog.csdn.net/arm11082/article/details/51851017
  3. 和菜鸟一起学linux之V4L2摄像头流程
    https://blog.csdn.net/eastmoon502136/article/details/8190262
  4. V4L2 超详细讲解
    https://wenku.baidu.com/view/acaef8d8f01dc281e43af004.html
  5. V4L2的学习建议和流程分析
    https://www.cnblogs.com/silence-hust/p/4464291.html
  6. V4L2常用命令标志符和结构
    https://blog.csdn.net/u011425939/article/details/5367186

|版权声明:欢迎文明转载共享,请标明文章出处。作者Blog地址 :https://blog.csdn.net/Mark_minGE/article/details/81427489

猜你喜欢

转载自blog.csdn.net/Mark_minGE/article/details/81427489