Linux摄像头(v4l2应用)——获取摄像头一帧图像

这篇具有很好参考价值的文章主要介绍了Linux摄像头(v4l2应用)——获取摄像头一帧图像。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一.V4L2简介

V4L2(Video for Linux 2):Linux内核中视频设备中的驱动框架,对于应用层它提供了一系列的API接口,同时对于硬件层,它适配大部分的视频设备,因此通过调用V4L2的接口函数可以适配大部分的视频设备。

二、操作流程

1.打开设备

当把摄像头插入到电脑后,执行ls /dev/vi* 可看到/dev目录下出现摄像头的video节点。这里出现了两个设备节点:dev/video0、dev/video1,是因为一个是图像/视频采集,一个是metadata采集。

v4l2摄像头采集,linux,学习,笔记

使用open函数打开摄像头节点

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

int main(int argc, char**argv)
{
	if(argc != 2)
	{
		printf("%s </dev/video0,1...>\n", argv[0]);
		return -1;
	}
	int fd = open(argv[1], O_RDWR);
	if(fd < 0)
	{
		perror("打开设备失败");
		return -1;
	}

	close(fd);
	return 0;
}
2.获取支持格式和功能

使用ioctl函数int ioctl(int fd, unsigned long request, ...)获取摄像头支持的格式,这里ioctl的参数可以在头文件videodev2.h中找到(路径为/usr/include/linux/)。对应操作命令如下表,这里需要获取摄像头支持的格式,所以操作命令为VIDIOC_ENUM_FMT,对应的结构体是struct v4l2_fmtdesc。另建议在source insight下下载linux源码,然后建立该工程,在该工程下写代码,便于查看对应结构体。

v4l2摄像头采集,linux,学习,笔记v4l2摄像头采集,linux,学习,笔记

关于v4l2_fmtdesc结构体
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];
};

 该结构体定义如上,因为v4l2不单单针对摄像头,所以在使用前需要对type成员进行初始化,v4l2_buf_type这个枚举总共有13个成员,这里选择1,即视频抓取。

v4l2摄像头采集,linux,学习,笔记

在用代码读取过程中时,因为支持多种格式,所以用while循环读取支持的格式,

关于v4l2_capability结构体
struct v4l2_capability
{
	u8 driver[16]; // 驱动名字
	u8 card[32]; // 设备名字
	u8 bus_info[32]; // 设备在系统中的位置
	u32 version;// 驱动版本号
	u32 capabilities;// 设备支持的操作
	u32 reserved[4]; // 保留字段
};

除了用 v4l2_fmtdesc结构体获取像素格式,还可以通过v4l2_capability结构体来获取设备的功能,主要看capabilities成员,其是否支持视频捕获(V4L2_CAP_VIDEO_CAPTURE)、以及是否支持流读写(V4L2_CAP_STREAMING)。

#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, char**argv)
{
	if(argc != 2)
	{
		printf("%s </dev/video0,1...>\n", argv[0]);
		return -1;
	}
	//打开摄像头设备
	int fd = open(argv[1], O_RDWR);
	if(fd < 0)
	{
		perror("打开设备失败");
		return -1;
	}
	//获取摄像头支持格式,使用ioctl函数int ioctl(int fd, unsigned long request, ...);
	struct v4l2_fmtdesc v4fmt;
	struct v4l2_capability cap;
	v4fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;	//选择视频抓取
	int i = 0;
	while(1)
	{
		v4fmt.index = i;
		i++;
		int ret = ioctl(fd, VIDIOC_ENUM_FMT, &v4fmt);
		if(ret < 0)
		{
			perror("获取格式失败");
			break;
		}
		printf("index = %d\n", v4fmt.index);
		printf("flags = %d\n", v4fmt.flags);
		printf("descrrption = %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 = %d\n", v4fmt.reserved[0]);
	}

	int ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);
	if(ret < 0)
		perror("获取功能失败");
	
	printf("drivers:%s\n", cap.driver);//读取驱动名字
	if(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)
		printf("%s 支持视频捕获\n", argv[1]);
	if(cap.capabilities & V4L2_CAP_STREAMING)
		printf("%s 支持流读写\n", argv[1]);
	close(fd);
	return 0;
	
}

运行结果

v4l2摄像头采集,linux,学习,笔记

