Android 多媒体之音频_android wmaextractor

还有兄弟不知道网络安全面试可以提前刷题吗?费时一周整理的160+网络安全面试题,金九银十,做网络安全面试里的显眼包!

王岚嵚工程师面试题(附答案),只能帮兄弟们到这儿了!如果你能答对70%,找一个安全工作,问题不大。

对于有1-3年工作经验,想要跳槽的朋友来说,也是很好的温习资料!

【完整版领取方式在文末!!】

93道网络安全面试题

需要体系化学习资料的朋友,可以加我V获取:vip204888 (备注网络安全)

内容实在太多,不一一截图了

黑客学习资源推荐

最后给大家分享一份全套的网络安全学习资料,给那些想学习 网络安全的小伙伴们一点帮助!

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

😝朋友们如果有需要的话,可以联系领取~

1️⃣零基础入门
① 学习路线

对于从来没有接触过网络安全的同学,我们帮你准备了详细的学习成长路线图。可以说是最科学最系统的学习路线,大家跟着这个大的方向学习准没问题。

image

② 路线对应学习视频

同时每个成长路线对应的板块都有配套的视频提供:

image-20231025112050764

2️⃣视频配套工具&国内外网安书籍、文档
① 工具

② 视频

image1

③ 书籍

image2

资源较为敏感,未展示全面,需要的最下面获取

在这里插入图片描述在这里插入图片描述

② 简历模板

在这里插入图片描述

因篇幅有限,资料较为敏感仅展示部分资料,添加上方即可获取👆

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以点击这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  1. 构建AudioTrack对象,并且把PCM的参数传到对象里面
  2. 调用start
  3. 调用write。

另外,其实AudioTrack以外,还有一个Audio系统,在该系统中主要包含三个核心的API,分别是:

  • AudioManager:主要是用来管理Audio系统的。
  • AudioTrack:主要是用来播放声音。
  • AudioRecord:主要是用来录音。
1.1.4 RingtoneManager

Ringtone为铃声、通知和其他类似声音提供快速播放的方法,该种方式播放音频时,还会涉及到一个核心的管理类”RingtoneManager”,该类作为管理类提供系统铃声列表检索方法,并且RingtoneManager可以生成Ringtone实例。具体的Ringtone的使用步骤和相关的方法如下所示:

  • 1、获取Ringtone对象实例:
//1.通过铃声uri获取
static Ringtone getRingtone(Context context, Uri ringtoneUri)
​
//2.通过铃声检索位置获取
Ringtone getRingtone(int position)
  • 2、RingtoneManager中重要的方法:
1. // 两个构造方法
(Activity activity)
RingtoneManager(Context context)
​
2. // 获取指定声音类型(铃声、通知、闹铃等)的默认声音的Uri
static Uri getDefaultUri(int type)
​
3. // 获取系统所有Ringtone的cursor
Cursor getCursor()
​
4. // 获取cursor指定位置的Ringtone uri
Uri getRingtoneUri(int position)
​
5. // 判断指定Uri是否为默认铃声
static boolean isDefault(Uri ringtoneUri)
​
6. //获取指定uri的所属类型
static int getDefaultType(Uri defaultRingtoneUri)
​
7. //将指定Uri设置为指定声音类型的默认声音
static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri)
  
8、//播放
void play() 
9、//停止播放
void stop()

1.1.5 音频及音效的播放总结

经过如上几种音效的播放方式的讲解,我们可以对音效的播放做简单的总结如下所示:

  • 1.对于延迟度要求不高,并且希望能够更全面的控制音乐的播放,MediaPlayer比较适合。
  • 2.声音短小,延迟度小,并且需要几种声音同时播放的场景,适合使用SoundPool。
  • 3.播放大文件音乐,如WAV无损音频和PCM无压缩音频,可使用更底层的播放方式AudioTrack。它支持流式播放,可以读取(可来自本地和网络)音频流,却播放延迟较小。
  • 4、AudioTrack直接支持WAV和PCM,其他音频需要解码成PCM格式才能播放。 .jet的音频比较少见(有的游戏中在使用),可使用专门的播放器JetPlayer播放。
  • 5.对于系统类声音的播放和操作,Ringtone更适合。
二、音频的采集

手机一般都有麦克风和摄像头,而Android系统就可以利用这些硬件来录制音视频了。为了增加对录制音视频的支持,Android系统提供了一个MediaRecorder的类。

