安卓App与ESP32Cam的视频传输

这篇具有很好参考价值的文章主要介绍了安卓App与ESP32Cam的视频传输。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

  1. 实现结果

    安卓App与ESP32Cam的视频传输
  2. app可以控制Esp32Cam的摄像头开关和闪光灯的开关
    安卓App与ESP32Cam的视频传输
  3. Esp32Cam代码
     
    #include <Arduino.h>
    #include <WiFi.h>
    #include "esp_camera.h"
    #include <vector>
    
    #define maxcache 1024  //图像数据包的大小
    
    const char* ssid = "****";
    const char* password = "*******";
    
    const int LED = 4;//闪光灯
    const int ZHESHI_LED = 33; //指示灯 
    bool cam_state = true;  //是否开启摄像头传输
    const int port = 8080;
    String  frame_begin = "FrameBegin"; //图像传输包头
    String  frame_over = "FrameOverr";  //图像传输包尾
    String  msg_begin = "Esp32Msg";  //消息传输头
    //创建服务器端
    WiFiServer server;
    //创建客户端
    WiFiClient client;
    
    //CAMERA_MODEL_AI_THINKER类型摄像头的引脚定义
    #define PWDN_GPIO_NUM     32
    #define RESET_GPIO_NUM    -1
    #define XCLK_GPIO_NUM      0
    #define SIOD_GPIO_NUM     26
    #define SIOC_GPIO_NUM     27
     
    #define Y9_GPIO_NUM       35
    #define Y8_GPIO_NUM       34
    #define Y7_GPIO_NUM       39
    #define Y6_GPIO_NUM       36
    #define Y5_GPIO_NUM       21
    #define Y4_GPIO_NUM       19
    #define Y3_GPIO_NUM       18
    #define Y2_GPIO_NUM        5
    #define VSYNC_GPIO_NUM    25
    #define HREF_GPIO_NUM     23
    #define PCLK_GPIO_NUM     22
    
    static camera_config_t camera_config = {
        .pin_pwdn = PWDN_GPIO_NUM,
        .pin_reset = RESET_GPIO_NUM,
        .pin_xclk = XCLK_GPIO_NUM,
        .pin_sscb_sda = SIOD_GPIO_NUM,
        .pin_sscb_scl = SIOC_GPIO_NUM,
        
        .pin_d7 = Y9_GPIO_NUM,
        .pin_d6 = Y8_GPIO_NUM,
        .pin_d5 = Y7_GPIO_NUM,
        .pin_d4 = Y6_GPIO_NUM,
        .pin_d3 = Y5_GPIO_NUM,
        .pin_d2 = Y4_GPIO_NUM,
        .pin_d1 = Y3_GPIO_NUM,
        .pin_d0 = Y2_GPIO_NUM,
        .pin_vsync = VSYNC_GPIO_NUM,
        .pin_href = HREF_GPIO_NUM,
        .pin_pclk = PCLK_GPIO_NUM,
        
        .xclk_freq_hz = 20000000,
        .ledc_timer = LEDC_TIMER_0,
        .ledc_channel = LEDC_CHANNEL_0,
        
        .pixel_format = PIXFORMAT_JPEG,
        .frame_size = FRAMESIZE_VGA,
        .jpeg_quality = 31,   //图像质量   0-63  数字越小质量越高
        .fb_count = 1,
    };
    //初始化摄像头
    esp_err_t camera_init() {
        //initialize the camera
        esp_err_t err = esp_camera_init(&camera_config);
        if (err != ESP_OK) {
            Serial.println("Camera Init Failed!");
            return err;
        }
        sensor_t * s = esp_camera_sensor_get();
        //initial sensors are flipped vertically and colors are a bit saturated
        if (s->id.PID == OV2640_PID) {
        //        s->set_vflip(s, 1);//flip it back
        //        s->set_brightness(s, 1);//up the blightness just a bit
        //        s->set_contrast(s, 1);
        }
        Serial.println("Camera Init OK!");
        return ESP_OK;
    }
    
    bool wifi_init(const char* ssid,const char* password ){
      WiFi.mode(WIFI_STA);
      WiFi.setSleep(false); //关闭STA模式下wifi休眠,提高响应速度
      #ifdef staticIP
        WiFi.config(staticIP, gateway, subnet);
      #endif
      WiFi.begin(ssid, password);
      uint8_t i = 0;
      Serial.println();
      while (WiFi.status() != WL_CONNECTED && i++ < 20) {
          delay(500);
          Serial.print(".");
      }
      if (i == 21) {
        Serial.println();
        Serial.print("Could not connect to"); 
        Serial.println(ssid);
        digitalWrite(ZHESHI_LED,HIGH);  //网络连接失败 熄灭指示灯
        return false;
      }
      Serial.print("Connecting to wifi "); 
      Serial.print(ssid);
      Serial.println(" success!"); 
      digitalWrite(ZHESHI_LED,LOW);  //网络连接成功 点亮指示灯
      return true;
    }
    
    void TCPServerInit(){
      //启动server
      server.begin(port);
      //关闭小包合并包功能,不会延时发送数据
      server.setNoDelay(true);
      Serial.print("Ready! TCP Server");
      Serial.print(WiFi.localIP());
      Serial.println(":8080 Running!");
    }
    void cssp(){
      camera_fb_t * fb = esp_camera_fb_get();
      uint8_t * temp = fb->buf; //这个是为了保存一个地址,在摄像头数据发送完毕后需要返回,否则会出现板子发送一段时间后自动重启,不断重复
      if (!fb)
      {
          Serial.println("Camera Capture Failed");
      }
      else
      { 
        //先发送Frame Begin 表示开始发送图片 然后将图片数据分包发送 每次发送1430 余数最后发送 
        //完毕后发送结束标志 Frame Over 表示一张图片发送完毕 
        client.print(frame_begin); //一张图片的起始标志
        // 将图片数据分段发送
        int leng = fb->len;
        int timess = leng/maxcache;
        int extra = leng%maxcache;
        for(int j = 0;j< timess;j++)
        {
          client.write(fb->buf, maxcache); 
          for(int i =0;i< maxcache;i++)
          {
            fb->buf++;
          }
        }
        client.write(fb->buf, extra);
        client.print(frame_over);      // 一张图片的结束标志
        //Serial.print("This Frame Length:");
        //Serial.print(fb->len);
        //Serial.println(".Succes To Send Image For TCP!");
        //return the frame buffer back to the driver for reuse
        fb->buf = temp; //将当时保存的指针重新返还
        esp_camera_fb_return(fb);  //这一步在发送完毕后要执行,具体作用还未可知。        
      }
      //delay(20);//短暂延时 增加数据传输可靠性        
    }
    void TCPServerMonitor(){
    if (server.hasClient()) {
      if ( client && client.connected()) {
        WiFiClient serverClient = server.available();
        serverClient.stop();
        Serial.println("Connection rejected!");
      }else{
        //分配最新的client
        client = server.available();
        client.println(msg_begin +  "Client is Connect!");
        Serial.println("Client is Connect!");
      }
    }
      //检测client发过来的数据
    if (client && client.connected()) {
      if (client.available()) {
        String line = client.readStringUntil('\n'); //读取数据到换行符
        if (line == "CamOFF"){
          cam_state = false;
          client.println(msg_begin +  "Camera OFF!");
        }
        if (line == "CamON"){
          cam_state = true;
          client.println(msg_begin +  "Camera ON!");
        }
        if (line == "LedOFF"){
          digitalWrite(LED, LOW);
          client.println(msg_begin +  "Led OFF!");
        }
        if (line == "LedON"){
          digitalWrite(LED, HIGH);
          client.println(msg_begin +  "Led ON!");
        }
        Serial.println(line);
      }
    }
      
    // 视频传输
    if(cam_state)
    {
      if (client && client.connected()) {
        cssp();
      }
    }
    }
    
    void setup() {
      Serial.begin(115200);
      pinMode(ZHESHI_LED, OUTPUT);
      digitalWrite(ZHESHI_LED, HIGH);
      pinMode(LED, OUTPUT);
      digitalWrite(LED, LOW);
      wifi_init(ssid,password);
      camera_init();
      TCPServerInit();
    }
    
    void loop() {
      TCPServerMonitor();
    }
    
  4. 安卓app代码
     
    package com.example.tcpclient_eap32cam_1025;
    
    
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.util.Log;
    import android.view.View;
    import android.widget.Button;
    import android.widget.EditText;
    import android.widget.ImageView;
    import android.widget.TextView;
    import android.widget.Toast;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.net.Socket;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class MainActivity extends AppCompatActivity {
        EditText host_editText,port_editText,send_data;
        TextView rec_data;
        Button connect_button,send;
        ImageView show_cam;
        Socket socket;
        InputStream inputStream;
        OutputStream outputStream;
        byte[] RevBuff = new byte[1024];  //定义接收数据流的包的大小
        MyHandler myHandler;
        byte[] temp = new byte[0];  //存放一帧图像的数据
        int headFlag = 0;    // 0 数据流不是图像数据   1 数据流是图像数据
        Bitmap bitmap = null;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            host_editText = findViewById(R.id.host_editText); //服务器地址
            port_editText = findViewById(R.id.port_editText);//服务器端口
            connect_button= findViewById(R.id.connect_button);//连接服务器按钮
            rec_data = findViewById(R.id.rec_data); //存放接收到的非图像数据
            send = findViewById(R.id.send);//发送数据按钮
            send_data = findViewById(R.id.send_data);//发送数据文本框
            connect_button.setText("连接"); //设置连接按钮名称为连接 如果已连接上显示断开
            show_cam = findViewById(R.id.show_cam); //存放图像数据
            myHandler = new MyHandler();
    //        连接服务器操作
            connect_button.setOnClickListener(new View.OnClickListener(){
                @Override
                public void onClick(View view) {
                    if(connect_button.getText() == "连接"){
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                                Message msg = myHandler.obtainMessage();
                                try {
                                    //如果 host_editText  port_editText为空的话 点击连接 会退出程序
                                    socket = new Socket((host_editText.getText()).toString(),Integer.valueOf(port_editText.getText().toString()));
                                    //socket = new Socket("192.168.0.3",8080);
                                    if(socket.isConnected()){
                                        msg.what = 0;//显示连接服务器成功信息
                                        inputStream = socket.getInputStream();
                                        outputStream = socket.getOutputStream();
                                        Recv();//接收数据
                                    }else{
                                        msg.what = 1;//显示连接服务器失败信息
                                    }
                                } catch (IOException e) {
                                    e.printStackTrace();
                                    msg.what = 1;//显示连接服务器失败信息
                                }
                                myHandler.sendMessage(msg);
                            }
                        }).start();
                    }else{
    //                    关闭socket连接
                        try { socket.close(); } catch (IOException e) { e.printStackTrace(); }
                        try { inputStream.close(); }catch (IOException e) { e.printStackTrace(); }
                        try { outputStream.close(); }catch (IOException e) { e.printStackTrace(); }
                        connect_button.setText("连接");
                    }
                }
            });
    //        发送数据
            send.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            try {
    //                            发送数据
                                outputStream.write(send_data.getText().toString().getBytes());
                            } catch (IOException e) {
    //                            如果发送数据失败 显示连接服务器失败信息
                                e.printStackTrace();
                                Message msg = myHandler.obtainMessage();
                                msg.what = 1;
                                myHandler.sendMessage(msg);
                            }
                        }
                    }).start();
                }
            });
        }
        //    接收数据方法
        public void Recv(){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while(socket != null && socket.isConnected()){
                        try {
                            int Len = inputStream.read(RevBuff);
                            if(Len != -1){
    //                          图像数据包的头  FrameBegin
                                boolean begin_cam_flag = RevBuff[0] == 70 && RevBuff[1] == 114 && RevBuff[2] == 97 && RevBuff[3] == 109 && RevBuff[4] == 101
                                        && RevBuff[5] == 66 && RevBuff[6] == 101 && RevBuff[7] == 103 && RevBuff[8] == 105 && RevBuff[9] == 110 ;
    //                            图像数据包的尾  FrameOverr
                                boolean end_cam_flag = RevBuff[0] == 70 && RevBuff[1] == 114 && RevBuff[2] == 97 && RevBuff[3] == 109 && RevBuff[4] == 101
                                        && RevBuff[5] == 79 && RevBuff[6] == 118 && RevBuff[7] == 101 && RevBuff[8] == 114 && RevBuff[9] == 114;
    //                            判断接收的包是不是图片的开头数据 是的话s说明下面的数据属于图片数据 将headFlag置1
                                if(headFlag == 0 && begin_cam_flag){
                                    headFlag = 1;
                                }else if(end_cam_flag){  //判断包是不是图像的结束包 是的话 将数据传给 myHandler  3 同时将headFlag置0
                                    Message msg = myHandler.obtainMessage();
                                    msg.what = 3;
                                    myHandler.sendMessage(msg);
                                    headFlag = 0;
                                }else if(headFlag == 1){ //如果 headFlag == 1 说明包是图像数据  将数据发给byteMerger方法 合并一帧图像
                                    temp = byteMerger(temp,RevBuff);
                                }
    //                            定义包头 Esp32Msg  判断包头 在向myHandler  2 发送数据    eadFlag == 0 && !end_cam_flag没用 会展示图像的数据
                                boolean begin_msg_begin = RevBuff[0] == 69 && RevBuff[1] == 115 && RevBuff[2] == 112 && RevBuff[3] == 51 && RevBuff[4] == 50
                                        && RevBuff[5] == 77 && RevBuff[6] == 115 && RevBuff[7] == 103 ;
                                if(begin_msg_begin){
                                    Message msg = myHandler.obtainMessage();
                                    msg.what = 2;
                                    msg.arg1 = Len;
                                    msg.obj = RevBuff;
                                    myHandler.sendMessage(msg);
                                }
                            }else{
    //                            如果Len = -1 说明接受异常  显示连接服务器失败信息  跳出循环
                                Message msg = myHandler.obtainMessage();
                                msg.what = 1;
                                myHandler.sendMessage(msg);
                                break;
                            }
                        } catch (IOException e) {
    //                        如果接受数据inputStream.read(RevBuff)语句执行失败 显示连接服务器失败信息  跳出循环
                            e.printStackTrace();
                            Message msg = myHandler.obtainMessage();
                            msg.what = 1;
                            myHandler.sendMessage(msg);
                            break;
                        }
                    }
                }
            }).start();
        }
    
        //    合并一帧图像数据  a 全局变量 temp   b  接受的一个数据包 RevBuff
        public byte[] byteMerger(byte[] a,byte[] b){
            int i = a.length + b.length;
            byte[] t = new byte[i]; //定义一个长度为 全局变量temp  和 数据包RevBuff 一起大小的字节数组 t
            System.arraycopy(a,0,t,0,a.length);  //先将 temp(先传过来的数据包)放进  t
            System.arraycopy(b,0,t,a.length,b.length);//然后将后进来的这各数据包放进t
            return t; //返回t给全局变量 temp
        }
        //处理一些不能在线程里面执行的信息
        class MyHandler extends Handler{
            public void handleMessage(Message msg){
                super.handleMessage(msg);
                switch (msg.what){
                    case 0:
    //                    连接服务器成功信息
                        Toast.makeText(MainActivity.this,"连接服务器成功!",Toast.LENGTH_SHORT).show();
                        connect_button.setText("断开");
                        break;
                    case 1:
    //                    连接服务器失败信息
                        Toast.makeText(MainActivity.this,"连接服务器失败!",Toast.LENGTH_SHORT).show();
                        break;
                    case 2:
    //                    处理接收到的非图像数据
                        byte[] Buffer = new byte[msg.arg1];
                        System.arraycopy((byte[])msg.obj,0,Buffer,0,msg.arg1);
                        SimpleDateFormat formatter= new SimpleDateFormat("yyyy-MM-dd 'at' HH:mm:ss z");
                        Date date = new Date(System.currentTimeMillis());
                        String content = (new String(Buffer)) + "----"  + formatter.format(date) + "\n";
                        rec_data.append(content);
                        break;
                    case 3:
    //                    处理接受到的图像数据 并展示
                        bitmap = BitmapFactory.decodeByteArray(temp, 0,temp.length);
                        show_cam.setImageBitmap(bitmap);//这句就能显示图片(bitmap数据没问题的情况下) 存在图像闪烁情况 待解决
                        temp = new byte[0];  //一帧图像显示结束  将 temp清零
                        break;
                    default: break;
                }
            }
        }
        //    销毁窗体 释放资源
        protected void onDestroy() {
            super.onDestroy();
            if(inputStream != null){
                try {inputStream.close();}catch(IOException e) {e.printStackTrace();}
            }
    
            if(outputStream != null){
                try {outputStream.close();} catch (IOException e) {e.printStackTrace();}
            }
            if(socket != null){
                try {socket.close();} catch (IOException e) {e.printStackTrace();}
            }
        }
    }
    
    
    
    
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout  xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:orientation="vertical"
        tools:context=".MainActivity"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <LinearLayout
            android:orientation="horizontal"
            tools:context=".MainActivity"
            android:layout_width="match_parent"
            android:layout_height="50dp"
            android:layout_marginBottom="4dp">
            <EditText
                android:layout_width="160dp"
                android:layout_height="50dp"
                android:id="@+id/host_editText"
                android:hint="服务器地址"
                android:selectAllOnFocus="true"
                android:inputType="phone"/>
            <EditText
                android:layout_width="160dp"
                android:layout_height="50dp"
                android:id="@+id/port_editText"
                android:hint="端口"
                android:inputType="phone"/>
            <Button
                android:id="@+id/connect_button"
                android:layout_width="80dp"
                android:layout_height="50dp"
                android:text="连接" />
        </LinearLayout>
    
        <ImageView
            android:id="@+id/show_cam"
            android:layout_width="match_parent"
            android:layout_height="220dp"
            android:background="#333"
            android:scaleType="center"
            android:layout_marginBottom="4dp"/>
        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="320dp">
            <TextView
                android:layout_width="fill_parent"
                android:layout_height="wrap_content"
                android:id="@+id/rec_data"
                android:hint="接收消息"/>
        </ScrollView>
    
        <EditText
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:id="@+id/send_data"
            android:layout_marginBottom="4dp"
            android:hint="发送消息"/>
        <Button
            android:text="发送"
            android:layout_width="match_parent"
            android:layout_height="60dp"
            android:id="@+id/send" />
    
    </LinearLayout>
    
    
    
    <uses-permission android:name="android.permission.INTERNET" />

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

到了这里,关于安卓App与ESP32Cam的视频传输的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • Arduino IDE + Esp32 Cam + 实现视频流 + 开发环境部署

    1、开发环境 Arduino ide 版本:2.2.1 esp32工具:2.0.5 示例代码

    2024年02月08日
    浏览(49)
  • ESP32-CAM网络摄像头系列-01-基于RTSP协议的局域网视频推流/拉流的简单实现

            由于项目需要,最近开始开坑关于ESP32-CAM系列的RTSP网络摄像头系列,该文章为该系列的第一篇文章。用于记录项目开发过程。         使用ESP32-CAM获取图像数据,并通过RTSP协议将获取到的视频流传输到上位机进行显示。         使用ESP32-CAM进行视频推流,

    2024年02月11日
    浏览(36)
  • Opencv保存ESP32-CAM视频流

    Opencv保存ESP32-CAM视频流 esp32cam是一个很便宜的视频模组,可以用作监控等功能。此时就需要保存esp32的视频流,方便查看等操作,python代码如下 前置条件:视频流正常,已安装opencv

    2024年02月08日
    浏览(39)
  • QT获取ESP32-CAM视频流分析

    1、前言       使用QT获取ESP32-CAM视频流的原理是在QT模拟浏览器发送http请求,然后ESP32-CAM返回视频流,当QT界面接收到数据后,对数据进行解析,然后合成图片进行显示。       在QT中发送http请求的方法很多,这里使用Qt网络模块中的类QNetworkReply发送http请求。 2、核心代

    2023年04月20日
    浏览(26)
  • QT+ESP32-CAM上位机获取视频流(附源码)

    第二章 qt获取esp32-cam视频流 本文是基于esp32-cam 官方示例扩展的QT上位机程序 可以获取视频流来做一些图像处理 帧率和直接用网页打开的帧率差不多(25FPS) 由于也是刚玩ESP32-cam 在网上想找个QT上位机的程序来做一些测试 但是找到的一些例程获取的帧率比较低所以开发了一个测

    2024年02月03日
    浏览(33)
  • esp32 cam不使用官方示例完成视频内网穿透

    刚才给大家讲解了esp32cam使用arduino ide官方示例内网穿透的方法,因目前免费的内网穿透软件无法完成公网ip的两个端口映射,因此作者去学习了另一个不使用官方示例也可以内网穿穿透的方法。 在此先介绍b站王铭东老师,我是在这位老师的基础上学习的,还有csdn上的这位老

    2024年04月14日
    浏览(22)
  • ESP32cam系列教程001:使用webcam摄像头实时查看视频

    ESP32-CAM是小尺寸的摄像头模组该模块可以作为最小系统独立工作,尺寸仅为 27*40.5*4.5mm ,可广泛应用于各种物联网场合,适用于家庭智能设备、工业无线控制、无线监控、QR无线识别,无线定位系统信号以及其它物联网应用,是物联网应用的理想解决方案。[^1] 其产品特性如下

    2024年02月06日
    浏览(31)
  • ESP32cam系列教程003:ESP32cam实现远程 HTTP_OTA 自动升级

    本教程是 ESP32cam 的系列教程之三,使用 Arduino IDE 对 ESP32cam 开发板进行开发。 本教程代码同样使用与其他 ESP32 开发板。 OTA 即空中下载技术(Over-the-Air Technology),其可以安全方便地升级设备的固件或软件。远程升级还可以大大降低成本,节省资源,它已成为物联网设备和产

    2024年02月14日
    浏览(32)
  • ESP32-CAM 使用 MicroPython 完成视频网络服务器 (Web Video Stream)

    ESP32-CAM 是安信可发布小尺寸的摄像头模组。该模块可以作为最小系统独立工作,尺寸仅为2740.54.5mm。 ESP32-CAM可广泛应用于各种物联网场合,适用于家庭智能设备、工业无线控制、无线监控、人脸识别以及其它物联网应用,是物联网应用的理想解决方案。 ESP32-CAM采用DIP封装,

    2024年02月13日
    浏览(27)
  • 半小时内实现Esp32-Cam模型训练和图像识别

    这个项目可以让你在半个小时内实现模型训练和图像识别,非常简单。 开始前先放效果视频点击这里 现成资源有很多,只要稍微找下然后把程序烧录到Esp32-Cam都可以实现该功能。详细内容前往学习即可,此处不赘述。 可以学习安信可官网的例程,权威。点击前往 教程很详细

    2024年02月01日
    浏览(30)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包