Android多媒体七:音乐的特效处理

Android提供了用于音乐播放时的音效控制器,比如均衡器、重低音以及显示音乐波形等。这些功能被定义在AudioEffect的子类中完成1:

AcousticEchoCanceler:回声消除器
AutomaticGainControl:自动增强控制器
NoiseSuppressor:噪音抑制器
BassBoost:重低音调节器
Equalizer:均衡器
PresetReverb:预设音场控制器
Visualizer:示波器

实例:音效控制器
在这里插入图片描述

代码实现:

com/example/administrator/Permission/PermissionManager.java
		package com.example.administrator.Permission;

import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;

import java.util.concurrent.ConcurrentHashMap;

/**
 * @time 2017/2/20
 * @fuction 动态权限管理封装类
 * @use 1. 在BaseActivity和BaseFragment重写onRequestPermissionsResult ,并实现如下代码PermissionManager.onRequestResult(requestCode, permissions, grantResults);
 * 2. 然后需要请求权限的时候调用requestPermission方法 即可
 */
public class PermissionManager {

    //维护的每个Activity的申请权限的监听
    //便于清除释放
    private static ConcurrentHashMap<Integer, PermissionsResultListener> mListenerMap = new ConcurrentHashMap<>();

    /**
     * 权限申请
     *
     * @param context             Activity
     * @param desc                再次申请权限的提示语
     * @param requestCode
     * @param permissionsListener
     * @param permissions
     */
    public static void requestPermission(Activity context,
                                         String desc,
                                         int requestCode,
                                         PermissionsResultListener permissionsListener,
                                         String... permissions) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            mListenerMap.put(requestCode, permissionsListener);
            if (checkEachSelfPermission(context, permissions)) {
                requestEachPermission(context, desc, permissions, requestCode);
            } else {
                mListenerMap.get(requestCode).onPermissionGranted(requestCode);
            }
        }
    }

    /**
     * 权限申请
     *
     * @param context             Fragment
     * @param desc                再次申请权限的提示语
     * @param requestCode
     * @param permissionsListener
     * @param permissions
     */
    public static void requestPermission(Fragment context,
                                         String desc,
                                         int requestCode,
                                         PermissionsResultListener permissionsListener,
                                         String... permissions) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            mListenerMap.put(requestCode, permissionsListener);

        if (checkEachSelfPermission(context.getActivity(), permissions)) {
            requestEachPermission(context, desc, permissions, requestCode);
        } else {
            mListenerMap.get(requestCode).onPermissionGranted(requestCode);
        }
    }
}


/**
 * 权限请求
 *
 * @param context     Activity
 * @param desc        再次申请权限的提示语
 * @param permissions
 * @param requestCode
 */
private static void requestEachPermission(final Activity context, String desc, final String[] permissions, final int requestCode) {
    if (shouldShowRequestPermissionRationale(context, permissions)) {
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle("权限申请")
                .setMessage(desc)
                .setPositiveButton("确认", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        ActivityCompat.requestPermissions(context, permissions, requestCode);
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        dialogInterface.dismiss();
                    }
                })
                .setCancelable(false)
                .show();
    } else {
        ActivityCompat.requestPermissions(context, permissions, requestCode);
    }
}

/**
 * 权限请求
 *
 * @param context     Fragment
 * @param desc        再次申请权限的提示语
 * @param permissions
 * @param requestCode
 */
private static void requestEachPermission(final Fragment context, String desc, final String[] permissions, final int requestCode) {
    if (shouldShowRequestPermissionRationale(context, permissions)) {
        AlertDialog.Builder builder = new AlertDialog.Builder(context.getActivity());
        builder.setTitle("权限申请")
                .setMessage(desc)
                .setPositiveButton("确认", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        context.requestPermissions(permissions, requestCode);
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        dialogInterface.dismiss();
                    }
                })
                .setCancelable(false)
                .show();
    } else {
        context.requestPermissions(permissions, requestCode);
    }
}

/**
 * 再次申请权限时,是否需要声明
 *
 * @param context     Activity
 * @param permissions
 * @return
 */
private static boolean shouldShowRequestPermissionRationale(Activity context, String[] permissions) {
    for (String permission : permissions) {
        if (ActivityCompat.shouldShowRequestPermissionRationale(context, permission)) {
            return true;
        }
    }
    return false;
}


