esp32cam的与安卓的udp服务视频传输

13 篇文章 2 订阅

esp32cam

/*
下载程序  按住接口板上的IO0 在程序上传的时候 按一下 开发板上的rst按钮  待程序开始上传  在松开 IO0
brownout detector was triggered报错  触发了断电探测器,估计是供电环境本来就不稳定
屏蔽
#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"

void setup() {
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable   detector
  //Your code
}

*/

#include "soc/soc.h"
#include "soc/rtc_cntl_reg.h"
#include <WiFi.h>  //wifi功能需要的库
#include <Arduino.h>
#include "esp_camera.h"
#include <vector>
#include <WiFiUdp.h> //引用以使用UDP
WiFiUDP Udp;                             //声明UDP对象
const char* wifi_SSID = "esp32";         //存储AP的名称信息 生成一个wifi 网络  方便客户端连接
const char* wifi_Password = "12345678";  //存储AP的密码信息
uint16_t udp_port = 1122;                //存储需要监听的端口号  eps32cam接收数据 和 往其他客户端 发送数据的端口
#define maxcache 1430  //图片数据分帧发送
bool is_tran_camera = false;  //是否传输视频数据
IPAddress ip_address;  //客户端的ip

#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 = 12,
  .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);
  }
  return ESP_OK;
}
//接收数据的处理函数
void recv_data() {
  int packetSize = Udp.parsePacket();  //获取当前队首数据包长度
  if (packetSize)                      //如果有数据可用
  {
    char buf[packetSize];
    Udp.read(buf, packetSize);  //读取当前包数据
    ip_address = Udp.remoteIP();
    String tmp = String(buf);
    //打开视频数据传输
    if(tmp.substring(0,5) == "camon"){
      is_tran_camera = true;
      Udp.beginPacket(ip_address, udp_port);
      Udp.print("Camera is ON!");
      Udp.endPacket();
    }else if(tmp.substring(0,6) == "camoff"){ // 关闭视频数据传输
      is_tran_camera = false;
      Udp.beginPacket(ip_address, udp_port);
      Udp.print("Camera is OFF!");
      Udp.endPacket();
    }else{
      Udp.beginPacket(ip_address, udp_port);
      Udp.print("RecvData:" + tmp);
      Udp.endPacket();
    }
  }
}
// 传输视频数据
void cssp() {
  camera_fb_t* fb = esp_camera_fb_get();
  uint8_t* temp = fb->buf;  //这个是为了保存一个地址,在摄像头数据发送完毕后需要返回,否则会出现板子发送一段时间后自动重启,不断重复
  if (!fb) {
    // Serial.print("Camera Capture Failed!");
    Udp.beginPacket(ip_address, udp_port);
    Udp.print("Camera Capture Failed!");
    Udp.endPacket();
  } else {
    Udp.beginPacket(ip_address, udp_port);
    Udp.print("FrameBegin");  //视频数据的标志头
    Udp.endPacket();
    // 将图片数据分段发送
    int leng = fb->len;
    int timess = leng / maxcache;
    int extra = leng % maxcache;
    for (int j = 0; j < timess; j++) {
      Udp.beginPacket(ip_address, udp_port);
      Udp.write(fb->buf, maxcache);
      Udp.endPacket();
      for (int i = 0; i < maxcache; i++) {
        fb->buf++;
      }
    }
    // 发送剩余数据
    Udp.beginPacket(ip_address, udp_port);
    Udp.write(fb->buf, extra);
    Udp.endPacket();
    Udp.beginPacket(ip_address, udp_port);
    Udp.print("FrameOverr");  //视频数据的标志尾
    Udp.endPacket();
    // Serial.print("This Frame Length:");
    // Serial.print(fb->len);
    // Serial.println(". \t Succes To Send Image For UDP!");
    //return the frame buffer back to the driver for reuse
    fb->buf = temp;            //将当时保存的指针重新返还
    esp_camera_fb_return(fb);  //这一步在发送完毕后要执行,具体作用还未可知。
  }
  delay(20);  //不加延时会导致数据发送混乱 稍微延时增加数据传输可靠性
}
// udp服务初始化
void udp_init() {
  WiFi.softAP(wifi_SSID, wifi_Password);  //打开ESP32热点
  Udp.begin(udp_port);                    //启动UDP监听这个端口   
}
void setup() {
  delay(5000);
  Serial.begin(115200);  //开启串口,波特率为115200
  //在创建udp服务以后  使用串口输出数据会报错  开发板一直重启 
  WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable   detector  屏蔽 brownout detector was triggered报错  防止一直重启 
  udp_init();
  camera_init();
  Serial.print(WiFi.softAPIP());  //串口输出模块IP地址
  Serial.print(":");              //串口输出模块IP地址
  Serial.println(udp_port);   
  Serial.println("Sys Is Running!");
}
 
