Linux(Ubutun)使用V4L2采集摄像头数据

Ubutun 安装V4L库

sudo apt-get install libv4l-dev

直接使用如上命令即可安装。

V4L2介绍 V4L与V4L2的区别
V4L是 Video for Linux的缩写,它是Linux 内核中关于视频设备的子系统,它为linux 下的视频驱动提供了统一的接口,使得应用程序可以使用统一的API 函数操作不同的视频设备,极大地简化了视频系统的开发和维护。
由于早期的 V4L 有很多缺陷,Bill Dirks 等人对其进行了重新设计,并取名为Video for Linux 2(V4L2使用),最早出现于Linux2.5.x 版本。V4L2 相比于V4L 有更好的扩展性和灵活性,并且支持的硬件设备更多。
因此在应用程序V4L编程实际是指v4l2,我们这个系列的以V4L2为主,但由于历史的原因,V4L2一般兼容V4L.所以很多程序可以用V4L接口。

V4L2(Video4Linux的缩写)是Linux下关于视频采集相关设备的驱动框架,为驱动和应用程序提供了一套统一的接口规范。

V4L2支持的设备十分广泛,但是其中只有很少一部分在本质上是真正的视频设备:

Video capture device : 从摄像头等设备上获取视频数据。对很多人来讲,video capture是V4L2的基本应用。设备名称为/dev/video,主设备号81,子设备号0~63
Video output device : 将视频数据编码为模拟信号输出。与video capture设备名相同。
Video overlay device : 将同步锁相视频数据(如TV)转换为VGA信号,或者将抓取的视频数据直接存放到视频卡的显存中。
Video output overlay device :也被称为OSD(On-Screen Display)
VBI device : 提供对VBI(Vertical Blanking Interval)数据的控制,发送VBI数据或抓取VBI数据。设备名/dev/vbi0~vbi31,主设备号81,子设备号224~255
Radio device : FM/AM发送和接收设备。设备名/dev/radio0~radio63,主设备号81,子设备号64~127

V4L2应用层
V4L2是一个字符设备,而V4L2的大部分功能都是通过设备文件的ioctl导出的。

可以将这些ioctl分类如下

Query Capability:查询设备支持的功能,只有VIDIOC_QUERY_CAP一个。
优先级相关:包括VIDIOC_G_PRIORITY,VIDIOC_S_PRIORITY,设置优先级。
capture相关:视频捕获相关Ioctl。

V4L2采集需要使用的ID
在这里插入图片描述
接下来我们就开始写代码,开始使用V4L2采集摄像头数据。
一、重写ioctl函数
这样保证设置IO不会因为中断而停止,并且保证了IO设置。

static inline int camera_ioctl(int fd, int request, void *arg)
{
    int r = -1;

    do
    {
        r = ioctl(fd, request, arg);
    } while (r < 0 && EINTR == errno);

    return r;
}

二、打开文件
在linux下‘一点哲学’,所以我们此时需要去打开摄像头对应虚拟化后的文件(注意,要将虚拟机的摄像头连接上,在虚拟机->可移动设备->USB Camera 断开与主机的连接)此时可以在/dev下找到video0(针对不同主机对应可能为video1、video0);

int open_camear_device(const char *path)
{
    int fd = open(path, O_RDWR, 0);
    if (-1 == fd)
    {
        perror("Open camera device:");
        return -1;
    }
    return fd;
}

三、我们需要初始化虚拟设备

//初始化
int init_camera(int fd)
{
    //检查摄像头设备,获取信息。
    v4l2_capability cap;
    if (-1 == camera_ioctl(fd, VIDIOC_QUERYCAP, &cap))
    {
        perror("ictol cap!");
        return -1;
    }
    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
    {
        fprintf(stderr, "is no video capture device\n");
        return -1;
    }

    if (!(cap.capabilities & V4L2_CAP_STREAMING))
    {
        fprintf(stderr, "does not support streaming i/o\n");
        return -1;
    }
    //打印摄像头相关信息
    printf("\nVIDOOC_QUERYCAP\n");
    printf("the camera driver is %s\n", cap.driver);
    printf("the camera card is %s\n", cap.card);
    printf("the camera bus info is %s\n", cap.bus_info);
    printf("the version is %d\n", cap.version);

    //摄像头所支持的像素格式
    v4l2_fmtdesc fmtdesc;
    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmtdesc.index = 0;
    printf("Support format:\n");
    while (camera_ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc)!= -1)
    {
        printf("\t%d.%s\n", fmtdesc.index + 1, fmtdesc.description);
        fmtdesc.index++;
    }

    //设置像素格式
    v4l2_format fmt;
    fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	fmt.fmt.pix.width = WIDTH;
	fmt.fmt.pix.height = HEIGHT;
    fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;	//yuv422
	fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;	//隔行扫描

    //应用设置格式 
    if (camera_ioctl(fd, VIDIOC_S_FMT, &fmt) < 0) {
		printf("VIDIOC_S_FMT\n");
		return -1;
	}

    //读出设置查看是否设置格式成功
    if (camera_ioctl(fd, VIDIOC_G_FMT, &fmt) < 0) {
		printf("VIDIOC_S_FMT\n");
		return -1;
	}

    printf("fmt.type:\t\t%d\n",fmt.type);
    printf("pix.pixelformat:\t%c%c%c%c\n",fmt.fmt.pix.pixelformat & 0xFF, (fmt.fmt.pix.pixelformat >> 8) & 0xFF,(fmt.fmt.pix.pixelformat >> 16) & 0xFF, (fmt.fmt.pix.pixelformat >> 24) & 0xFF);
    printf("pix.height:\t\t%d\n",fmt.fmt.pix.height);
    printf("pix.width:\t\t%d\n",fmt.fmt.pix.width);
    printf("pix.field:\t\t%d\n",fmt.fmt.pix.field);

    //设置fps
    struct v4l2_streamparm parm;
    parm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    parm.parm.capture.timeperframe.numerator = 10;
    parm.parm.capture.timeperframe.denominator = 10;

    if (ioctl(fd, VIDIOC_S_PARM, &parm) == -1)
    {
        perror("VIDIOC_S_PARM failed\n");
        return false;
    }

    printf("init camera success!\n");

    init_memm(fd);

}