/**
 * 再次申请权限时,是否需要声明
 *
 * @param context     Fragment
 * @param permissions
 * @return
 */
private static boolean shouldShowRequestPermissionRationale(Fragment context, String[] permissions) {
    for (String permission : permissions) {
        if (context.shouldShowRequestPermissionRationale(permission)) {
            return true;
        }
    }
    return false;
}


/**
 * 检察每个权限是否申请
 *
 * @param permissions
 * @return true 需要申请权限,false 已申请权限
 */
private static boolean checkEachSelfPermission(Context context, String[] permissions) {
    for (String permission : permissions) {
        if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
            return true;
        }
    }
    return false;
}

/**
 * 权限申请处理回调
 * 写在Activity或者Fragment的onRequestPermissionsResult 方法内
 *
 * @param requestCode
 * @param permissions
 * @param grantResults
 */
public static void onRequestResult(int requestCode,
                                   @NonNull String[] permissions,
                                   @NonNull int[] grantResults) {
    PermissionsResultListener permissionsResultListener = mListenerMap.get(requestCode);
    if (permissionsResultListener == null) {
        return;
    }
    if (grantResults.length > 0 &&
            grantResults[0] == PackageManager.PERMISSION_GRANTED) {
        permissionsResultListener.onPermissionGranted(requestCode);
    } else {
        permissionsResultListener.onPermissionDenied(requestCode);
    }
}

/**
 * 清除掉已用完的Listner
 */
public static void clearListner(int requestCode) {
    if (mListenerMap.get(requestCode) != null) {
        mListenerMap.remove(requestCode);
    }
}


public interface PermissionsResultListener {

    /**
     * 权限申请成功回调
     */
    void onPermissionGranted(int requestCode);

    /**
     * 权限申请失败回调
     */
    void onPermissionDenied(int requestCode);
}

/**
 * 危险权限 授权一个就等于同组都授权了
 * Manifest.permission.XXX
 *
 * group:android.permission-group.CONTACTS
 permission:android.permission.WRITE_CONTACTS
 permission:android.permission.GET_ACCOUNTS
 permission:android.permission.READ_CONTACTS

 group:android.permission-group.PHONE
 permission:android.permission.READ_CALL_LOG
 permission:android.permission.READ_PHONE_STATE
 permission:android.permission.CALL_PHONE
 permission:android.permission.WRITE_CALL_LOG
 permission:android.permission.USE_SIP
 permission:android.permission.PROCESS_OUTGOING_CALLS
 permission:com.android.voicemail.permission.ADD_VOICEMAIL

 group:android.permission-group.CALENDAR
 permission:android.permission.READ_CALENDAR
 permission:android.permission.WRITE_CALENDAR

 group:android.permission-group.CAMERA
 permission:android.permission.CAMERA

 group:android.permission-group.SENSORS
 permission:android.permission.BODY_SENSORS

 group:android.permission-group.LOCATION
 permission:android.permission.ACCESS_FINE_LOCATION
 permission:android.permission.ACCESS_COARSE_LOCATION

 group:android.permission-group.STORAGE
 permission:android.permission.READ_EXTERNAL_STORAGE
 permission:android.permission.WRITE_EXTERNAL_STORAGE

 group:android.permission-group.MICROPHONE
 permission:android.permission.RECORD_AUDIO

 group:android.permission-group.SMS
 permission:android.permission.READ_SMS
 permission:android.permission.RECEIVE_WAP_PUSH
 permission:android.permission.RECEIVE_MMS
 permission:android.permission.RECEIVE_SMS
 permission:android.permission.SEND_SMS
 permission:android.permission.READ_CELL_BROADCASTS
 */

}

com/example/administrator/MainActivity.java

package com.example.administrator;

import java.util.ArrayList;
import java.util.List;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.audiofx.BassBoost;
import android.media.audiofx.Equalizer;
import android.media.audiofx.PresetReverb;
import android.media.audiofx.Visualizer;
import android.os.Bundle;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;

import com.example.administrator.Permission.PermissionManager;

