v4l2应用程序接口

一、v412-概述

V4L2(Video For Linux Two)是内核提供给应用程序访问音、视频驱动的统一接口。V412可以支持多种设备,它可以有以下几种接口:

  • 视频采集接口(video capture interface):这种应用的设备可以是高频头或者摄像头,V4L2的最初设计就是应用于这种功能的。
  • 视频输出接口(video output interface):可以驱动计算机的外围视频图像设备,如可以输出电视信号格式的设备。
  • 直接传输视频接口(video overlay interface):它的主要工作是把从视频采集设备采集过来的信号直接输出到输出设备之上,而不用经过系统的CPU.
  • 视频间隔消隐信号接口(VBI interface):它可以使应用可以访问传输消隐期的视频信号。
  • 收音机接口(radio interface):可用来处理从AM或FM高频头设备接收来的音频流。

二、v412-框架结构

Linux系统中视频输入设备主要包括以下四个部分:

  • 字符设备驱动程序核心:V4L2本身就是一个字符设备,具有字符设备所有的特性,暴露接口给用户空间;
  • V4L2驱动核心:主要是构建一个内核中标准视频设备驱动的框架,为视频操作提供统一的接口函数;
  • 平台V4L2设备驱动:在V4L2框架下,根据平台自身的特性实现与平台相关的V4L2驱动部分,包括注册video_device和v412_dev。
  • 具体的sensor驱动:主要上电、提供工作时钟、视频图像裁剪、流IO开启等,实现各种设备控制方法供上层调用并注册v412_subdev。

在这里插入图片描述

三、v412-应用程序读取图像的流程

在这里插入图片描述

1.打开设备(xawtv,cheese)

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
    // 1.打开设备
    int fd = open("/dev/video0", O_RDWR);
    if (fd < 0)
    {
        perror("open  video0 faild");
        return -1;
    }
    //关闭设备
    close(fd);

    return 0;
}

2.获取设备支持格式

使用的函数:

int ioctl(int __fd,unsigned long int __request,...)

获取摄像头格式VIDIOC_ENUM_FMT–对应存储格式的结构体struct v4l2_fmtdesc

#define VIDIOC_ENUM_FMT         _IOWR('V',  2, struct v4l2_fmtdesc)
struct v4l2_fmtdesc {
	__u32		    index;             //要查询的格式序号,应用程序设置
	__u32		    type;               //帧类型,应用程序设置
	__u32               flags;			//是否为压缩格式
	__u8		    description[32];   //格式名称
	__u32		    pixelformat;       //听支持的格式
	__u32		    mbus_code;		
	__u32		    reserved[3];		//保留
};

enum v4l2_buf_type:

enum v4l2_buf_type {
	V4L2_BUF_TYPE_VIDEO_CAPTURE        = 1, //捕获采集
	V4L2_BUF_TYPE_VIDEO_OUTPUT         = 2, //输出
	V4L2_BUF_TYPE_VIDEO_OVERLAY        = 3, //覆盖
};

查看支持的格式

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/videodev2.h> //命令码

int main(int argc, const char *argv[])
{
    // 1.打开设备
    int fd = open("/dev/video0", O_RDWR);
    if (fd < 0)
    {
        perror("open  video0 faild");
        return -1;
    }
    // 2.获取摄像头支持的格式ioctl
    struct v4l2_fmtdesc v4fmt;
    v4fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; //类型为输出
    // v4fmt.index = 0;

    int i = 0;
    while (1)
    {
        v4fmt.index = i++;
        int ret = ioctl(fd, VIDIOC_ENUM_FMT, &v4fmt);
        if (ret < 0)
        {
            perror("itctl get fmt faild");
            break;
        }
        printf("index = %d\n", v4fmt.index);
        printf("flags = %d\n", v4fmt.flags);
        printf("description = %s\n", v4fmt.description);
        unsigned char *p = (unsigned char *)&v4fmt.pixelformat;

        printf("pixelformat = %c%c%c%c\n",p[0],p[1],p[2],p[3]);
        printf("reserved[0] = %d\n", v4fmt.reserved[0]);
        printf("------------------------------------------\n");
    }
    // 9.关闭设备
    close(fd);

    return 0;
}

在这里插入图片描述

3. 配置摄像头采集格式

struct v4l2_format {
	__u32	 type;//缓冲帧数据格式
	union {
		struct v4l2_pix_format		pix;     //视频设备使用
		struct v4l2_pix_format_mplane	pix_mp;  /* V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE */
		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 */
		struct v4l2_sdr_format		sdr;     /* V4L2_BUF_TYPE_SDR_CAPTURE */
		struct v4l2_meta_format		meta;    /* V4L2_BUF_TYPE_META_CAPTURE */
		__u8	raw_data[200];                   /* user-defined */
	} fmt;
};


    struct v4l2_format vfmt;
    vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;      //摄像头采集
    vfmt.fmt.pix.width = 640;                     //设置宽
    vfmt.fmt.pix.height = 480;                    //设置高
    vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //设置视频采集格式
    int ret = ioctl(fd, VIDIOC_S_FMT, &vfmt);
    if (ret < 0)
    {
        perror("VIDIOC_S_FMT faild");
    }