与MediaPlayer类非常相似MediaRecorder也有它自己的状态图,MediaRecorder的各个状态介绍如下:

  • Initial:初始化状态。使用new()方法创建MediaRecorder对象或者调用了reset()方法时,该MediaRecorder对象处于Initial状态。
  • Initialized:已初始化状态,在Initial状态调用setAudioSource()或setVideoSource()方法进入该状态。在这个状态可以通过setOutputFormat()方法设置输出格式,此时MediaRecorder转换为DataSourceConfigured状态。另外,通过reset()重新进入Initial状态。
  • DataSourceConfigured:数据源配置状态,这期间可以设定编码方式、输出文件、屏幕旋转、预览显示等等。可以在Initialized状态通过setOutputFormat()方法进入该状态。可以通过prepare()方法到达Prepared状态。
  • Prepared:就绪状态,在DataSourceConfigured状态通过prepare()方法进入该状态。可以通过start()进入录制状态。另外,可以通过reset()方法回到Initialized状态。
  • Recording:录制状态,通过调用start()方法进入该状态。另外,它可以通过stop()方法或reset()方法回到Initial状态。
  • Released:释放状态,可以通过在Initial状态调用release()方法来进入这个状态,这时将会释放所有和MediaRecorder对象绑定的资源。
  • Error:错误状态,当错误发生的时候进入这个状态,它可以通过reset()方法进入Initial状态。

需要说明的是,与MediaPlayer相似,使用MediaRecorder录音录像时需要严格遵守状态函数调用的先后顺序,在不同的状态调用不同的函数,否则会出现异常。如上的文字描述可以转换为如下状态图:

三、Android中多音视频编解码

音视频的原始数据非常庞大,难以存储和传输。要解决音视频数据的存储和传输问题,需要做如下处理:

  • 音视频编码:即对数据进行压缩,音视频数据压缩技术就是音视频编码。编码的目的就是在最小图像或音频信息丢失情况下得到最大的压缩。
  • 音视频解码:解码是相对编码的,其目的是最大限度的还原原始图像或声音信息。
  • 编解码的作用:编解码的意义就是便于数据传输和存储。

而我们知道音视频编解码格式非常多(h264、h265、vp8、vp9、aac、opus……),实现每种编解码都需要引入外部库,导致项目臃肿、包体积过大且运行性能差。

因此Google提出了一套标准,这就是MediaCodec。具体来说,了解MediaCodec可以从以下几个方面来说:

  • 定义:MediaCodec是Google公司专门为Android开发者和芯片厂商搭建的一套用于调用硬件编解码器组件的统一接口,全部遵循该接口规范即可简单的使用,主要的目的在于统一标准。
  • 特点:与常规编解码库相比,MediaCodec具有非常明显的优势,它速度快、效率高、CPU占用率低、内存小、节省包体积。使用MediaCodec可以解决项目臃肿、减小包体积和提升编解码性能。

关于MediaCodec的工作原理,可以参见下图所示:

工作步骤如下所示:

  • MediaCodec处理输入数据后生成输出数据。通过异步方式处理数据,并使用一组输入和输出缓冲区。
  • 输入端:请求一个空的输入缓冲区,用数据填充它并将其发送到编解码器进行处理。
  • 输出端:编解码器处理完数据并将其转换到一个空的输出缓冲区。最后,请求一个已填满的输出缓冲区,使用它的内容并将其释放回编解码器。
可以操作的数据类型

MediaCodec可以对三种数据进行操作,分别是:

  • 编码数据
  • 原始音频数据
  • 原始视频数据
MediaCodec的状态管理

MediaCodec存在三种状态:停止(stoped)、执行(executing)、释放(released)。

  • 停止状态:包含三个子状态:配置(configured)、未初始化(uninitialized)、错误(error)
  • 执行状态:包含三个子状态:刷新(flushed)、运行(running)、结束流(end-of-stream)

MediaCodec 发展

Android系统中关于MediaCodec的介绍,可以参考Android的官方网站提供的信息:MediaCodec  |  Android Developers

MediaCodec 是在 Android 4.1版本(API16 )中出现并可用的,它提供了一种极其原始的接口。MediaCodec类同时存在 Java和C++层中,但是只有前者是公共访问方法。

在Android 4.3 (API18)中,MediaCodec被扩展为通过 Surface 提供输入的方法(通过 createInputSurface方法),允许来自于相机的预览或者是经过OpenGL ES呈现。在该版本中,MediaCodec是第一个过了CTS测试的版本。所谓的CTS,全称是Compatibility Test Suite,主要是google推出的一种设备兼容性测试规范,用来保证不同设备一致的用户体验的规范。

除此之外,4.3版本还引入了 MediaMuxer。MediaMuxer允许将AVC编解码器(原始H.264基本流)的输出转换为.MP4格式,可以和音频流一起转码也可以单独转换。

