正点原子imx6ull: QT视频监控项目使用yuyv格式的usb摄像头

这篇具有很好参考价值的文章主要介绍了正点原子imx6ull: QT视频监控项目使用yuyv格式的usb摄像头。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

前言

二、修改qt例程

1、添加yuyv转rgb的函数到capture_thread.cpp

2、声明屏幕缓冲变量

3、yuyv转rgb的实际处理

 三、测试

    1、开发板获取摄像头数据测试

    2、客户端与服务器通信测试

四、修改后的正点原子video_server项目代码


前言

正点原子《I.MX6U 嵌入式 Qt 开发指南》教程使用的是ov系列的摄像头,输出rgb格式,可以直接显示到屏幕,不需要进行格式转化。由于我使用的是yuyv格式的usb摄像头,下面进行适配。

参考:正点原子《I.MX6U 嵌入式 Qt 开发指南》第二十八章 视频监控项目

lcd大小:4.3寸 480*272

QT文件:  server: video_server     client: video_client

一、YUYV摄像头直接使用原始例程

先查看usb摄像头的输出像素格式:v4l2-ctl -d /dev/video1 --all

正点原子imx6ull: QT视频监控项目使用yuyv格式的usb摄像头

直接修改正点原子的例程,修改pixformat=V4L2_PIX_FMT_YUYV

 正点原子imx6ull: QT视频监控项目使用yuyv格式的usb摄像头

此时使用例程,显示效果如下,很显然原因是摄像头采集的yuyv数据,以Format_RGB16显示到QImage控件上导致了格式异常。

正点原子imx6ull: QT视频监控项目使用yuyv格式的usb摄像头

二、修改qt例程

1、添加yuyv转rgb的函数到capture_thread.cpp

void yuyv_to_rgb(unsigned char* yuv,unsigned char* rgb)
{
    unsigned int i;
    unsigned char* y0 = yuv + 0;
    unsigned char* u0 = yuv + 1;
    unsigned char* y1 = yuv + 2;
    unsigned char* v0 = yuv + 3;

    unsigned  char* r0 = rgb + 0;
    unsigned  char* g0 = rgb + 1;
    unsigned  char* b0 = rgb + 2;
    unsigned  char* r1 = rgb + 3;
    unsigned  char* g1 = rgb + 4;
    unsigned  char* b1 = rgb + 5;

    float rt0 = 0, gt0 = 0, bt0 = 0, rt1 = 0, gt1 = 0, bt1 = 0;

    for(i = 0; i <= (WIDTH * HEIGHT) / 2 ;i++)
    {
        bt0 = 1.164 * (*y0 - 16) + 2.018 * (*u0 - 128);
        gt0 = 1.164 * (*y0 - 16) - 0.813 * (*v0 - 128) - 0.394 * (*u0 - 128);
        rt0 = 1.164 * (*y0 - 16) + 1.596 * (*v0 - 128);

        bt1 = 1.164 * (*y1 - 16) + 2.018 * (*u0 - 128);
        gt1 = 1.164 * (*y1 - 16) - 0.813 * (*v0 - 128) - 0.394 * (*u0 - 128);
        rt1 = 1.164 * (*y1 - 16) + 1.596 * (*v0 - 128);

        if(rt0 > 250)      rt0 = 255;
        if(rt0< 0)        rt0 = 0;

        if(gt0 > 250)     gt0 = 255;
        if(gt0 < 0)    gt0 = 0;

        if(bt0 > 250)    bt0 = 255;
        if(bt0 < 0)    bt0 = 0;

        if(rt1 > 250)    rt1 = 255;
        if(rt1 < 0)    rt1 = 0;

        if(gt1 > 250)    gt1 = 255;
        if(gt1 < 0)    gt1 = 0;

        if(bt1 > 250)    bt1 = 255;
        if(bt1 < 0)    bt1 = 0;

        *r0 = (unsigned char)rt0;
        *g0 = (unsigned char)gt0;
        *b0 = (unsigned char)bt0;

        *r1 = (unsigned char)rt1;
        *g1 = (unsigned char)gt1;
        *b1 = (unsigned char)bt1;

        yuv = yuv + 4;
        rgb = rgb + 6;
        if(yuv == NULL)
          break;

        y0 = yuv;
        u0 = yuv + 1;
        y1 = yuv + 2;
        v0 = yuv + 3;

        r0 = rgb + 0;
        g0 = rgb + 1;
        b0 = rgb + 2;
        r1 = rgb + 3;
        g1 = rgb + 4;
        b1 = rgb + 5;
    }
}

2、声明屏幕缓冲变量

这里的WIDTH和HEIGHT需要根据实际显示设置,由于我的lcd为480*272,因此我设置视频数据为320*240

#define WIDTH     320
#define HEIGHT    240

unsigned char src_buffer[WIDTH*HEIGHT*2];        //用来装v4l2出队的yuyv数据
unsigned char rgb_buffer[WIDTH*HEIGHT*3];        //用来装yuyv转换为rgb的数据

3、yuyv转rgb的实际处理

在帧缓冲出队函数中,对帧缓冲数据进行memcpy到src_buffer,再调用yuyv_to_rgb函数,将转换为rgb的数据保存到rgb_buffer中。然后将rgb_buffer显示到屏幕。

正点原子imx6ull: QT视频监控项目使用yuyv格式的usb摄像头

 三、测试

    1、开发板获取摄像头数据测试

    将QT工程video_server交叉编译后,放到开发板上运行,查看显示到本地显示的数据是否正常。此时与一开始相比,颜色显示已经正常。

正点原子imx6ull: QT视频监控项目使用yuyv格式的usb摄像头

    2、客户端与服务器通信测试

    将QT工程video_client编译后在pc上运行。

    将开发板上的video_server勾选开启广播后,运行,即可看到客户端收到的视频数据。

正点原子imx6ull: QT视频监控项目使用yuyv格式的usb摄像头

四、修改后的正点原子video_server项目代码

/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 2021-2030. All rights reserved.
* @projectName   video_server
* @brief         mainwindow.cpp
* @author        Deng Zhimao
* @email         dengzhimao@alientek.com
* @link          www.openedv.com
* @date          2021-11-19
*******************************************************************/
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    this->setGeometry(0, 0, 480 , 272);     //由于屏幕大小不同,进行了修改

    videoLabel = new QLabel(this);
    videoLabel->setText("未获取到图像数据或未开启本地显示");
    videoLabel->setStyleSheet("QWidget {color: white;}");
    videoLabel->setAlignment(Qt::AlignCenter);
    videoLabel->resize(480, 272);           //由于屏幕大小不同,进行了修改

    checkBox1 = new QCheckBox(this);
    checkBox2 = new QCheckBox(this);

    checkBox1->resize(120, 50);
    checkBox2->resize(120, 50);

    checkBox1->setText("本地显示");
    checkBox2->setText("开启广播");

    checkBox1->setStyleSheet("QCheckBox {color: yellow;}"
                             "QCheckBox:indicator {width: 40; height: 40;}");
    checkBox2->setStyleSheet("QCheckBox {color: yellow;}"
                             "QCheckBox:indicator {width: 40; height: 40}");

    /* 按钮 */
    startCaptureButton = new QPushButton(this);
    startCaptureButton->setCheckable(true);
    startCaptureButton->setText("开始采集摄像头数据");

    /* 设置背景颜色为黑色 */
    QColor color = QColor(Qt::black);
    QPalette p;
    p.setColor(QPalette::Window, color);
    this->setPalette(p);

    /* 样式表 */
    startCaptureButton->setStyleSheet("QPushButton {background-color: white; border-radius: 30}"
                                      "QPushButton:pressed  {background-color: red;}");

    captureThread = new CaptureThread(this);

    connect(startCaptureButton, SIGNAL(clicked(bool)), captureThread, SLOT(setThreadStart(bool)));
    connect(startCaptureButton, SIGNAL(clicked(bool)), this, SLOT(startCaptureButtonClicked(bool)));
    connect(captureThread, SIGNAL(imageReady(QImage)), this, SLOT(showImage(QImage)));
    connect(checkBox1, SIGNAL(clicked(bool)), captureThread, SLOT(setLocalDisplay(bool)));
    connect(checkBox2, SIGNAL(clicked(bool)), captureThread, SLOT(setBroadcast(bool)));
}

MainWindow::~MainWindow()
{
}

void MainWindow::showImage(QImage image)
{
    videoLabel->setPixmap(QPixmap::fromImage(image));
}

void MainWindow::resizeEvent(QResizeEvent *event)
{
    Q_UNUSED(event)
    startCaptureButton->move((this->width() - 200) / 2, this->height() - 80);
    startCaptureButton->resize(200, 60);
    videoLabel->move((this->width() - 480) / 2, (this->height() - 272) / 2);        //由于屏幕大小不同,进行了修改
    checkBox1->move(this->width() - 120, this->height() / 2 - 50);
    checkBox2->move(this->width() - 120, this->height() / 2 + 25);
}

void MainWindow::startCaptureButtonClicked(bool start)
{
    if (start)
        startCaptureButton->setText("停止采集摄像头数据");
    else
        startCaptureButton->setText("开始采集摄像头数据");
}

 文章来源地址https://www.toymoban.com/news/detail-420256.html

/******************************************************************
Copyright © Deng Zhimao Co., Ltd. 2021-2030. All rights reserved.
* @projectName   video_server
* @brief         capture_thread.cpp
* @author        Deng Zhimao
* @email         dengzhimao@alientek.com
* @link          www.openedv.com
* @date          2021-11-19
*******************************************************************/
#include "capture_thread.h"
#include <string.h>

#define WIDTH   320     //摄像头拍摄以及显示的大小
#define HEIGHT  240

void yuyv_to_rgb(unsigned char* yuv,unsigned char* rgb)
{
    unsigned int i;
    unsigned char* y0 = yuv + 0;
    unsigned char* u0 = yuv + 1;
    unsigned char* y1 = yuv + 2;
    unsigned char* v0 = yuv + 3;

    unsigned  char* r0 = rgb + 0;
    unsigned  char* g0 = rgb + 1;
    unsigned  char* b0 = rgb + 2;
    unsigned  char* r1 = rgb + 3;
    unsigned  char* g1 = rgb + 4;
    unsigned  char* b1 = rgb + 5;

    float rt0 = 0, gt0 = 0, bt0 = 0, rt1 = 0, gt1 = 0, bt1 = 0;

    for(i = 0; i <= (WIDTH * HEIGHT) / 2 ;i++)
    {
        bt0 = 1.164 * (*y0 - 16) + 2.018 * (*u0 - 128);
        gt0 = 1.164 * (*y0 - 16) - 0.813 * (*v0 - 128) - 0.394 * (*u0 - 128);
        rt0 = 1.164 * (*y0 - 16) + 1.596 * (*v0 - 128);

        bt1 = 1.164 * (*y1 - 16) + 2.018 * (*u0 - 128);
        gt1 = 1.164 * (*y1 - 16) - 0.813 * (*v0 - 128) - 0.394 * (*u0 - 128);
        rt1 = 1.164 * (*y1 - 16) + 1.596 * (*v0 - 128);


        if(rt0 > 250)   rt0 = 255;
        if(rt0< 0)      rt0 = 0;

        if(gt0 > 250)   gt0 = 255;
        if(gt0 < 0)     gt0 = 0;

        if(bt0 > 250)   bt0 = 255;
        if(bt0 < 0)     bt0 = 0;

        if(rt1 > 250)   rt1 = 255;
        if(rt1 < 0)     rt1 = 0;

        if(gt1 > 250)   gt1 = 255;
        if(gt1 < 0)     gt1 = 0;

        if(bt1 > 250)   bt1 = 255;
        if(bt1 < 0)     bt1 = 0;

        *r0 = (unsigned char)rt0;
        *g0 = (unsigned char)gt0;
        *b0 = (unsigned char)bt0;

        *r1 = (unsigned char)rt1;
        *g1 = (unsigned char)gt1;
        *b1 = (unsigned char)bt1;

        yuv = yuv + 4;
        rgb = rgb + 6;
        if(yuv == NULL)
          break;

        y0 = yuv;
        u0 = yuv + 1;
        y1 = yuv + 2;
        v0 = yuv + 3;

        r0 = rgb + 0;
        g0 = rgb + 1;
        b0 = rgb + 2;
        r1 = rgb + 3;
        g1 = rgb + 4;
        b1 = rgb + 5;
    }
}

void CaptureThread::run()
{
    /* 下面的代码请参考正点原子C应用编程V4L2章节,摄像头编程,这里不作解释 */
#ifdef linux
#ifndef __arm__
    return;
#endif
    int video_fd = -1;
    struct v4l2_format fmt;
    struct v4l2_requestbuffers req_bufs;
    static struct v4l2_buffer buf;
    int n_buf;
    struct buffer_info bufs_info[VIDEO_BUFFER_COUNT];
    enum v4l2_buf_type type;

    unsigned char src_buffer[WIDTH*HEIGHT*2];
    unsigned char rgb_buffer[WIDTH*HEIGHT*2];

    video_fd = open(VIDEO_DEV, O_RDWR);
    if (0 > video_fd) {
        printf("ERROR: failed to open video device %s\n", VIDEO_DEV);
        return ;
    }

    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;
    fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;

    if (0 > ioctl(video_fd, VIDIOC_S_FMT, &fmt)) {
        printf("ERROR: failed to VIDIOC_S_FMT\n");
        close(video_fd);
        return ;
    }

    if (0 > ioctl(video_fd, VIDIOC_G_FMT, &fmt)) {
        printf("ERROR: failed to VIDIOC_S_FMT\n");
        close(video_fd);
        return ;
    }
    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.width:\t\t%d\n",fmt.fmt.pix.width);
    printf("pix.height:\t\t%d\n",fmt.fmt.pix.height);
    printf("pix.field:\t\t%d\n",fmt.fmt.pix.field);

    req_bufs.count = VIDEO_BUFFER_COUNT;
    req_bufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req_bufs.memory = V4L2_MEMORY_MMAP;

    if (0 > ioctl(video_fd, VIDIOC_REQBUFS, &req_bufs)) {
        printf("ERROR: failed to VIDIOC_REQBUFS\n");
        return ;
    }

    buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    buf.memory = V4L2_MEMORY_MMAP;
    for (n_buf = 0; n_buf < VIDEO_BUFFER_COUNT; n_buf++) {

        buf.index = n_buf;
        if (0 > ioctl(video_fd, VIDIOC_QUERYBUF, &buf)) {
            printf("ERROR: failed to VIDIOC_QUERYBUF\n");
            return ;
        }

        bufs_info[n_buf].length = buf.length;
        bufs_info[n_buf].start = mmap(NULL, buf.length,
                                      PROT_READ | PROT_WRITE, MAP_SHARED,
                                      video_fd, buf.m.offset);
        if (MAP_FAILED == bufs_info[n_buf].start) {
            printf("ERROR: failed to mmap video buffer, size 0x%x\n", buf.length);
            return ;
        }
    }

    for (n_buf = 0; n_buf < VIDEO_BUFFER_COUNT; n_buf++) {

        buf.index = n_buf;
        if (0 > ioctl(video_fd, VIDIOC_QBUF, &buf)) {
            printf("ERROR: failed to VIDIOC_QBUF\n");
            return ;
        }
    }

    type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    if (0 > ioctl(video_fd, VIDIOC_STREAMON, &type)) {
        printf("ERROR: failed to VIDIOC_STREAMON\n");
        return ;
    }

    while (startFlag) {

        for (n_buf = 0; n_buf < VIDEO_BUFFER_COUNT; n_buf++) {

            buf.index = n_buf;

            //出队
            if (0 > ioctl(video_fd, VIDIOC_DQBUF, &buf)) {
                printf("ERROR: failed to VIDIOC_DQBUF\n");
                return;
            }
//            printf("now memcpy the src buf, buf start=%d, len=%d\r\n", bufs_info[n_buf].start, bufs_info[n_buf].length);
            memcpy(src_buffer, bufs_info[n_buf].start, bufs_info[n_buf].length);                        //将帧缓冲数据复制到到src_buffer
            yuyv_to_rgb((unsigned char*)bufs_info[n_buf].start, rgb_buffer);                            //yuyv转rgb
            QImage qImage(rgb_buffer, fmt.fmt.pix.width, fmt.fmt.pix.height, QImage::Format_RGB16);     //显示到屏幕
//            QImage qImage((unsigned char*)bufs_info[n_buf].start, fmt.fmt.pix.width, fmt.fmt.pix.height, QImage::Format_RGB16);

            /* 是否开启本地显示,开启本地显示可能会导致开启广播卡顿,它们互相制约 */
            if (startLocalDisplay)
                emit imageReady(qImage);

            /* 是否开启广播,开启广播会导致本地显示卡顿,它们互相制约 */
            if (startBroadcast) {
                /* udp套接字 */
                QUdpSocket udpSocket;

                /* QByteArray类型 */
                QByteArray byte;

                /* 建立一个用于IO读写的缓冲区 */
                QBuffer buff(&byte);

                /* image转为byte的类型,再存入buff */
                qImage.save(&buff, "JPEG", -1);

                /* 转换为base64Byte类型 */
                QByteArray base64Byte = byte.toBase64();

                /* 由udpSocket以广播的形式传输数据,端口号为8888 */
                udpSocket.writeDatagram(base64Byte.data(), base64Byte.size(), QHostAddress::Broadcast, 8888);
            }

            //入队
            if (0 > ioctl(video_fd, VIDIOC_QBUF, &buf)) {
                printf("ERROR: failed to VIDIOC_QBUF\n");
                return;
            }
        }
    }

    msleep(800);//at lease 650

    for (int i = 0; i < VIDEO_BUFFER_COUNT; i++) {
        munmap(bufs_info[i].start, buf.length);
    }

    close(video_fd);
#endif
}