void loop() {
  recv_data();
  if (is_tran_camera) {
    cssp();
  }
}




 

Android

<?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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/local_ip_kj"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:text="" />
    <TextView
        android:id="@+id/local_port_kj"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:text="" />
    <Button
        android:id="@+id/startudp_btn_kj"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:text="开启UDP服务" />
    <ImageView
        android:id="@+id/show_cam_kj"
        android:layout_width="match_parent"
        android:layout_height="220dp"
        android:background="#333"
        android:scaleType="center"
        android:layout_marginBottom="4dp"/>
    <TextView
        android:id="@+id/recv_data_kj"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:text="" />
    <TextView
        android:id="@+id/remote_ip_kj"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:text="192.168.4.1" />
    <TextView
        android:id="@+id/remote_port_kj"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:text="1122" />
    <EditText
        android:id="@+id/msg_kj"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:text="" />
    <Button
        android:id="@+id/send_btn_kj"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:text="发送" />


</LinearLayout>
package com.example.esp32cam_udp;


import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
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.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

public class MainActivity extends AppCompatActivity {
    private TextView local_ip_kj,local_port_kj,remote_ip_kj,remote_port_kj,recv_data_kj;
    private Button startudp_btn_kj,send_btn_kj;
    private ImageView show_cam_kj;
    private EditText msg_kj;
    String local_ip = "";
    int port = 1122;

    private DatagramSocket socket;  //接收数据用的socket
    int data_len = 1430;//与esp32cam约定的数据传输的长度
    int headFlag = 0;// 0 数据流不是图像数据   1 数据流是图像数据
    byte[] RevBuff = new byte[data_len];//定义接收数据流的包的大小
    byte[] temp = new byte[0];  //存放一帧图像的数据
    Bitmap bitmap = null; //展示图片
    MyHandler myHandler; //处理信息
    String recv_data = ""; //保存接收到的非图像数据
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myHandler = new MyHandler();
        show_cam_kj = findViewById(R.id.show_cam_kj);
        local_ip_kj = findViewById(R.id.local_ip_kj);
        local_port_kj = findViewById(R.id.local_port_kj);
        remote_ip_kj = findViewById(R.id.remote_ip_kj);
        remote_port_kj = findViewById(R.id.remote_port_kj);
        msg_kj = findViewById(R.id.msg_kj);
        startudp_btn_kj = findViewById(R.id.startudp_btn_kj);
        send_btn_kj = findViewById(R.id.send_btn_kj);
        recv_data_kj = findViewById(R.id.recv_data_kj);
        //获取本机ip地址
        local_ip = IPUtils.getIpAddress(this);
        Message msg = myHandler.obtainMessage();
        msg.what = 0;
        msg.obj = local_ip;
        myHandler.sendMessage(msg);
        //Log.e("IP",local_ip);
//        开启udp服务
        startudp_btn_kj.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //生成接受数据使用的socket
                try {
                    InetAddress serverAdder = InetAddress.getByName(local_ip); //本手机的ip地址
                    socket = new DatagramSocket(port,serverAdder);
                    Message msg = myHandler.obtainMessage();
                    msg.what = 1;
                    msg.obj = "UDP服务开启成功!";
                    myHandler.sendMessage(msg);
//                    开启接收数据的线程
                    new Thread(new RecvData()).start();
                } catch (SocketException | UnknownHostException e) {
                    e.printStackTrace();
                }
            }
        });
