Android 多媒体之音频_android wmaextractor

  • 音频采集/播放:已有音频如何播放;如何采集一段音频;
  • 音频算法处理:主要包括去噪、静音检测、回声消除、音效处理、功放/增强、混音/分离,等等
  • 音频的编解码和格式转换:不同格式之间的转码操作
  • 音频传输协议的开发:主要包括SIP,A2DP、AVRCP,等等

另外,如果要进行音频开发,需要了解一些音频的概念作为前置知识,一些常见的概念如下所示:

  • SampleRate:采样率,每秒采集声音的数量,它用赫兹(Hz)来表示。采样频率越高,音频质量越好。常用的音频采样频率有:8kHz、16kHz、44.1kHz、48kHz 等。
  • Channel:声道数,表示声音录制时的音源数量或回放时相应的扬声器数量。常用的是单声道(Mono)和双声道(Stereo)。要记住这两个词:Stereo和Mono。
  • BitDepth:采样精度,每个采样点用多少数据量表示,它以位(Bit)为单位。位数越多,表示得就越精细,声音质量自然就越好,当然数据量也越大。常见的位宽是:8bit 或者 16bit。
  • BitRate:比特率,每秒音频占用的比特数量,单位是 bps(Bit Per Second),比特率越高,压缩比越小,声音质量越好,音频体积也越大。
一、音频播放

说到音视频多媒体,首先就有一个概念叫:媒体格式。也就是我们常说的不同格式的音视频文件。在Android这个开放系统平台中,支持的媒体格式还是很丰富的,详细内容如下:

音频格式和编解码器

总结来说,Android中常见的音频压缩格式有:MP3,AAC,OGG,WMA,Opus,FLAC,APE,m4a,AMR,等等。

1.1 音频的播放
1.1.1 MediaPlayer

首先认识两个基础的概念和API:

  • MediaPlayer:用于播放声音和视频的主要 API。Android 多媒体框架支持播放各种常见媒体类型,可以轻松地将音频、视频和图片集成到应用中。可以使用 MediaPlayer API,播放存储在应用资源(原始资源)内的媒体文件、文件系统中的独立文件或者通过网络连接获得的数据流中的音频或视频。
  • AudioManager:该类API用于管理设备上的音频源和音频输出。

另外需要说一下,MediaPlayerl除了能够获取、解码以及播放音频和视频,而且只需很简单的设置即可以外。它还支持多种不同的媒体源,比如:

  • 本地资源:即res目录下的音频资源。
  • URI:比如可能是通过Content Provider解析到的某个资源URI
  • 网络:通过网络,获取流式传输数据进行播放。
使用步骤
  • 1、初始化MediaPlayer对象
  • 2、准备播放工作:准备工作主要是音频数据源的获取或者是音频数据的解码操作等,该过程属于耗时操作,因此需要在工作线程中进行。
  • 3、音频状态管理:在准备工作过后,可以对音频进行播放、暂停等操作。同时需要注意的是MediaPlayer是有状态的,包括:Idle、Initialized、Prepared、Started、Paused、PlaybackCompleted等状态。当在进行状态的切换时,需要注意几个点:
    • ① Started(开始)/Paused(暂停)到Stopped(停止)是单向转换,无法再从Stopped直接转换到Started,需要经历Prepared重新装载才可以重新播放。
    • ② Initialized(初始化)状态需要装载数据才可以进行start()播放,但是如果使用prepareAsync()方法异步准备,需要等待准备完成再开始播放,这里需要使用一个回调方法:setOnPreparedListener(),它会在异步装载完成后调用。
    • ③ End(结束)状态是游离在其他状态之外的,在任何状态皆可切换,一般在不需要继续使用MediaPlayer的时候,才会使用release()回收资源。
    • ④ Error(错误)状态是游离在其他状态之外的,只有在MediaPlayer发生错误的时候才会转换。为了保持应用的用户体验,通常会监听setOnErrorListener()回调方法,它会在MediaPlayer发生错误的时候被回调。
