基于V4L2驱动的摄像头应用程序开发

本文档详细介绍了基于V4L2驱动的摄像头应用程序开发,涉及相关数据结构如v4l2_capability、v4l2_fmtdesc等,并通过ioctl命令字进行操作。示例程序show.c演示了如何打开、初始化设备,使用mmap进行内存映射,捕获并保存视频帧。程序运行后生成test.yuyv文件,可在Windows上用YUVViewer.exe查看。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


       这几天在学习写一个基于v4l2的摄像头应用程序,网上找了很多资料和程序,终于成功的采集到了图像。关于vl42的应用网上很多资料,我根据自己的理解总结了一下自己的学习成果。

对vl42的应用层编写就两点:相关数据结构和ioctl命令字。我参考的是这篇文章:http://blog.youkuaiyun.com/eastmoon502136/article/details/8190262

 

一、相关数据结构:参见 include/linux/videodev2.h

1、设备能力结构

struct v4l2_capability {

         __u8         driver[16];                 /*驱动名 */

         __u8         card[32];                     /*设备名*/         

         __u8         bus_info[32];            /*设备在系统中的位置  */

         __u32   version;          /*驱动版本号 */

         __u32       capabilities;              /*设备能力,即设备支持的操作*/

         __u32       reserved[4];              /*保留字段*/

};

 

struct v4l2_fmtdesc {

         __u32                    index;              /*要查询的帧格式序号,由应用程序设置     */

         enum v4l2_buf_type  type;            /*帧类型,由应用程序设置        */

         __u32               flags;                     /*是否为压缩格式*/

         __u8                       description[32];      /*格式名称 */

         __u32                    pixelformat;         /*格式      */

         __u32                    reserved[4];

};

 

2、数据格式结构

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;

};

 

3、像素格式结构

struct v4l2_pix_format {

         __u32                       width;                //宽度

         __u32                          height;                        //高度

         __u32                          pixelformat;

         enum v4l2_field     field;                            //帧格式

         __u32                   bytesperline;   /* for padding, zero if unused */

         __u32                      sizeimage;

         enum v4l2_colorspace     colorspace;

         __u32                          priv;          /* private data, depends on pixelformat */

};

 

4、请求缓冲

struct v4l2_requestbuffers {

         __u32                          count;                //缓存数量

         enum v4l2_buf_type      type;             //数据流类型

         enum v4l2_memory        memory;

         __u32                          reserved[2];

};

 

5、数据流类型

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_VBI_CAPTURE          = 4,

         V4L2_BUF_TYPE_VBI_OUTPUT           = 5,

         V4L2_BUF_TYPE_SLICED_VBI_CAPTURE   = 6,

         V4L2_BUF_TYPE_SLICED_VBI_OUTPUT    = 7,

#if 1

         /* Experimental */

         V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY = 8,

#endif

         V4L2_BUF_TYPE_PRIVATE              = 0x80,

};

 

enum v4l2_memory {

         V4L2_MEMORY_MMAP             = 1,

         V4L2_MEMORY_USERPTR          = 2,

         V4L2_MEMORY_OVERLAY          = 3,

};

二、ioctl命令字

命令标字

功能

VIDIOC_REQBUFS

申请缓存区

VIDIOC_QUERYBUF

把VIDIOC_REQBUFS中分配的数据缓存转换成物理地址

VIDIOC_QUERYCAP

查询设备属性

VIDIOC_ENUM_FMT

获取当前驱动支持的视频格式

VIDIOC_S_FMT

设置当视频采集图像格式

VIDIOC_G_FMT

读取当视频采集图格式

VIDIOC_TRY_FMT

验证当前驱动的显示格式

VIDIOC_CROPCAP

查询设备的修剪能力

VIDIOC_S_CROP

设置视频信号的边框

VIDIOC_G_CROP

读取视频信号的边框

VIDIOC_QBUF

把数据从缓存区中读取出来

VIDIOC_DQBUF

把缓存区内存放回缓存队列

VIDIOC_STREAMON

开启视频采集

VIDIOC_STREAMOFF

结束视频采集

VIDIOC_QUERYSTD

检查当前视频采集设备支持的标准

 

三、成功运行的程序:show.c

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <assert.h>  
 
#include <getopt.h>  
 
