android音效的加载方式

  • Post author:
  • Post category:其他



下面是MediaPlayer和SoundPool类的对比特性:


1.soundpool可以播一些短的反应速度要求高的声音,



比如游戏中的爆破声,而mediaplayer适合播放长点的。



2. SoundPool载入音乐文件使用了独立的线程,不会阻塞UI主线程的操作。但是这里如果音效文件过大没有载入完成,我们调用play方法时可能产生严 重的后果,这里Android SDK提供了一个SoundPool.OnLoadCompleteListener类来帮助我们了解媒体文件是否载入完成,我们重载 onLoadComplete(SoundPool soundPool, int sampleId, int status) 方法即可获得。



3. 从上面的onLoadComplete方法可以看出该类有很多参数,比如类似id,是的SoundPool在load时可以处理多个媒体一次初始化并放入内存中,这里效率比MediaPlayer高了很多。



4. SoundPool类支持同时播放多个音效,这对于游戏来说是十分必要的,而MediaPlayer类是同步执行的只能一个文件一个文件的播放。




1. 游戏音效SoundPool


游 戏中会根据不同的动作 , 产生各种音效 , 这些音效的特点是短暂(叫声,爆炸声可能持续不到一秒) , 重复(一个文件不断重复播放) , 并且同时播放(比如打怪时怪的叫声 , 和技能释放的声音需要同时播放) , 即时(技能用处之后声音马上随着玩家操作发出,不能有延迟).


MediaPlayer会占用大量的系统资源 , 并且不能同时播放 , 并且无法实现即时音效 , 这里引入了一个新的类 — SoundPool , 这个类完全满足上面提出的四点要求 , 可以无延时播放游戏中的短暂音效 .







2. 相关API介绍




(1) SoundPool




构造方法 : SoundPool(int maxStreams, int streamType, int srcQuality) ;



参数解析 :


maxStream : 该参数是定义最多能同时播放的多少音效 .


streamType : 该参数定义音频类型 , 游戏中一般设置为AudioManager.STREAM_MUSIC .


srcQuality : 该参数用来设置音频质量 , 这个参数目前没有作用 , 这里设置为 0;







加载音频文件方法 : int load(Context context, int resId, int priority);



参数解析 :


context : 上下文对象;


resId : 要加载的资源文件 , 即R.raw.music…


priority : 优先级别 , 这里没有作用 , 设置为1.







播放音效方法 : int play(int soundId, float leftVolume, float rightVolume, int priority, int loop, float rate);



参数解析 :


soundId : 这个id不是资源id , 指的是利用load方法加载资源文件返回的id值 , 这个要区别清楚.


leftVolume : 左声道的音量 , 这个音量是一个 0 ~ 1的数 , 这个小数是当前音量/最大音量的结果;


rightVolume : 右声道的音量 , 这个音量与左声道的音量是同一种音量;


priority : 优先级参数 , 0为最低, 这里设置为1;


loop : 音效循环的次数 , 0为不循环 , -1为永远循环;


rate : 音效回放的速度 , 这个值是在0.5~2.0f之间 , 1f是正常速度;




暂停音效播放方法 : pause(int streamId);



参数streamId : 这个参数是play()方法执行完之后的返回值 , 这个返回值是正在播放的音效的一个标识 , 对正在播放的音效进行操作的时候 , 就需要这个标识来对其进行操作;


通知音效播放方法 : stop(int streamId) , 这个参数与上面的pause()方法中的streamId参数是一个效果.




(2)AudioManager


获取方法 : AudioManager对象时系统服务, 可以通过调用上下文对象的getSystemService(Context.AUDIO_SERVICE)获取 , 注意获取到之后 , 需要将对象墙砖为AudioManager对象才可以使用.


eg : AudioManager audioManager = (AudioManager)getApplicationContext().getSystemService(Context.AUDIO_SERVICE);





利用AudioManager获取当前音量的方法 : float currVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);


利用AudioManager获取当前系统最大音量方法 : float maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);





使用这两个音量就可以计算出运行SoundPool音效的音量 , 当前音量 / 系统最大音量 , 结果就是soundPool.play()方法中需要传入的音量 ;



