在学习Android源码的过程中,跟MediaPlayer 和 SoundPool都有过碰面,所以稍微整理一下这两个跟播放音频有关的类。
MediaPlayer 在Apk Music中得到广泛使用,所以通过Android源码中的App-Music可以很好的学习和掌握MediaPlayer类。SoundPool在Android源码中也有使用,比如锁屏,锁屏播放锁屏和解锁音频就是用的SoundPool。
一、MediaPlayer:
用于控制播放音频/视频文件和流。
状态图:
通过上面的状态图,可以更加详细的了解到MediaPlayer对象在运行时的生命周期和状态。
法: getCurrentPosition(),getDuration(), getVideoHeight(), getVideoWidth(), setAudioStreamType(int), setLooping(boolean),setVolume(float, float), pause(), start(), stop(), seekTo(int), prepare() or prepareAsync()。如果其中有一个方法被调用,框架将无法回调客户端程序注册的OnErrorListener.onError()方法。但若这个MediaPlayer对象调用了reset()方法之后,再调用以上的那些方法,内部的播放引擎就会回调客户端程序注册的OnErrorListener.onError()方法,并将错误的状态传入。
建议在一个MediaPlayer对象不再被使用时,应立即调用release()方法来释放在内部播放引擎中与这个MediaPlayer对象关联的资源。资源可能包括如独立的硬件加速组件,若没有调用release()方法可能会导致之后的MediaPlayer对象无法使用这种独立的硬件资源,从而导致程序实现或运行失败。一旦MediaPlayer对象进入了End状态,它就不能被再度使用,也没有办法回到其它状态。
另外,MediaPlayer对象通过"new"操作被创建,此时它处在Idle状态,而那些通过重载Create方法来创建的MediaPlayer对象是不处在Idle状态的。事实上,这些被重载方法创建的MediaPlayer对象处于prepared状态,因为重载create方法本身已经调用了prepare()方法。可参看源码:
<span style="font-size:14px;">public static MediaPlayer create(Context context, Uri uri, SurfaceHolder holder,
AudioAttributes audioAttributes, int audioSessionId) {
try {
MediaPlayer mp = new MediaPlayer();
final AudioAttributes aa = audioAttributes != null ? audioAttributes :
new AudioAttributes.Builder().build();
mp.setAudioAttributes(aa);
mp.setAudioSessionId(audioSessionId);
mp.setDataSource(context, uri);
if (holder != null) {
mp.setDisplay(holder);
}
mp.prepare();
return mp;
} catch (IOException ex) {
Log.d(TAG, "create failed:", ex);
// fall through
} catch (IllegalArgumentException ex) {
Log.d(TAG, "create failed:", ex);
// fall through
} catch (SecurityException ex) {
Log.d(TAG, "create failed:", ex);
// fall through
}
return null;
}</span>
2、通常,一些播放控制操作可能因为各种原因而失败,比如:不支持的audio/vedio格式,不良交错的audio/vedio,分辨率太高,流超时等等。因此,错误报告和恢复在这种情况下就非常重要。有时,由于编程错误,在处于无效状态的情况下调用了一个播放控制操作也可能发生。在所有这些错误条件下,如果OnErrorListener预先通过通过调用MediaPlayer.setOnErrorListener(android .media.MediaPlayer.OnErrorListener)方法被注册,内部的播放引擎会调用。
关注错误的发生是很重要的,一旦错误发生,MediaPlayer对象进入Error状态。
MediaPlayer对象处在错误状态,并且从错误中恢复。此时要重新使用MediaPlayer对象,reset()方法会被调用,用来重置MediaPlayer对象返回它的Idle状态。
保持良好的编程习惯,需要时定要注册OnErrorListener,以便更好的查找错误。
在无效状态调用以下方法会抛出IllegalStateException异常:prepare(), prepareAsync(),setDataSource()。
3、调用setDataSource(FileDescriptor), or setDataSource(String), or setDataSource(Context, Uri), orsetDataSource(FileDescriptor, long, long), or setDataSource(MediaDataSource)方法,会让MediaPlayer对象从Idle状态转变到Initialized状态。
setDataSource()IllegalStateException异常。
4、对于MediaPlayer对象来说,在播放开始之前,一定会先进入prepared状态。
有两种方法(同步和异步)可以使MediaPlayer对象进入Prepared状态:要么调用prepare()方法(同步),此方法返回就表示该MediaPlayer对象已经进入了Prepared状态;要么调用prepareAsync()方法(异步),此方法会使此MediaPlayer对象进入Preparing状态并返回,而内部的播放引擎会继续未完成的准备工作。当同步返回时或异步的准备工作完全完成时就会调用程序员提供的OnPreparedListener.onPrepared()监听方法。可以调用MediaPlayer.setOnPreparedListener(android.media.MediaPlayer.OnPreparedListener)方法来注册OnPreparedListener.
值的重点关注的是:prepared状态是过度状态,在此状态下,调用任何有副作用的方法的行为都是不确定的。
IllegalStateException异常。
当MediaPlayer对象处于prepared状态的时候,可以调整音频/视频的属性,如音量,播放时是否一直亮屏,循环播放等。
5、要开始播放,start()方法必须被调用,在start()返回true之后,MediaPlayer对象处于Started状态,isPlaying()方法可以用来检测MediaPlayer对象是否处在Started状态。
当MediaPlayer对象处于Started状态时,内部播放引擎会调用程序员提供的OnBufferingUpdateListener.onBufferingUpdate()回调方法,此回调方法允许应用程序追踪流播放的缓存状态。
对于一个已经处于Started状态的MediaPlayer对象,调用start()方法没有影响。
6、播放可以被暂停和停止,以及调整当前播放位置。当调用pause()方法并返回时,会使MediaPlayer对象进入Paused状态。注意Started与Paused状态的相互转换在内部的播放引擎中是异步的。所以可能需要一点时间在isPlaying()方法中更新状态,若在播放流内容,这段时间可能会有几秒钟。
对于一个处于paused状态的MediaPlayer对象,调用start()方法可以让它回到播放状态,并且此时播放的位置还是跟之前一样,当这些发生以后,MediaPlayer对象转变到Started状态。
对于一个处于paused状态的MediaPlayer对象,调用pause()方法,对其没有影响。
7、调用stop()方法停止播放,引起一个处于Started状态、Paused状态、Prepared状态、或者播放完成状态的MediaPlayer对象进入Stoped状态。
一旦MediaPlayer对象处于Stoped状态,不能开始播放,直到调用prepare()或者prepareAsync()方法来设置MediaPlayer对象进入Prepared状态以后才能再次进入Started状态。
对于一个处于Stoped状态的MediaPlayer对象,调用stop()方法,对其没有影响。
8、调用seekTo()方法可以调整播放的位置。
seekTo(int)方法是异步执行的,所以它可以马上返回,但是实际定位播放操作可能需要一段时间才能完成,尤其是播放流形式的音频/视频。当实际的定位播放操作完成之后,内部的播放引擎会调用程序员提供的OnSeekComplete.onSeekComplete()回调方法。可以通过setOnSeekCompleteListener(OnSeekCompleteListener)方法注册。
seekTo(int)方法在其他状态下也可以被调用,比如:Prepared状态、Paused状态、PlaybackCompleted状态。
另外,确切的播放位置可以通过getCurrentPosition()来检索得到。这对于如音乐播放器应用不断更新播放进度是很有帮助的。
9、当播放到流的末尾,播放就完成了。
如果调用了setLooping(boolean)方法开启了循环模式,那么这个MediaPlayer对象会重新进入Started状态。
若没有开启循环模式,那么内部的播放引擎会调用程序员提供的OnCompletion.onCompletion()回调方法。可以通过调用MediaPlayer.setOnCompletionListener(OnCompletionListener)方法来设置。内部的播放引擎一旦调用了OnCompletion.onCompletion()回调方法,说明这个MediaPlayer对象进入了PlaybackCompleted状态。
当处于PlaybackCompleted状态时,调用start()可以重新开始从音频/视频源的开始播放。
有效和无效状态对比:
方法名 | 有效状态 | 无效状态 | 评论 |
attachAuxEffect | {Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} | {Idle, Error} | 这个方法必须在setDataSource之后调用,不会改变MediaPlayer对象的状态。 |
getAudioSessionId | any | {} | 这个方法能在任何状态下调用,不会改变MediaPlayer对象的状态。 |
getCurrentPosition | {Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} | {Error} | 在有效状态下成功调用此方法不会改变MediaPlayer对象的状态,在无效状态下调用此方法,会让对象进入Error状态。 |
getDuration | {Prepared, Started, Paused, Stopped, PlaybackCompleted} | {Idle, Initialized, Error} | 在有效状态下成功调用此方法不会改变MediaPlayer对象的状态,在无效状态下调用此方法,会让对象进入Error状态。 |
getVideoHeight | {Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} | {Error} | 在有效状态下成功调用此方法不会改变MediaPlayer对象的状态,在无效状态下调用此方法,会让对象进入Error状态。 |
getVideoWidth | {Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} | {Error} | 在有效状态下成功调用此方法不会改变MediaPlayer对象的状态,在无效状态下调用此方法,会让对象进入Error状态。 |
isPlaying | {Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} | {Error} | 在有效状态下成功调用此方法不会改变MediaPlayer对象的状态,在无效状态下调用此方法,会让对象进入Error状态。 |
pause | {Started, Paused, PlaybackCompleted} | {Idle, Initialized, Prepared, Stopped, Error} | 在有效状态下成功调用此方法会让MediaPlayer对象进入Paused状态,在无效状态下调用此方法,会让对象进入Error状态。 |
prepare | {Initialized, Stopped} | {Idle, Prepared, Started, Paused, PlaybackCompleted, Error} | 在有效状态下成功调用此方法会让MediaPlayer对象进入Prepared状态,在无效状态下调用此方法,会抛出IllegalStateException异常。 |
prepareAsync | {Initialized, Stopped} | {Idle, Prepared, Started, Paused, PlaybackCompleted, Error} | 在有效状态下成功调用此方法会让MediaPlayer对象进入Prepared状态,在无效状态下调用此方法,会抛出IllegalStateException异常。 |
release | any | {} | 调用release()方法以后,MediaPlayer对象不再可用。 |
reset | {Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, Error} | {} | 调用reset()方法以后,MediaPlayer对象将被创建。 |
seekTo | {Prepared, Started, Paused, PlaybackCompleted} | {Idle, Initialized, Stopped, Error} | 在有效状态下成功调用此方法不会改变MediaPlayer对象的状态,在无效状态下调用此方法,会让对象进入Error状态。 |
setAudioAttributes | {Idle, Initialized, Stopped, Prepared, Started, Paused, PlaybackCompleted} | {Error} | 成功调用此方法不会改变MediaPlayer对象的状态,为了目标audio属性类型变的有效,这个方法必须在prepare() or prepareAsync()之前调用。 |
setAudioSessionId | {Idle} | {Initialized, Prepar | 这个方法必须在Idle状态下调用,因为audio session ID必须在调用setDataSource之前知道,调用此方法不会让MediaPlayer的状态改变。 |
setAudioStreamType | {Idle, Initialized, Stopped, Prepared, Started, Paused, PlaybackCompleted} | {Error} | 成功调用此方法不会改变MediaPlayer对象的状态,为了目标audio strean类型变的有效,这个方法必须在prepare() or prepareAsync()之前调用。 |
setAuxEffectSendLevel | any | {} | 调用此方法不会改变MediaPlayer对象的状态。 |
setDataSource | {Idle} | {Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, Error} | 在有效的状态下成功调用此方法会让MediaPlayer对象的状态转变到Initialized状态,在无效状态下调用此方法会抛出IllegalStateException异常。 |
setDisplay | any | {} | 这个方法可以在任何状态下调用,并且不会改变MediaPlayer对象的状态。 |
setSurface | any | {} | 这个方法可以在任何状态下调用,并且不会改变MediaPlayer对象的状态。 |
setVideoScalingMode | {Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted} | {Idle, Error} | 成功调用此方法不会改变MediaPlayer对象的状态。 |
setLooping | {Idle, Initialized, Stopped, Prepared, Started, Paused, PlaybackCompleted} | {Error} | 在有效状态下成功调用此方法不会改变MediaPlayer对象的状态,在无效状态下调用此方法,会让对象进入Error状态。 |
isLooping | any | {} | 这个方法可以在任何状态下调用,并且不会改变MediaPlayer对象的状态。 |
setOnBufferingUpdateListener | any | {} | 这个方法可以在任何状态下调用,并且不会改变MediaPlayer对象的状态。 |
setOnCompletionListener | any | {} | 这个方法可以在任何状态下调用,并且不会改变MediaPlayer对象的状态。 |
setOnErrorListener | any | {} | 这个方法可以在任何状态下调用,并且不会改变MediaPlayer对象的状态。 |
setOnPreparedListener | any | {} | 这个方法可以在任何状态下调用,并且不会改变MediaPlayer对象的状态。 |
setOnSeekCompleteListener | any | {} | 这个方法可以在任何状态下调用,并且不会改变MediaPlayer对象的状态。 |
setPlaybackParams | any | {} | 这个方法可以在任何状态下调用,并且不会改变MediaPlayer对象的状态。 |
setScreenOnWhilePlaying | any | {} | 这个方法可以在任何状态下调用,并且不会改变MediaPlayer对象的状态。 |
setVolume | {Idle, Initialized, Stopped, Prepared, Started, Paused, PlaybackCompleted} | {Error} | 成功调用此方法不会改变MediaPlayer对象的状态。 |
setWakeMode | any | {} | 这个方法可以在任何状态下调用,并且不会改变MediaPlayer对象的状态。 |
start | {Prepared, Started, Paused, PlaybackCompleted} | {Idle, Initialized, Stopped, Error} | 在有效状态下成功调用此方法会让MediaPlayer对象进入Started状态,在无效状态下调用此方法,会让对象进入Error状态。 |
stop | {Prepared, Started, Stopped, Paused, PlaybackCompleted} | {Idle, Initialized, Error} | 在有效状态下成功调用此方法会让MediaPlayer对象进入Stoped状态,在无效状态下调用此方法,会让对象进入Error状态。 |
getTrackInfo | {Prepared, Started, Stopped, Paused, PlaybackCompleted} | {Idle, Initialized, Error} | 成功调用此方法不会改变MediaPlayer对象的状态。 |
addTimedTextSource | {Prepared, Started, Stopped, Paused, PlaybackCompleted} | {Idle, Initialized, Error} | 成功调用此方法不会改变MediaPlayer对象的状态。 |
selectTrack | {Prepared, Started, Stopped, Paused, PlaybackCompleted} | {Idle, Initialized, Error} | 成功调用此方法不会改变MediaPlayer对象的状态。 |
deselectTrack | {Prepared, Started, Stopped, Paused, PlaybackCompleted} | {Idle, Initialized, Error} | 成功调用此方法不会改变MediaPlayer对象的状态。 |
MediaPlayer简单实用介绍:
1):在清单文件中配置权限:
<span style="font-size:14px;"> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" /></span>
2):创建MediaPlayer对象:
播放指定音频资源:
<span style="font-size:14px;">MediaPlayer mediaPlayer = MediaPlayer.create(context, R.raw.sound_file_1);
mediaPlayer.start(); // no need to call prepare(); create() does that for you</span>
播放ulr连接的音频资源:
Uri myUri = ....; // initialize Uri here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(getApplicationContext(), myUri);
mediaPlayer.prepare();
mediaPlayer.start();
或者
String url = "http://........"; // your URL here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // might take long! (for buffering, etc)
mediaPlayer.start();
3)播放完以后,需要释放资源:
<span style="font-size:14px;"> mediaPlayer.release();
mediaPlayer = null;</span>
简单的使用就这么简单,正规的使用学习Android 源码中Music App会有更多帮助。
AudioManager提供了监听audio播放焦点的接口:AudioManager.OnAudioFocusChangeListener。比如手机正在播放Music,此时Music应用获得了audio焦点。此时手机端又有电话打进来,系统监听到这一行为,就会优先准备播放来电铃音,这个时候播放来电铃音的模块就要获得audio焦点,同时告知AudioManager audio焦点要改变。那么实现AudioManager.OnAudioFocusChangeListener接口的应用都会回调这个接口内的方法onAudioFocusChange();此时Music应用指导audio焦点被其他模块获取后,onAudioFocusChange()方法被调用,然后在此方法中停止播放音乐,让系统先去播放来电铃音。系统播放完来电铃音以后就会释放audio 播放焦点,AudioManager又会知道这一行为,此时在Music中,onAudioFocusChange()又会被调用,最后音乐又开始接着播放。
二、SoundPool
SoundPool类是Android用于管理和播放应用程序音频资源的类。
一个SoundPool对象可以看作是一个可以从APK中导入资源或者从文件系统中载入文件的样本集合。SoundPool library利用MediaPlayer服务将音频解码成原始的16位PCM单声道或立体声流。这允许应用程序附带压缩码流,而不必忍受播放过程中解压缩的CPU负载和延迟。
1)音频数量说明:
除了低延迟播放,SoundPool还可以管理多个音频流。当SoundPool对象构造时,maxStreams参数设置的流数目表示在单一的SoundPool中,同一时间所能播放流的最大数量。利用SoundPool可以跟踪活跃流的数量。如果其数量超过流的最大数目,SoundPool会基于优先级自动停止先前播放的流。限制流的最大数目,有利于减轻CPU的负荷,减少音频混合对视觉和UI性能的影响。
说明:
声音可以通过设置一个非零循环值来循环。如果值为-1表示从不循环。在这种情况下,应用程序必须明确地调用stop()方法来停止声音。其他非零值表示声音按指定数量的时间重复。
说明:
在SoundPool中,播放速率也是可以改变。1.0的播放率表示声音按照其原始频率(如果必要的话,将重新采样硬件输出频率)。而2.0的播放速率表示声音按照其原始频率的两倍播放。如果为0.5的播放速率,表示播放速率是原始频率的一半。播放速率的取值范围是0.5至2.0。
4)优先级说明:
优先级运行是从低到高。数字越大优先级越高。当用户调用play()方法时,如果活跃的流数目大于规定的maxStreams参数,流分配器将会结束优先级最低的流。如果存在具有相同的低优先级的多个数据流,它会选择最老的流停止。在新数据流的优先级比所有活跃流低的情况下,新的声音不会被播放和play()方法将返回零流ID
一旦声音被成功加载和播放,应用程序可以调用SoundPool.play()来播放声音。播放过程中的流可被暂停或继续播放。应用程序也可以通过调整播放速率实时为多普勒或合成效果改变音高。
流可能会由于资源的限制被停止,streamID是一个对特定流实例的引用。如果流被停止并且允许更高优先级的流播放,流就不再有效了。然而,应用程序允许调用没有错误的streamID方法。因为如果应用程序不需要关心流的生命周期,这可能有助于简化程序逻辑。
SoundPool构造方法:
public SoundPool (int maxStreams, int streamType, int srcQuality)。
SoundPool.Builder类来创建和配置SoundPool实例。
SoundPool Play方法:
public final int play (int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)
参数说明:
soundID | Load()函数返回的声音ID号 |
leftVolume | 左声道音量设置 (range = 0.0 to 1.0) |
rightVolume | 右声道音量设置 |
priority | 指定播放声音的优先级,数值越高,优先级越大 |
loop | 指定是否循环。-1表示无限循环,0表示不循环,其他值表示要重复播放的次数 |
rate | 指定播放速率 |
返回值
成功返回streamID , 失败返回zero。
SoundPool释放资源:
在不要使用的时候,通过release()方法释放资源。
public final void release ()
SoundPool —— 适合短促且对反应速度比较高的情况(游戏音效或按键声等)。
MediaPlayer —— 适合比较长且对时间要求不高的情况。