运行结果说明我的摄像头支持视频捕获,同时支持流读写支持两种像素格式YUYV和MJPG,读不到video1支持的像素格式,该摄像头的两个设备节点仅video0用于视频采集。关于YUYV和MJPG可以看这篇博文。

https://blog.csdn.net/u014260892/article/details/51883723

3.配置摄像头

在视频采集之前需要设置视频采集格式,定义v4l2_format结构体变量,然后通过结构体v4l2_pix_format来设置采集的高、宽以及像素格式(YUYV),设置之后,可以采用打印的方式来查看是否设置成功。

struct v4l2_format {
	__u32	 type;
	union {
		struct v4l2_pix_format		pix;     /* V4L2_BUF_TYPE_VIDEO_CAPTURE */
		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 */
		__u8	raw_data[200];                   /* user-defined */
	} fmt;
};

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 */
};

测试程序如下:

#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, char**argv)
{
	if(argc != 2)
	{
		printf("%s </dev/video0,1...>\n", argv[0]);
		return -1;
	}
	//打开摄像头设备
	int fd = open(argv[1], O_RDWR);
	if(fd < 0)
	{
		perror("打开设备失败");
		return -1;
	}
	//设置摄像头采集格式
	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);// VIDIOC_S_FMT:设置捕获格式
	if(ret < 0)
	{
		perror("设置采集格式错误");
	}

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

 运行结果和设置一样

v4l2摄像头采集,linux,学习,笔记

4.向内核申请帧缓冲队列并映射

V4L2读取数据时有两种方式,第一种是用read读取(调用read函数),第二种是用流(streaming)读取,在第二步上已经获取到我的设备支持流读写,为了提高效率采用流读写,流读写就是在内核中维护一个缓存队列,然后再映射到用户空间,应用层直接读取队列中的数据。

步骤为:申请缓冲区->逐个查询申请到的缓冲区->逐个映射->逐个放入队列中

#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>
#include <string.h>
#include <sys/mman.h>
int main(int argc, char**argv)
{
	if(argc != 2)
	{
		printf("%s </dev/video0,1...>\n", argv[0]);
		return -1;
	}
	//打开摄像头设备
	int fd = open(argv[1], O_RDWR);
	if(fd < 0)
	{
		perror("打开设备失败");
		return -1;
	}
	//设置摄像头采集格式
	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);// VIDIOC_S_FMT:设置捕获格式
	if(ret < 0)
	{
		perror("设置采集格式错误");
	}

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

	//申请缓冲队列
	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("申请缓冲队列失败");
	}
	
	//映射,映射之前需要查询缓存信息->每个缓冲区逐个映射->将缓冲区放入队列
	struct v4l2_buffer mapbuffer;
	unsigned char *mmpaddr[4];//用于存储映射后的首地址
	mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//初始化type
	for(int i = 0; i < 4; i++)
	{
		mapbuffer.index = i;
		ret = ioctl(fd, VIDIOC_QUERYBUF, &mapbuffer);	//查询缓存信息
		if(ret < 0)
			perror("查询缓存队列失败");
		mmpaddr[i] = (unsigned char *)mmap(NULL, mapbuffer.length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, mapbuffer.m.offset);//mapbuffer.m.offset映射文件的偏移量
		//放入队列
		ret = ioctl(fd, VIDIOC_QBUF, &mapbuffer);
		if(ret < 0)
			perror("放入队列失败");
	}
	close(fd);
	return 0;
	
}
5.采集视频

做完前面的设置就可以进行采集数据,打开设备->读取数据->关闭设备->释放映射。

读取数据的本质就是从上一个步骤中映射的队列中取出数据,取出数据后需要将该缓冲区放入队列中,以便于再去采集下一帧数据。

为了便于查看,在设置采集格式时,选择MJPEG格式,用fopen函数建立一个1.jpg文件,用fwrite函数保存读到的一帧数据。

