v4l2协议,v4l2编程

  v4l2协议,v4l2编程

  先看读/写。如果VIDIOC_QUERYCAP调用返回的v4l2_capability参数中V4L2_CAP_READWRITE设置为true,则表示支持读/写I/O。这是最简单最原始的方法。它需要复制数据(而不是像内存映射那样只交换指针),不交换元数据(比如帧计数器和时间戳,可以用来识别丢帧和同步帧)。虽然这是最原始的方法,但由于其简单性,对于简单的应用程序(如捕捉静止图像)非常有用。

  如果使用读/写方法,则必须同时支持另外两个函数,select()和poll()。这两个功能用于多路复用I/O。

  流式传输有两种方式,驱动程序对这两种方式的支持通过使用VIDIOC_REQBUFS来确定:

  int ioctl(int fd,int request,struct v4 L2 _ request buffers * argp);

  关于存储器映射模式,存储器映射缓冲器通过VIDIOC_REQBUFS应用于设备存储器,并且必须在映射进入应用虚拟地址空间之前应用。至于用户指针,用户缓冲区是应用自己创建的,驱动只是通过VIDIOC_REQBUFS转换成User pointers的I/O模式。这两种方法都不会复制数据,只是缓冲区指针的交互。

  我们先来看看数据结构v4l2_requestbuffers:

  __u32计数

  //要申请的缓冲区数量。仅当内存设置为V4L2 _内存_MMAP时,才会设置该参数

  枚举v4l2_buf_type类型

  枚举v4l2_memory内存

  //v4l 2 _ MEMORY _ MMAP或V4L2_MEMORY_USERPTR

  对于内存映射模式,要申请设备内存下的缓冲区,应用程序必须初始化上述三个参数。驱动程序返回的缓冲区数量可能等于count,或者小于或大于count。少于那个可能是内存不足,多于那个可能是驱动为了更好的完成相应的功能而增加的缓冲。如果驱动程序不支持内存映射,调用这个ioctl将返回EINVAL。

  因为内存映射模式分配的是真实的物理内存,而不是虚拟内存,所以使用后必须使用munmap()来释放。

  应用程序可以再次调用VIDICO_REQBUFS来改变缓冲区的数量,但前提是必须先释放映射的缓冲区。您可以先执行munmap,然后将参数count设置为0,以释放所有缓冲区。

  对于用户指针I/O,应用只需要设置上述类型和内存类型。

  申请buffer之后,内存映射之前,首先要用VIDIOC_QUERYBUF获取分配的缓冲区信息,然后传递给函数mmap()进行映射:

  int ioctl(int fd,int request,struct v4 L2 _ buffer * argp);

  VIDIOC_QUERYBUF是一种用于这种内存映射模式的方法。没有必要在用户指针模式下使用该功能。在调用之前,应用需要在v4l2_buffer中设置两个参数,一个是缓冲区类型,一个是索引号(有效值从0到应用的缓冲区数减1)。调用此ioctl会将相应缓冲区中的标志:V4L2 _ BUF _ flag _ mapped、V4L2 _ BUF _ flag _ queued和V4L2_BUF_FLAG_DONE设置为有效。让我们仔细看看数据结构v4l2_buffer:

  __u32索引

  //应用程序来设置,只是为了声明哪个缓冲区

  枚举v4l2_buf_type类型

  __u32字节已使用

  //如果是输入流,//缓冲区中已经使用的字节数由驱动程序设置,否则由应用程序设置。

  __u32标志

  //定义了buffer的一些标志位来表示这个buffer在哪个队列,比如输入队列还是输出队列(v4l 2 _ BUF _ flag _ queued v4l 2 _ BUF _ flag _ done),是否是关键帧等。详情请参考规格。

  枚举v4l2_memory内存

  //v4 L2 _内存_ MMAP/v4 L2 _内存_用户ptr/v4 L2 _内存_覆盖

  联盟m

  __u32偏移量

  //当内存类型为v4l 2 _ me memory _ MMAP时,主要用于表示设备内存中缓冲区相对起始位置的偏移量,主要用于MMAP()参数中,对应用没有影响。

  无符号long userptr

  //当内存类型为V4L2_MEMORY_USERPTR时,这是指向虚拟内存中buffer的指针,由应用程序设置。

  __u32长度

  //缓冲区的大小

  驱动程序中管理两个缓冲队列,一个输入队列和一个输出队列。对于捕获设备,当输入队列中的缓冲区被数据填满时,它将自动成为输出队列。在等待调用VIDIOC_DQBUF处理数据后,再次调用VIDIOC_QBUF将缓冲区放回输入队列。对于输出设备,缓冲区在显示后自动成为输出队列。

  所有刚初始化的映射缓冲区最初都处于出队状态,由驱动程序管理,应用程序无法访问。对于capture应用程序,首先通过VIDIOC_QBUF将所有映射的缓冲区添加到队列中,然后通过VIDIOC_STREAMON启动capture,应用程序进入read循环,在这里应用程序将等待直到一个缓冲区被填满,可以从队列中出列,然后在数据用完时入队到输入队列中;对于输出应用程序,首先应用程序用数据填充缓冲区,然后将它排入队列。当足够多的缓冲区进入队列时,它调用VIDIOC_STREAMON输出数据。

  有两种方法可以阻止应用程序的执行,直到缓冲区可以出队。默认情况下,当调用VIDIOC_DQBUF时,它将被阻塞,直到传出队列中有数据。但是,如果在打开设备文件时使用O_NONBLOCK,那么在调用VIDIOC_DQBUF且没有数据读取时,会立即返回。另一种方法是调用select和poll来监视文件描述符是否可读。

  两个ioctl,VIDIOC_STREAMON和VIDIOC_STREAMOFF用于开始和停止捕获或输出,VIDIOC_STREAMOFF删除输入和输出队列中的所有缓冲区。

  所以如果drvier要实现内存映射I/O,就必须支持vidioc _ reqbufs,vidioc _ querybuf,vidioc _ qbuf,vidioc _ streamon和vidioc _ streamoff ioctl,mmap(),munmap(),select()和poll()函数。

  用户指针是一种I/O方法,结合了读/写和内存映射的优点。缓冲区由应用程序本身应用,可以在虚拟内存或共享内存中。在捕获和输出方面,它与内存映射方法基本相同。这里只提到它申请内存的方式。

  在用户指针模式下,申请的内存也是以内存页大小为单位对齐的,对buffersize也有一定的限制。这就是示例代码中计算缓冲区大小的方式。暂时不知道这样分配缓冲区大小的依据是什么。就像这样简单地使用它:

  page _ size=get pagesize();

  buffer _ size=(buffer _ size page _ size-1)~(page _ size1);

  缓冲区[n_buffers]。start=memalign ( page_size,

  buffer _ size);

  3、开始_捕获

  经过以上一系列的数据协商和缓冲区分配,可以调用VIDIOC_QBUF将所有缓冲区添加到输入队列中,调用VIDIOC_STREAM0N开始捕获数据:

  int ioctl(int fd,int request,struct v4 L2 _ buffer * argp);

  //VIDIOC_QBUF VIDIOC_DQBUF

  int ioctl(int fd,int request,const int * argp);

  //vidi oc _ stream 0 vidi oc _ stream off(int参数为缓冲区类型)

  4、主循环

  开始捕获数据后,您将进入一个主循环。您可以使用select或poll来监控文件描述符的状态。一旦数据可读,您将调用函数来读取数据。

  5、读_帧

  读取数据因I/O模式而异:

  读/写模式直接从文件描述符中读取一帧大小的数据;

  在内存映射模式下,首先将一个缓冲区从输出队列中出队,然后处理帧数据,并在处理完成后将其放入输入队列。

  在用户指针模式下,首先从输出队列中取出一个缓冲区,然后判断该缓冲区是否是应用程序开始申请的缓冲区,然后处理该缓冲区,最后放入输入队列。

  6、停止捕获/取消激活设备/关闭设备

  最后,捕获、释放资源并关闭设备。

  下面是一个示例代码:

  #包含stdio.h

  #包含stdlib.h

  #包含字符串. h

  # include fcntl . h/*低级

  输入/输出*/

  #包括unistd.h

  #包含错误号h

  #包含malloc.h

  #包含系统/统计信息

  #包含sys/types.h

  #包含系统/时间. h

  #include sys/mman.h

  #包含sys/ioctl.h

  #包含linux/videodev2.h

  #定义设备/开发/视频

  静态结构v4l2 _ requestbuffers请求

  结构缓冲区

  {

  无效*开始

  unsignedintlength

  };

  静态结构缓冲区*缓冲区;

  静态结构v4l2 _ buffer缓冲区

  usb_camera.c

  #包含" head.h "

  intmain()

  {

  intfd

  FD=open _ device();

  获取设备信息(FD);

  get _ frame _ fmt(FD);

  获取当前帧信息(FD);

  try _ format _ support(FD);

  设置_帧_格式(FD);

  apply _ memory _ buf(FD);

  内存映射(FD);

  缓冲区入队(FD);

  关闭(FD);

  返回0;

  }

  intopen _ device()

  {

  intfd

  if(-1==(fd=open(DEVICE,O_RDWR)))

  printf(信息:无法打开视频设备\ n’);

  其他

  printf(信息:打开设备:%d\n ,FD);

  返回FD;

  }

  获取设备信息(intfd)

  {

  结构v4l2 _能力上限

  if(-1==ioctl(fd,VIDIOC_QUERYCAP,CAP))

  printf( info:vidi oc _ query cap ERROR \ n );

  其他

  printf(信息:驱动程序名称:%s.卡名:%s.总线信息:%s.驱动程序版本:%u.%u.%u\n ,

  cap.driver,cap.card,cap.bus_info,(第16版)0XFF,(第8版)0XFF,cap .版本0x ff);

  返回1;

  }

  intget_frame_fmt(intfd)

  {

  结构v4l2 _ fmtdesc fmtdesc

  fmtdesc。索引=0;

  fmtdesc。TYPE=v4 L2 _缓冲_类型_视频_捕获;

  printf( info:Support format:);

  while(ioctl(fd,VIDIOC_ENUM_FMT,fmtdesc)!=-1)

  {

  printf(\t%d.%s ,fmtdesc.index 1,fmtdesc。描述);

  fmtdesc.index

  }

  printf( \ n );

  返回1;

  }

  int获取当前帧信息(intfd)

  {

  结构v4l2 _格式fmt

  fmt。TYPE=v4 L2 _缓冲_类型_视频_捕获;

  ioctl(fd,VIDIOC_G_FMT,fmt);

  printf(信息:当前数据格式信息:\ n \ t %d\n \高度:% d \ n ,fmt.fmt.pix.width,fmt。fmt。pix。身高);

  结构v4l2 _ fmtdesc fmtdesc

  fmtdesc。索引=0;

  fmtdesc。TYPE=v4 L2 _缓冲_类型_视频_捕获;

  while(ioctl(fd,VIDIOC_ENUM_FMT,fmtdesc)!=-1)

  {

  如果(fmt desc。像素格式fmt。fmt。pix。像素格式)

  {

  printf( \ t格式:%s\n ,fmt desc。描述);

  打破;

  }

  fmtdesc.index

  }

  返回1;

  }

  inttry_format_support(intfd)

  {

  结构v4l2 _格式fmt

  fmt。TYPE=v4 L2 _缓冲_类型_视频_捕获;

  //fmt。fmt。PIX。像素格式=v4 L2 _ PIX _ FMT _ RGB 32;

  fmt。fmt。PIX。像素格式=v4 L2 _ PIX _ FMT _ YUYV;

  if(ioctl(fd,VIDIOC_TRY_FMT,fmt)==-1)

  if(errno==EINVAL)

  printf(信息:不支持格式RGB32!\ n’);

  返回1;

  }

  整数帧格式

  {

  结构v4l2 _格式fmt

  fmt。TYPE=v4 L2 _缓冲_类型_视频_捕获;

  fmt。fmt。pix。宽度=640;

  fmt。fmt。pix。身高=480;

  fmt。fmt。PIX。像素格式=v4 L2 _ PIX _ FMT _ YUYV;

  fmt。fmt。pix。FIELD=v4 L2 _场_交错;

  if(ioctl(fd,VIDIOC_S_FMT,fmt)==-1)

  if(errno==EINVAL)

  printf(信息:设置帧格式错误!\ n’);

  返回1;

  }

  intapply_memory_buf(intfd)

  {

  //结构v4 L2请求缓冲区请求;

  请求。计数=4;

  请求。TYPE=v4 L2 _缓冲_类型_视频_捕获;

  请求。内存=v4l 2 _内存_ MMAP;

  if(-1==ioctl(fd,VIDIOC_REQBUFS,req))

  printf(info:VIDIOC_REQBUFS失败\ n’);

  其他

  printf(info:VIDIOC_REQBUFS成功\ n’);

  返回1;

  }

  intmemory_mapping(intfd)

  {

  unsignedintn _ buffers

  缓冲区=(结构缓冲区*)calloc(请求。计数,sizeof(结构

  缓冲));

  如果(!缓冲区){

  fprintf(stderr,内存不足\ n’);

  退出(退出_失败);

  }

  //映射

  for(n _ buffers=0;n _缓冲器请求计数;n_buffers){

  //结构v4 L2 _缓冲区buf

  memset( buf,0,sizeof(buf));

  BUF。TYPE=v4 L2 _缓冲_类型_视频_捕获;

  buf。内存=v4l 2 _内存_ MMAP;

  buf.index=n _ buffers

  //查询序号为n _缓冲器的缓冲区,得到其起始物理地址和大小

  if(-1==ioctl(fd,VIDIOC_QUERYBUF,BUF))

  退出(-1);

  缓冲区[n_buffers].长度=缓冲长度

  //映射内存

  缓冲区[n_buffers].start=mmap(空,缓冲长度,PROT _读PROT写,MAP_SHARED,fd,buf。m .偏移);

  if(MAP _ FAILED==buffers[n _ buffers]).开始)

  退出(-1);

  }

  printf(信息:内存映射成功\n ).

  返回1;

  }

  intbuffer_enqueue(intfd)

  {

  未签名的

  枚举v4l2_buf_type类型;

  //将缓冲帧放入队列

  for(I=0;I 4;我)

  {

  结构v4l 2 _ buf buf

  BUF。TYPE=v4 L2 _缓冲_类型_视频_捕获;

  buf。内存=v4l 2 _内存_ MMAP;

  buf。index=I;

  if(-1==ioctl(fd,VIDIOC_QBUF,BUF))

  printf(缓冲区排队失败\ n’);

  }

  TYPE=v4l 2 _ BUF _ TYPE _ VIDEO _ CAPTURE;

  //打开流

  if(-1==ioctl(fd,VIDIOC_STREAMON,type))

  printf(信息:打开流失败\ n’);

  其他

  printf(信息:打开流成功\ n’);

  返回1;

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: