基于libusb库和AOA协议实现通过USB与安卓设备通信

流程:

手机通过USB连接到设备时,触发一次插入事件,设备通过VID和PID来判断USB是否为AOA模式

不是AOA模式:则进入切换到AOA模式流程

1.打开usb设备

2.发送AOA协议报文交互

3.关闭usb设备

USB会出现一次逻辑拔插过程:触发一次拔出事件和一次插入事件,手机切换VID和PID为AOA模式

4.USB已切换为AOA模式

5.获取USB接口信息

6.打开USB设备并声明读写接口,建立AOA数据通道

是AOA模式:

1.获取USB接口信息

2.打开USB设备并声明读写接口,建立AOA数据通道

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include "libusb.h"

#define BULK_RECV_EP    0x83
#define BULK_SEND_EP    0x02
#define INT_RECV_EP     0x81
#define INT_SEND_EP     0x01

#define VID_GOOGLE            0x18D1
#define    PID_AOA_ACC            0x2D00
#define    PID_AOA_ACC_ADB        0x2D01
#define    PID_AOA_AU            0x2D02
#define    PID_AOA_AU_ADB        0x2D03
#define    PID_AOA_ACC_AU        0x2D04
#define    PID_AOA_ACC_AU_ADB    0x2D05

static char manufacturer[] = "Lutixia";
static char modelName[] = "Demo";
static char description[] = "Android Aoa Interface"; 
static char version[] = "1.0";
static char uri[] = "https://www.baidu.com/";
static char serialNumber[] = "1234567890";


typedef struct 
{
    unsigned int pid;
    unsigned int vid;
    unsigned char bInEndpointAddress;
    unsigned char bOutEndpointAddress;
    unsigned char bInterfaceNumber;
    libusb_device *dev;
    pthread_mutex_t stLock;
    libusb_device_handle *handle;
    libusb_hotplug_callback_handle hotplugCbh;
    int event;
    int bNumConfigurations;
    //libusb_device **devs;
} usb_dev_mngr_s;

static usb_dev_mngr_s gstUsbMngr;

void usb_error(int code, int line)
{
    fprintf(stdout,"line %d:", line);
    
    switch (code) 
    {
        case LIBUSB_ERROR_IO:
            fprintf(stderr, "LIBUSB_ERROR_IO\n");
            break;
        case LIBUSB_ERROR_INVALID_PARAM:
            fprintf(stderr, "LIBUSB_ERROR_INVALID_PARAM\n");
            break;
        case LIBUSB_ERROR_ACCESS:
            fprintf(stderr, "Error: LIBUSB_ERROR_ACCESS\n");
            break;
        case LIBUSB_ERROR_NO_DEVICE:
            fprintf(stderr,    "LIBUSB_ERROR_NO_DEVICE\n");
            break;
        case LIBUSB_ERROR_NOT_FOUND:
            fprintf(stderr,    "LIBUSB_ERROR_NOT_FOUND\n");
            break;
        case LIBUSB_ERROR_BUSY:
            fprintf(stderr,    "LIBUSB_ERROR_BUSY\n");
            break;
        case LIBUSB_ERROR_TIMEOUT:
            fprintf(stderr,    "LIBUSB_ERROR_TIMEOUT\n");
            break;
        case LIBUSB_ERROR_OVERFLOW:
            fprintf(stderr,    "LIBUSB_ERROR_OVERFLOW\n");
            break;
        case LIBUSB_ERROR_PIPE:
            fprintf(stderr,    "LIBUSB_ERROR_PIPE\n");
            break;
        case LIBUSB_ERROR_INTERRUPTED:
            fprintf(stderr,    "LIBUSB_ERROR_INTERRUPTED\n");
            break;
        case LIBUSB_ERROR_NO_MEM:
            fprintf(stderr,    "LIBUSB_ERROR_NO_MEM\n");
            break;
        case LIBUSB_ERROR_NOT_SUPPORTED:
            fprintf(stderr,    "LIBUSB_ERROR_NOT_SUPPORTED\n");
            break;
        case LIBUSB_ERROR_OTHER:
            fprintf(stderr,    "LIBUSB_ERROR_OTHER\n");
            break;
        default:
            fprintf(stderr,"unkown error\n");
            break;
    }
}