#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>
#include <string.h>
#include <sys/mman.h>
int main(int argc, char**argv)
{
	if(argc != 2)
	{
		printf("%s </dev/video0,1...>\n", argv[0]);
		return -1;
	}
	//1.打开摄像头设备
	int fd = open(argv[1], O_RDWR);
	if(fd < 0)
	{
		perror("打开设备失败");
		return -1;
	}
	//2.设置摄像头采集格式
	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_MJPEG;//设置视频采集像素格式

	int ret = ioctl(fd, VIDIOC_S_FMT, &vfmt);// VIDIOC_S_FMT:设置捕获格式
	if(ret < 0)
	{
		perror("设置采集格式错误");
	}

	memset(&vfmt, 0, sizeof(vfmt));
	vfmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	ret = ioctl(fd, VIDIOC_G_FMT, &vfmt);	
	if(ret < 0)
	{
		perror("读取采集格式失败");
	}
	printf("width = %d\n", vfmt.fmt.pix.width);
	printf("width = %d\n", vfmt.fmt.pix.height);
	unsigned char *p = (unsigned char*)&vfmt.fmt.pix.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("申请缓冲队列失败");
	}
	
	//映射,映射之前需要查询缓存信息->每个缓冲区逐个映射->将缓冲区放入队列
	struct v4l2_buffer mapbuffer;
	unsigned char *mmpaddr[4];//用于存储映射后的首地址
	unsigned int addr_length[4];//存储映射后空间的大小
	mapbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;//初始化type
	for(int i = 0; i < 4; i++)
	{
		mapbuffer.index = i;
		ret = ioctl(fd, VIDIOC_QUERYBUF, &mapbuffer);	//查询缓存信息
		if(ret < 0)
			perror("查询缓存队列失败");
		mmpaddr[i] = (unsigned char *)mmap(NULL, mapbuffer.length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, mapbuffer.m.offset);//mapbuffer.m.offset映射文件的偏移量
		addr_length[i] = mapbuffer.length;
		//放入队列
		ret = ioctl(fd, VIDIOC_QBUF, &mapbuffer);
		if(ret < 0)
			perror("放入队列失败");
	}

	//打开设备
	int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	ret = ioctl(fd, VIDIOC_STREAMON, &type);
	if(ret < 0)
		perror("打开设备失败");
	//从队列中提取一帧数据
	struct v4l2_buffer readbuffer;
	readbuffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer);//从缓冲队列获取一帧数据(出队列)
	//出队列后得到缓存的索引index,得到对应缓存映射的地址mmpaddr[readbuffer.index]
	if(ret < 0)
		perror("获取数据失败");
	FILE *file = fopen("1.jpg", "w+");//建立文件用于保存一帧数据
	fwrite(mmpaddr[readbuffer.index], readbuffer.length, 1, file);
	fclose(file);
	//读取数据后将缓冲区放入队列
	ret = ioctl(fd, VIDIOC_QBUF, &readbuffer);
	if(ret < 0)
		perror("放入队列失败");
	//关闭设备
	ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
	if(ret < 0)
		perror("关闭设备失败");
	//取消映射
	for(int i = 0; i < 4; i++)
		munmap(mmpaddr[i], addr_length[i]);
	close(fd);
	return 0;
	
}

运行程序,目录下顺利得到jpg文件,但是打开时报 (Not a JPEG file: starts with 0xe0 0xc1)错误

三、采集到的jpg图片报(Not a JPEG file: starts with 0xe0 0xc1)错误

如上所示,顺利采集到1.jpg图片,但是测试好几次均报该错误,在仔细检查设置无误后,猜测可能是还未采集到数据就已经读取出来了,所以在队列中获取一帧数据的前面加了sleep(5)延迟5s采集,但仅有一次成功,可以读出但采集的数据有损坏,而且修改sleep的数值,每次的错误文件的两个头码还不相同。同时在申请缓存之前将查询缓存信息之前现将结构体清0,然后再映射,发现还是有该问题。另外尝试了加poll函数等待读取,同样无法读取。

于是我将采集格式变为yuyv格式,发现程序阻塞至ret = ioctl(fd, VIDIOC_DQBUF, &readbuffer),查找相关帖子发现是虚拟机中USB兼容性问题,将设置中USB兼容性改为USB3.1阻塞消失,同时jpg格式也顺利采集到数据。1.jpg文件也可以顺利打开。

v4l2摄像头采集,linux,学习,笔记

v4l2摄像头采集,linux,学习,笔记文章来源地址https://www.toymoban.com/news/detail-829034.html

