v4l2设备驱动开发_v4l2 驱动 - CSDN
  • 文章目录目录前言`v4l2`解析`v4l2`介绍应用程序通过`V4L2`接口采集视频数据步骤相关结构体解析总结参考链接 前言 在移植罗技C270摄像头到6818的过程中,内核已经检测到了USB摄像头,但是直接用OpenCV的API(比如...

    目录

    前言

    在移植罗技C270摄像头到6818的过程中,内核已经检测到了USB摄像头,但是直接用OpenCV的API(比如CvCapture*cvCaptureFromCAM(int index)接口,无法打开USB摄像头,至少目前我是这么认为的。然后,网上搜索答案说是要使用V4l2进行操作。没有别的办法!只有一边学一边试试看行不行喽!

    感谢超群天晴大神的(原创)基于ZedBoard的Webcam设计(一):USB摄像头(V4L2接口)的图片采集这篇博文中提供的源码,我直接移植后,在6818是成功获取bmp图片和yuv图片。虽然在Ubuntu中打开bmp图片和yuv图片是错误的!!!

    在找到超群天晴的博文后,我又找到这两篇博客和菜鸟一起学linux之V4L2摄像头应用流程嵌入式LINUX环境下视频采集知识验证成功后,就是花时间理解,然后获取视频流了!希望获取的视频流能够流畅!

    学习!分享!感谢!

    v4l2解析

    v4l2介绍

    v4l2linux操作系统下用于采集图片、视频和音频数据的API接口,配合适当的视频采集设备和相应的驱动程序,可以实现图片、视频、音频等的采集。在远程会议、可视电话、视频监控系统和嵌入式多媒体终端中都有广泛应用。
    在linux下,所有外设都被看成一种特殊的文件,称为"设备文件",可以像访问普通文件一样对设备文件进行访问。
    V4L2支持两种方式来采集图像:内存映射(mmap)和直接读取方式(read)。V4L2在include/linux/video.h文件下定义了一些重要的数据结构,在采集图像的过程中,就是通过对这些数据的操作来获得最终的图像数据。Linux系统V4L2使能可在内核编译阶段配置,默认情况下是在make menuconfig是打开的。

    linux下视频捕捉具体的linux调用参见下图:
    1

    应用程序可以通过V4L2进行视频采集。V4L2支持内存映射(mmap)方式和直接读取方式(read)方式采集数据。前者一般用于连续的视频数据采集,后者常用静态图片数据采集。

    v4l2 中不仅定义了通用API元素,图像的格式,输入/输出方法,还定义了Linux内核驱动处理视频信息的一系列接口,这些接口主要有:

    视频采集接口——Video Capture interface 视频输出接口 ——Video Output Interface; 视频覆盖/预览接口——Video Overlay Interface; 视频输出覆盖接口—— Video Output Overlay Interface; 编解码接口——Codec Interface

    应用程序通过V4L2接口采集视频数据步骤

    • 打开视频设备文件,通过视频采集的参数初始化,通过V4L2接口设置视频图像属性。

    • 申请若干视频采集的帧缓存区,并将这些帧缓冲区从内核空间映射到用户空间,便于应用程序读取/处理视频数据。

    • 将申请到的帧缓冲区在视频采集输入队列排队,并启动视频采集。

    • 驱动开始视频数据的采集,应用程序从视频采集输出队列中取出帧缓冲区,处理后,将帧缓冲区重新放入视频采集输入队列,循环往复采集连续的视频数据。

    • 停止视频采集。
      具体实现过程如下图:
      2

    • 整理下其中ioctl控制符:

    VIDIOC_QUERYCAP	查询设备的属性
    VIDIOC_ENUM_FMT 帧格式
    VIDIOC_S_FMT 设置视频帧格式,对应struct v4l2_format
    VIDIOC_G_FMT 获取视频帧格式等
    VIDIOC_REQBUFS 请求/申请若干个帧缓冲区,一般为不少于3个
    VIDIOC_QUERYBUF 查询帧缓冲区在内核空间的长度和偏移量
    VIDIOC_QBUF 将申请到的帧缓冲区全部放入视频采集输出队列
    VIDIOC_STREAMON 开始视频流数据的采集
    VIDIOC_DQBUF 应用程序从视频采集输出队列中取出已含有采集数据的帧缓冲区
    VIDIOC_STREAMOFF 应用程序将该帧缓冲区重新挂入输入队列
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    其实,图解过程已经很详细了,但是比较笨,而且图片也比较容易眼花,重新总结下。整个过程:
    首先:先启动视频采集,驱动程序开始采集一帧数据,把采集的数据放入视频采集输入队列的第一个帧缓冲区,一帧数据采集完成,也就是第一个帧缓冲区存满一帧数据后,驱动程序将该帧缓冲区移至视频采集输出队列,等待应用程序从输出队列取出。驱动程序则继续采集下一帧数据放入第二个缓冲区,同样帧缓冲区存满下一帧数据后,被放入视频采集输出队列。
    然后:应用程序从视频采集输出队列中取出含有视频数据的帧缓冲区,处理帧缓冲区中的视频数据,如存储或压缩。
    最后:应用程序将处理完数据的帧缓冲区重新放入视频采集输入队列,这样可以循环采集。
    看到这里,摘录一段Hi3519的开发文档中《系统控制》部分的介绍:

    视频缓存池主要向媒体业务提供大块物理内存管理功能,负责内存的分配和回收,充分发挥内存缓存池的作用,让物理内存资源在各个媒体处理模块中合理使用。
    一组大小相同、物理地址连续的缓存块组成一个视频缓存池。
    视频输入通道需要使用公共视频缓存池。所有的视频输入通道都可以从公共视频缓存池中获取视频缓存块用于保存采集的图像。由于视频输入通道不提供创建爱你和销毁公共视频缓存池功能。因此,在系统初始化之前,必须为视频输入通道配置公共缓存池。根据业务的不同,公共缓存池的数量、缓存块的大小和数量不同。缓存块的生存期是指经过VPSS通道传给后续模块的情形。如果该缓存块完全没有经过VPSS通道传给其他模块,则将在VPSS模块处理后被放回公共缓存池。
    3
    所以,我们从摄像头中获取的视频帧数据会放入视频缓存队列中,当其他模块需要处理对应的视频帧的时候,就会占用缓存块,也就是这一块内存被占用,当处理完之后,对应的数据通过VO/VENC/VDA显示之后,这一缓存块就没有用了,可以回收利用。现在来看,其实海思的底层处理和linux的底层处理是一样的。不过海思本身使用的就是linux内核。应该也就是对这一块进行封装了而已吧!
    4
    从这张图可以看出,海思的公共视频缓存池按我的理解应该有两部分,一部分是视频采集输入队列,另一部分是视频采集输出队列,VI通道是是视频采集输出队列中获取的视频帧,而中间linux内核的驱动程序会在视频采集输入队列中填充视频帧,变成视频输出队列。
    每一个帧缓冲区都有一个对应的状态标志变量,其中每一个比特代表一个状态:

    V4L2_BUF_FLAG_UNMAPPED 	0B0000
    V4L2_BUF_FLAG_MAPPED 	0B0001
    V4L2_BUF_FLAG_ENQUEUED 	0B0010
    V4L2_BUF_FLAG_DONE 		0B0100
    
    • 1
    • 2
    • 3
    • 4

    缓冲区的状态转化如图:
    5
    在申请了缓冲区(VIDIOC_REQBUFS)后标志为0000,但是这时候还没有映射到应用层,所以在Mmap之后,缓冲区的表示为0001。
    应用程序(VIDIOC_DQBUF)从视频采集输出队列中取出已含有采集数据的帧缓冲区,这时候(V4L2_BUF_FLAG_DONE|V4L2_BUF_FLAG_MAPPED),所以缓冲区标志为0101.
    VIDIOC_QBUF将申请到的帧缓冲区全部放入视频采集输出队列:V4L2_BUF_FLAG_ENQUEUED|V4L2_BUF_FLAG_MAPPED

    相关结构体解析

    1. VIDIOC_QUERYCAP-------->struct v4l2_capability
    struct v4l2_capability {
    	__u8	driver[16];    // 驱动模块的名字
    	__u8	card[32];      // 设备名字
    	__u8	bus_info[32];  // 总线信息
    	__u32   version;       // 内核版本
    	__u32	capabilities;  // 整个物理设备支持的功能
    	__u32	device_caps;   // 通过这个特定设备访问的功能
    	__u32	reserved[3];
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    如下是我的罗技C270摄像头的通过VIDIOC_QUERYCAP获取的设备功能

    driver:         uvcvideo
    card:           UVC Camera (046d:0825)
    bus_info:       usb-nxp-ehci-1.3
    version:        197671
    capabilities:   4000001
    Device /dev/video9: supports capture.
    Device /dev/video9: supports streaming.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    其中capabilities: 4000001通过与各种宏位与,可以获得物理设备的功能属性。比如:

            //#define V4L2_CAP_VIDEO_CAPTURE		0x00000001  /* Is a video capture device */ // 是否支持视频捕获
         	if ((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE) == V4L2_CAP_VIDEO_CAPTURE) 
         	{
                printf("Device %s: supports capture.\n", FILE_VIDEO);
    		}
    
        <span class="token comment">//#define V4L2_CAP_STREAMING            0x04000000  /* streaming I/O ioctls */ // 是否支持输入输出流控制</span>
    	<span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>cap<span class="token punctuation">.</span>capabilities <span class="token operator">&amp;</span> V4L2_CAP_STREAMING<span class="token punctuation">)</span> <span class="token operator">==</span> V4L2_CAP_STREAMING<span class="token punctuation">)</span> 
    	<span class="token punctuation">{</span>
            <span class="token function">printf</span><span class="token punctuation">(</span><span class="token string">"Device %s: supports streaming.\n"</span><span class="token punctuation">,</span> FILE_VIDEO<span class="token punctuation">)</span><span class="token punctuation">;</span>
    	<span class="token punctuation">}</span>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1. VIDIOC_ENUM_FMT-------->struct v4l2_fmtdesc
    /*
     *	F O R M A T   E N U M E R A T I O N
     */
    struct v4l2_fmtdesc {
    	__u32		    index;           /* Format number      */
    	__u32		    type;            /* enum v4l2_buf_type */
    	__u32           flags;
    	__u8		    description[32]; /* Description string */
    	__u32		    pixelformat;     /* Format fourcc      */
    	__u32		    reserved[4];
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    通过这个结构体,可以显示对应的摄像头所支持视频帧格式。

    struct v4l2_fmtdesc fmtdesc;  
    fmtdesc.index = 0;  
    fmtdesc.type  = V4L2_BUF_TYPE_VIDEO_CAPTURE;  
    printf("Supportformat:/n");  
    while(ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc) != -1)  
    {  
    printf("/t%d.%s/n",fmtdesc.index+1,fmtdesc.description);  
    fmtdesc.index++;  
    }  
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    我的摄像头输出如下:

    Support format:
            1.YUV 4:2:2 (YUYV)
            2.MJPEG
    
    • 1
    • 2
    • 3

    所以,我要读取视频帧的时候就要使用YUV422这种格式。
    3. VIDIOC_S_FMT&VIDIOC_G_FMT-------->struct v4l2_format
    查看或设置视频帧格式

    struct v4l2_format {
    	__u32	 type; // 帧类型
    	union {
    		/* V4L2_BUF_TYPE_VIDEO_CAPTURE */
    		struct v4l2_pix_format pix;  //像素格式
    		/* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */
    		struct v4l2_pix_format_mplane pix_mp; 
    		/* V4L2_BUF_TYPE_VIDEO_OVERLAY */
    		struct v4l2_window win; 
    		/* V4L2_BUF_TYPE_VBI_CAPTURE */
    		struct v4l2_vbi_format vbi; 
    		/* V4L2_BUF_TYPE_SLICED_VBI_CAPTURE */
    		struct v4l2_sliced_vbi_format sliced; 
    		/* V4L2_BUF_TYPE_SDR_CAPTURE */
    		struct v4l2_sdr_format		sdr; 
    		/* user-defined */    
    		__u8	raw_data[200];                  
    	} fmt;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    /*
     *	V I D E O   I M A G E   F O R M A T
     */
    struct v4l2_pix_format {
    	__u32           width;  // 像素高度
    	__u32			height; // 像素宽度
    	__u32			pixelformat; // 像素格式
    	__u32			field;		/* enum v4l2_field */
    	__u32           bytesperline; /* for padding, zero if unused */
    	__u32          		sizeimage;
    	__u32			colorspace;	/* enum v4l2_colorspace */
    	__u32			priv;		/* private data, depends on pixelformat */
    	__u32			flags;		/* format flags (V4L2_PIX_FMT_FLAG_*) */
    	__u32			ycbcr_enc;	/* enum v4l2_ycbcr_encoding */
    	__u32			quantization;	/* enum v4l2_quantization */
    	__u32			xfer_func;	/* enum v4l2_xfer_func */
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    仔细看,发现这些其实和海思中的一些结构体非常类似,linux真的很强大!
    4. VIDIOC_CROPCAP-------->struct v4l2_cropcap
    5. VIDIOC_G_PARM&VIDIOC_S_PARM-------->struct v4l2_streamparm
    设置Stream信息,主要设置帧率

    struct v4l2_streamparm {
    	__u32	 type;			/* enum v4l2_buf_type */
    	union {
    		struct v4l2_captureparm	capture;
    		struct v4l2_outputparm	output;
    		__u8	raw_data[200];  /* user-defined */
    	} parm;
    };
    struct v4l2_captureparm {
    	/*  Supported modes */
    	__u32		   capability;	
    	/*  Current mode */  
    	__u32		   capturemode;	  
    	/*  Time per frame in seconds */
    	struct v4l2_fract  timeperframe;  
    	/*  Driver-specific extensions */
    	__u32		   extendedmode;  
    	/*  # of buffers for read */
    	__u32          readbuffers;   
    	__u32		   reserved[4];
    };
    // timeperframe
    // numerator和denominator可描述为每numerator秒有denominator帧
    struct v4l2_fract {
    	__u32   numerator;         // 分子
    	__u32   denominator;       // 分母 
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    1. VIDIOC_REQBUFS-------->struct v4l2_requestbuffers
      申请和管理缓冲区,应用程序和设备有三种交换数据方法,直接read/write(裸机)、内存映射(系统),用户指针(…)。一般在操作系统管理下,都是使用内存映射的方式。
    /*
     *	M E M O R Y - M A P P I N G   B U F F E R S
     */
    struct v4l2_requestbuffers {
    	__u32			count;  // 缓冲区内缓冲帧的数目
    	__u32			type;   // 缓冲帧数据格式
    	__u32			memory; // 
    	__u32			reserved[2];
    };
    

    enum v4l2_memory {
    V4L2_MEMORY_MMAP = 1, // 内存映射
    V4L2_MEMORY_USERPTR = 2, // 用户指针
    V4L2_MEMORY_OVERLAY = 3,
    V4L2_MEMORY_DMABUF = 4,
    };

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    1. VIDIOC_QUERYBUF-------->struct v4l2_buffer
    struct v4l2_buffer {
    	__u32			index;           // buffer的id
    	__u32			type;            // enum v4l2_buf_type
    	__u32			bytesused;       // buf中已经使用的字节数
    	__u32			flags;           // MMAP 或 USERPTR
    	__u32			field;
    	struct timeval		timestamp;   // 帧时间戳
    	struct v4l2_timecode	timecode;
    	__u32			sequence;        // 队列中的序号
    
    <span class="token comment">/* memory location */</span>
    __u32			memory<span class="token punctuation">;</span>
    <span class="token keyword">union</span> <span class="token punctuation">{</span>
    	__u32           offset<span class="token punctuation">;</span>      <span class="token comment">// 设备内存起始offset</span>
    	<span class="token keyword">unsigned</span> <span class="token keyword">long</span>   userptr<span class="token punctuation">;</span>     <span class="token comment">// 指向用户空间的指针</span>
    	<span class="token keyword">struct</span> v4l2_plane <span class="token operator">*</span>planes<span class="token punctuation">;</span>
    	__s32		fd<span class="token punctuation">;</span>
    <span class="token punctuation">}</span> m<span class="token punctuation">;</span>
    __u32			length<span class="token punctuation">;</span>         <span class="token comment">// 缓存帧长度</span>
    __u32			reserved2<span class="token punctuation">;</span>
    __u32			reserved<span class="token punctuation">;</span>
    

    };

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    1. VIDIOC_DQBUF
      应用程序从视频采集输出队列中取出已含有采集数据的帧缓冲区

    2. VIDIOC_STREAMON&VIDIOC_STREAMOFF
      开始视频采集和关闭视频采集

    3. VIDIOC_QBUF
      应用程序将该帧缓冲区重新挂入输入队列

    总结

    使用C语言高级应用—操作linux下V4L2摄像头应用程序源码成功的在我的开发板上显示出了800600的视频图像。我的开发板的显示屏是1024600的,当我设置为1024600时,实际显示为1024576,感觉很奇怪!而且显示的视频是反的,都是小问题。总之,显示视频的那一刻真的好开心!

    参考链接

    和菜鸟一起学linux之V4L2摄像头应用流程
    (原创)基于ZedBoard的Webcam设计(一):USB摄像头(V4L2接口)的图片采集
    V4L2 API详解 <二> Camera详细设置
    V4L2 API
    V4L2采集yuv视频花屏:Linux视频采集与编码(一)
    C语言高级应用—操作linux下V4L2摄像头应用程序
    v4l2 编程接口(一) — ioctl
    capture.c
    支持的uvc设备查询

            </div>
    					<link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-7b4cdcb592.css" rel="stylesheet">
                </div>
    
    展开全文
  • 三、 V4L2 API及数据结构 ...包括一套数据结构和底层V4L2驱动接口。 1、常用的结构体在内核目录include/linux/videodev2.h中定义  struct v4l2_requestbuffers //申请帧缓冲,对应命令VIDIOC_REQBUFS   struct

    三、            V4L2 API及数据结构

    V4L2是V4L的升级版本,为linux下视频设备程序提供了一套接口规范。包括一套数据结构和底层V4L2驱动接口。

    1、常用的结构体在内核目录include/linux/videodev2.h中定义

       struct v4l2_requestbuffers //申请帧缓冲,对应命令VIDIOC_REQBUFS 
       struct v4l2_capability //视频设备的功能,对应命令VIDIOC_QUERYCAP 
       struct v4l2_input   //视频输入信息,对应命令VIDIOC_ENUMINPUT
       struct v4l2_standard //视频的制式,比如PAL,NTSC,对应命令VIDIOC_ENUMSTD 
       struct v4l2_format    //帧的格式,对应命令VIDIOC_G_FMT、VIDIOC_S_FMT等
       struct v4l2_buffer   //驱动中的一帧图像缓存,对应命令VIDIOC_QUERYBUF

       struct v4l2_crop   //视频信号矩形边框

          v4l2_std_id   //视频制式

    2、常用的IOCTL接口命令也在include/linux/videodev2.h中定义

    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 //检查当前视频设备支持的标准,例如PAL或NTSC。

     

     

    3、操作流程

    V4L2提供了很多访问接口,你可以根据具体需要选择操作方法。需要注意的是,很少有驱动完全实现了所有的接口功能。所以在使用时需要参考驱动源码,或仔细阅读驱动提供者的使用说明。

    下面列举出一种操作的流程,供参考。

    (1)打开设备文件

    int fd = open(Devicename,mode);

        Devicename:/dev/video0、/dev/video1 ……

         Mode:O_RDWR [| O_NONBLOCK]

           如果使用非阻塞模式调用视频设备,则当没有可用的视频数据时,不会阻塞,而立刻返回。

    (2)取得设备的capability

    struct v4l2_capability capability;

                  int ret = ioctl(fd, VIDIOC_QUERYCAP, &capability);     

    看看设备具有什么功能,比如是否具有视频输入特性。

    (3)选择视频输入

    struct v4l2_input input;

    ……初始化input

    int ret = ioctl(fd, VIDIOC_QUERYCAP, &input);     

    一个视频设备可以有多个视频输入。如果只有一路输入,这个功能可以没有。

    (4)检测视频支持的制式

                v4l2_std_id std;

                do {

                     ret = ioctl(fd, VIDIOC_QUERYSTD, &std);

                } while (ret == -1 && errno == EAGAIN);

                switch (std) {

                    case V4L2_STD_NTSC: 

                          //……

             case V4L2_STD_PAL:

                 //……

    }

    (5)设置视频捕获格式

    struct v4l2_format fmt;

    fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;

    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;

    fmt.fmt.pix.height = height;

    fmt.fmt.pix.width = width;

    fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;

    ret = ioctl(fd, VIDIOC_S_FMT, &fmt);

    if(ret) {

    perror("VIDIOC_S_FMT/n");

    close(fd);

    return -1;

    }

    (6)向驱动申请帧缓存

         struct v4l2_requestbuffers  req;

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

               return -1;

    }

           v4l2_requestbuffers结构中定义了缓存的数量,驱动会据此申请对应数量的视频缓存。多个缓存可以用于建立FIFO,来提高视频采集的效率。

    (7)获取每个缓存的信息,并mmap到用户空间

    typedef struct VideoBuffer {

        void   *start;

        size_t  length;

    } VideoBuffer;

                                                                  

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

    struct v4l2_buffer    buf;

     

    for (numBufs = 0; numBufs < req.count; numBufs++) {//映射所有的缓存

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

        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

        buf.memory = V4L2_MEMORY_MMAP;

        buf.index = numBufs;

        if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {//获取到对应index的缓存信息,此处主要利用length信息及offset信息来完成后面的mmap操作。

            return -1;

        }

     

        buffers[numBufs].length = buf.length;

        // 转换成相对地址

        buffers[numBufs].start = mmap(NULL, buf.length,

            PROT_READ | PROT_WRITE,

            MAP_SHARED,

            fd, buf.m.offset);

     

        if (buffers[numBufs].start == MAP_FAILED) {

            return -1;

        }

     

    (8)开始采集视频

    int buf_type= V4L2_BUF_TYPE_VIDEO_CAPTURE;

    int ret = ioctl(fd, VIDIOC_STREAMON, &buf_type);

     

    (9)取出FIFO缓存中已经采样的帧缓存

    struct v4l2_buffer buf;

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

    buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;

    buf.memory=V4L2_MEMORY_MMAP;

    buf.index=0;//此值由下面的ioctl返回

    if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1)

    {

        return -1;

    }

    根据返回的buf.index找到对应的mmap映射好的缓存,取出视频数据。

    (10)将刚刚处理完的缓冲重新入队列尾,这样可以循环采集

    if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {

        return -1;

    }

    (11)停止视频的采集

    int ret = ioctl(fd, VIDIOC_STREAMOFF, &buf_type);

    (12)关闭视频设备

    close(fd);

     

     

    四、            V4L2驱动框架

    上述流程的各个操作都需要有底层V4L2驱动的支持。内核中有一些非常完善的例子。

    比如:linux-2.6.26内核目录/drivers/media/video//zc301/zc301_core.c 中的ZC301视频驱动代码。上面的V4L2操作流程涉及的功能在其中都有实现。

    1、V4L2驱动注册、注销函数

           Video核心层(drivers/media/video/videodev.c)提供了注册函数

    int video_register_device(struct video_device *vfd, int type, int nr)

    video_device:  要构建的核心数据结构

    Type:  表示设备类型,此设备号的基地址受此变量的影响

    Nr:    如果end-base>nr>0 :次设备号=base(基准值,受type影响)+nr;

    否则:系统自动分配合适的次设备号

           具体驱动只需要构建video_device结构,然后调用注册函数既可。

    如:zc301_core.c中的

           err = video_register_device(cam->v4ldev, VFL_TYPE_GRABBER,

                              video_nr[dev_nr]);

           Video核心层(drivers/media/video/videodev.c)提供了注销函数

    void video_unregister_device(struct video_device *vfd)

     

    2、struct video_device 的构建

                  video_device结构包含了视频设备的属性和操作方法。参见zc301_core.c

    strcpy(cam->v4ldev->name, "ZC0301[P] PC Camera");

           cam->v4ldev->owner = THIS_MODULE;

           cam->v4ldev->type = VID_TYPE_CAPTURE | VID_TYPE_SCALES;

           cam->v4ldev->fops = &zc0301_fops;

           cam->v4ldev->minor = video_nr[dev_nr];

           cam->v4ldev->release = video_device_release;

           video_set_drvdata(cam->v4ldev, cam);

           大家发现在这个zc301的驱动中并没有实现struct video_device中的很多操作函数,如:vidioc_querycap、vidioc_g_fmt_cap等。主要原因是struct file_operations zc0301_fops中的zc0301_ioctl实现了前面的所有ioctl操作。所以就不需要在struct video_device再实现struct video_device中的那些操作了。

           另一种实现方法如下:

    static struct video_device camif_dev =

    {

           .name             = "s3c2440 camif",

           .type              = VID_TYPE_CAPTURE|VID_TYPE_SCALES|VID_TYPE_SUBCAPTURE,

           .fops              = &camif_fops,

           .minor            = -1,

           .release    = camif_dev_release,

           .vidioc_querycap      = vidioc_querycap,

           .vidioc_enum_fmt_cap  = vidioc_enum_fmt_cap,

           .vidioc_g_fmt_cap     = vidioc_g_fmt_cap,

           .vidioc_s_fmt_cap     = vidioc_s_fmt_cap,

           .vidioc_queryctrl = vidioc_queryctrl,

           .vidioc_g_ctrl = vidioc_g_ctrl,

           .vidioc_s_ctrl = vidioc_s_ctrl,

    };

    static struct file_operations camif_fops =

    {

           .owner           = THIS_MODULE,

           .open             = camif_open,

           .release    = camif_release,

           .read              = camif_read,

           .poll        = camif_poll,

           .ioctl              = video_ioctl2, /* V4L2 ioctl handler */

           .mmap           = camif_mmap,

           .llseek            = no_llseek,

    };

    注意:video_ioctl2是videodev.c中是实现的。video_ioctl2中会根据ioctl不同的cmd来

    调用video_device中的操作方法。

    3、Video核心层的实现

           参见内核/drivers/media/videodev.c

    (1)注册256个视频设备

           static int __init videodev_init(void)

    {

    int ret;

               if (register_chrdev(VIDEO_MAJOR, VIDEO_NAME, &video_fops)) {

                      return -EIO;

               }

               ret = class_register(&video_class);

    ……

    }

    上面的代码注册了256个视频设备,并注册了video_class类。video_fops为这256个设备共同的操作方法。

    (2)V4L2驱动注册函数的实现

     

    int video_register_device(struct video_device *vfd, int type, int nr)

    {

    int i=0;

    int base;

    int end;

    int ret;

           char *name_base;

     

           switch(type) //根据不同的type确定设备名称、次设备号

           {

                  case VFL_TYPE_GRABBER:

                         base=MINOR_VFL_TYPE_GRABBER_MIN;

                         end=MINOR_VFL_TYPE_GRABBER_MAX+1;

                         name_base = "video";

                         break;

                  case VFL_TYPE_VTX:

                         base=MINOR_VFL_TYPE_VTX_MIN;

                         end=MINOR_VFL_TYPE_VTX_MAX+1;

                         name_base = "vtx";

                         break;

                  case VFL_TYPE_VBI:

                         base=MINOR_VFL_TYPE_VBI_MIN;

                         end=MINOR_VFL_TYPE_VBI_MAX+1;

                         name_base = "vbi";

                         break;

                  case VFL_TYPE_RADIO:

                         base=MINOR_VFL_TYPE_RADIO_MIN;

                         end=MINOR_VFL_TYPE_RADIO_MAX+1;

                         name_base = "radio";

                         break;

                  default:

                         printk(KERN_ERR "%s called with unknown type: %d/n",

                                __func__, type);

                         return -1;

           }

     

           /* 计算出次设备号 */

           mutex_lock(&videodev_lock);

           if (nr >= 0  &&  nr < end-base) {

                  /* use the one the driver asked for */

                  i = base+nr;

                  if (NULL != video_device[i]) {

                         mutex_unlock(&videodev_lock);

                         return -ENFILE;

                  }

           } else {

                  /* use first free */

                  for(i=base;i<end;i++)

                         if (NULL == video_device[i])

                                break;

                  if (i == end) {

                         mutex_unlock(&videodev_lock);

                         return -ENFILE;

                  }

           }

           video_device[i]=vfd; //保存video_device结构指针到系统的结构数组中,最终的次设备号和i相关。

           vfd->minor=i;

           mutex_unlock(&videodev_lock);

           mutex_init(&vfd->lock);

     

           /* sysfs class */

           memset(&vfd->class_dev, 0x00, sizeof(vfd->class_dev));

           if (vfd->dev)

                  vfd->class_dev.parent = vfd->dev;

           vfd->class_dev.class       = &video_class;

           vfd->class_dev.devt       = MKDEV(VIDEO_MAJOR, vfd->minor);

           sprintf(vfd->class_dev.bus_id, "%s%d", name_base, i - base);//最后在/dev目录下的名称

           ret = device_register(&vfd->class_dev);//结合udevmdev可以实现自动在/dev下创建设备节点

           ……

    }

    从上面的注册函数中可以看出V4L2驱动的注册事实上只是完成了设备节点的创建,如:/dev/video0。和video_device结构指针的保存。

    (3)视频驱动的打开过程

    当用户空间调用open打开对应的视频文件时,如:

    int fd = open(/dev/video0, O_RDWR);

    对应/dev/video0的文件操作结构是/drivers/media/videodev.c中定义的video_fops。

    static const struct file_operations video_fops=

    {

           .owner           = THIS_MODULE,

           .llseek            = no_llseek,

           .open             = video_open,

    };

    奇怪吧,这里只实现了open操作。那么后面的其它操作呢?还是先看看video_open吧。

    static int video_open(struct inode *inode, struct file *file)

    {

           unsigned int minor = iminor(inode);

           int err = 0;

           struct video_device *vfl;

           const struct file_operations *old_fops;

     

           if(minor>=VIDEO_NUM_DEVICES)

                  return -ENODEV;

           mutex_lock(&videodev_lock);

           vfl=video_device[minor];

           if(vfl==NULL) {

                  mutex_unlock(&videodev_lock);

                  request_module("char-major-%d-%d", VIDEO_MAJOR, minor);

                  mutex_lock(&videodev_lock);

                  vfl=video_device[minor]; //根据次设备号取出video_device结构

                  if (vfl==NULL) {

                         mutex_unlock(&videodev_lock);

                         return -ENODEV;

                  }

           }

           old_fops = file->f_op;

           file->f_op = fops_get(vfl->fops);//替换此打开文件的file_operation结构。后面的其它针对此文件的操作都由新的结构来负责了。也就是由每个具体的video_devicefops负责。

           if(file->f_op->open)

                  err = file->f_op->open(inode,file);

           if (err) {

                  fops_put(file->f_op);

                  file->f_op = fops_get(old_fops);

           }

    ……

    }

    以上是我对V4L2的一些理解,希望能对大家了解V4L2有一些帮助!

    展开全文
  • 编写基于V4L2视频驱动主要涉及到以下几个知识点: ● 摄像头方面的知识 要了解选用的摄像头的特性,包括访问控制方法、各种参数的配置方法、信号输出类型等。 ● Camera解码器、控制器 如果摄像头是模拟量输出的,...
  • 包括一套数据结构和底层V4L2驱动接口。 1、常用的结构体在内核目录include/linux/videodev2.h中定义 struct v4l2_requestbuffers //申请帧缓冲,对应命令VIDIOC_REQBUFS  struct v4l2_capability

    V4L2是V4L的升级版本,为linux下视频设备程序提供了一套接口规范。包括一套数据结构和底层V4L2驱动接口。

    1、常用的结构体在内核目录include/linux/videodev2.h中定义

    struct v4l2_requestbuffers        //申请帧缓冲,对应命令VIDIOC_REQBUFS
            struct v4l2_capability        //视频设备的功能,对应命令VIDIOC_QUERYCAP
            struct v4l2_input        //视频输入信息,对应命令VIDIOC_ENUMINPUT
            struct v4l2_standard        //视频的制式,比如PAL,NTSC,对应命令VIDIOC_ENUMSTD
            struct v4l2_format        //帧的格式,对应命令VIDIOC_G_FMT、VIDIOC_S_FMT等
            struct v4l2_buffer        //驱动中的一帧图像缓存,对应命令VIDIOC_QUERYBUF
            struct v4l2_crop        //视频信号矩形边框
            v4l2_std_id        //视频制式

    2、常用的IOCTL接口命令也在include/linux/videodev2.h中定义

    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         //检查当前视频设备支持的标准,例如PAL或NTSC。

    3、操作流程

    V4L2提供了很多访问接口,你可以根据具体需要选择操作方法。需要注意的是,很少有驱动完全实现了所有的接口功能。所以在使用时需要参考驱动源码,或仔细阅读驱动提供者的使用说明。

    下面列举出一种操作的流程,供参考。

    (1)打开设备文件
                      int fd = open(Devicename,mode);
                      Devicename:/dev/video0、/dev/video1 ……
                      Mode:O_RDWR [| O_NONBLOCK]

    如果使用非阻塞模式调用视频设备,则当没有可用的视频数据时,不会阻塞,而立刻返回。

    (2)取得设备的capability

              struct v4l2_capability capability;
                      int ret = ioctl(fd, VIDIOC_QUERYCAP, &capability);

    看看设备具有什么功能,比如是否具有视频输入特性。

    (3)选择视频输入

              struct v4l2_input input;
                      ……初始化input
                      int ret = ioctl(fd, VIDIOC_QUERYCAP, &input);

    一个视频设备可以有多个视频输入。如果只有一路输入,这个功能可以没有。

    (4)检测视频支持的制式

              v4l2_std_id std;
                      do {
                                    ret = ioctl(fd, VIDIOC_QUERYSTD, &std);
                       } while (ret == -1 && errno == EAGAIN);
                    switch (std) {
                    case V4L2_STD_NTSC:
                                    //……
                    case V4L2_STD_PAL:
                                    //……
                    }

    (5)设置视频捕获格式

              struct v4l2_format fmt;
                      fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
                      fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_UYVY;
                      fmt.fmt.pix.height = height;
                      fmt.fmt.pix.width = width;
                      fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;
                      ret = ioctl(fd, VIDIOC_S_FMT, &fmt);
                      if(ret) {
                              perror("VIDIOC_S_FMT\n");
                              close(fd);
                              return -1;
                      }

    (6)向驱动申请帧缓存

              struct v4l2_requestbuffers req;
                       if (ioctl(fd, VIDIOC_REQBUFS, &req) == -1) {
                              return -1;
                       }

    v4l2_requestbuffers结构中定义了缓存的数量,驱动会据此申请对应数量的视频缓存。多个缓存可以用于建立FIFO,来提高视频采集的效率。

    (7)获取每个缓存的信息,并mmap到用户空间

              typedef struct VideoBuffer {
                              void *start;
                              size_t length;
                      } VideoBuffer;

              VideoBuffer* buffers = calloc( req.count, sizeof(*buffers) );
                      struct v4l2_buffer buf;

              for (numBufs = 0; numBufs < req.count; numBufs++) {//映射所有的缓存
                              memset( &buf, 0, sizeof(buf) );
                              buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
                              buf.memory = V4L2_MEMORY_MMAP;
                              buf.index = numBufs;
                              if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {//获取到对应index的缓存信息,此处主要利用length信息及offset信息来完成后面的mmap操作。
                                      return -1;
                              }

                      buffers[numBufs].length = buf.length;
                              // 转换成相对地址
                              buffers[numBufs].start = mmap(NULL, buf.length,
                                      PROT_READ | PROT_WRITE,
                                      MAP_SHARED,
                                      fd, buf.m.offset);

                      if (buffers[numBufs].start == MAP_FAILED) {
                                      return -1;
                              }

    (8)开始采集视频

              int buf_type= V4L2_BUF_TYPE_VIDEO_CAPTURE;
                      int ret = ioctl(fd, VIDIOC_STREAMON, &buf_type);

    (9)取出FIFO缓存中已经采样的帧缓存

              struct v4l2_buffer buf;
                      memset(&buf,0,sizeof(buf));
                      buf.type=V4L2_BUF_TYPE_VIDEO_CAPTURE;
                      buf.memory=V4L2_MEMORY_MMAP;
                      buf.index=0;//此值由下面的ioctl返回
                      if (ioctl(fd, VIDIOC_DQBUF, &buf) == -1)
                      {
                              return -1;
                      }

    根据返回的buf.index找到对应的mmap映射好的缓存,取出视频数据。

    (10)将刚刚处理完的缓冲重新入队列尾,这样可以循环采集

              if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
                              return -1;
                      }

    (11)停止视频的采集

              int ret = ioctl(fd, VIDIOC_STREAMOFF, &buf_type);

    (12)关闭视频设备

              close(fd);

    四、 V4L2驱动框架

    上述流程的各个操作都需要有底层V4L2驱动的支持。内核中有一些非常完善的例子。

    比如:linux-2.6.26内核目录/drivers/media/video//zc301/zc301_core.c 中的ZC301视频驱动代码。上面的V4L2操作流程涉及的功能在其中都有实现。

    1、V4L2驱动注册、注销函数

    Video核心层(drivers/media/video/videodev.c)提供了注册函数
                  int video_register_device(struct video_device *vfd, int type, int nr)
                        video_device: 要构建的核心数据结构
                        Type: 表示设备类型,此设备号的基地址受此变量的影响
                        Nr: 如果end-base>nr>0 :次设备号=base(基准值,受type影响)+nr;
                        否则:系统自动分配合适的次设备号

    具体驱动只需要构建video_device结构,然后调用注册函数既可。

    如:zc301_core.c中的
                        err = video_register_device(cam->v4ldev, VFL_TYPE_GRABBER,
                                  video_nr[dev_nr]);
              Video核心层(drivers/media/video/videodev.c)提供了注销函数
                        void video_unregister_device(struct video_device *vfd)

    2、struct video_device 的构建

          video_device结构包含了视频设备的属性和操作方法。参见zc301_core.c

          strcpy(cam->v4ldev->name, "ZC0301[P] PC Camera");
                  cam->v4ldev->owner = THIS_MODULE;
                  cam->v4ldev->type = VID_TYPE_CAPTURE | VID_TYPE_SCALES;
                  cam->v4ldev->fops = &zc0301_fops;
                  cam->v4ldev->minor = video_nr[dev_nr];
                  cam->v4ldev->release = video_device_release;
                  video_set_drvdata(cam->v4ldev, cam);

    大家发现在这个zc301的驱动中并没有实现struct video_device中的很多操作函数,如:vidioc_querycap、vidioc_g_fmt_cap等。主要原因是struct file_operations zc0301_fops中的zc0301_ioctl实现了前面的所有ioctl操作。所以就不需要在struct video_device再实现struct video_device中的那些操作了。

    另一种实现方法如下:

    static struct video_device camif_dev =
            {
                    .name = "s3c2440 camif",
                    .type = VID_TYPE_CAPTURE|VID_TYPE_SCALES|VID_TYPE_SUBCAPTURE,
                    .fops = &camif_fops,
                    .minor = -1,
                    .release = camif_dev_release,
                    .vidioc_querycap = vidioc_querycap,
                    .vidioc_enum_fmt_cap = vidioc_enum_fmt_cap,
                    .vidioc_g_fmt_cap = vidioc_g_fmt_cap,
                    .vidioc_s_fmt_cap = vidioc_s_fmt_cap,
                    .vidioc_queryctrl = vidioc_queryctrl,
                    .vidioc_g_ctrl = vidioc_g_ctrl,
                    .vidioc_s_ctrl = vidioc_s_ctrl,
            };
            static struct file_operations camif_fops =
            {
                    .owner = THIS_MODULE,
                    .open = camif_open,
                    .release = camif_release,
                    .read = camif_read,
                    .poll = camif_poll,
                    .ioctl = video_ioctl2, /* V4L2 ioctl handler */
                    .mmap = camif_mmap,
                    .llseek = no_llseek,
            };

    注意:video_ioctl2是videodev.c中是实现的。video_ioctl2中会根据ioctl不同的cmd来调用video_device中的操作方法。

    3、Video核心层的实现

    参见内核/drivers/media/videodev.c

    (1)注册256个视频设备

    static int __init videodev_init(void)
            {
                    int ret;
                    if (register_chrdev(VIDEO_MAJOR, VIDEO_NAME, &video_fops)) {
                            return -EIO;
                    }
                    ret = class_register(&video_class);
                    ……
            }

    上面的代码注册了256个视频设备,并注册了video_class类。video_fops为这256个设备共同的操作方法。

    (2)V4L2驱动注册函数的实现

    int video_register_device(struct video_device *vfd, int type, int nr)
            {
                    int i=0;
                    int base;
                    int end;
                    int ret;
                    char *name_base;

            switch(type) //根据不同的type确定设备名称、次设备号
                    {
                            case VFL_TYPE_GRABBER:
                                    base=MINOR_VFL_TYPE_GRABBER_MIN;
                                    end=MINOR_VFL_TYPE_GRABBER_MAX+1;
                                    name_base = "video";

                                    break;
                            case VFL_TYPE_VTX:
                                    base=MINOR_VFL_TYPE_VTX_MIN;
                                    end=MINOR_VFL_TYPE_VTX_MAX+1;
                                    name_base = "vtx";
                                    break;
                            case VFL_TYPE_VBI:
                                    base=MINOR_VFL_TYPE_VBI_MIN;
                                    end=MINOR_VFL_TYPE_VBI_MAX+1;
                                    name_base = "vbi";
                                    break;
                            case VFL_TYPE_RADIO:
                                    base=MINOR_VFL_TYPE_RADIO_MIN;
                                    end=MINOR_VFL_TYPE_RADIO_MAX+1;
                                    name_base = "radio";
                                    break;
                            default:
                                    printk(KERN_ERR "%s called with unknown type: %d\n",
                                            __func__, type);
                                    return -1;
                    }

            /* 计算出次设备号 */
                    mutex_lock(&videodev_lock);
                    if (nr >= 0 && nr < end-base) {
                            /* use the one the driver asked for */
                            i = base+nr;
                            if (NULL != video_device[i]) {
                                    mutex_unlock(&videodev_lock);
                                    return -ENFILE;
                            }
                    } else {
                            /* use first free */
                            for(i=base;i<end;i++)
                                    if (NULL == video_device[i])
                                            break;
                            if (i == end) {
                                    mutex_unlock(&videodev_lock);
                                    return -ENFILE;
                            }
                    }
                    video_device[i]=vfd; //保存video_device结构指针到系统的结构数组中,最终的次设备号和i相关。
                    vfd->minor=i;
                    mutex_unlock(&videodev_lock);
                    mutex_init(&vfd->lock);

            /* sysfs class */
                    memset(&vfd->class_dev, 0x00, sizeof(vfd->class_dev));
                    if (vfd->dev)
                            vfd->class_dev.parent = vfd->dev;
                    vfd->class_dev.class = &video_class;
                    vfd->class_dev.devt = MKDEV(VIDEO_MAJOR, vfd->minor);
                    sprintf(vfd->class_dev.bus_id, "%s%d", name_base, i - base);//最后在/dev目录下的名称
                    ret = device_register(&vfd->class_dev);//结合udev或mdev可以实现自动在/dev下创建设备节点

                    ……
            }

    从上面的注册函数中可以看出V4L2驱动的注册事实上只是完成了设备节点的创建,如:/dev/video0。和video_device结构指针的保存。

    (3)视频驱动的打开过程

    当用户空间调用open打开对应的视频文件时,如:

    int fd = open(/dev/video0, O_RDWR);

    对应/dev/video0的文件操作结构是/drivers/media/videodev.c中定义的video_fops。

    static const struct file_operations video_fops=
            {
                    .owner = THIS_MODULE,
                    .llseek = no_llseek,
                    .open = video_open,
            };

    奇怪吧,这里只实现了open操作。那么后面的其它操作呢?还是先看看video_open吧。

    static int video_open(struct inode *inode, struct file *file)
            {
                    unsigned int minor = iminor(inode);
                    int err = 0;
                    struct video_device *vfl;
                    const struct file_operations *old_fops;

            if(minor>=VIDEO_NUM_DEVICES)
                            return -ENODEV;
                    mutex_lock(&videodev_lock);
                    vfl=video_device[minor];
                    if(vfl==NULL) {
                            mutex_unlock(&videodev_lock);
                            request_module("char-major-%d-%d", VIDEO_MAJOR, minor);
                            mutex_lock(&videodev_lock);
                            vfl=video_device[minor]; //根据次设备号取出video_device结构
                            if (vfl==NULL) {
                                    mutex_unlock(&videodev_lock);
                                    return -ENODEV;
                            }
                    }
                    old_fops = file->f_op;
                    file->f_op = fops_get(vfl->fops);//替换此打开文件的file_operation结构。后面的其它针对此文件的操作都由新的结构来负责了。也就是由每个具体的video_device的fops负责。

                    if(file->f_op->open)
                            err = file->f_op->open(inode,file);
                    if (err) {
                            fops_put(file->f_op);
                            file->f_op = fops_get(old_fops);
                    }
                    ……
            }


    展开全文
  • V4L2驱动开发详解

    千次阅读 2020-04-23 19:38:11
    环境: OS:Ubuntu 16.04 (Win10 hypev) Kernel Version:3.13.0-24-generic 这里终极目标是注册一个/dev/video0的设备,再通过一个应用程序去读取它: #include &...linux/module.h&...media/v...

    环境:

    OS:Ubuntu 16.04 (Win10 hypev)

    Kernel Version:3.13.0-24-generic

    这里终极目标是注册一个/dev/video0的设备,再通过一个应用程序去读取它:

    #include <linux/module.h>
    #include <linux/videodev2.h>
    #include <media/videobuf2-vmalloc.h>
    #include <media/v4l2-device.h>
    #include <media/v4l2-ioctl.h>
    #include <media/v4l2-ctrls.h>
    #include <media/v4l2-fh.h>
    #include <media/v4l2-event.h>
    #include <media/v4l2-common.h>
    // 声明结构体sv供v4l2_device_register使用
    struct sv{
        struct v4l2_device v4l2_dev;
        struct video_device vdev;
    }; 
    
    static struct sv sdev;
    // 查询设备支持的功能
    static int sv_querycap(struct file *file, void *priv, struct v4l2_capability *vcap)
    {
        struct sv *sv = video_drvdata(file);
        strlcpy(vcap->driver, sv->vdev.name,sizeof(vcap->driver));
        strlcpy(vcap->card, "Stanway test card",sizeof(vcap->card));
        strlcpy(vcap->bus_info, "Stanway test bus",sizeof(vcap->bus_info));
        vcap->capabilities = V4L2_CAP_VIDEO_CAPTURE|V4L2_CAP_READWRITE; // report capabilities
        printk(KERN_INFO "[stanway]%s, %d, \n", __func__,__LINE__);
        return 0;
    }
    
    /***********************************************
     *
     *V4L2模块函数
     *
     **********************************************/
    
    static const struct v4l2_file_operations sv_fops = {
        .owner = THIS_MODULE,
        .open = v4l2_fh_open, // open /dev/video0
        .release = v4l2_fh_release, // close /dev/video
        .unlocked_ioctl = video_ioctl2,
    };
    
    static const struct v4l2_ioctl_ops sv_ioctl_ops = {
        .vidioc_querycap = sv_querycap, // ioctl VIDIOC_QUERYCAP 时会调用sv_querycap函数
    };
    
    static int __init vivi_init(void)
    {
        struct sv *sv;
        struct v4l2_device *v4l2_dev;
        int ret;
    
        sv = &sdev;
        v4l2_dev = &sv->v4l2_dev;
        //init v4l2 name, version
        strlcpy(v4l2_dev->name, "sv", sizeof(v4l2_dev->name));
        v4l2_info(v4l2_dev, "Color SV VGA driver %s\n", "0.0.1"); //output V4l2 info
        ret = v4l2_device_register(NULL, v4l2_dev);
        if (ret < 0)
        {
          printk(KERN_INFO "Could not register v4l2_device\n");
          return ret;
        }
    
        //setup video
        strlcpy(sv->vdev.name, "My vivi driver", sizeof(sv->vdev.name));
        sv->vdev.v4l2_dev = v4l2_dev; // set v4;2_device address to video_device
        sv->vdev.fops = &sv_fops; // v4l2_file_operations
        sv->vdev.ioctl_ops = &sv_ioctl_ops; // v4l2_ioctl_ops
        sv->vdev.release = video_device_release_empty;
        set_bit(V4L2_FL_USES_V4L2_FH, &sv->vdev.flags);
        video_set_drvdata(&sv->vdev, sv); //将sv设置为驱动私有数据
    
        if (video_register_device(&sv->vdev, VFL_TYPE_GRABBER, -1) != 0){
            printk(KERN_INFO "[stanway%s, %d, video_register_device FAIL\n", __func__,__LINE__);
            ret = -ENODEV;
            goto out_dev;
        }
        printk(KERN_INFO "[stanway]%s, %d, module inserted\n", __func__, __LINE__);
        return 0;
    
    out_dev:
        v4l2_device_unregister(&sv->v4l2_dev);
        video_unregister_device(&sv->vdev);
        return ret;
    }
    
    static void __exit vivi_exit(void)
    {
        struct sv *sv;
        sv = &sdev;
    
        printk(KERN_INFO "[stanway] %s, %d, module remove\n", __func__, __LINE__);
        video_unregister_device(&sv->vdev);
        v4l2_device_unregister(&sv->v4l2_dev);
    }
    
    module_init(vivi_init);
    module_exit(vivi_exit);
    MODULE_DESCRIPTION("Stanway test module");
    MODULE_AUTHOR("Stanway Hu");
    MODULE_LICENSE("GPL");

    Makefile:

    #
    #Makefile for kernel test
    #
    CONFIG_MODULE_SIG=n
    PWD := $(shell pwd)
    KVERSION := $(shell uname -r)
    KERNEL_DIR = /usr/src/linux-headers-$(KVERSION)/
    
    MODULE_NAME = myv4l2
    obj-m := $(MODULE_NAME).o
    
    all:
            make -C $(KERNEL_DIR) M=$(PWD) modules
    clean:
            make -C $(KERNEL_DIR) M=$(PWD) clean

    编译:

    make

    加载:

    sudo insmod myv412.ko

    dmesg报错:

    [11095.173012] myv4l2: Unknown symbol video_ioctl2 (err 0)
    [11095.173017] myv4l2: Unknown symbol v4l2_fh_open (err 0)
    [11095.173020] myv4l2: Unknown symbol video_devdata (err 0)
    [11095.173022] myv4l2: Unknown symbol v4l2_fh_release (err 0)
    [11095.173026] myv4l2: Unknown symbol video_unregister_device (err 0)
    [11095.173029] myv4l2: Unknown symbol v4l2_device_register (err 0)
    [11095.173032] myv4l2: Unknown symbol __video_register_device (err 0)
    [11095.173034] myv4l2: Unknown symbol v4l2_device_unregister (err 0)
    [11095.173037] myv4l2: Unknown symbol video_device_release_empty (err 0)

    解决:

    1.安装v4l2支持:

    sudo apt-get install v4l2loopback-dkms

    2.进入目录:/lib/modules/3.13.0-24-generic/kernel/drivers/media/v4l2-core,运行:

    sudo insmod videodev.ko

    3.再insmod myv4l2.ko即可:lsmod

    ls /dev/video

    dmesg:

    再sudo rmmod myv4l2

    现在再来编一个应用程序vtest.c读取/dev/video0:

    #include <errno.h>
    #include <fcntl.h>
    #include <linux/videodev2.h>
    #include <stdint.h>
    #include <stdio.h>
    #include <string.h>
    #include <sys/ioctl.h>
    #include <sys/mman.h>
    #include <unistd.h>
    
    
    static int xioctl(int fd,int request, void *arg)
    {
    int r;
    do r=ioctl(fd,request,arg);
    while(-1 ==r && EINTR == errno);
    
    
    return r;
    }
    
    
    
    
    int print_caps(int fd)
    {
    struct v4l2_capability caps={};
    
    
    perror("print_caps enter");
    
    
    if(-1 == xioctl(fd,VIDIOC_QUERYCAP,&caps))
    {
    perror("Querying cap fail");
    return 1;        
    }
    
    
    printf( "Driver Caps:\n"
    	  "  Driver: \"%s\"\n"
    	  "  Card: \"%s\"\n"
    	  "  Bus: \"%s\"\n"
    	  "  Version: %d.%d\n"
    	  "  Capabilities: %08x\n",
    	  caps.driver,
    	  caps.card,
    	  caps.bus_info,
    	  (caps.version>>16)&&0xff,
    	  (caps.version>>24)&&0xff,
    	  caps.capabilities);
    	  
    	  
    perror("print_caps enter");
    
    
    return 0;    
    }
    
    
    int main()
    {
    int fd;
    fd=open("/dev/video0",O_RDWR);
    if(fd ==-1)
    {
        perror("opening video device fail");
        return 1;
          
    }
    perror("opening video device success");
    
    
    if(print_caps(fd))
    {
      return 1;
    }else{
      
    }
    perror("close fd");
    close(fd);
    
    
    return 0;
    
    
    }  
    	  "  Driver: \"%s\"\n"
    	  "  Card: \"%s\"\n"
    	  "  Bus: \"%s\"\n"
    	  "  Version: %d.%d\n"
    	  "  Capabilities: %08x\n",
    	  caps.driver,
    	  caps.card,
    	  caps.bus_info,
    	  (caps.version>>16)&&0xff,
    	  (caps.version>>24)&&0xff,
    	  caps.capabilities);
    	  
    	  
    perror("print_caps enter");
    
    
    return 0;    
    }
    
    
    int main()
    {
    int fd;
    fd=open("/dev/video0",O_RDWR);
    if(fd ==-1)
    {
        perror("opening video device fail");
        return 1;
          
    }
    perror("opening video device success");
    
    
    if(print_caps(fd))
    {
      return 1;
    }else{
      
    }
    perror("close fd");
    close(fd);
    
    
    return 0;
    
    
    }  
    

    再gcc vtest.c -o test,再运行./vtest:

    V4L2架构概览:

    由框架可知,有两种方式编写sensor的驱动程序:

    一是直接将sensor作为video device,若采用这种方式需要自己处理内存管理问题,比较复杂。

    二是将sensor作为子设备供上一层V4L2核心调用,采用这种方式只需要调用V4L2提供的API即可。

    上面例子使用的就是第二种方式。

    下面介绍一下V4L2整体情况:

    V4L2提供一套数据结构和底层V4L2驱动接口规范供Linux下的视频设备程序使用,主要是一系列回调函数,如设置摄像头频率,帧率,视频压缩格式和图像参数等,还可用于其他多媒体开发,如音频等。

    一般采用V4L2驱动的摄像头设备文件是/dev/video0,V4L2支持两种方式采集图像:内存映射方式mmap和直接读取方式read。

    V4L2的重要数据结构都存放在/include/linux/videodev2.h文件中,在采集图像过程中,就是通过操作这些数据结构来获得最终图像数据,Linux系统对V4L2的支持是从Linux 2.5.x版本开始的,它可在内核编译阶段配置,或后期安装,默认情况下都有此开发接口。

    videodev2.hl类似位置,其内部实际指向另外的.h:

    V4L2定义了通用API元素,图像的格式,输入输出方法,以及Linux内核驱动处理视频信息的一系列接口,有主要以下五大接口:

    1.视频采集接口(video capture interface)

    2.视频输出接口(video output interface)

    3.视频覆盖预览接口 (video overlay interface) --- 此功能会将采集到的画面保存在内存中,同时不需要经过其他处理直接将画面显示在屏幕上

    4.视频输出覆盖接口 (video output overlay interface)--- 此功能打开,第3个功能需要关闭

    5.编解码接口 (codec interface)

    V4L2结构体:

    1.常用结构体在文件./include/uapi/linux/videodev2.h下定义:

    struct v4l2_requestbuffers:申请缓冲区,对应命令为VIDIOC_REQBUFS

    VIDIOC_REQBUFS命令通过该结构体向驱动发出申请,请求一片连续的内存空间用于缓存视频信息。

    cout:申请的缓冲区个数,是根据图像占用空间大小而设定的

    type:视频捕获模式,枚举v4l2_buf_type

    memory:内存区的使用方式

    struct v4l2_capability:视频设备的功能,对应命令为VIDIOC_QUERYCAP

    这里就是上例中用到的结构体:

    1>.driver[16]:表示驱动名,这个名需要与struct video_device中的name字段匹配

    2>.card[32]:表示设备名

    3>.bus_info[32]:表示设备在系统中的位置

    4>.version:表示驱动版本号

    5>.capabilities:表示设备支持的操作,常见值有V4L2_CAP_VIDEO_CAPTURE|V4L2_CAP_STREAMING表示的是一个视频捕捉设备且具有数据流控制模式,支持的模式可见如下枚举,它是后面v4l2_format结构体type字段的值:

    常见捕获模式为视频捕获模式V4L2_BUF_TYPE_VIDEO_CAPTURE,在此模式下采用fmt枚举域:v4l2_pix_format:

    其中:

    width:视频宽

    height:视频高

    pixelformat:视频数据格式(常见值有V4L2_PIX_FMT_YUV422P|V4L2_PIX_FMT_RGB565)

    field:v4l2_field的枚举量

    bytesperline:一行图像占用的字节数

    sizeimage:图像占用的总字节数

    colorspace:设备的颜色空间

    6>.reserved[4]:保留字段

    struct v4l2_input:视频输入信息,对应命令VIDIOC_ENUMINPUT

    index:表示哪一个输入

    name:输入标签

    type:输入类型

    audioset:关联的音频(位域)

    tunner:关联的调谐器(FM/AM?)

    std:

    status:

    reserved:

    struct v4l2_standard:视频制式,比如PAL,NTSC,对应命令VIDIOC_ENUMSTD

    struct v4l2_format:帧格式,对应命令VIDIOC_G_FMT,VIDIOC_S_FMT等

    struct v4l2_buffer:驱动中的一帧图像缓存,对应命令VIDIOC_QUERYBUF

    index:缓存编号

    type:视频捕获模式

    bytesused:缓存已使用空间大小

    flags:缓存当前状态,常见值有V4L2_BUF_FLAG_MAPPED|V4L2_BUF_FLAG_QUEUED|V4L2_BUF_FLAG_DONE,分别表示当前缓存已经映射,缓存可以采集数据,缓存可以提取数据。

    timestamp:时间戳

    sequence:缓存序号

    memory:缓存使用方式

    offset:当前缓存与内存区起始地址的偏移

    length:缓存大小

    reserved2:

    reserved:一般用于传递物理地址

    VIDIOC_QBUF和VIDIOC_DQBUF命令都采用该结构与驱动通信:

    VIDIOC_QBUF命令向驱动传递应用程序已经处理完的缓存,即将缓存加入空闲可捕获视频队列,传递的主要参数为index

    VIDIOC_DQBUF命令向驱动获取已经存放有视频数据的缓存,该结构体的各个字段几乎都会被更新,但主要的参数也是index,应用程序会根据index确定可用数据的起始地址和范围。

    struct v4l2_crop:视频信号矩形边框

     

    2.常用的IOCTL接口命令也在文件./include/uapi/linux/videodev2.h中定义:

    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:检查当前视频设备支持的标准,如PATL或NTSC

    调用V4L2的流程:

    打开设备 -> 检查和设置设备属性 -> 设置帧格式 -> 设置一种输入输出方法,缓冲区管理 - > 循环获取数据 ->关闭设备

    1.打开设备:在V4L2中,视频设备被看作一个文件,使用open函数即可打开该设备,有两种模式:

    一是非阻塞模式打开设备,这种模式下即使尚未捕获到数据,驱动依旧会把缓冲DQBUFF内的数据返回给应用层:

    int fd = open("/dev/video0", O_RDWR|O_NONBLOCK);

    二是阻塞模式打开设备,这种模式下必须捕获到数据才返回,否则一直等待:

    int fd = open("/dev/video0", O_RDWR);

    2.获取设备特性:查看设备都支持什么功能,比如是否具有视频输入功能

    struct v4l2_capability caps={};

    int ret = ioctl(fd, VIDIOC_QUERYCAP, &caps);

    //获取成功,检查是否有视频捕获功能

    if (!(caps.capabilities & V4L2_CAP_VIDEO_CAPTURE)){

    ...

    }

    //是否具有流控制功能

     

    if (!(caps.capabilities & V4L2_CAP_STREAMING)){

    ...

    }

    3.选择视频输入:

    struct v4l2_input input;

    .......这里初始化input?

    int ret = ioctl(fd, VIDIOC_QUERYCAP, &input);

    一个视频设备可以有多个视频输入,若只有一路输入,这个功能可省去。

    VIDIOC_G_INPUT和VIDIOC_S_INPUT用于查询和选择当前的input,一个video设备节点可能对应多个视频源,比如saf7113可最多支持四路cvbs输入,若上层想要在四个cvbs视频输入间切换,则就要调用ioctl(fd, VIDIOC_S_INPUT, &input)来切换。

    VIDIOC_G_INPUT和VIDIOC_G_OUTPUT返回当前的video input和output的index值。

    4.检测视频支持的制式:

    v4l2_std_id std;

    do{

        ret = ioctl(fd, VIDIOC_QUERYSTD, &std);

    }while (ret == -1 && errno == EAGAIN);

    switch (std)

    {

    case V4L2_STD_NTSC:

        ....

    case V4L2_STD_PAL:

        ...

    }

    5.设置视频捕获格式:v4l2_format结构体用以设置摄像头的视频制式,帧格式等,在设置这个参数时应先填好v4l2_format的各个域,如type传输流类型,fmt.pix.width宽,fmt.pix.height高,fmt.pix.pixelformat采样类型,如YUV4:2:2,后通过VIDIOC_S_FMT命令设置视频捕捉格式。

    struct v4l2_format fmt;

    memset(&fmt, 0, sizeof(fmt));

    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    fmt.fmt.pix.width = g_display_width;

    fmt.fmt.pix.heigth = g_display_height;

    fmt.fmt.pix.pixelformat = g_fmt;

    fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;

    //设置设备捕获视频的格式

    if (ioctl(dev->fd, VIDIOC_S_FMT, &fmt) < 0)

    {

    ...

    }

    若该视频设备驱动不支持所设定的图像格式,视频驱动会重新修改struct v4l2_format结构体变量的值为该设备所支持的图像格式,因此在程序设计时,设定完所有的视频格式后,要获取实际的视频格式,需要重新读取struct v4l2_format结构体变量。

    6.向驱动申请帧缓存:一般不超过5个,CAP_BUF_NUM = 4

    struct v4l2_requestbuffers req;/* 申请设备的缓存区 */
    memset(&req, 0, sizeof(req));
    req.count = CAP_BUF_NUM;  //申请一个拥有四个缓冲帧的缓冲区
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;
    
    if (ioctl(dev->fd, VIDIOC_REQBUFS, &req) < 0)
    {
        if (EINVAL == errno)
        {
            printf(stderr, "%s does not support "
                     "memory mapping\n", dev->dev);
            return TFAIL;
        }
        else
        {
            printf(stderr, "%s does not support "
                     "memory mapping, unknow error\n", dev->dev);
            return TFAIL;
        }
    }
    if (req.count < 2)
    {
        printf(stderr, "Insufficient buffer memory on %s\n",
                 dev->dev);
        return TFAIL;
    }

    v4l2_requestbuffers结构体定义了缓存的数量,驱动会根据此申请对应数量的视频缓存,多个缓存可用于建立FIFO,来提高视频采集的效率,控制命令为VIDIOC_REQBUFS

    主要功能:请求V4L2驱动分配视频缓冲区,也就是申请V4L2视频驱动分配内存,V4L2是视频设备的驱动层,它位于内核空间,因此通过VIDIOC_REQBUFS控制命令申请的内存空间位于内核空间中,应用程序不能直接访问,需要调用mmap内存映射函数把内核空间的内存映射到用户空间后,应用程序通过访问用户空间地址来访问内核空间。若成功,则会在V4L2驱动层分配好视频缓冲区。

    7.获取每个缓存的信息,并mmap到用户空间

    应用层和设备有三种交换数据的方法,直接read/write,内存映射mmap和用户指针,这里只说mmap。

    typedef struct VideoBuffer {   //定义一个结构体来映射每个缓冲帧
        void *start;
        size_t length;
    } VideoBuffer;
    VideoBuffer* buffers = calloc( req.count, sizeof(*buffers) );
    struct v4l2_buffer buf;
    for (numBufs = 0; numBufs < req.count; numBufs++) {//映射所有的缓存
        memset( &buf, 0, sizeof(buf) );
        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        buf.memory = V4L2_MEMORY_MMAP;
        buf.index = numBufs;
        if (ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1) {//获取到对应index的缓存信息,此处主要利用length信息及offset信息来完成后面的mmap操作。
            return -1;
    }
    buffers[numBufs].length = buf.length;
    // 转换成相对地址
    buffers[numBufs].start = mmap(NULL, buf.length,
                                  PROT_READ | PROT_WRITE,
                                  MAP_SHARED,
                                  fd, buf.m.offset);
    if (buffers[numBufs].start == MAP_FAILED) {
        return -1;
    }

    mmap语法:

    void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);

    参数:

    addr:映射的起始地址,一般为NULL,让内核自动选择

    length:被映射内存块的长度

    prot:标志映射后能否被读写,其值为PROT_EXEC,PROT_READ,PROT_WRITE,PROT_NONE

    flags:确定此内存映射能否被其他进程共享:MAP_SHARED,MAP_PRIVATE

    fd:返回成功映射后的地址,不成功返回MAP_FAILED((void)-1)

    offset:被映射对象内容的起点

    munmap语法:

    int munmap( void * addr, size_t len ) ;

    执行成功返回0,失败返回-1。该调用在进程地址空间中解除一个映射关系

    addr:为调用mmap()时返回的地址

    len:为映射区的大小

    详细参考网址

    8.开始采集视频,也就是在缓冲区处理好之后就可获得视频了:在开始之前,还需要把缓冲帧放入缓冲队列中。

    //把四个缓冲帧放入队列
    for (i = 0; i < CAPBUFNUM; i++)
    {
    memset(&buf, 0, sizeof(buf));
    buf.type = V4L2BUFTYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    buf.index = i;
    buf.m.offset = dev->buffer[i].offset;
    / 将空闲的内存加入可捕获视频的队列 /
    if(ioctl(dev->fd, VIDIOC_QBUF, &buf) < 0)
    {
    printf(“ERROR: VIDIOC_QBUF[%s], FUNC[%s], LINE[%d]\n”, dev->dev, __FUNCTION, __LINE);
    return TFAIL;
    }
    }
    
    type = V4L2BUFTYPEVIDEOCAPTURE;
    / 打开设备视频流 /
    if(ioctl(dev->fd, VIDIOC_STREAMON, &type) < 0)
    {
    printf(“ERROR: VIDIOC_STREAMON[%s], FUNC[%s], LINE[%d]\n”, dev->dev, __FUNCTION, __LINE);
    return TFAIL;
    }

    在前期初始化完成之后,只是解决了一帧视频数据的格式和大小问题,而连续视频帧数据的采集需要用帧缓冲区队列的方式来解决,也就是要通过驱动程序在内存中申请多个缓冲区来存放视频数据。以下三点比较重要:

    第1点.应用程序通过API提供的方法VIDIOC_REQBUFS申请若干个视频数据的帧缓冲区,申请帧缓冲区数量不低于3个,每个帧缓冲区存放一帧视频数据,这些帧缓存区在内核空间。

    第2点.应用程序通过API提供的查询方法VIDIOC_QUERYBUF查询到帧缓冲区在内核空间的长度和偏移量地址

    第3点.应用程序再通过mmap,将申请到的内核空间帧缓冲区的地址映射到用户空间地址,这样就可以直接处理帧缓冲区的数据

    执行步骤,以下三步:

    第一步:将帧缓冲区排队在视频输入队列中,并启动视频采集

    在驱动程序处理视频的过程中,会定义两个队列:视频采集输入队列incoming queues和视频采集输出队列outgoing queues,前者等待驱动放入视频数据的队列,后者是驱动程序已经放入视频数据的队列,将申请到的帧缓冲区在视频采集输入队列排队,并启动视频采集。

    第二步:循环往复,采集连续的视频数据

    一是启动视频采集后,驱动程序开始采集一帧数据,把采集的数据放入视频采集输入队列的第一个帧缓冲区,一帧数据采集完成后,也就是第一个帧缓冲区存满一帧数据后,驱动程序将该帧缓冲区移到视频采集输出队列,等待应用程序从输出队列取出,驱动程序接下来采集下一帧数据,放入第二个帧缓冲区,同样在第二个帧缓冲区存满下一帧数据后,也会被放入视频采集输出队列中。

    二是应用程序从视频采集输出队列中取出含有视频数据的帧缓冲区后,会处理帧缓冲区中的视频数据,比如存储或压缩这些数据。

    9.取出FIFO缓冲中已经采样的帧缓存:

    struct v4l2_buffer capture_buf;
    memset(&capture_buf, 0, sizeof(capture_buf));
    capture_buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    capture_buf.memory = V4L2_MEMORY_MMAP;
    /* 将已经捕获好视频的内存拉出已捕获视频队列 */
    if (ioctl(dev.fd, VIDIOC_DQBUF, &capture_buf) < 0)
     {
       printf("ERROR: VIDIOC_DQBUF[%s], FUNC[%s], LINE[%d]\n", dev, __FUNCTION__, __LINE__);
       return TFAIL;
       }
     }
     image_data_handle(buffer[capture_buf.index].start, capture_buf.bytesused);

    10.将刚刚处理完的缓重新入队列尾,实现循环采集

    if (ioctl(fd, VIDIOC_QBUF, &buf) == -1) {
          return -1;
    }

     

    以上第8,9,10三步可用以下图示描述:

     

    11.停止视频采集,解除映射

    int ret = ioctl(fd, VIDIOC_STREAMOFF, &buf_type);
    munmap(buffer[j].start, buffer[j].length);

    12.关闭视频设备

    close(fd);

    简洁版流程描述:

    (1)打开视频设备文件。int fd=open(“/dev/video0”,O_RDWR);
    (2)查询视频设备的能力,比如是否具有视频输入,或者音频输入输出等。ioctl(fd_v4l, VIDIOC_QUERYCAP, &cap)
    (3)设置视频采集的参数
    设置视频的制式,制式包括PAL/NTSC,使用ioctl(fd_v4l, VIDIOC_S_STD, &std_id)
    设置视频图像的采集窗口的大小,使用ioctl(fd_v4l, VIDIOC_S_CROP, &crop)
    设置视频帧格式,包括帧的点阵格式,宽度和高度等,使用ioctl(fd_v4l, VIDIOC_S_FMT, &fmt)
    设置视频的帧率,使用ioctl(fd_v4l, VIDIOC_S_PARM, &parm)
    设置视频的旋转方式,使用ioctl(fd_v4l, VIDIOC_S_CTRL, &ctrl)
    (4)向驱动申请视频流数据的帧缓冲区
    请求/申请若干个帧缓冲区,一般为不少于3个,使用ioctl(fd_v4l, VIDIOC_REQBUFS, &req)
    查询帧缓冲区在内核空间中的长度和偏移量 ioctl(fd_v4l, VIDIOC_QUERYBUF, &buf)
    (5)应用程序通过内存映射,将帧缓冲区的地址映射到用户空间,这样就可以直接操作采集到的帧了,而不必去复制。
    buffers[i].start = mmap (NULL, buffers[i].length, PROT_READ | PROT_WRITE, MAP_SHARED, fd_v4l, buffers[i].offset);
    (6)将申请到的帧缓冲全部放入视频采集输出队列,以便存放采集的数据。ioctl (fd_v4l, VIDIOC_QBUF, &buf)
    (7)开始视频流数据的采集。 ioctl (fd_v4l, VIDIOC_STREAMON, &type)
    (8) 驱动将采集到的一帧视频数据存入输入队列第一个帧缓冲区,存完后将该帧缓冲区移至视频采集输出队列。
    (9)应用程序从视频采集输出队列中取出已含有采集数据的帧缓冲区。ioctl (fd_v4l, VIDIOC_DQBUF, &buf) ,应用程序处理该帧缓冲区的原始视频数据。
    (10)处理完后,应用程序的将该帧缓冲区重新排入输入队列,这样便可以循环采集数据。ioctl (fd_v4l, VIDIOC_QBUF, &buf)
    重复上述步骤8到10,直到停止采集数据。
    (11)停止视频的采集。ioctl (fd_v4l, VIDIOC_STREAMOFF, &type)
    (12)释放申请的视频帧缓冲区unmap,关闭视频设备文件close(fd_v4l)。
    以上的程序流程,包含了视频设备采集连续的视频数据的逻辑关系。而在实际运用中,往往还要加入对视频数据进行处理(如压缩编码)的工作,否则,视频流数据量相当大,需要很大的存储空间和传输带宽。

    详细参考网址1

    详细参考网址2

    详细参考网址3

    详细参考网址4

    展开全文
  • Linux V4L2驱动架构解析与开发导引 Andrew按:众所周知,linux中可以采用灵活的多层次的驱动架构来对接口进行统一与抽象,最低层次的驱动总是直接面向硬件的,而最高层次的驱动在linux中被划分为“面向字符设备、...
  • v4l2驱动编写篇

    千次阅读 2016-11-02 15:29:03
    作为一个驱动作者,当挖掘头文件的时候,你可能也得看看include/media/v4l2-dev.h,它定义了许多你将来要打交道的结构体。 一个视频驱动很可能要有处理PCI总线,或USB总线的部分。这里我们不会花什么时间还接触这些...
  • 视频驱动V4L2子系统驱动架构-框架

    千次阅读 2018-08-16 16:08:31
    V4L2驱动框架 v4l2驱动架构如图所示,v4l2也就是video for linux two,那么也就是说还有One了,v4l2前面还有v4l 图中芯片模块对应Soc的各个子模块,video_device结构体主要用来控制Soc的video模块,v4l2_device会...
  • V4L2视频驱动程序开发已经进入尾声,本次视频支持多个通道的stream同时传输,即有多个设备文件关联到驱动。最高支持48个stream同时输入。 应用程序在获取stream的时候,需要用到select,而驱动程序中的poll方法将被...
  • linux设备驱动之——V4L2

    万次阅读 2013-01-09 09:17:51
    Video for Linux Two    V4L2的是V4L的第二个版本。原来的V4L被引入到Linux内核2.1.x的...Video4Linux2驱动程序包括Video4Linux1应用的兼容模式,但实际上,支持是不完整的,并建议V4L2设备使用V4L2的模式。
  • Android设备驱动之——V4L2

    万次阅读 2012-05-10 09:08:27
    Video for Linux Two    V4L2的是V4L的第二个版本。原来的V4L被引入到Linux内核2.1.x的...Video4Linux2驱动程序包括Video4Linux1应用的兼容模式,但实际上,支持是不完整的,并建议V4L2设备使用V4L2的模式。现在
  • V4L2开发资料汇总

    2020-07-30 23:30:27
    花了很大的经历,搜集并整理了v4l2开发的文档,这些文档写的比较好,并非网上胡编乱造乱转发的资料,先汇总后共享给大家,资源列表: ①linux驱动学习笔记Camif ②V4L2 spec 中文 v0.625 ...⑩V4L2驱动的移植与应用(三)
  • 在我的上一篇文章基于V4L2驱动程序的USB摄像头Android(JNI)的编写(一)中,我详细介绍了如何配置V4L2驱动程序的采集环境,那么在这篇文章中,我将详细分析V4L2采集视频的过程。一、向驱动程序申请缓冲帧 缓冲帧,...
  • V4L2最简单驱动实例

    千次阅读 2013-12-10 10:42:16
    V4L2最简单驱动实例 头文件 ......................... static const struct v4l2_file_operations myvivi_fops = {  .owner = THIS_MODULE, }; static struct video_device *myvivi_device; static void myvivi_...
  • 我们要做的是写个硬件相关驱动,其中用到了核心层V4l2-dev提供的API函数。比如内核 中的vivi.c,是一个虚拟视频驱动+虚拟摄像头的例子。实际中我们需要检测到摄像头设备,然后在调用注册函数,产生/dev/vide
  • 基于V4L2框架的linux驱动程序编写

    千次阅读 2014-11-26 15:31:57
    6467T Vpif的V4L2 linux驱动程序框架 在linux中,使用V4L2框架来管理视频采集设备V4L2框架主要包括两层:V4L2Device和V4L2 Subdevice,其中V4L2 Device是虚拟的设备,用以管理V4L2 Subdevice,而Subdevice才是...
  • V4L2摄像头驱动移植

    千次阅读 2016-04-02 23:59:09
    摄像v4l2驱动 make menuconfig  Device Drivers ---> Multimedia support --->  Video For Linux [*] Video capture adapters ---> [*] V4L USB devices ---> USB Video Class (UVC)
  • 【Linux开发V4L2驱动框架分析学习

    千次阅读 2016-08-28 09:52:19
    Video4Linux2是Linux内核中关于视频设备的内核驱动框架,为上层的访问底层的视频设备提供了统一的接口。凡是内核中的子系统都有抽象底层硬件的差异,为上层提供统一的接口和提取出公共代码避免代码冗余等好处。就像...
  • 编写基于V4L2视频驱动主要涉及到以下几个知识点:  ● 摄像头方面的知识  要了解选用的摄像头的特性,包括访问控制方法、各种参数的配置方法、信号输出类型等。  ● Camera解码器、控制器  如果摄像头是...
  • v4l2驱动框架分析-1

    千次阅读 2018-11-07 08:00:17
    (1) cimutils应用程序维护了哪些结构体,v4l2驱动框架维护了哪些结构体 (2)/dev/video0 这个节点怎么创建的 (3)应用层open 设备节点/dev/video0 的时候,内核中的调用关系和具体干的工作 (4)应用层ioctl 操作后,...
1 2 3 4 5 ... 20
收藏数 2,815
精华内容 1,126
关键字:

v4l2设备驱动开发