static int usb_getEndpoint(const struct libusb_interface_descriptor * interface, usb_dev_mngr_s* user_device)
{
    int i;
    int ret = 0;
    const struct libusb_endpoint_descriptor *epdesc;
        
    for(i=0; i<interface->bNumEndpoints; i++)
    {
        epdesc = &interface->endpoint[i];
        
        if(epdesc->bmAttributes == LIBUSB_TRANSFER_TYPE_BULK) //transfer type :bulk 
        {
            if(epdesc->bEndpointAddress & LIBUSB_ENDPOINT_IN) // in endpoint
            {  
                printf("EP IN: %02x \n", epdesc->bEndpointAddress);
                   user_device->bInEndpointAddress = epdesc->bEndpointAddress;
            }
            //else if (epdesc->bEndpointAddress & LIBUSB_ENDPOINT_OUT)// out endpoint
            else if (!(epdesc->bEndpointAddress & LIBUSB_ENDPOINT_IN))
            {
                printf("EP OUT: %02x \n", epdesc->bEndpointAddress);
                   user_device->bOutEndpointAddress = epdesc->bEndpointAddress;
            }
        }

        if (user_device->bInEndpointAddress && user_device->bOutEndpointAddress)
        {
            ret = 1;
            break;
        }
    }

    return ret;
}


static int usb_getUSBInfo(usb_dev_mngr_s* user_device)
{
    int rv = -1;
    int i,j,k;
    struct libusb_config_descriptor *conf_desc = NULL;
    const struct libusb_interface *inter;
    const struct libusb_interface_descriptor *interdesc;
    
    for (i = 0; i < user_device->bNumConfigurations; i++)
    {
        if(user_device->dev != NULL)
        {
            if (LIBUSB_SUCCESS != libusb_get_config_descriptor(user_device->dev, i, &conf_desc))
            {
                continue;
            }
        }
        
        for (j = 0; j < conf_desc->bNumInterfaces; j++)
        {
            inter = &conf_desc->interface[j];
            
            for (k=0; k < inter->num_altsetting; k++)
            {
                interdesc = &inter->altsetting[k];
            
                if(interdesc->bInterfaceClass == LIBUSB_CLASS_VENDOR_SPEC && interdesc->bInterfaceSubClass == LIBUSB_CLASS_VENDOR_SPEC)
                {
                    if (user_device->bInEndpointAddress <= 0 || user_device->bOutEndpointAddress <= 0)
                    {
                         if(usb_getEndpoint(interdesc, user_device))
                        {
                            user_device->bInterfaceNumber = interdesc->bInterfaceNumber;
                            rv = 0;
                            break;
                        }
                    }
                }
                else
                {
                    printf("bInterfaceClass:%02x, bInterfaceSubClass:%02x\n", interdesc->bInterfaceClass, interdesc->bInterfaceSubClass);
                }
            }
        }

        libusb_free_config_descriptor(conf_desc);
        conf_desc = NULL;

        if (0 == rv)
        {
            break;
        }
    }

    return rv;
}

//检查安卓设备是否处于Accessory模式
static int usb_checkAccessory(uint16_t idVendor, uint16_t idProduct)
{
    if (idVendor == VID_GOOGLE)
    {
        switch (idProduct)
        {
            case PID_AOA_ACC:
            case PID_AOA_ACC_ADB:
            case PID_AOA_ACC_AU:
            case PID_AOA_ACC_AU_ADB:
                return 1;

            //音频
            case PID_AOA_AU:    
            case PID_AOA_AU_ADB:
                break;
            
            default:
                break;
        }
    }

    return 0;
}

static int usb_sendCtrl(libusb_device_handle *handle, char *buff, int req, int index)
{
    int ret = 0;
    
    if (NULL != buff) 
    {
        ret = libusb_control_transfer(handle, 0x40, req, 0, index, buff, strlen(buff) + 1, 0);
    } 
    else 
    {
        ret = libusb_control_transfer(handle, 0x40, req, 0, index, NULL, 0, 0);
    }

    if (ret < 0) {
        usb_error(ret, __LINE__);
    }

    return ret;
}