注意事项
  • 1、使用Service播放音频。在使用MediaPlayer播放音频流时,推荐使用一个Service来承载MediaPlayer,而不是直接在Activity里使用。
  • 2、使用唤醒锁。Android系统的功耗设计里,为了节约电池消耗,如果设备处于睡眠状态,系统将试图降低或者关闭一些没设备必须的特性,包括CPU和Wifi硬件。如果是一个后台播放音乐的应用,降低CPU可能导致在后台运行的时候干扰音频的正常播放,关闭Wifi将可能导致网络音频流的获取出现错误。因此为了保证功能的正常使用,我们必须阻止系统关闭服务。可以使用wake locks(唤醒锁),它会告诉系统你正在使用某些功能,这样就可以一直保持该功能处于唤醒状态,即使锁屏无操作也能继续使用。这个锁会在paused和stoped状态下释放。
1.1.2 SoundPool

如果应用程序经常播放密集、急促而又短暂的音效(如游戏音效)那么使用MediaPlayer显得有些不太适合了。因为MediaPlayer存在如下缺点:

  • 1、延时时间较长,且资源占用率高。
  • 2、不支持多个音频同时播放。

Android中除了MediaPlayer播放音频之外还提供了SoundPool来播放音效,SoundPool使用音效池的概念来管理多个短促的音效,例如它可以开始就加载20个音效,以后在程序中按音效的ID进行播放。SoundPool的特点和使用长江如下:

  • 1、主要用于播放一些较短的声音片段。
  • 2、SoundPool对CPU资源占用量低和反应延迟小。
  • 3、SoundPool还支持自行设置声音的品质、音量、 播放比率等参数。

SoundPool的API说明如下:

  • 1、SoundPool(int maxStreams, int streamType, int srcQuality):指定它总共支持多少个声音(也就是池的大小)、声音的品质。该方法属于5.0以下版本使用。
  • 2、SoundPool.Builder:从5.0版本开始使用的是SoundPool.Builder模式。
  • 3、load(Context context, int resld, int priority):从 resld 所对应的资源加载声音。
  • 4、load(FileDescriptor fd, long offset, long length, int priority):加载 fd 所对应的文件的offset开始、长度为length的声音。
  • 5、load(AssetFileDescriptor afd, int priority):从afd 所对应的文件中加载声音。
  • 6、load(String path, int priority):从path 对应的文件去加载声音。
  • 说明:4个load方法中都有一个priority参数,该参数目前还没有任何作用,Android建议将该 参数设为1,保持和未来的兼容性。
  • play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate):指定播放哪个声音,2和3参数的意思是音量,priority指定播放的优先级,数值越大优先级越高;loop用于指定是否循环,0不循环,-1位循环;rate指定播放的比率,可选值为0.5 - 2 ,1为正常比率。
1.1.3 AudioTrack

AudioTrack属于更偏底层的音频播放,在Android的framework层有MediaPlayerService,其内部就是使用了AudioTrack。AudioTrack用于单个音频播放和管理,相比于MediaPlayer具有:精炼、高效的优点。因此,对于AutioTrack可以总结如下:

  • 使用场景:更适合实时产生播放数据的情况,如加密的音频,MediaPlayer是束手无策的,AudioTrack可以处理。
  • 要求:AudioTrack用于播放PCM(PCM无压缩的音频格式)音乐流的回放,如果要播需放其它格式音频,需要相应的解码器,这也是AudioTrack用的比较少的原因,原因在于需要程序开发者自己解码音频。
  • 播放模式:
    • ① AudioTrack播放音频有两种播放模式,一种是静态模式,即加载的数据和资源可以直接全部加载完毕,加载方式简单,效率也比较高。但是如果数据量很大,往往不适合;
    • ② 流模式和网络上播放视频是类似的,即数据是按照一定规律不断地传递给接收方的。音频文件过大 音频属性要求高,比如采样率高、深度大的数据;另外如果音频数据是实时产生的,这种情况就只能用流模式。