public class MainActivity extends Activity implements PermissionManager.PermissionsResultListener{
    // 定义播放声音的MediaPlayer
    private MediaPlayer mPlayer;
    // 定义系统的频谱
    private Visualizer mVisualizer;
    // 定义系统的均衡器
    private Equalizer mEqualizer;
    // 定义系统的重低音控制器
    private BassBoost mBass;
    // 定义系统的预设音场控制器
    private PresetReverb mPresetReverb;
    private LinearLayout layout;
    private List<Short> reverbNames = new ArrayList<Short>();
    private List<String> reverbVals = new ArrayList<String>();

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //设置音频流 - STREAM_MUSIC:音乐回放即媒体音量
    setVolumeControlStream(AudioManager.STREAM_MUSIC);
    layout = new LinearLayout(this);//代码创建布局
    layout.setOrientation(LinearLayout.VERTICAL);//设置为线性布局-上下排列
    setContentView(layout);//将布局添加到 Activity
    PermissionManager.requestPermission(this, "音场效果必要的权限", 1, this, Manifest.permission.RECORD_AUDIO, Manifest.permission.MODIFY_AUDIO_SETTINGS);
    // 创建MediaPlayer对象,并添加音频
    // 音频路径为  res/raw/beautiful.mp3
    mPlayer = MediaPlayer.create(this, R.raw.bomb);
    // 初始化示波器
    setupVisualizer();
    // 初始化均衡控制器
    setupEqualizer();
    // 初始化重低音控制器
    setupBassBoost();
    // 初始化预设音场控制器
    setupPresetReverb();
    // 开发播放音乐
    mPlayer.start();
}

/**
 * 初始化频谱
 */
private void setupVisualizer() {
    // 创建MyVisualizerView组件,用于显示波形图
    final MyVisualizerView mVisualizerView = new MyVisualizerView(this);
    mVisualizerView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) (120f * getResources().getDisplayMetrics().density)));
    // 将MyVisualizerView组件添加到layout容器中
    layout.addView(mVisualizerView);
    // 以MediaPlayer的AudioSessionId创建Visualizer
    // 相当于设置Visualizer负责显示该MediaPlayer的音频数据
    mVisualizer = new Visualizer(mPlayer.getAudioSessionId());
    //设置需要转换的音乐内容长度,专业的说这就是采样,该采样值一般为2的指数倍,如64,128,256,512,1024。
    mVisualizer.setCaptureSize(Visualizer.getCaptureSizeRange()[1]);
    // 为mVisualizer设置监听器
    /*
     * Visualizer.setDataCaptureListener(OnDataCaptureListener listener, int rate, boolean waveform, boolean fft
     *
     *      listener,表监听函数,匿名内部类实现该接口,该接口需要实现两个函数
            rate, 表示采样的周期,即隔多久采样一次,联系前文就是隔多久采样128个数据
            iswave,是波形信号
            isfft,是FFT信号,表示是获取波形信号还是频域信号

     */
    mVisualizer.setDataCaptureListener(new Visualizer.OnDataCaptureListener() {
        //这个回调应该采集的是快速傅里叶变换有关的数据
        @Override
        public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate) {
        }

        //这个回调应该采集的是波形数据
        @Override
        public void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate) {
            // 用waveform波形数据更新mVisualizerView组件
            mVisualizerView.updateVisualizer(waveform);
        }
    }, Visualizer.getMaxCaptureRate() / 2, true, false);
    mVisualizer.setEnabled(true);
}

/**
 * 初始化均衡控制器
 */