到了这里,关于Linux摄像头(v4l2应用)——获取摄像头一帧图像的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Ubuntu下python-opencv无法打开摄像头,open VIDEOIO(V4L2:/dev/video0): can‘t open camera by index

    我们在ubuntu下使用opencv获取摄像头画面是,报错 open VIDEOIO(V4L2:/dev/video0): can‘t open camera by index 然后观察虚拟机桌面的右下角,如果出现摄像头有小绿点表示连接成功 然后我们来测试一下,摄像头的画面 ####### 这是摄像头传输回来的画面

    2024年02月16日
    浏览(42)
  • RK3568 android11 移植 v4l2loopback 虚拟摄像头

    v4l2loopback是一个Linux内核模块,它允许用户创建虚拟视频设备。这种虚拟视频设备可以用于各种用途,例如将实际摄像头的视频流复制到虚拟设备上,或者用于视频流的处理和分析等。v4l2loopback的主要作用是 创建一个虚拟的Video4Linux2设备,它可以接收来自其他应用程序的视频

    2024年01月19日
    浏览(64)
  • Linux之V4L2驱动框架

    目录 一、V4L2简介 二、V4L2操作流程  1.打开摄像头 2.查询设备的属性/能力/功能 3.获取摄像头支持的格式 4.设置摄像头的采集通道 5.设置/获取摄像头的采集格式和参数 6.申请帧缓冲、内存映射、入队 (1)申请帧缓冲 (2)内存映射 (3)入队 7.开启视频采集 8.读取数据、对数

    2024年02月08日
    浏览(38)
  • 项目之利用 V4L2应用程序框架 进行视频录制

    目录 知识储备: 视频采集方式: 处理采集数据: 相关结构体: 对于设备的操作步骤:         V4L2较V4L有较大的改动,并已成为 2.6 的标准接口,函盖 videodvbFM... ,多数驱动都在向 V4l2 迁移。更好地了解 V4L2 先从应用入手,然后再深入到内核中结合物理设备/接口的规范实现

    2023年04月09日
    浏览(42)
  • V4L2+QT+USB摄像头实时显示视频(Arm,Linux,window均适用)

    笔者自从学习了Framebuffer编程和V4L2编程之后,就想实现在LCD屏上显示实时视频 笔者学习过正点I.MX6U Linux C编程中的相关内容,然而原子的例程是针对OV5640摄像头写的,像素格式是RGB 然而USB摄像头大多支持MJPEG或者YUYV格式,如果要在屏幕上显示需要进行格式转换,而 转换像素

    2023年04月08日
    浏览(45)
  • 【C++】开源:Linux端V4L2视频设备库

    😏 ★,° :.☆( ̄▽ ̄)/$: .°★ 😏 这篇文章主要介绍Linux端V4L2视频设备库。 无专精则不能成,无涉猎则不能通。——梁启超 欢迎来到我的博客,一起学习,共同进步。 喜欢的朋友可以关注一下,下次更新不迷路🥞 Video4Linux2( V4L2 )是一个用于Linux操作系统的视频设备驱动框

    2024年02月11日
    浏览(34)
  • V4L2常用调试命令

    这篇文章简单记录一下RK平台基于V4L2框架camera调试过程中常用的一些命令: (1)查看拓扑结构:查看media0的pipeline (2)抓数据流命令: 对video0节点,设置格式为NV12,分辨率为1920x1080,不裁剪,4个buf轮转,--verbose的作用是刷出帧率。 (3)抓图命令: 类似上面的,将图像保

    2024年02月03日
    浏览(46)
  • linux v4l2架构分析之异步注册v4l2_async_subdev_notifier_register、v4l2_async_register_subdev、v4l2_async_notifie

            在camera驱动注册中,v4l2_async_subdev_notifier_register、v4l2_async_register_subdev、v4l2_async_notifier_register这几个函数都会被使用到,三者在异步注册的实现中是紧密关联的,所以本文将三者放在一起进行分析。本文主要介绍异步注册的功能的整体实现框架,为了更好把握整体思

    2024年02月14日
    浏览(48)
  • 内存不足V4L2 申请DMC缓存报错问题

    当内存不足时,V4L2可能存在申请DMA缓存报错,如下日志:

    2024年02月12日
    浏览(38)
  • 汽车IVI中控开发入门及进阶(十二):V4L2视频

    前言     IVI,In-Vehicle Infotainment,智能座舱信息娱乐系统,或称车载信息娱乐系统, 汽车中控也被称为车机、车载多媒体、车载娱乐等, 它是智能座舱的重要组成部分。IVI 采用车载专用中央处理器,基于车身总线系统和联网服务提供车载综合信息处理功能,包括音频播放、

    2024年02月04日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包