使用AudioTrack公有三个步骤:

共有三个步骤:

  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) {


#### 如何成为Android高级架构师!

 架构师必须具备抽象思维和分析的能力,这是你进行系统分析和系统分解的基本素质。只有具备这样的能力,架构师才能看清系统的整体,掌控全局,这也是架构师大局观的形成基础。 **你如何具备这种能力呢?一是来自于经验,二是来自于学习。** 

架构师不仅要具备在问题领域上的经验,也需要具备在软件工程领域内的经验。也就是说,架构师必须能够准确得理解需求,然后用软件工程的思想,把需求转化和分解成可用计算机语言实现的程度。经验的积累是需要一个时间过程的,这个过程谁也帮不了你,是需要你去经历的。

**但是,如果你有意识地去培养,不断吸取前人的经验的话,还是可以缩短这个周期的。这也是我整理架构师进阶此系列的始动力之一。** 

***

##### 成为Android架构师必备知识技能

![](https://img-blog.csdnimg.cn/img_convert/f4be1257330198deddcc5c9c05de51c8.webp?x-oss-process=image/format,png)

##### 对应导图的学习笔记(由阿里P8大牛手写,我负责整理成PDF笔记)

![](https://img-blog.csdnimg.cn/img_convert/b8fff69de5b655008cf004b30d679bcd.webp?x-oss-process=image/format,png)

##### 部分内容展示

**《设计思想解读开源框架》**

* **目录**
  ![](https://img-blog.csdnimg.cn/img_convert/1d3ff9066edacceda7f98574ff3cee22.webp?x-oss-process=image/format,png)
* **热修复设计**
  ![](https://upload-images.jianshu.io/upload_images/22459598-81da28c8be6b8b98.png?imageMogr2/aut

> **本文已被[开源项目:【一线大厂面试真题解析+核心总结学习笔记+最新全套讲解视频+实战项目源码讲义】](https://docs.qq.com/doc/DSmxTbFJ1cmN1R2dB)收录**

o-orient/strip%7CimageView2/2/w/1240)
* **插件化框架设计**
  ![](https://img-blog.csdnimg.cn/img_convert/51b15d2fdf4fe541e87d41c91f73967f.webp?x-oss-process=image/format,png)
  **《360°全方面性能优化》**
  ![](https://img-blog.csdnimg.cn/img_convert/7960f6a1065343474a713e9bcc0db583.webp?x-oss-process=image/format,png)
* **设计思想与代码质量优化**
  ![](https://img-blog.csdnimg.cn/img_convert/6d021bb0432508808a5312163e043133.webp?x-oss-process=image/format,png)
* **程序性能优化**
  ![](https://img-blog.csdnimg.cn/img_convert/067b23b89f2fc5bb7737005264d97040.webp?x-oss-process=image/format,png)

ges/22459598-81da28c8be6b8b98.png?imageMogr2/aut

> **本文已被[开源项目:【一线大厂面试真题解析+核心总结学习笔记+最新全套讲解视频+实战项目源码讲义】](https://docs.qq.com/doc/DSmxTbFJ1cmN1R2dB)收录**

o-orient/strip%7CimageView2/2/w/1240)
* **插件化框架设计**
  [外链图片转存中...(img-SIeNlfw5-1723370345043)]
  **《360°全方面性能优化》**
  [外链图片转存中...(img-Fi4FvXie-1723370345044)]
* **设计思想与代码质量优化**
  [外链图片转存中...(img-rzmVEivF-1723370345044)]
* **程序性能优化**
  [外链图片转存中...(img-3K7hDXgk-1723370345045)]

> **本文已被[CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》](https://docs.qq.com/doc/DSkNLaERkbnFoS0ZF)收录**
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值