private void setupEqualizer() {
    // 以MediaPlayer的AudioSessionId创建Equalizer
    // 相当于设置Equalizer负责控制该MediaPlayer
    mEqualizer = new Equalizer(0, mPlayer.getAudioSessionId());
    // 启用均衡控制效果
    mEqualizer.setEnabled(true);
    TextView eqTitle = new TextView(this);
    eqTitle.setText("均衡器");
    layout.addView(eqTitle);
    // 获取均衡控制器支持最小值和最大值
    final short minEQLevel = mEqualizer.getBandLevelRange()[0];//第一个下标为最低的限度范围
    short maxEQLevel = mEqualizer.getBandLevelRange()[1];  // 第二个下标为最高的限度范围
    // 获取均衡控制器支持的所有频率
    short brands = mEqualizer.getNumberOfBands();
    for (short i = 0; i < brands; i++) {
        TextView eqTextView = new TextView(this);
        // 创建一个TextView,用于显示频率
        eqTextView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        eqTextView.setGravity(Gravity.CENTER_HORIZONTAL);
        // 设置该均衡控制器的频率
        eqTextView.setText((mEqualizer.getCenterFreq(i) / 1000) + "Hz");
        layout.addView(eqTextView);
        // 创建一个水平排列组件的LinearLayout
        LinearLayout tmpLayout = new LinearLayout(this);
        tmpLayout.setOrientation(LinearLayout.HORIZONTAL);
        // 创建显示均衡控制器最小值的TextView
        TextView minDbTextView = new TextView(this);
        minDbTextView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        // 显示均衡控制器的最小值
        minDbTextView.setText((minEQLevel / 100) + "dB");
        // 创建显示均衡控制器最大值的TextView
        TextView maxDbTextView = new TextView(this);
        maxDbTextView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
        // 显示均衡控制器的最大值
        maxDbTextView.setText((maxEQLevel / 100) + "dB");
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        layoutParams.weight = 1;
        // 定义SeekBar做为调整工具
        SeekBar bar = new SeekBar(this);
        bar.setLayoutParams(layoutParams);
        bar.setMax(maxEQLevel - minEQLevel);
        bar.setProgress(mEqualizer.getBandLevel(i));
        final short brand = i;
        // 为SeekBar的拖动事件设置事件监听器
        bar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                // 设置该频率的均衡值
                mEqualizer.setBandLevel(brand, (short) (progress + minEQLevel));
            }

            @Override
            public void onStartTrackingTouch(SeekBar seekBar) {
            }

            @Override
            public void onStopTrackingTouch(SeekBar seekBar) {
            }
        });
        // 使用水平排列组件的LinearLayout“盛装”3个组件
        tmpLayout.addView(minDbTextView);
        tmpLayout.addView(bar);
        tmpLayout.addView(maxDbTextView);
        // 将水平排列组件的LinearLayout添加到myLayout容器中
        layout.addView(tmpLayout);
    }
}

/**
 * 初始化重低音控制器
 */
private void setupBassBoost() {
    // 以MediaPlayer的AudioSessionId创建BassBoost
    // 相当于设置BassBoost负责控制该MediaPlayer
    mBass = new BassBoost(0, mPlayer.getAudioSessionId());
    // 设置启用重低音效果
    mBass.setEnabled(true);
    TextView bbTitle = new TextView(this);
    bbTitle.setText("重低音:");
    layout.addView(bbTitle);
    // 使用SeekBar做为重低音的调整工具
    SeekBar bar = new SeekBar(this);
    // 重低音的范围为0~1000
    bar.setMax(1000);
    bar.setProgress(0);
    // 为SeekBar的拖动事件设置事件监听器
    bar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
        @Override
        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
            // 设置重低音的强度
            mBass.setStrength((short) progress);
        }

        @Override
        public void onStartTrackingTouch(SeekBar seekBar) {
        }

        @Override
        public void onStopTrackingTouch(SeekBar seekBar) {
        }
    });
    layout.addView(bar);
}

/**
 * 初始化预设音场控制器
 */
private void setupPresetReverb() {
    // 以MediaPlayer的AudioSessionId创建PresetReverb
    // 相当于设置PresetReverb负责控制该MediaPlayer
    mPresetReverb = new PresetReverb(0, mPlayer.getAudioSessionId());
    // 设置启用预设音场控制
    mPresetReverb.setEnabled(true);
    TextView prTitle = new TextView(this);
    prTitle.setText("音场");
    layout.addView(prTitle);
    // 获取系统支持的所有预设音场
    for (short i = 0; i < mEqualizer.getNumberOfPresets(); i++) {
        reverbNames.add(i);
        reverbVals.add(mEqualizer.getPresetName(i));
    }
    // 使用Spinner做为音场选择工具
    Spinner sp = new Spinner(this);
    sp.setAdapter(new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_spinner_item, reverbVals));
    // 为Spinner的列表项选中事件设置监听器
    sp.setOnItemSelectedListener(new Spinner.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
            // 设定音场
            mPresetReverb.setPreset(reverbNames.get(arg2));
        }

        @Override
        public void onNothingSelected(AdapterView<?> arg0) {
        }
    });
    layout.addView(sp);
}