//查看当前设置的格式
    memset(&vfmt, 0, sizeof(vfmt));
    vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(fd, VIDIOC_G_FMT, &vfmt);
    if (ret < 0)
    {
        perror("VIDIOC_G_FMT faild");
    }
    printf("vfmt.fmt.pix.width = %d\n", vfmt.fmt.pix.width);
    printf("vfmt.fmt.pix.height  = %d\n", vfmt.fmt.pix.height);
    unsigned char *p = (unsigned char *)&v4fmt.pixelformat;
    printf("pixelformat = %c%c%c%c\n", p[0], p[1], p[2], p[3]);

设置成功

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xaITfMmy-1663034565425)(图片/1662892837308.png)]

4.分配内核空间(申请内核缓冲区队列)

#define VIDIOC_REQBUFS		_IOWR('V',  8, struct v4l2_requestbuffers)//分配内存

struct v4l2_requestbuffers {
	__u32			count;//缓存区个数
	__u32			type;//缓冲帧数据格式
	__u32			memory;//映射方式
	__u32			capabilities;
	__u32			reserved[1];
};

enum v4l2_memory {
	V4L2_MEMORY_MMAP             = 1,
	V4L2_MEMORY_USERPTR          = 2,
	V4L2_MEMORY_OVERLAY          = 3,
	V4L2_MEMORY_DMABUF           = 4,
};
// 4.申请内核空间
    struct v4l2_requestbuffers reqbuffer;
    reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    reqbuffer.count = 4;                 //申请4个缓冲区
    reqbuffer.memory = V4L2_MEMORY_MMAP; //映射方式
    ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuffer);
    if (ret < 0)
    {
        perror("VIDIOC_REQBUFS faild");
    }

5.把内核的缓冲区队列映射到用户空间

#define VIDIOC_QUERYBUF		_IOWR('V',  9, struct v4l2_buffer)
#define VIDIOC_QBUF		_IOWR('V', 15, struct v4l2_buffer)
#define VIDIOC_DQBUF		_IOWR('V', 17, struct v4l2_buffer)
struct v4l2_buffer {
	__u32			index;//buffer 序号
	__u32			type; //buffer类型
	__u32			bytesused;//缓冲区已使用byte数
	__u32			flags;// 区分是MMAP 还是USERPTR
	__u32			field;
	struct timeval		timestamp;//时间戳,代表帧捕获的时间
	__u32			sequence;// 队列中的序号
	/* memory location */
	__u32			memory;//表示缓冲区是内存映射缓冲区还是用户空间缓冲区
	union {
		__u32           offset;//内核缓冲区的位置
		unsigned long   userptr;//缓冲区的用户空间地址
		struct v4l2_plane *planes;
		__s32		fd;
	} m;
	__u32			length;//缓冲区大小,单位byte
	__u32			reserved2;
};
//5.映射
    unsigned char *mptr[4];//保存映射后用户空间首地址
    struct v4l2_buffer mapbuffer;
    //初始化type,index
    mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    for(int i = 0;i <4;i++){
        mapbuffer.index = i;
        ret = ioctl(fd,VIDIOC_QUERYBUF,&mapbuffer);//从内核空间查询一空间做映射
        if(ret <0 ){
            perror("VIDIOC_QUERYBUF faild");
        }
        
        mptr[i] = (unsigned char *)mmap(NULL,mapbuffer.length,PROT_READ|PROT_WRITE,MAP_SHARED,fd,mapbuffer.m.offset);
        //使用完毕,放回
        ret = ioctl(fd,VIDIOC_QBUF,&mapbuffer);
        if(ret < 0){
            perror("VIDIOC_QBUF faild");
        }
    }

6.开始采集

VIDIOC_STREAMON(开始采集写数据到队列中)

VIDIOC_DQBUF (告诉内核我要某一个数据,内核不可以修改)

VIDIOC_QBUF(告诉内核我已经使用完毕)

VIDIOC_STREAMOFF(停止采集-不在向队列中写数据)

#define VIDIOC_STREAMON		 _IOW('V', 18, int)
#define VIDIOC_DQBUF		_IOWR('V', 17, struct v4l2_buffer)
#define VIDIOC_QBUF		_IOWR('V', 15, struct v4l2_buffer)#define VIDIOC_STREAMOFF	 _IOW('V', 19, int)
 // 6.开始采集
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(fd, VIDIOC_STREAMON, &type);
    if (ret < 0)
    {
        perror("VIDIOC_STREAMON faild");
    }

7.采集数据

 //从队列中提取一帧数据
    struct v4l2_buffer readbuffer;
    readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer);
    if (ret < 0)
    {
        perror("VIDIOC_DQBUF faild");
    }
    FILE *file = fopen("my.jpg", "w+");
    // mptr[readbuffer.index];
    fwrite(mptr[readbuffer.index], readbuffer.length, 1, file);
    fclose(file);

    //通知内核已经使用完毕
    ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);
    if (ret < 0)
    {
        perror("VIDIOC_QBUF faild");
    }