3. 程序代码

public class MainActivity extends Activity implements OnClickListener {  
      
        private SoundPool soundPool;  
        private HashMap<Integer, Integer> hashMap;  
        private int currStreamId;  
          
        @Override  
        public void onCreate(Bundle savedInstanceState) {  
            super.onCreate(savedInstanceState);  
            setContentView(R.layout.activity_main);  
              
            initSoundPool();  
        }  
      
        private void initSoundPool() {  
            soundPool = new SoundPool(3, AudioManager.STREAM_MUSIC, 0);   
            hashMap = new HashMap<Integer, Integer>();  
            hashMap.put(1, soundPool.load(getApplicationContext(), R.raw.musictest, 1));  
        }  
      
        @Override  
        public void onClick(View v) {  
            switch (v.getId()) {  
                case R.id.bt_play:  
                    play(1, 0);  
                    Toast.makeText(getApplicationContext(), "播放即时音效", Toast.LENGTH_LONG).show();  
                    break;  
                case R.id.bt_stop:  
                    soundPool.stop(currStreamId);  
                    Toast.makeText(getApplicationContext(), "暂停播放", Toast.LENGTH_LONG).show();  
                    break;  
                default:  
                    break;  
            }  
        }  
          
        private void play(int sound, int loop) {  
            AudioManager audioManager = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);  
            float currVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);  
            float maxVolume = audioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);  
            float volume = currVolume / maxVolume;  
            currStreamId = soundPool.play(hashMap.get(sound), volume, volume, 1, loop, 1.0f);  
            System.out.println(currStreamId);  
        }  
    } 


4. 程序的注意点


  • 音效文件需要放在res的raw下.

  • SoundPool播放的音效要小于7秒 , 否则会出现加载失败的现象;

  • 播放的大小尽量不超过1M,太大会影响播放;

  • 在Android平台上使用的即时文件越小越好 , 必要的时候可以降低采样频率或者将立体声改为单声道;

  • 都说MediaPlayer比较耗资源,在一样的情况下(文件一致),只使用一个MediaPlayer的对象的reset(),prepare(),start()这些方法速度的慢也体验不出来。SoundPool和MediaPlayer都可以使用,且相对而言MediaPlayer要稳定些;


  • 当调用load方法的时候实际就是把音效加载到了 SoundPool中,此时返回的streamId其实就是该音效在SoundPool中的Id,这个ID从0还是1来着(有点记不清了) 递增,不过要注意的是,不要超过  256  这个临界点。也就是说第257个声音加载进去后,调用play方法其实是播不出来的,说不定还会挤掉一些前面加载好的声音。这个256的限制通过查看SDK源码基本就能了解清楚,它底层就那么实现的,用一个类似堆栈来存;


  • 在创建的时候 maxStream这个参数代表能够同时播放的最大音效数,这里切忌合理使用,写的太大后会报AudioFlinger could not  create track, status: -12 。。。。一旦报了这个错,你就听不到声音了;


  • 如果你音效多,也不要指望unload方法来清除掉一些音效后再load新的进去,虽然unload后音效卸载了,但是前面分给它在SoundPool里面的Id可没有释放掉,也就是说这个时候你load新的进去只会在后面继续累加,然后累加多了就超过256了,然后就就听不到声音,然后就没有然后了。要想彻底清掉前面的音效请使用release方法,它会连内存中占用的资源一起释放掉;


  • load需要一点点时间,load后不要马上unload,load —play–unload的做法并不可取,不要load太大的音效,它只会申请1M的内存空间。SoundPool出错后通常会看到return的值是0











SoundPool —— 适合短促且对反应速度比较高的情况(游戏音效或按键声等)




下面介绍SoundPool的创建过程:






1. 创建一个SoundPool


(构造函数)






public SoundPool(int maxStream, int streamType, int srcQuality)






maxStream —— 同时播放的流的最大数量




streamType —— 流的类型,一般为STREAM_MUSIC(具体在AudioManager类中列出)




srcQuality —— 采样率转化质量,当前无效果,使用0作为默认值






初始化一个实例:


SoundPool soundPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);