@Override
protected void onPause() {
    super.onPause();
    if (isFinishing() && mPlayer != null) {
        // 释放所有对象
        mVisualizer.release();
        mEqualizer.release();
        mPresetReverb.release();
        mBass.release();
        mPlayer.release();
        mPlayer = null;
    }
}

/**
 * 根据Visualizer传来的数据动态绘制波形效果,分别为:
 * 块状波形、柱状波形、曲线波形
 */
private static class MyVisualizerView extends View {
    // bytes数组保存了波形抽样点的值
    private byte[] bytes;
    private float[] points;
    private Paint paint = new Paint();
    private Rect rect = new Rect();
    private byte type = 0;

    public MyVisualizerView(Context context) {
        super(context);
        bytes = null;
        // 设置画笔的属性
        paint.setStrokeWidth(1f);
        paint.setAntiAlias(true);//抗锯齿
        paint.setColor(Color.YELLOW);//画笔颜色
        paint.setStyle(Style.FILL);
    }

    public void updateVisualizer(byte[] ftt) {
        bytes = ftt;
        // 通知该组件重绘自己。
        invalidate();
    }

    @Override
    public boolean onTouchEvent(MotionEvent me) {
        // 当用户触碰该组件时,切换波形类型
        if (me.getAction() != MotionEvent.ACTION_DOWN) {
            return false;
        }
        type++;
        if (type >= 3) {
            type = 0;
        }
        return true;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (bytes == null) {
            return;
        }
        // 绘制白色背景
        canvas.drawColor(Color.WHITE);
        // 使用rect对象记录该组件的宽度和高度
        rect.set(0, 0, getWidth(), getHeight());
        switch (type) {
            // -------绘制块状的波形图-------
            case 0:
                for (int i = 0; i < bytes.length - 1; i++) {
                    float left = getWidth() * i / (bytes.length - 1);
                    // 根据波形值计算该矩形的高度
                    float top = rect.height() - (byte) (bytes[i + 1] + 128) * rect.height() / 128;
                    float right = left + 1;
                    float bottom = rect.height();
                    canvas.drawRect(left, top, right, bottom, paint);
                }
                break;
            // -------绘制柱状的波形图(每隔18个抽样点绘制一个矩形)-------
            case 1:
                for (int i = 0; i < bytes.length - 1; i += 18) {
                    float left = rect.width() * i / (bytes.length - 1);
                    // 根据波形值计算该矩形的高度
                    float top = rect.height() - (byte) (bytes[i + 1] + 128) * rect.height() / 128;
                    float right = left + 6;
                    float bottom = rect.height();
                    canvas.drawRect(left, top, right, bottom, paint);
                }
                break;
            // -------绘制曲线波形图-------
            case 2:
                // 如果point数组还未初始化
                if (points == null || points.length < bytes.length * 4) {
                    points = new float[bytes.length * 4];
                }
                for (int i = 0; i < bytes.length - 1; i++) {
                    // 计算第i个点的x坐标
                    points[i * 4] = rect.width() * i / (bytes.length - 1);
                    // 根据bytes[i]的值(波形点的值)计算第i个点的y坐标
                    points[i * 4 + 1] = (rect.height() / 2) + ((byte) (bytes[i] + 128)) * 128 / (rect.height() / 2);
                    // 计算第i+1个点的x坐标
                    points[i * 4 + 2] = rect.width() * (i + 1) / (bytes.length - 1);
                    // 根据bytes[i+1]的值(波形点的值)计算第i+1个点的y坐标
                    points[i * 4 + 3] = (rect.height() / 2) + ((byte) (bytes[i + 1] + 128)) * 128 / (rect.height() / 2);
                }
                // 绘制波形曲线
                canvas.drawLines(points, paint);
                break;
        }
    }
}

@Override
public void onPermissionGranted(int requestCode) {
    Toast.makeText(this, "申请权限成功", Toast.LENGTH_SHORT).show();
}

@Override
public void onPermissionDenied(int requestCode) {
    Toast.makeText(this, "申请权限失败", Toast.LENGTH_SHORT).show();
}

}

添加权限:

<!-- 使用音场效果必要的权限 -->
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS/"/>
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值