音视频编辑

MediaCodec通常与MediaExtractor、MediaSync、MediaMuxer、MediaCrypto、MediaDrm、Image、Surface和AudioTrack一起使用,几乎可以实现大部分音视频相关功能。主要的操作步骤如下所示:

  • 1、初始化。
  • 2、MediaExtractor:提取音视频编码数据,MediaExtractor用于对音视频文件解封装,提取出已编码的媒体数据。
  • 3、MediaCodec:使用解码器进行解码。
  • 4、处理:对音视频进行处理。
  • 5、编码:使用MediaCodec编码器对音视频数据编码。
  • 6、合成:MediaMuxer合成音视频文件。MediaMuxer用于封装编码后的音视频数据,目前支持MP4、Webm和3GP文件作为输出。
  • 7、 释放资源。

代码中的体现如下:

- createEncoderByType/createDecoderByType
- configure
- start
- while(true) {
    - dequeueInputBuffer
    - queueInputBuffer
    - dequeueOutputBuffer
    - releaseOutputBuffer
}
- stop
- release
使用MediaCodec编码音频
  • 初始化MediaCodec对象,如下所示:
private static MediaCodec createAudioEncoder() throws IOException {
   MediaCodec codec = MediaCodec.createEncoderByType(AUDIO_MIME);
   MediaFormat format = new MediaFormat();
   format.setString(MediaFormat.KEY_MIME, AUDIO_MIME);
   format.setInteger(MediaFormat.KEY_BIT_RATE, 64000);
   format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
   format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 44100);
   format.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
   codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
   return codec;
 }
  • 读取PCM数据,执行编码操作。