#include <fcntl.h>  
#include <unistd.h>  
#include <errno.h>  
#include <malloc.h>  
#include <sys/stat.h>  
#include <sys/types.h>  
#include <sys/time.h>  
#include <sys/mman.h>  
#include <sys/ioctl.h>  
 
#include <asm/types.h>  
 
#include <linux/videodev2.h>  
 
#define CLEAR(x) memset(&(x),0,sizeof(x))  
 
//定义一个缓存结构体  
struct buffer 

  void* start;     //空指针  
  size_t length;   //长度大小  
}; 
 
#define CAPTURE_FILE "test.yuyv"  
 
static char* dev_name = NULL;   //设备名  
static int fd = -1;              //文件序列  
static struct buffer* buffers = NULL;    //缓存对象  
static unsigned int n_buffers = 0;   //缓存数  
  
static void errno_exit(const char* s)   //错误输出     

   fprintf(stderr,"%s error %d, %s\n",s,errno,strerror(errno)); 
      exit(EXIT_FAILURE); 

 
/*
static void process_image(const void* p, size_t length)   //处理获取图像
{
   // fputc('!',stdout);
 //fflush(stdout);
 FILE *fp = fopen(CAPTURE_FILE, "a");
 
    if (fp<0) 
    {
        printf("open frame data file failed\n");
        exit(EXIT_FAILURE);
    }
 fwrite(p,length, 1, fp);
 perror("fwrite error");
 
 fclose(fp);
    printf("Capture one frame saved in %s\n", CAPTURE_FILE);
}
*/ 
 
static void read_frame(FILE *fp)   //读取数据帧,用的都是mmap  

   struct v4l2_buffer buf; 
   unsigned int i; 
   buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
   buf.memory = V4L2_MEMORY_MMAP; 
 
   if(-1 == ioctl(fd,VIDIOC_DQBUF,&buf))   //读取  
          errno_exit("VIDIOC_DQBUF"); 
  
    fwrite(buffers[buf.index].start, buf.length, 1, fp); 
 
   if(-1 == ioctl(fd,VIDIOC_QBUF,&buf))    //放回缓存  
       errno_exit("VIDIOC_QBUF"); 
 

 
static void mainloop(void) 

    unsigned int count; 
 count = 100; 
 FILE *fp = fopen(CAPTURE_FILE, "w"); 
 
    if (fp<0)  
    { 
        printf("open frame data file failed\n"); 
        exit(EXIT_FAILURE); 
    } 
 while(count-- > 0) 
 { 
 
   read_frame(fp);   
 } 
 fclose(fp); 
    printf("Capture one frame saved in %s\n", CAPTURE_FILE); 

 
static void stop_capturing(void)  //停止捕获帧  

   enum v4l2_buf_type type; 
   type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
 
   if(-1 == ioctl(fd,VIDIOC_STREAMOFF,&type))  //关闭视频流  
    errno_exit("VIDIOC_STREAMOFF"); 
   printf("\nStreamOff success!\n"); 

 
static void start_capturing(void)   //开始捕获帧  

   unsigned int i; 
   enum v4l2_buf_type type; 
 
   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; 
 
  if(-1 == ioctl(fd,VIDIOC_QBUF,&buf))   //放入缓存  
   errno_exit("VIDIOC_QBUF"); 
   } 
 
   type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
 
   if(-1 == ioctl(fd,VIDIOC_STREAMON,&type))   //打开视频流  
      errno_exit("VIDIOC_STREAMON"); 
   else printf("StreamOn success!\n"); 

 
static void uninit_device(void)   //释放存储空间  

   unsigned int i; 
 
   for(i =0;i < n_buffers;i++) 
   { 
      if(-1 == munmap(buffers[i].start,buffers[i].length)) 
    errno_exit("munmap"); 
   } 
 
   free(buffers); 

 
 