//        发送消息
        send_btn_kj.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                new Thread(new SendData()).start();
            }
        });
    }

    public class SendData implements Runnable{

        @Override
        public void run() {
            String server_ip = remote_ip_kj.getText().toString();
            int server_port = Integer.parseInt(remote_port_kj.getText().toString());
            InetAddress server_serverAdder = null;
            try {
                server_serverAdder = InetAddress.getByName(server_ip);
            } catch (UnknownHostException e) {
                throw new RuntimeException(e);
            }
            DatagramSocket send_socket = null;
            try {
                send_socket = new DatagramSocket();
            } catch (SocketException e) {
                throw new RuntimeException(e);
            }
            String tmp = msg_kj.getText().toString();
            byte[] buf = tmp.getBytes();
            DatagramPacket send_packet = new DatagramPacket(buf, buf.length, server_serverAdder, server_port);
            try {
                send_socket.send(send_packet);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            send_socket.close();
        }
    }

    //    接收数据的线程
    public class RecvData implements Runnable {
        public void run() {
            // 接收UDP广播,有的手机不支持
            while (true) {
                byte[] recbuf = new byte[data_len];
                DatagramPacket recpacket = new DatagramPacket(recbuf,
                        recbuf.length);
                try {
                    socket.receive(recpacket);
                    RevBuff = recpacket.getData();
//                                              图像数据包的头  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;
                    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);
                    }else{
                        //接收到的非图像数据
                        Message msg = myHandler.obtainMessage();
                        msg.what = 2;
                        msg.obj = new String(RevBuff); //字符串长度
                        myHandler.sendMessage(msg);
//                        Log.v("Message:",new String(RevBuff));
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    //处理一些不能在线程里面执行的信息
    class MyHandler extends Handler {
        public void handleMessage(Message msg){
            super.handleMessage(msg);
            switch (msg.what){
                case 0:
//                    获取ip c成功
                    local_ip_kj.setText(local_ip);
                    local_port_kj.setText(String.valueOf(port));
                    break;
                case 1:
//                    UDP服务开启成功
                    Toast.makeText((Context) MainActivity.this, msg.obj.toString(),Toast.LENGTH_SHORT).show();
                    break;
                case 2:
//                    处理接收到的非图像数据
//                    Toast.makeText((Context) MainActivity.this, msg.obj.toString(),Toast.LENGTH_SHORT).show();
                    String tmp = recv_data + msg.obj.toString() + "\n";
                    recv_data = tmp;
                    recv_data_kj.setText(tmp);
                    break;
                case 3:
                    try {
                        //处理接受到的图像数据 并展示
                        bitmap = BitmapFactory.decodeByteArray(temp, 0,temp.length);
                        show_cam_kj.setImageBitmap(bitmap);//这句就能显示图片(bitmap数据没问题的情况下) 存在图像闪烁情况 待解决
                        temp = new byte[0];  //一帧图像显示结束  将 temp清零
                    }catch (Exception e){
                        Log.i("Error","Error image data!");
                    }
                    break;
                default: break;
            }
        }
    }

    //    合并一帧图像数据  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
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        socket.close();
    }
}





package com.example.esp32cam_udp;

import android.content.Context;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;

import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;

/**
 * =======================================================
 * 版权:Copyright LiYing 2015-2016. All rights reserved.
 * 作者:liying - liruoer2008@yeah.net
 * 日期:2016/12/19 19:43
 * 版本:1.0
 * 描述:IP地址工具类
 * 备注:返回本机IP地址  文件名称 IPUtils.java   与MainActivity并列位置
 * =======================================================
 */
public class IPUtils {

    /**
     * 获取本机IPv4地址
     *
     * @param context
     * @return 本机IPv4地址;null:无网络连接
     */
    public static String getIpAddress(Context context) {
        // 获取WiFi服务
        WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
        // 判断WiFi是否开启
        if (wifiManager.isWifiEnabled()) {
            // 已经开启了WiFi
            WifiInfo wifiInfo = wifiManager.getConnectionInfo();
            int ipAddress = wifiInfo.getIpAddress();
            String ip = intToIp(ipAddress);
            return ip;
        } else {
            // 未开启WiFi
            return getIpAddress();
        }
    }

    private static String intToIp(int ipAddress) {
        return (ipAddress & 0xFF) + "." +
                ((ipAddress >> 8) & 0xFF) + "." +
                ((ipAddress >> 16) & 0xFF) + "." +
                (ipAddress >> 24 & 0xFF);
    }

    /**
     * 获取本机IPv4地址
     *
     * @return 本机IPv4地址;null:无网络连接
     */
    private static String getIpAddress() {
        try {
            NetworkInterface networkInterface;
            InetAddress inetAddress;
            for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
                networkInterface = en.nextElement();
                for (Enumeration<InetAddress> enumIpAddr = networkInterface.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
                    inetAddress = enumIpAddr.nextElement();
                    if (!inetAddress.isLoopbackAddress() && !inetAddress.isLinkLocalAddress()) {
                        return inetAddress.getHostAddress();
                    }
                }
            }
            return null;
        } catch (SocketException ex) {
            ex.printStackTrace();
            return null;
        }
    }
}


<uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
</manifest>

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
我可以为您提供一些参考。您可以使用ESP32-CAM模块和ESP32控制器来实现视频传输。首先,您需要为ESP32-CAM设置摄像头,并使用WiFi连接将图像传输到ESP32。您可以使用Arduino IDE或其他IDE来编写代码。 以下是用于ESP32-CAM和ESP32之间视频传输的简单代码示例: 1.设置ESP32-CAM摄像头 #include "esp_camera.h" void setup() { camera_config_t config; config.ledc_timer = LEDC_TIMER_0; config.ledc_channel = LEDC_CHANNEL_0; config.pin_d0 = 5; config.pin_d1 = 18; config.pin_d2 = 19; config.pin_d3 = 21; config.pin_d4 = 36; config.pin_d5 = 39; config.pin_d6 = 34; config.pin_d7 = 35; config.pin_xclk = 0; config.pin_pclk = 22; config.pin_vsync = 25; config.pin_href = 23; config.pin_sscb_sda = 26; config.pin_sscb_scl = 27; config.pin_pwdn = 32; config.pin_reset = -1; config.xclk_freq_hz = 20000000; config.pixel_format = PIXFORMAT_JPEG; // 初始化图像传感器 esp_err_t err = esp_camera_init(&config); if (err != ESP_OK) { Serial.printf("Camera init failed with error 0x%x", err); return; } } 2.获取图像并将其传输到ESP32 #include "esp_camera.h" #include <WiFi.h> const char* ssid = "your-ssid"; const char* password = "your-password"; const char* streamUrl = "http://your-server/stream"; void setup() { // ... // 初始化Wi-Fi连接 WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi..."); } } void loop() { camera_fb_t * fb = esp_camera_fb_get(); if (!fb) { Serial.println("Failed to capture image"); return; } WiFiClient client; if (!client.connect(streamUrl, 80)) { Serial.println("Connection failed"); return; } // 发送HTTP POST请求 client.println("POST /stream HTTP/1.1"); client.println("Content-Type: image/jpeg"); client.print("Content-Length: "); client.println(fb->len); client.println(); // 发送JPEG格式图像 client.write(fb->buf, fb->len); // 释放摄像头缓存 esp_camera_fb_return(fb); } 3.搭建接收视频的服务器并显示视频 将视频流发送到您自己的服务器之后,您需要设置一个Web服务器来显示视频。您可以使用Node.js和Socket.io来实现这一点。以下是一个简单的Node.js代码示例: var app = require('http').createServer(handler) var io = require('socket.io')(app); var fs = require('fs'); var url = require('url'); app.listen(80); function handler (req, res) { var reqUrl = url.parse(req.url, true); var filePath = '.' + reqUrl.pathname; if (filePath == './') { filePath = './index.html'; } fs.readFile(filePath, function(err, data) { if (err) { res.writeHead(404); return res.end('Error 404: Not Found'); } res.writeHead(200); res.end(data); }); } io.on('connection', function (socket) { console.log('client connected'); socket.on('stream', function(image) { io.emit('stream', image); }); }); 在Web应用程序中,您需要一个可以显示视频的HTML页面。您可以使用canvas标签或video标签来显示视频。将视频流转换为JS图像对象,然后将其绘制在画布上。这里是一个简单的HTML和JavaScript代码示例: <!DOCTYPE html> <html> <head> <title>ESP32 Video Stream</title> </head> <body> <canvas id="canvas"></canvas> <script src="/socket.io/socket.io.js"></script> <script> var canvas = document.getElementById('canvas'); var ctx = canvas.getContext('2d'); var socket = io(); socket.on('connect', function() { console.log('connected'); }); socket.on('stream', function(image) { // 将图像转换为JS图像对象 var img = new Image(); img.onload = function() { // 在画布上绘制图像 ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); }; img.src = 'data:image/jpeg;base64,' + image; }); </script> </body> </html> 希望这些代码对您有所帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值