static int usb_setupAccessory(libusb_device *dev)
{
    unsigned char ioBuffer[2] = {0};
    int aoaVersion;
    int ret = 0;
    libusb_device_handle *handle = NULL;
    
    ret = libusb_open (dev, &handle);
    if (LIBUSB_SUCCESS != ret) 
    {
        printf("libusb_open failed, ret:%d\n", ret);
        usb_error(ret, __LINE__);
        return -1;
    }

    //如果USB连接到内核驱动则进行 detach
    //libusb_set_auto_detach_kernel_driver(gstUsbMngr.handle, 1);
    if(libusb_kernel_driver_active(handle, 0) > 0)
    {
        printf("kernel driver active, ignoring device");
        if(libusb_detach_kernel_driver(handle, 0) != LIBUSB_SUCCESS) 
        {
            printf("failed to detach kernel driver, ignoring device");
            goto EXIT;;
        }
    }
    
    //发送序号为51的USB报文,获取手机的AOA协议版本,目前为1或2
    ret = libusb_control_transfer(handle, 0xC0, 51, 0, 0, ioBuffer, 2, 1000);
    if (ret < 0) 
    {
        usb_error(ret, __LINE__);
        goto EXIT;
    }

    aoaVersion  = ioBuffer[1] << 8 | ioBuffer[0];
    printf("AOA Verion: %d \n", aoaVersion);
    if (aoaVersion != 1 && aoaVersion != 2)
    {
        goto EXIT;
    }

    ret = -1;
    usleep(1000);

    //发送序号为52的USB报文,涉及制造商、型号、版本、设备描述、序列号、uri
    if (usb_sendCtrl(handle, manufacturer, 52, 0) < 0) {
        goto EXIT;
    }
    if (usb_sendCtrl(handle, modelName, 52, 1) < 0) {
        goto EXIT;
    }
    if (usb_sendCtrl(handle, description, 52, 2) < 0) {
        goto EXIT;
    }
    if (usb_sendCtrl(handle, version, 52, 3) < 0) {
        goto EXIT;
    }
    if (usb_sendCtrl(handle, uri, 52, 4) < 0) {
        goto EXIT;
    }
    if (usb_sendCtrl(handle, serialNumber, 52, 5) < 0) {
        goto EXIT;
    }

    printf("Accessory Identification sent\n");

    //发送序号为53的USB报文,切换USB模式
    if (usb_sendCtrl(handle, NULL, 53, 0) < 0) {
        goto EXIT;
    }

    ret = 0;

EXIT:    
    libusb_close(handle);
    handle = NULL;
    return ret;
}

static int usb_openUSB(void)
{
    int rc = 0;
    
    rc = libusb_open(gstUsbMngr.dev, &gstUsbMngr.handle);
    if (LIBUSB_SUCCESS != rc) 
    {
        return -1;
    }

    //声明读写接口
    rc = libusb_claim_interface(gstUsbMngr.handle, gstUsbMngr.bInterfaceNumber);
    if (LIBUSB_SUCCESS != rc)
    {
        libusb_close(gstUsbMngr.handle);
        gstUsbMngr.handle = NULL;
        return -1;    
    }

    return 0;
}

/*
    不能在usb_hotplugCallback调用usb_handleHotplugArrived和usb_handleHotplugLeft
    否则会出现LIBUSB_ERROR_BUSY
*/
int usb_handleHotplugArrived(void)
{
    int rc = 0;

    printf("===>> usb_handleHotplugArrived \n");
    pthread_mutex_lock(&gstUsbMngr.stLock);

    if (!usb_checkAccessory(gstUsbMngr.vid, gstUsbMngr.pid))//不是AOA模式
    {
        rc = usb_setupAccessory(gstUsbMngr.dev);
        if (rc)
        {
            fprintf (stderr, "usb_setupAccessory Error, rc:%d\n", rc);
            pthread_mutex_unlock(&gstUsbMngr.stLock);
            return -1;
        }
    }
    else    //是AOA模式
    {
        rc = usb_getUSBInfo(&gstUsbMngr);
        if (rc)
        {
            fprintf (stderr, "usb_getUSBInfo Error, rc:%d\n", rc);
            pthread_mutex_unlock(&gstUsbMngr.stLock);
            return -1;
        }

        rc = usb_openUSB();
        if (rc)
        {
            fprintf (stderr, "usb_openUSB Error, rc:%d\n", rc);
            pthread_mutex_unlock(&gstUsbMngr.stLock);
            return -1;
        }
    }
    
    pthread_mutex_unlock(&gstUsbMngr.stLock);
    return 0;
}

