在开发上,习惯的将音频、视频功能的使用称之为多媒体,实际上如果讲的宽泛一些的话,相机的使用,比如拍照,录制视频等,也可以划分到多媒体的范畴里面。
从本节课开始,我们就来看看Android中多媒体的API使用和具体的功能。
本篇文章我们先从音频开发聊起。
零、音频开发场景、内容和基本概念在说音频开发之前,我们可以先想一想自己琢磨一下,哪些应用场景会用到音频开发。主要的应用场景大致包括:
音频播放器录音机语音***音视频监控应用音视频直播应用音频编辑/处理软件蓝牙耳机/音箱……如果我们要成系统的学习多媒体和音视频的开发,大致会有涉及到哪些方面的知识呢,归纳来看主要有一下几个方面的内容:
音频采集/播放:已有音频如何播放;如何采集一段音频;音频算法处理:主要包括去噪、静音检测、回声消除、音效处理、功放/增强、混音/分离,等等音频的编解码和格式转换:不同格式之间的转码操作音频传输协议的开发:主要包括SIP,A2DP、AVRCP,等等另外,如果要进行音频开发,需要了解一些音频的概念作为前置知识,一些常见的概念如下所示:
SampleRate:采样率,每秒采集声音的数量,它用赫兹(Hz)来表示。采样频率越高,音频质量越好。常用的音频采样频率有:8kHz、16kHz、44.1kHz、48kHz等。Channel:声道数,表示声音录制时的音源数量或回放时相应的扬声器数量。常用的是单声道(Mono)和双声道(Stereo)。要记住这两个词:Stereo和Mono。BitDepth:采样精度,每个采样点用多少数据量表示,它以位(Bit)为单位。位数越多,表示得就越精细,声音质量自然就越好,当然数据量也越大。常见的位宽是:8bit或者16bit。BitRate:比特率,每秒音频占用的比特数量,单位是bps(BitPerSecond),比特率越高,压缩比越小,声音质量越好,音频体积也越大。一、音频播放说到音视频多媒体,首先就有一个概念叫:媒体格式。也就是我们常说的不同格式的音视频文件。在Android这个***系统平台中,支持的媒体格式还是很丰富的,详细内容如下:
音频格式和编解码器总结来说,Android中常见的音频压缩格式有:MP3,AAC,OGG,WMA,Opus,FLAC,APE,m4a,AMR,等等。
1.1音频的播放1.1.1MediaPlayer首先认识两个基础的概念和API:
MediaPlayer:用于播放声音和视频的主要API。Android多媒体框架支持播放各种常见媒体类型,可以轻松地将音频、视频和图片集成到应用中。可以使用MediaPlayerAPI,播放存储在应用资源(原始资源)内的媒体文件、文件系统中的独立文件或者通过网络连接获得的数据流中的音频或视频。AudioManager:该类API用于管理设备上的音频源和音频输出。另外需要说一下,MediaPlayerl除了能够获取、解码以及播放音频和视频,而且只需很简单的设置即可以外。它还支持多种不同的媒体源,比如:
本地资源:即res目录下的音频资源。URI:比如可能是通过ContentProvider解析到的某个资源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将可能导致网络音频流的获取出现错误。因此为了保证功能的正常使用,我们必须阻止系统关闭服务。可以使用wakelocks(唤醒锁),它会告诉系统你正在使用某些功能,这样就可以一直保持该功能处于唤醒状态,即使锁屏无操作也能继续使用。这个锁会在paused和stoped状态下释放。1.1.2SoundPool如果应用程序经常播放密集、急促而又短暂的音效(如游戏音效)那么使用MediaPlayer显得有些不太适合了。因为MediaPlayer存在如下缺点:
1、延时时间较长,且资源占用率高。2、不支持多个音频同时播放。Android中除了MediaPlayer播放音频之外还提供了SoundPool来播放音效,SoundPool使用音效池的概念来管理多个短促的音效,例如它可以开始就加载20个音效,以后在程序中按音效的ID进行播放。SoundPool的特点和使用长江如下:
1、主要用于播放一些较短的声音片段。2、SoundPool对CPU资源占用量低和反应延迟小。3、SoundPool还支持自行设置声音的品质、音量、播放比率等参数。SoundPool的API说明如下:
1、SoundPool(intmaxStreams,intstreamType,intsrcQuality):指定它总共支持多少个声音(也就是池的大小)、声音的品质。该***属于5.0以下版本使用。2、SoundPool.Builder:从5.0版本开始使用的是SoundPool.Builder模式。3、load(Contextcontext,intresld,intpriority):从resld所对应的资源加载声音。4、load(FileDescriptorfd,longoffset,longlength,intpriority):加载fd所对应的文件的offset开始、长度为length的声音。5、load(AssetFileDescriptorafd,intpriority):从afd所对应的文件中加载声音。6、load(Stringpath,intpriority):从path对应的文件去加载声音。说明:4个load***中都有一个priority参数,该参数目前还没有任何作用,Android建议将该参数设为1,保持和未来的兼容性。play(intsoundID,floatleftVolume,floatrightVolume,intpriority,intloop,floatrate):指定播放哪个声音,2和3参数的意思是音量,priority指定播放的优先级,数值越大优先级越高;loop用于指定是否循环,0不循环,-1位循环;rate指定播放的比率,可选值为0.5–2,1为正常比率。1.1.3AudioTrackAudioTrack属于更偏底层的音频播放,在Android的framework层有MediaPlayerService,其内部就是使用了AudioTrack。AudioTrack用于单个音频播放和管理,相比于MediaPlayer具有:精炼、高效的优点。因此,对于AutioTrack可以总结如下:
使用场景:更适合实时产生播放数据的情况,如加密的音频,MediaPlayer是束手无策的,AudioTrack可以处理。要求:AudioTrack用于播放PCM(PCM无压缩的音频格式)音乐流的回放,如果要播需放其它格式音频,需要相应的解码器,这也是AudioTrack用的比较少的原因,原因在于需要程序开发者自己解码音频。播放模式:①AudioTrack播放音频有两种播放模式,一种是静态模式,即加载的数据和资源可以直接全部加载完毕,加载方式简单,效率也比较高。但是如果数据量很大,往往不适合;②流模式和网络上播放视频是类似的,即数据是按照一定规律不断地传递给接收方的。音频文件过大音频属性要求高,比如采样率高、深度大的数据;另外如果音频数据是实时产生的,这种情况就只能用流模式。使用AudioTrack公有三个步骤:
共有三个步骤:
构建AudioTrack对象,并且把PCM的参数传到对象里面调用start调用write。另外,其实AudioTrack以外,还有一个Audio系统,在该系统中主要包含三个核心的API,分别是:
AudioManager:主要是用来管理Audio系统的。AudioTrack:主要是用来播放声音。AudioRecord:主要是用来录音。1.1.4RingtoneManagerRingtone为***、通知和其他类似声音提供快速播放的***,该种方式播放音频时,还会涉及到一个核心的管理类”RingtoneManager”,该类作为管理类提供系统***列表检索***,并且RingtoneManager可以生成Ringtone实例。具体的Ringtone的使用步骤和相关的***如下所示:
1、获取Ringtone对象实例://1.通过***uri获取staticRingtonegetRingtone(Contextcontext,UriringtoneUri)//2.通过***检索位置获取RingtonegetRingtone(intposition)2、RingtoneManager中重要的***:1.//两个构造***(Activityactivity)RingtoneManager(Contextcontext)2.//获取指定声音类型(***、通知、闹铃等)的默认声音的UristaticUrigetDefaultUri(inttype)3.//获取系统所有Ringtone的cursorCursorgetCursor()4.//获取cursor指定位置的RingtoneuriUrigetRingtoneUri(intposition)5.//判断指定Uri是否为默认***staticbooleanisDefault(UriringtoneUri)6.//获取指定uri的所属类型staticintgetDefaultType(UridefaultRingtoneUri)7.//将指定Uri设置为指定声音类型的默认声音staticvoidsetActualDefaultRingtoneUri(Contextcontext,inttype,UriringtoneUri)8、//播放voidplay()9、//停止播放voidstop()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的官方网站提供的信息:https://developer.android.google.cn/reference/kotlin/android/media/MediaCodec
MediaCodec是在Android4.1版本(API16)中出现并可用的,它提供了一种极其原始的接口。MediaCodec类同时存在Java和C++层中,但是只有前者是公共访问***。
在Android4.3(API18)中,MediaCodec被扩展为通过Surface提供输入的***(通过createInputSurface***),允许来自于相机的预览或者是经过OpenGLES呈现。在该版本中,MediaCodec是第一个过了CTS测试的版本。所谓的CTS,全称是CompatibilityTestSuite,主要是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对象,如下所示:privatestaticMediaCodeccreateAudioEncoder()throwsIOException{MediaCodeccodec=MediaCodec.createEncoderByType(AUDIO_MIME);MediaFormatformat=newMediaFormat();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);returncodec;}读取PCM数据,执行编码操作。...while(!sawOutputEOS){if(!sawInputEOS){inputBufIndex=audioEncoder.dequeueInputBuffer(10_000);if(inputBufIndex>=0){ByteBufferinputBuffer=audioInputBuffers[inputBufIndex];//先清空缓冲区inputBuffer.clear();intbufferSize=inputBuffer.remaining();if(bufferSize!=rawInputBytes.length){rawInputBytes=newbyte[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){//Simplyignorecodecconfigbuffers.if((outBufferInfo.flags&MediaCodec.BUFFER_FLAG_CODEC_CONFIG)!=0){Log.i(TAG,"audioencoder:codecconfigbuffer");audioEncoder.releaseOutputBuffer(outputBufIndex,false);continue;}if(outBufferInfo.size!=0){ByteBufferoutBuffer=audioOutputBuffers[outputBufIndex];outBuffer.position(outBufferInfo.offset);outBuffer.limit(outBufferInfo.offset+outBufferInfo.size);//Log.v(TAG,String.format("writingaudiosample:size=%s,presentationTimeUs=%s",outBufferInfo.size,outBufferInfo.presentationTimeUs));if(lastAudioPresentationTimeUs<=outBufferInfo.presentationTimeUs){lastAudioPresentationTimeUs=outBufferInfo.presentationTimeUs;intoutBufSize=outBufferInfo.size;intoutPacketSize=outBufSize+7;outBuffer.position(outBufferInfo.offset);outBuffer.limit(outBufferInfo.offset+outBufSize);byte[]outData=newbyte[outPacketSize];addADTStoPacket(outData,outPacketSize);outBuffer.get(outData,7,outBufSize);fosAccAudio.write(outData,0,outData.length);//Log.v(TAG,outData.length+"byteswritten.");}else{Log.e(TAG,"errorsample!itspresentationTimeUsshouldnotlowerthanbefore.");}}audioEncoder.releaseOutputBuffer(outputBufIndex,false);if((outBufferInfo.flags&MediaCodec.BUFFER_FLAG_END_OF_STREAM)!=0){sawOutputEOS=true;}}elseif(outputBufIndex==MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED){audioOutputBuffers=audioEncoder.getOutputBuffers();}elseif(outputBufIndex==MediaCodec.INFO_OUTPUT_FORMAT_CHANGED){MediaFormataudioFormat=audioEncoder.getOutputFormat();Log.i(TAG,"formatchange:"+audioFormat);}}...以上是MediaCodec的编码执行操作。如果是解码,与编码过程相反即可完成。
总结
优点:MediaCodec是Android重要的底层多媒体组件,合理使用MediaCodec可以实现播放器、直播、视频编辑、视频录制、视频通话、视频会议等几乎所有音视频相关的编解码功能,且与常规编解码库相比拥有绝对的性能优势。不足:MediaCodec也存在一些缺点,兼容性、稳定性都比较差,开发过程中会经常遇到机型、版本等适配问题,这些都可以通过适配合理解决。四、音频NDKAPI开发如果遇到一些要求更高的项目开发,对音频有高性能的需求,比如说:所需的不仅仅是简单的声音播放或录制功能。它们需要响应式实时系统行为。一些典型用例如:音频合成器、电子鼓、音乐学习应用、DJ混音、音效、视频/音频会议等这类要求特别高的需求时。就要从更深层次的底层来提供功能支持,这里就会用到NDK开发。
首先来了解一下NDK,全称是NativeDevelopmentKit,翻译为原生开发工具包,主要的作用是可以让开发者在Android应用中利用C和c++代码的工具,可用以从自己的源代码构建,或者利用现有的预构建库。
本部分的内容可以在如下的Android官方网站中进行查看和学习:https://developer.android.google.cn/ndk/guides/audio
Android官方给提供了如下选择:
OpenSLES:全称为OpenSoundLibraryforEmbeddedSystems,嵌入式音频加速标准。OpenSLES是无授权费、跨平台、针对嵌入式系统精心优化的硬件音频加速API,为嵌入式移动多媒体设备上的本地应用程序开发者提供了标准化、高性能、低响应时间的音频功能实现***,同时还实现了软/硬件音频性能的直接跨平台部署,不仅降低了执行难度,而且促进了高级音频市场的发展。与Android的关系:Android2.3即API9时开始支持OpenSLES标准,通过NDK提供相应的API开发接口。Android实现的OpenSLES只是OpenSL的子集,然后进行了扩展。Android中OpenSLES的相关资料:https://developer.android.google.cn/ndk/guides/audio/openslAAudio:在Android8.0版本后引入的音频库,该音频库需要使用C语言在Native层进行调用,属于NDK开发范畴。AAudio是OpenSLES库的轻量级实现,同样具有低延迟,高性能的特点。需要特别注意的是,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的主页地址如下:https://github.com/FFmpeg/FFmpeg官方网站的地址是:https://ffmpeg.org/
其中包含的库主要包括:
libavcodec:音/视频编码库。libavformat:音视频格式的生成和解析等操作。libavutil:公共的工具函数。该程序最初在Linux平台上开发和使用,目前在windows、mac上均可以使用。
在Android中使用FFmpeg如果需要在Android中使用FFmpeg,需要进行集成。需要经过几个步骤:
编译:首先要下载FFmpeg,并进行编译,编译出Android中需要的文件。将编译后的内容集成到Android项目中。测试并调用集成的FFmpeg中的***。SpeexSpeex主要是针对语音的开源免费,无专利保护的一种音频压缩格式,是专门为码率在2-44kbps的语音压缩而设计。Speex的特点主要包括:
窄带(8kHz),宽带(16kHz)和超宽带(32kHz)压缩于同一位流可变比特率(VBR)非连续传输(DTX)感官回声消除(AEC)噪音屏蔽SlikSlik算法主要的作用是实现语音和音频的编解码,其主要的特点是:
支持4种采样率:8KHz、12KHz、16KHz、24KHz;三种复杂度:低、中、高。编码码率在6~40kbps。提供了定点C代码,非常有利于向ARM、DSP移植和优化。六、总结本篇文档,我们用很长的篇幅介绍了多媒体开发中的音频功能的开发和使用,在具体的开发和应用中,重点应该放在对整体知识的理解和架构的梳理上,不要拘泥于某个API的使用,参数的作用等。归根到底,不同的实现方案,不同的解决方案最终的落脚点和代码操作步骤几乎是相同的。再次回顾总结我们本篇内容:
音频的播放:MediaPlayer、SoundPool、AudioTrack、RingtoneManager音频的采集:MediaRecorder音频格式的转换:MediaCodec底层库的支持和使用:OpenSLES、AAudio、Oboe开源库的了解和介绍:FFmpeg