创建了一个最多支持5个流同时播放的,类型标记为音乐的SoundPool。









2. 加载音频资源










可以通过四种途径来记载一个音频资源:




int load(AssetFileDescriptor afd, int priority)






通过一个AssetFileDescriptor对象




int load(Context context, int resId, int priority)






通过一个资源ID




int load(String path, int priority)






通过指定的路径加载




int load(FileDescriptor fd, long offset, long length, int priority)






通过FileDescriptor加载






*API中指出,其中的priority参数目前没有效果,建议设置为1。








一个SoundPool能同时管理多个音频,所以可以通过多次调用load函数来记载,如果记载成功将返回一个非0的


soundID


,用于播放时指定特定的音频。





int

soundID1

= soundPool.load(this, R.raw.sound1, 1);


if(

soundID1

==0){



// 记载失败


}else{



// 加载成功


}


int

soundID2

= soundPool.load(this, R.raw.sound2, 1);









这里加载了两个流,并分别记录了返回的


soundID











需要注意的是,






流的加载过程是一个将音频解压为原始16位PCM数据的过程,由一个后台线程来进行处理异步,所以初始化后不能立即播放,需要等待一点时间。












3. 播放控制










有以下几个函数可用于控制播放:




final int play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate)






播放指定音频的音效,并返回一个


streamID







priority —— 流的优先级,值越大优先级高,影响当同时播放数量超出了最大支持数时SoundPool对该流的处理;




loop —— 循环播放的次数,0为值播放一次,-1为无限循环,其他值为播放loop+1次(例如,3为一共播放4次).




rate —— 播放的速率,范围0.5-2.0(0.5为一半速率,1.0为正常速率,2.0为两倍速率)




final void pause(int streamID)






暂停指定播放流的音效(


streamID


应通过play()返回)。




final void resume(int streamID)






继续播放指定播放流的音效(


streamID


应通过play()返回)。




final void stop(int streamID)






终止指定播放流的音效(


streamID


应通过play()返回)。










这里需要注意的是,






1.play()函数传递的是一个load()返回的


soundID——指向一个被记载的音频资源


,如果播放成功则返回一个非0的


streamID——指向一个成功播放的流


;同一个


soundID


可以通过多次调用play()而获得多个不同的


streamID


(只要不超出同时播放的最大数量);




2.pause()、resume()和stop()是针对播放流操作的,传递的是play()返回的


streamID


;




3.play()中的priority参数,只在同时播放的流的数量超过了预先设定的最大数量是起作用,管理器将自动终止优先级低的播放流。如果存在多个同样优先级的流,再进一步根据其创建事件来处理,新创建的流的年龄是最小的,将被终止;




4.无论如何,程序退出时,手动终止播放并释放资源是必要的。










4. 更多属性设置










其实就是paly()中的一些参数的独立设置:




final void setLoop(int streamID, int loop)






设置指定播放流的循环.




final void setVolume(int streamID, float leftVolume, float rightVolume)






设置指定播放流的音量.




final void setPriority(int streamID, int priority)






设置指定播放流的优先级,上面已说明priority的作用.




final void setRate(int streamID, float rate)






设置指定播放流的速率,0.5-2.0.















5. 释放资源










可操作的函数有:




final boolean unload(int soundID)






卸载一个指定的音频资源.




final void release()






释放SoundPool中的所有音频资源.











下面对以上进行总结:






一个SoundPool可以:




1.管理多个音频资源,通过load()函数,成功则返回非0的soundID;




2.同时播放多个音频,通过play()函数,成功则返回非0的streamID;




3.pause()、resume()和stop()等操作是针对streamID(播放流)的;




4.当设置为无限循环时,需要手动调用stop()来终止播放;




5.播放流的优先级(play()中的priority参数),只在同时播放数超过设定的最大数时起作用;









6.程序中不用考虑(play触发的)播放流的生命周期,无效的soundID/streamID不会导致程序错误。






参考1:http://blog.csdn.net/qduningning/article/details/8680575

参考2:http://www.open-open.com/lib/view/open1390879650507.html

参考3:http://blog.csdn.net/xiaominghimi/article/details/6101737



版权声明:本文为wangqjpp原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。