int usb_handleHotplugLeft(void)
{    
    if (gstUsbMngr.handle)
    {
        libusb_release_interface (gstUsbMngr.handle, gstUsbMngr.bInterfaceNumber);
        libusb_close (gstUsbMngr.handle);
        gstUsbMngr.handle = NULL;
    }

    return 0;
}

static int LIBUSB_CALL usb_hotplugCallback(libusb_context *ctx, libusb_device *dev, libusb_hotplug_event event, void *user_data)
{
    int rc;
    struct libusb_device_descriptor desc;

    pthread_mutex_lock(&gstUsbMngr.stLock);
    
    rc = libusb_get_device_descriptor(dev, &desc);
    if (LIBUSB_SUCCESS != rc) {
        fprintf (stderr, "Error getting device descriptor\n");
        pthread_mutex_unlock(&gstUsbMngr.stLock);
        return -1;
    }

    if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED)//插入
    {
        if (0x1d6b == desc.idVendor && 0x0002 == desc.idProduct) //过滤设备本身存在的usb设备
        {
            pthread_mutex_unlock(&gstUsbMngr.stLock);
            return 0;
        }
        
        printf ("Device attached: %04x:%04x\n", desc.idVendor, desc.idProduct);

        gstUsbMngr.vid = desc.idVendor;
        gstUsbMngr.pid = desc.idProduct;
        gstUsbMngr.bInEndpointAddress = 0;
        gstUsbMngr.bOutEndpointAddress = 0;
        gstUsbMngr.bNumConfigurations = desc.bNumConfigurations;
        gstUsbMngr.dev = dev;
        gstUsbMngr.event = event;
        
    }
    else if (event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT)//拔出
    {
        printf ("Device detached: %04x:%04x\n", desc.idVendor, desc.idProduct);
        gstUsbMngr.event = event;
    }
    else
    {
        printf("event wrong, event: %d \n", event);        
        gstUsbMngr.event = 0;
    }
    
    pthread_mutex_unlock(&gstUsbMngr.stLock);
    return 0;
}

//监听事件
void *usb_eventMonitorProcess(void *args)
{
    int rc = 0;
    
    while(1)
    {
        //处理事件
        if (gstUsbMngr.event == LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED)
        {
            usb_handleHotplugArrived();
            gstUsbMngr.event = 0;
        }
        else if (gstUsbMngr.event == LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT)
        {
            usb_handleHotplugLeft();
            gstUsbMngr.event = 0;
        }

        //事件循环(阻塞),读写USB会触发
        rc = libusb_handle_events(NULL);
        if (rc < 0)
        {
            printf("libusb_handle_events() failed: %s\n", libusb_error_name(rc));
        }
    }    
    
    return NULL;
}

int usb_write(char *buf, int bufLen)
{
    int rc  = 0;
    int nActualBytes = 0;
    
    pthread_mutex_lock(&gstUsbMngr.stLock);
    
    if (NULL == gstUsbMngr.handle || 0 != gstUsbMngr.event)
    {
        pthread_mutex_unlock(&gstUsbMngr.stLock);
        return -1;
    }
    
    rc = libusb_bulk_transfer(gstUsbMngr.handle, gstUsbMngr.bOutEndpointAddress, (unsigned char *)buf, bufLen, &nActualBytes, 1000);
    if (rc < 0)
    {
        printf("libusb_bulk_transfer(0x01) write failed:[%s] \n", libusb_strerror(rc));
        pthread_mutex_unlock(&gstUsbMngr.stLock);
        return rc;
    }
    
    pthread_mutex_unlock(&gstUsbMngr.stLock);
    return nActualBytes;
}