四、虚拟化缓存,创建虚拟缓存映射到用户态
这个时候我们需要自定义一个用户态缓存的结构体

typedef struct __video_buffer
{
    void *start;
    size_t length;
} video_buf_t;

然后开始虚拟缓存,建立映射

int init_memm(int fd)
{
    struct v4l2_requestbuffers req;
    struct v4l2_buffer buf;

    memset(&req, 0, sizeof(struct v4l2_requestbuffers));
    memset(&buf, 0, sizeof(struct v4l2_buffer));
    req.count = 4;
    req.memory = V4L2_MEMORY_MMAP;
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    //分配内存
    if (camera_ioctl(fd, VIDIOC_REQBUFS, &req) == -1)
    {
        perror("fail to request buffer");
    }

    if (req.count < 2) {
		printf("Insufficient buffer memory\n");
		return -1;
	}

    cout << "frame buffer request done" << endl;

    //分配用户态内存
    framebuf = (video_buf_t *)calloc(req.count, sizeof(video_buf_t));
    if(!framebuf)
    {
        printf("frambuf err calloc");
        return -1;
    }

    for (int i = 0; i < req.count; i++)
    {
        buf.index = i;
        
        //回收缓冲
        if (camera_ioctl(fd, VIDIOC_QUERYBUF, &buf) == -1)
        {
            perror("VIDIOC_QUERYBUF failed!\n");
            return -1;
        }

        //mmap buffer
        framebuf[i].length = buf.length;
        framebuf[i].start = mmap(NULL, buf.length,
                                 PROT_READ | PROT_WRITE,
                                 MAP_SHARED, fd, buf.m.offset);
       
        if (framebuf[i].start == MAP_FAILED)
        {
            perror("mmap failed!\n");
            return -1;
        }
    }

    cout << "init memm success" << endl;

}

五、设置IO开始采集
注意在开始采集之前我们需要将缓存回收,然后开始采集,此时已经设置IO开始采集了,之后我们只需要从缓存中读取数据即可

int start_capture(int fd)
{
    unsigned int i;
    v4l2_buffer buf;
	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    memset(&buf, 0, sizeof(struct v4l2_buffer));
	
    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.memory = V4L2_MEMORY_MMAP;
    //放回缓冲区
    for (i = 0; i < 4; i++) 
    {
		buf.index = i;
        
		if (camera_ioctl(fd, VIDIOC_QBUF, &buf) < 0)
        {
			perror("VIDIOC_DQBUF err\n");
			return -1;
		}
	}

    //开始采集
    if (camera_ioctl(fd, VIDIOC_STREAMON, &type) < 0) 
    {
		printf("VIDIOC_STREAMON\n");
		return -1;
	}

    cout << "satart capture....." << endl;
}

六、读取采集好的数据
在这个过程中,process_data 代表你自己得到采集的数据需要如何处理,我就不列举出来了。你可以对其进行RGB转换显示成图片。注意这是读取一帧数据,如果需要一直读取视频,则需要进行while循环,即数据为framebuf[buf.index].start
长度为buf.length;

int read_frame(int fd, int fd_yuv)
{
    v4l2_buffer buf;
    memset(&buf, 0, sizeof(struct v4l2_buffer));

    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.memory = V4L2_MEMORY_MMAP;

	if (camera_ioctl(fd, VIDIOC_DQBUF, &buf) < 0) {
		switch (errno) {
		case EAGAIN:
			return 0;
		case EIO:
			/* Could ignore EIO, see spec. */
			/* fall through */
		default:

			return -1;
		}
	}

    //处理数据
    process_data(fd_yuv, (unsigned char *)framebuf[buf.index].start, buf.length);
    //放回缓冲
    if (camera_ioctl(fd, VIDIOC_QBUF, &buf) < 0) 
    {
        printf("VIDIOC_DQBUF ERROR\n");
		return -1;
	}

    printf("read  frame done\n");
}

七、收尾工作,取消缓存、停止采集

int camera_uninit(int fd)
{
    unsigned int i;
    for (i = 0; i < 4; i++) 
    {
		if (munmap(framebuf[i].start, framebuf[i].length) < 0) 
        {
			printf("munmap\n");
            break;
		}
	}
    printf("ummp buf....\n");

	free(framebuf);
    return 1;
}
int stop_caputer(int fd)
{
    //停止采集
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	if (camera_ioctl(fd, VIDIOC_STREAMOFF, &type) < 0) 
    {
		perror("VIDIOC_STREAMOFF");
	}
    
    printf("Stop caputer...\n");
    return -1;
}

这样整个采集过程就完成了

发布了23 篇原创文章 · 获赞 9 · 访问量 1674

猜你喜欢

转载自blog.csdn.net/weixin_42590177/article/details/104543472