...
while (!sawOutputEOS) {
                if (!sawInputEOS) {
                    inputBufIndex = audioEncoder.dequeueInputBuffer(10_000);
                    if (inputBufIndex >= 0) {
                        ByteBuffer inputBuffer = audioInputBuffers[inputBufIndex];
                        //先清空缓冲区
                        inputBuffer.clear();
                        int bufferSize = inputBuffer.remaining();
                        if (bufferSize != rawInputBytes.length) {
                            rawInputBytes = new byte[bufferSize];
                        }
                        //读取
                        readRawAudioCount = fisRawAudio.read(rawInputBytes);
                        //判断是否到文件的末尾
                        if (readRawAudioCount == -1) {
                            readRawAudioEOS = true;
                        }
                        if (readRawAudioEOS) {
                            audioEncoder.queueInputBuffer(inputBufIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
                            sawInputEOS = true;
                        } else {
                            //放入缓冲区
                            inputBuffer.put(rawInputBytes, 0, readRawAudioCount);
                            rawAudioSize += readRawAudioCount;
                            //放入编码队列
                            audioEncoder.queueInputBuffer(inputBufIndex, 0, readRawAudioCount, audioTimeUs, 0);
                            audioTimeUs = (long) (1_000_000 * ((float) rawAudioSize / AUDIO_BYTES_PER_SAMPLE));
                        }
                    }
                }
​
                //输出端
                outputBufIndex = audioEncoder.dequeueOutputBuffer(outBufferInfo, 10_000);
                if (outputBufIndex >= 0) {
                    // Simply ignore codec config buffers.
                    if ((outBufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
                        Log.i(TAG, "audio encoder: codec config buffer");
                        audioEncoder.releaseOutputBuffer(outputBufIndex, false);
                        continue;
                    }
                    if (outBufferInfo.size != 0) {
                        ByteBuffer outBuffer = audioOutputBuffers[outputBufIndex];
                        outBuffer.position(outBufferInfo.offset);
                        outBuffer.limit(outBufferInfo.offset + outBufferInfo.size);
                        //Log.v(TAG, String.format(" writing audio sample : size=%s , presentationTimeUs=%s", outBufferInfo.size, outBufferInfo.presentationTimeUs));
                        if (lastAudioPresentationTimeUs <= outBufferInfo.presentationTimeUs) {
                            lastAudioPresentationTimeUs = outBufferInfo.presentationTimeUs;
                            int outBufSize = outBufferInfo.size;
                            int outPacketSize = outBufSize + 7;
                            outBuffer.position(outBufferInfo.offset);
                            outBuffer.limit(outBufferInfo.offset + outBufSize);
                            byte[] outData = new byte[outPacketSize];
                            addADTStoPacket(outData, outPacketSize);
                            outBuffer.get(outData, 7, outBufSize);
                            fosAccAudio.write(outData, 0, outData.length);
                            //Log.v(TAG, outData.length + " bytes written.");
                        } else {
                            Log.e(TAG, "error sample! its presentationTimeUs should not lower than before.");
                        }
                    }
                    audioEncoder.releaseOutputBuffer(outputBufIndex, false);
                    if ((outBufferInfo.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
                        sawOutputEOS = true;
                    }
                } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
                    audioOutputBuffers = audioEncoder.getOutputBuffers();
                } else if (outputBufIndex == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                    MediaFormat audioFormat = audioEncoder.getOutputFormat();
                    Log.i(TAG, "format change : " + audioFormat);
                }
            }
...

以上是MediaCodec的编码执行操作。如果是解码,与编码过程相反即可完成。

总结

  • 优点:MediaCodec是Android重要的底层多媒体组件,合理使用MediaCodec可以实现播放器、直播、视频编辑、视频录制、视频通话、视频会议等几乎所有音视频相关的编解码功能,且与常规编解码库相比拥有绝对的性能优势。
  • 不足:MediaCodec也存在一些缺点,兼容性、稳定性都比较差,开发过程中会经常遇到机型、版本等适配问题,这些都可以通过适配合理解决。
四、音频NDK API开发

如果遇到一些要求更高的项目开发,对音频有高性能的需求,比如说:所需的不仅仅是简单的声音播放或录制功能。它们需要响应式实时系统行为。一些典型用例如:音频合成器、电子鼓、音乐学习应用、DJ 混音、音效、视频/音频会议等这类要求特别高的需求时。就要从更深层次的底层来提供功能支持,这里就会用到NDK开发。

首先来了解一下NDK,全称是Native Development Kit,翻译为原生开发工具包,主要的作用是可以让开发者在Android应用中利用C和c++代码的工具,可用以从自己的源代码构建,或者利用现有的预构建库。

本部分的内容可以在如下的Android官方网站中进行查看和学习:高性能音频  |  Android NDK  |  Android Developers

Android官方给提供了如下选择:

  • OpenSL ES:全称为Open Sound Library for Embedded Systems,嵌入式音频加速标准。OpenSL ES是无授权费、跨平台、针对嵌入式系统精心优化的硬件音频加速 API,为嵌入式移动多媒体设备上的本地 应用程序开发者提供了标准化、高性能、低响应时间的音频功能实现方法,同时还实现了软/硬件音频性能的直接跨平台部署,不仅降低了执行难度,而且促进了高级音频市场的发展。
    • 与Android的关系:Android 2.3即API9时开始支持 OpenSL ES 标准,通过 NDK 提供相应的 API 开发接口。Android 实现的OpenSL ES只是OpenSL的子集,然后进行了扩展。
    • Android 中OpenSL ES的相关资料:OpenSL ES  |  Android NDK  |  Android Developers
  • AAudio:在 Android 8.0 版本后引入的音频库 , 该音频库需要使用C语言在Native层进行调用 , 属于NDK开发范畴 。AAudio是OpenSL ES 库的轻量级实现,同样具有低延迟 , 高性能的特点。需要特别注意的是,AAudio作为一款定位为轻量级的音频库,只提供写入音频流进行发音的功能 , 不负责音频设备管理 , 文件 I / O , 音频编解码 等操作 ;
    • 音频输入:从话筒 , 耳机等音频输入设备中 , 使用AAudio音频流采集音频数据 , 读取性能高 , 低延迟 。
    • 音频输出:将音频流写入到 AAudio,以极高性能方式将音频流输出到发音设备中 。
  • Oboe:该库是基于AAudio封装的一个开源库,在github上有开源的地址,链接如下:
     https://github.com/google/oboe该库与AAudio是使用C++编写的适用于Android开发的高效率的音频开发,依然属于NDK开发的范畴。Google官方推荐使用该库。
五、音频算法的开源库
FFmpeg:路人皆知

FFmpeg是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源程序。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec,为了保证高可移植性和编解码质量,libavcodec里很多code都是从头开发的。

只要是做音视频开发的开发者,几乎没有不知道FFmpeg库的。在github上可以找到FFmpeg的主页地址如下: GitHub - FFmpeg/FFmpeg: Mirror of https://git.ffmpeg.org/ffmpeg.git官方网站的地址是:FFmpeg

其中包含的库主要包括:

  • libavcodec:音/视频编码库。
  • libavformat:音视频格式的生成和解析等操作。
  • libavutil:公共的工具函数。

该程序最初在Linux平台上开发和使用,目前在windows、mac上均可以使用。

在Android中使用FFmpeg

如果需要在Android中使用FFmpeg,需要进行集成。需要经过几个步骤:

  • 编译:首先要下载FFmpeg,并进行编译,编译出Android中需要的文件。
  • 将编译后的内容集成到Android项目中。
  • 测试并调用集成的FFmpeg中的方法。
Speex

Speex主要是针对语音的开源免费,无专利保护的一种音频压缩格式,是专门为码率在2-44kbps的语音压缩而设计。Speex的特点主要包括:

  • 窄带(8kHz),宽带(16kHz)和超宽带(32kHz)压缩于同一位流
  • 可变比特率(VBR)
  • 非连续传输(DTX)
  • 感官回声消除(AEC)
  • 噪音屏蔽
Slik

Slik算法主要的作用是实现语音和音频的编解码,其主要的特点是:

  • 支持4种采样率:8KHz、12KHz、16KHz、24KHz;三种复杂度:低、中、高。
  • 编码码率在 6~40kbps。

如何自学黑客&网络安全

黑客零基础入门学习路线&规划

初级黑客
1、网络安全理论知识(2天)
①了解行业相关背景,前景,确定发展方向。
②学习网络安全相关法律法规。
③网络安全运营的概念。
④等保简介、等保规定、流程和规范。(非常重要)

2、渗透测试基础(一周)
①渗透测试的流程、分类、标准
②信息收集技术:主动/被动信息搜集、Nmap工具、Google Hacking
③漏洞扫描、漏洞利用、原理,利用方法、工具(MSF)、绕过IDS和反病毒侦察
④主机攻防演练:MS17-010、MS08-067、MS10-046、MS12-20等

3、操作系统基础(一周)
①Windows系统常见功能和命令
②Kali Linux系统常见功能和命令
③操作系统安全(系统入侵排查/系统加固基础)

4、计算机网络基础(一周)
①计算机网络基础、协议和架构
②网络通信原理、OSI模型、数据转发流程
③常见协议解析(HTTP、TCP/IP、ARP等)
④网络攻击技术与网络安全防御技术
⑤Web漏洞原理与防御:主动/被动攻击、DDOS攻击、CVE漏洞复现

5、数据库基础操作(2天)
①数据库基础
②SQL语言基础
③数据库安全加固

6、Web渗透(1周)
①HTML、CSS和JavaScript简介
②OWASP Top10
③Web漏洞扫描工具
④Web渗透工具:Nmap、BurpSuite、SQLMap、其他(菜刀、漏扫等)
恭喜你,如果学到这里,你基本可以从事一份网络安全相关的工作,比如渗透测试、Web 渗透、安全服务、安全分析等岗位;如果等保模块学的好,还可以从事等保工程师。薪资区间6k-15k

到此为止,大概1个月的时间。你已经成为了一名“脚本小子”。那么你还想往下探索吗?

如果你想要入坑黑客&网络安全,笔者给大家准备了一份:282G全网最全的网络安全资料包评论区留言即可领取!

7、脚本编程(初级/中级/高级)
在网络安全领域。是否具备编程能力是“脚本小子”和真正黑客的本质区别。在实际的渗透测试过程中,面对复杂多变的网络环境,当常用工具不能满足实际需求的时候,往往需要对现有工具进行扩展,或者编写符合我们要求的工具、自动化脚本,这个时候就需要具备一定的编程能力。在分秒必争的CTF竞赛中,想要高效地使用自制的脚本工具来实现各种目的,更是需要拥有编程能力.

如果你零基础入门,笔者建议选择脚本语言Python/PHP/Go/Java中的一种,对常用库进行编程学习;搭建开发环境和选择IDE,PHP环境推荐Wamp和XAMPP, IDE强烈推荐Sublime;·Python编程学习,学习内容包含:语法、正则、文件、 网络、多线程等常用库,推荐《Python核心编程》,不要看完;·用Python编写漏洞的exp,然后写一个简单的网络爬虫;·PHP基本语法学习并书写一个简单的博客系统;熟悉MVC架构,并试着学习一个PHP框架或者Python框架 (可选);·了解Bootstrap的布局或者CSS。

8、超级黑客
这部分内容对零基础的同学来说还比较遥远,就不展开细说了,附上学习路线。
img

网络安全工程师企业级学习路线

img
如图片过大被平台压缩导致看不清的话,评论区点赞和评论区留言获取吧。我都会回复的

视频配套资料&国内外网安书籍、文档&工具

需要体系化学习资料的朋友,可以加我V获取:vip204888 (备注网络安全)

当然除了有配套的视频,同时也为大家整理了各种文档和书籍资料&工具,并且已经帮大家分好类了。

img
一些笔者自己买的、其他平台白嫖不到的视频教程。
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化资料的朋友,可以点击这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值