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
 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
  #include
  #include
  函数原型: 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
  原型: 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
{
  /*The buffer (stream) type, same as struct v4l2_formattype*/
  enum v4l2_buf_type type;		 
  union 
  {
    /*Parameters for capture devices, used when type is V4L2_BUF_TYPE_VIDEO_CAPTURE.*/
    struct v4l2_captureparm capture;  
    /*Parameters for output devices, used when type is V4L2_BUF_TYPE_VIDEO_OUTPUT*/
    struct v4l2_outputparm	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

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
   函数:
    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 , #include
   函数: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; icount; 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

你可能感兴趣的:(视频)