static void init_mmap(void)   //初始化读取方式  

   struct v4l2_requestbuffers req; 
   CLEAR(req); 
 
   req.count = 2; 
   req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
   req.memory = V4L2_MEMORY_MMAP; 
 
   if(-1 == ioctl(fd,VIDIOC_REQBUFS,&req))   //分配内存  
   { 
      if(EINVAL == errno) 
   { 
     fprintf(stderr,"%s does not support memory mapping\n",dev_name); 
    exit(EXIT_FAILURE); 
   } 
   else 
    errno_exit("VIDIOC_REQBUFS"); 
   } 
 
 
   buffers = calloc(req.count,sizeof(*buffers));   //分配缓存  
   if(!buffers) 
   { 
      fprintf(stderr,"Out of memory\n"); 
    exit(EXIT_FAILURE); 
   } 
 
   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)) 
     errno_exit("VIDIOC_QUERYBUF"); 
 
    buffers[n_buffers].length = buf.length;   //设置映射方式为mmap  
    buffers[n_buffers].start = mmap(NULL,buf.length,PROT_READ | PROT_WRITE,MAP_SHARED,fd,buf.m.offset); 
 
    if(MAP_FAILED == buffers[n_buffers].start) 
     errno_exit("mmap"); 
   } 

 
static void init_device(void)  //初始化设备  

   struct v4l2_capability cap; 
   struct v4l2_format fmt; 
   unsigned int min; 
 
   if(-1 == ioctl(fd,VIDIOC_QUERYCAP,&cap)) 
   { 
       if(EINVAL == errno) 
    { 
       fprintf(stderr,"%s is no V4L2 device\n",dev_name); 
    exit(EXIT_FAILURE); 
    } 
    else  
     errno_exit("VIDIOC_QUERYCAP"); 
   } 
 
   if(!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) 
   { 
        fprintf(stderr,"%s is no video  capture device\n",dev_name); 
          exit(EXIT_FAILURE); 
   } 
 
   if(!(cap.capabilities & V4L2_CAP_STREAMING)) 
   { 
       fprintf(stderr,"%s does not support streaming i/o\n",dev_name); 
    exit(EXIT_FAILURE); 
   } 
//////////////////////////////////////////  
   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)) 
    errno_exit("VIDIOC_S_FMT"); 
   printf("Set frame format successful!\n"); 
   init_mmap(); 
 

 
static void close_device(void)   //关闭设备  

   if(-1 == close(fd)) 
    errno_exit("close"); 
   fd = -1; 
   printf("Close the device successful!\n"); 

 
static void open_device(void)    //打开设备  

   fd = open(dev_name,O_RDWR,0); 
   if(-1 ==fd) 
   { 
      fprintf(stderr,"Cannot open %s\n",dev_name); 
   exit(EXIT_FAILURE); 
   } 
   printf("Open the %s successful\n",dev_name); 

 
int main(int argc,char* argv[]) 

   dev_name = "/dev/video0"; 
 
   open_device(); 
   init_device(); 
   start_capturing(); 
   mainloop(); 
   stop_capturing(); 
   uninit_device(); 
   close_device(); 
   exit(EXIT_SUCCESS); 
   return 0; 
}



//---------------------------------------------------------------------

Makefile编写:

KERNELDIR ?=/home/student/linux-2.6.32.2

show: show.c
 gcc -I$(KERNELDIR) -o $@ $^

clean :
 rm show

//------------------------------------------------------------------------
将摄像头直接连接到PC机的usb口,会在虚拟机linux下有检测到摄像头的提示,表示摄像头可以使用,然后make,./show 就可以运行完毕

 

四、对上述show.c 程序的说明

网上很多程序会把上述相关数据结构整合到一个自定义的数据结构中,我认为没有必要,因为核心是通过mmap映射后的指针,所以相关数据结构只需要函数中局部变量就行,没必要整成全局变量,而buffers则需要全局变量,便于使用。

show.c源程序中最重要的是buffers指针,在init_mmap()函数中,首先 通过buffers = calloc(req.count,sizeof(*buffers)); 使它指向count个内存空间,然后通过 

buffers[n_buffers].length = buf.length;   //设置映射方式为mmap   
buffers[n_buffers].start = mmap(NULL,buf.length,PROT_READ | PROT_WRITE,MAP_SHARED,fd,buf.m.offset); 

length的大小是640*480*2个字节,即一副图像大小。

将buffers和驱动层的缓存通过mmap联系起来,将驱动层的缓存映射到应用层。

最后通过read_frame()函数将buffers指向的地址数据写道fp中,fp指向"test.yuyv"

 

程序运行后会得到一个test.yuyv文件,通过samba服务器从虚拟机拷贝到Windows,用YUVViewer.exe软件打开,设置一下格式为YUYV,640X480然后play即可。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值