游戏里用到BGM和音效,常用播放音频的方法主要通过SoundPool和MediaPlayer两个类,单独使用时各有利弊,唯有结合才能扬长避短。
SoundPool
SoundPool,可以同时播放多个音效,但是缓存只有1M,只适合简短的游戏音效,不适合音乐。涉及的API主要有:load(加载音效,返回soundID)、unload、play(设置音量和循环参数,播放指定soundID且已加载的音效资源,并返回streamID)、stop(停止指定streamID的音效)、release(释放资源)。
具体API内容参照官方文档,这里我说明几点需要注意的问题:
1、SoundPool的unload的方法实际释放资源效果并不理想,当load加载超过256个将会出问题,只有release方法才能释放所有音效资源(注意是所有资源,需要重新加载)。
2、SoundPool是独立的线程加载音效,所以在调用load方法之后立刻play是没有效果的,这里需要实现SoundPool的OnLoadCompleteListener接口来回调,以判断是否完成资源加载,不过这个接口需要API8(2.2.x以上)支持。由于我需要针对可很能SDK版本低于2.0的机型,于是没有用回调接口,而是游戏初始化时加载所有最常用的音效,在每个场景加载或释放该场景需要的音效。具体使用的地方直接播放即可。实际上所有常用的音效加起来也不会超过1分钟,占用资源很少,用这个方法简单实用。
MediaPlayer
MediaPlayer,用以播放媒体文件,适合BGM等较大资源的音乐。涉及API主要是:setDataSource(设置音频源,可以是assets、sd卡、流)、isPlaying、stop、start、pause、reset、prepare(设置源以后需要调用此方法准备资源进入待播放状态)。
注意事项:
1、MediaPlayer也是独立的线程,但是不建议用多个MediaPlayer,一方面本身BGM播放时间长占用资源大,另一方面MediaPlayer开销也大。
2、MediaPlayer非常不适合播放音效,用它连续播放音乐会卡顿。绝大部分游戏,同时有且仅有一个BGM处于播放状态,所以游戏中用MediaPlayer播放BGM,用SoundPool播放音效,两者可以同时存在。
3、MediaPlayer播放下一个音频时,先使用reset方法使其恢复到idle状态,再设定音频源,否则连续几次就会出错。
4、release()以后即是end状态,这个MediaPlayer不能再使用。
5、设置完数据源后,你需要调用prepare()或prepareAsync()使资源进入待播放状态。对于流,你应该调用prepareAsync(),立即返回,而不是阻塞,直到足够的数据被缓冲。
6、跟SoundPool一样,MediaPlayer也不是调用了prepare方法就立刻可以start的,MediaPlayer线程需要一个加载并准备资源的时间。需要知道MediaPlayer什么时候完成资源准备,可以实现setOnPreparedListener接口。跟SoundPool情况不同,游戏BGM切换频繁,且不能一次性加载,setOnPreparedListener的回调方法在此显得尤为重要。非常巧的是,这个接口不需要高级别的SDK支持,难道是谷歌故意为之?
?
我的个人博客里有我封装的音频管理器代码,地址:www.leestorm.com。代码里没有给出FileLoader这个接口,实际上它是一个策略模式,实现者分别实现从SD卡、Assets、res以及网络加载资源的功能,留给各位自己去完成。