8.停止采集

	//停止采集
    ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
    if (ret < 0)
    {
        perror("VIDIOC_STREAMOFF faild");
    }

9.释放映射

//9.释放映射
    for(int i = 0;i<4 ;i++){
        munmap(mptr[i],size[i]);
    }

10.关闭设备

 close(fd);

四、完整代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <linux/videodev2.h> //命令码
#include <sys/mman.h>

int main(int argc, const char *argv[])
{
    // 1.打开设备
    int fd = open("/dev/video0", O_RDWR);
    if (fd < 0)
    {
        perror("open  video0 faild");
        return -1;
    }
    // 2.获取摄像头支持的格式ioctl
    struct v4l2_fmtdesc v4fmt;
    v4fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    // v4fmt.index = 0;

    int i = 0;
    while (1)
    {
        v4fmt.index = i++;
        int ret = ioctl(fd, VIDIOC_ENUM_FMT, &v4fmt);
        if (ret < 0)
        {
            perror("itctl get fmt faild");
            break;
        }
        printf("index = %d\n", v4fmt.index);
        printf("flags = %d\n", v4fmt.flags);
        printf("description = %s\n", v4fmt.description);
        unsigned char *p = (unsigned char *)&v4fmt.pixelformat;
        printf("pixelformat = %c%c%c%c\n", p[0], p[1], p[2], p[3]);
        printf("reserved[0] = %d\n", v4fmt.reserved[0]);
        printf("------------------------------------------\n");
    }

    // 3.设置采集格式
    struct v4l2_format vfmt;
    vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;      //摄像头采集
    vfmt.fmt.pix.width = 640;                     //设置宽
    vfmt.fmt.pix.height = 480;                    //设置高
    //vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //设置视频采集格式
    vfmt.fmt.pix.pixelformat = V4L2_PIX_FMT_JPEG;
    int ret = ioctl(fd, VIDIOC_S_FMT, &vfmt);
    if (ret < 0)
    {
        perror("VIDIOC_S_FMT faild");
    }
    //查看是否设置成功

    memset(&vfmt, 0, sizeof(vfmt));
    vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(fd, VIDIOC_G_FMT, &vfmt); //获取视频采集格式
    if (ret < 0)
    {
        perror("VIDIOC_G_FMT faild");
    }
    printf("vfmt.fmt.pix.width = %d\n", vfmt.fmt.pix.width);
    printf("vfmt.fmt.pix.height  = %d\n", vfmt.fmt.pix.height);
    unsigned char *p = (unsigned char *)&v4fmt.pixelformat;
    printf("pixelformat = %c%c%c%c\n", p[0], p[1], p[2], p[3]);

    // 4.申请内核空间
    struct v4l2_requestbuffers reqbuffer;
    reqbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    reqbuffer.count = 4;                 //申请4个缓冲区
    reqbuffer.memory = V4L2_MEMORY_MMAP; //映射方式
    ret = ioctl(fd, VIDIOC_REQBUFS, &reqbuffer);
    if (ret < 0)
    {
        perror("VIDIOC_REQBUFS faild");
    }
    // 5.映射
    unsigned char *mptr[4]; //保存映射后用户空间首地址
    unsigned int size[4];

    struct v4l2_buffer mapbuffer;
    //初始化type,index
    mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    for (int i = 0; i < 4; i++)
    {
        mapbuffer.index = i;
        ret = ioctl(fd, VIDIOC_QUERYBUF, &mapbuffer); //从内核空间查询一空间做映射
        if (ret < 0)
        {
            perror("VIDIOC_QUERYBUF faild");
        }

        mptr[i] = (unsigned char *)mmap(NULL, mapbuffer.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, mapbuffer.m.offset);
        
        size[i] = mapbuffer.length;
        //使用完毕,放回
        ret = ioctl(fd, VIDIOC_QBUF, &mapbuffer);
        if (ret < 0)
        {
            perror("VIDIOC_QBUF faild");
        }
    }

    // 6.开始采集
    int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(fd, VIDIOC_STREAMON, &type);
    if (ret < 0)
    {
        perror("VIDIOC_STREAMON faild");
    }

    //7.从队列中提取一帧数据
    struct v4l2_buffer readbuffer;
    readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer);
    if (ret < 0)
    {
        perror("VIDIOC_DQBUF faild");
    }
    FILE *file = fopen("my.jpg", "w+");
    
    fwrite(mptr[readbuffer.index], readbuffer.length, 1, file);
    fclose(file);

    //通知内核已经使用完毕
    ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);
    if (ret < 0)
    {
        perror("VIDIOC_QBUF faild");
    }

    //8.停止采集
    ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
    if (ret < 0)
    {
        perror("VIDIOC_STREAMOFF faild");
    }
    //9.释放映射
    for(int i = 0;i<4 ;i++){
        munmap(mptr[i],size[i]);
    }

    // 10.关闭设备
    close(fd);

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值