int usb_read(char *buf, int bufLen)
{
    int rc  = 0;
    int nActualBytes = 0; 
        
    pthread_mutex_lock(&gstUsbMngr.stLock);
    
    if (NULL == gstUsbMngr.handle || 0 != gstUsbMngr.event)
    {
        pthread_mutex_unlock(&gstUsbMngr.stLock);
        return -1;
    }

    rc = libusb_bulk_transfer(gstUsbMngr.handle, gstUsbMngr.bInEndpointAddress, (unsigned char *)buf, bufLen, &nActualBytes, 1000);
    if (rc < 0)
    {
        //printf("libusb_bulk_transfer(0x81) read failed:[%s] \n", libusb_strerror(rc));
        pthread_mutex_unlock(&gstUsbMngr.stLock);
        return rc;
    }
    
    pthread_mutex_unlock(&gstUsbMngr.stLock);
    return nActualBytes;
}

//测试读写
void *usb_readProcess(void *args)
{
    int rc = 0;
    char readBuf[4*1024] = {0};
    int nActualBytes = 0;
        
    while (1)
    {
        if (NULL == gstUsbMngr.handle)
        {
            usleep(100*1000);
            continue;
        }
        
        rc = usb_read(readBuf, sizeof(readBuf));
        if (rc > 0)
        {
            usb_write(readBuf, rc);
        }    
    }    
}

int usb_init(void)
{
    int product_id, vendor_id, class_id;
    int rc;
    pthread_t tid;
    
    vendor_id = LIBUSB_HOTPLUG_MATCH_ANY;
    product_id = LIBUSB_HOTPLUG_MATCH_ANY;
    class_id = LIBUSB_HOTPLUG_MATCH_ANY;

    memset(&gstUsbMngr, 0x0, sizeof(usb_dev_mngr_s));
    
    rc = pthread_mutex_init(&gstUsbMngr.stLock, NULL);
    if (rc)
    {
        printf("pthread_mutex_init failed \n");
        return EXIT_FAILURE;
    }    

    rc = libusb_init (NULL);
    if (rc < 0)
    {
        printf("failed to initialise libusb: %s\n", libusb_error_name(rc));
        return EXIT_FAILURE;
    }
    
    //获取当前库是否支持热插拔
    if (!libusb_has_capability (LIBUSB_CAP_HAS_HOTPLUG)) {
        printf("Hotplug capabilities are not supported on this platform\n");
        libusb_exit (NULL);
        return EXIT_FAILURE;
    }
    
    //注册热插拔事件回调
    rc = libusb_hotplug_register_callback (NULL, LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, LIBUSB_HOTPLUG_ENUMERATE, vendor_id,
        product_id, class_id, usb_hotplugCallback, NULL, &gstUsbMngr.hotplugCbh);
    if (LIBUSB_SUCCESS != rc) {
        printf("libusb_hotplug_register_callback failed \n");
        libusb_exit (NULL);
        return EXIT_FAILURE;
    }
    
    //开启事件处理线程
    pthread_create(&tid, NULL, usb_eventMonitorProcess, NULL);
    
    return LIBUSB_SUCCESS;
}

int usb_deinit(void)
{
    if (gstUsbMngr.handle) {
        libusb_release_interface(gstUsbMngr.handle, gstUsbMngr.bInterfaceNumber);
        libusb_close (gstUsbMngr.handle);
        gstUsbMngr.handle = NULL;
    }
    
    if (0 < gstUsbMngr.hotplugCbh) {
        libusb_hotplug_deregister_callback(NULL, gstUsbMngr.hotplugCbh);        
    }
    
    pthread_mutex_destroy(&gstUsbMngr.stLock);  
    libusb_exit (NULL);
    return LIBUSB_SUCCESS;
}