到了这里,关于正点原子imx6ull: QT视频监控项目使用yuyv格式的usb摄像头的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 正点原子imx6ull网络环境配置:开发板和电脑通过网线直连、电脑WiFi上网

    开发板通过网线连接电脑。电脑连接wifi  1)打开vm设置 2)设置网络适配器为桥接模式,不要勾选 “赋值物理网络连接状态” 3) 添加一个网络适配器并设置成NAT模式,供虚拟机上网。          默认添加的网络适配器是 NAT 模式的,如果不是 NAT 模式则要手动设置成 NAT

    2024年01月18日
    浏览(49)
  • Linux系统下imx6ull QT编程—— C++基础(一)

    学习 C++的面向对象编程,对学习 Qt 有很大的帮助 效率上,肯定是 C 语言的 scanf 和 printf 的效率高,但是没有 C++中的 cin 和 cout 使用方便。 x 可以是任意数据类型,甚至可以写成一个表达式,这比 C 语言需要指定数据类型方便多了,endl 指的是换行符,与 C 语言的“n”效果一

    2024年02月07日
    浏览(42)
  • 【IMX6ULL驱动开发学习】02.IMX6ULL烧写Linux系统

    由于我买的是正点原子的IMX6ULL阿尔法开发板,但是我是看韦东山老师视频学习的驱动 所以这里我烧录的方法是按照韦东山老师的课程来的 这里给出烧写Linux系统用到的工具 链接:https://pan.baidu.com/s/1bD-xxn3K8xQAVkJSaJmTzQ 提取码:af6w 下载解压后,可以看到烧写工具 烧写Linux系统

    2024年02月13日
    浏览(43)
  • Linux系统下imx6ull QT编程—— C++数据封装与数据抽象(八)

    封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全。数据封装引申出了另一个重要的 OOP 概念,即数据隐藏。数据封装是一种把数据和操作数据的函数捆绑在一起的机制,数据抽象是一种仅向用户暴

    2024年02月07日
    浏览(65)
  • 基于IMX6ULL的AP3216C的QT动态数据曲线图显示

    前言: 本文为手把手教学 Linux+QT 的典型基础项目 AP3216C 的数据折线图显示,项目使用正点原子的 IMX6ULL  阿尔法( Cortex-A7 系列)开发板。项目需要实现 AP3216C 在 Linux 系统下的驱动,使用 QT 设计 AP3216C 的数据显示页面作为项目的应用层。该项目属于非常简单的入门级项目,核心

    2024年02月16日
    浏览(38)
  • 将windows下编写的QT代码在arm开发板(imx6ull)上运行

    tslib 是 Linux 下的一个触摸屏(Touch Screen)驱动库,它可以很好地支持各种不同类型的触摸屏设备,并提供标准的输入接口。在编译 Qt 时,启用 tslib 特性可以使得 Qt 应用程序能够更好地支持触摸屏交互,从而提升应用程序在嵌入式设备上的用户体验。 对于 ARM 架构下的 Qt 环

    2024年02月13日
    浏览(38)
  • IMX6ULL + SPI LCD(驱动IC ILI9341)显示简单的QT界面

    使用正点原子的IMX6ULL Linux开发板 开发板底板原理图版本:V2.1 核心板原理图版本:V1.6 LCD :MSP2402 (IC ILI9341) 开发板上引出的引脚是在JP6上,只看JP6会发现没有可用的SPI引脚,但是查看底板原理图中与核心板相连的位置会发现其实JP6上的UART2的TX/RX/CTS/RTS 四个引脚正好可以复用

    2024年02月06日
    浏览(33)
  • iMX6ULL驱动开发 | 让imx6ull开发板支持usb接口FC游戏手柄

    手边有一闲置的linux开发板iMX6ULL一直在吃灰,不用来搞点事情,总觉得对不住它。业余打发时间就玩起来吧,总比刷某音强。从某多多上买来一个usb接口的游戏手柄,让开发板支持以下它,后续就可以接着在上面玩童年经典游戏啦。  我使用的是正点原子的I.MX6U-ALPHA 开发板,

    2024年02月14日
    浏览(39)
  • 【IMX6ULL驱动开发学习】08.IMX6ULL通过GPIO子系统函数点亮LED

    通过GPIO子系统函数点亮LED 1、GPIO子系统函数 1.1 确定 led 的GPIO标号,查看内核中的gpiochip 查看 gpiochip ,以正点原子的IMX6ULL阿尔法开发板为例 查看原理图,发现led接的引脚是 GPIO1_IO3,对应 /sys/kernel/debug/gpio 中的 gpiochip0 组,gpiochip0 组从0开始算起, 所以 GPIO1_IO3 对应的标号就

    2024年02月10日
    浏览(62)
  • 【IMX6ULL驱动开发学习】22.IMX6ULL开发板读取ADC(以MQ-135为例)

    IMX6ULL一共有两个ADC,每个ADC都有八个通道,但他们共用一个ADC控制器 在imx6ull.dtsi文件中已经帮我们定义好了adc1的节点部分信息 注意 num-channels = 2; ,这个表示指定使用ADC1的两个通道,即通道1和通道2 如果你要使用多个ADC通道,修改这个值即可 配置ADC引脚的 pinctrl ,在自己的

    2024年02月12日
    浏览(39)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包