int main(int argc, char *argv[])
{
    int rc;
    pthread_t tid;
        
    rc = usb_init();
    if (LIBUSB_SUCCESS != rc)
    {
        return -1;
    }
    
    pthread_create(&tid, NULL, usb_readProcess, NULL);
    
    while (1)
    {
        sleep(1);
    }    

    usb_deinit();
    return EXIT_SUCCESS;
}

参考:

libusb API说明:

https://www.cnblogs.com/kwinwei/p/14437975.html

libusb 官方API说明:

https://libusb.sourceforge.io/api-1.0/libusb_api.html

AOA协议:

https://www.taodocs.com/p-753985804.html

  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: libusb是一款开源的USB设备访问,用于在用户空间下与USB设备进行通信,它支持设备的(初始化、发送控制传输命令、批量传输、中断传输、异步传输),不仅支持在Linux平台上使用,还支持在Windows、MAC OS X等平台上使用。对于具有以下情况的开发者只能使用libusb进行开发:需要快速开发应用程序;需要具有跨平台的移植性;需要在用户空间与USB设备进行通信;需要使用USB的批量传输、中断传输、异步传输等传输方式;需要控制USB设备的状态信息。 使用libusb需要先安装libusb-1.0的开发包,在使用函数前需要先调用libusb_init()函数对进行初始化,获取设备列表可通过调用libusb_get_device_list()函数,获取设备描述符可通过libusb_get_device_descriptor()函数。对于USB通信可通过libusb_control_transfer()函数进行设备控制传输。同时还有异步传输和同步传输两种方法,可通过回调函数获取传输状态。 libusb的优点是它是跨平台的,能够支持不同操作系统和编译平台。使用libusb的开发者可以轻松地在不同的操作系统中进行开发和调试。同时,由于使用了libusb,将不再需要内核模块支持,它可以帮助开发者轻松地实现USB设备协议规范和必要的数据处理,从而更加高效地进行开发。不过需要注意的是,由于libusb在进行USB通信时需要进行复杂的数据传输协议和校验,因此其编程难度较大,需要开发者拥有较好的编程基础。 ### 回答2: libusb是一个跨平台的开源用户空间USB,可用于在Linux、Windows、macOS等不同操作系统中进行USB通信。它提供了丰富的API,可以用于设备的发现、配置、数据传输和控制等方面。 使用libusb进行USB通信需要首先初始化,并对设备进行枚举,从而获得正确的设备句柄。之后可以通过读/写数据端点来进行数据传输,还可以进行控制传输,调整设备配置,发送控制命令等操作。在传输数据时,可以设置超时时间以避免阻塞。 与其他USB相比,libusb具有跨平台,轻量级,可移植性高等优点。它可以使用自由软件协议开源自由使用,而且易于添加到项目中。在开发USB设备应用程序时,使用libusb可以大大提高开发效率,并降低开发难度。 总之,libusb是一个功能强大的USB通信,适用于各种应用领域,它为软件开发人员提供了接口,使得USB设备与计算机之间的通信变得更加简单和高效,而且易于移植和开发。 ### 回答3: libusb是一个可移植且独立于操作系统的应用程序接口,主要用于在用户空间进行USB通信。该支持操作系统如Windows、Linux、Mac OS、FreeBSD等,同时也支持USB设备的各种功能,例如控制传输、批量传输、中断传输和同步传输等。 通过使用这个,应用程序就能够直接访问USB设备,无需编写复杂的驱动程序,从而简化了开发流程,并提高了应用程序的可移植性和可维护性。 使用libusb时,我们可以使用其API来设置USB设备的传输方式、检测USB设备的插拔状态、请求和接收数据等操作。 另外,该还提供了对USB控制请求和端点映射的支持,并允许应用程序与多个USB设备进行通信。 需要注意的是,在使用libusb时,需要设置USB设备的访问权限,以避免权限访问限制导致的访问失败等问题。同时,在进行数据传输时也需要遵循USB传输协议,如控制传输时需要按照设备描述符、配置描述符等协议进行相关的控制。 总之,使用libusbUSB通信可以方便地实现USB设备的控制和数据传输,并且能够跨平台使用,具有良好的可移植性和可维护性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值