前言
在Android中播放音頻文件經常會用到MediaPlayer,但是MediaPlayer存在一些不足的地方,如:資源占用量較高、加載延遲時間較長、不支持多個音頻同時播放等。這些缺點決定了MediaPlayer在某些需要密集使用不同音頻的情況不會理想,例如游戲開發。在游戲開發中,我們經常需要播放一些游戲的音效,這些音效的都需要是短促、密集、延遲小的,在這種場景下,需要使用到SoundPool來替代MediaPlayer播放這些音效,本篇博客就主要講解SoundPool的使用以及需要注意的地方,最后將以一個示例演示SoundPool的使用。
本篇博客的主要內容:
SoundPool(聲音池),所處於"android.media.SoundPool"包下,主要用於播放一些較短的聲音片段,支持從程序的資源或文件系統加載。與MediaPlayer相比,SoundPool的優勢在於CPU的資源占用量低、反應延遲小,並且可以加載多個音頻到SoundPool中,通過資源ID來管理。另外SoundPool還支持執行設置聲音的品質、音量、播放比率等參數。
SoundPool提供一個構造函數,以下是它的完整簽名:
SoundPool(int maxStreams,int streamType,int srcQuality)
通過上面的構造函數即可完成SoundPool的初始化,第一個參數為音頻池最多支持裝載多少個音頻,就是音頻池的大小;第二個參數指定聲音的類型,在AudioManager類中以常量的形式定義,一般指定為AudioManager.STREAM_MUSIC即可;第三個參數為音頻的質量,默認為0,這個參數為預留參數,現在沒有實際意義,為擴展預留字段,一般傳0即可。
對於一個音頻池,涉及到音頻的加載、播放、暫停、繼續、釋放資源等操作,SoundPool也為我們提供了相應的方法,其底層也是用C++編寫的native方法。以下介紹一些常用的SoundPool方法:
- int load(Context context,int resId,int priority):從一個文件夾raw下裝載一段音頻資源,返回值為音頻資源在SoundPool的ID。
- int load(String path,int priority):從一個資源文件的路徑裝載一段音頻資源,返回值為音頻資源在SoundPool的ID。
- final int play(int soundID,float leftVolume,float rightVolume,int priority,int loop,float rate):根據資源ID,播放一段音頻資源。
- final void pause(int streamID):根據裝載資源ID,暫停音頻資源的播放。
- final void resume(int streamID):根據裝載資源ID,繼續播放暫停的音頻資源。
- final void stop(int streamID):根據裝載資源ID,停止音頻資源的播放。
- final boolean unload(int soundID) :從音頻池中卸載音頻資源ID為soundID的資源。
- final void release():釋放音頻池資源。
上面方法無疑Load()和play()是最重要的,Load()具有多種重載方法,從參數名就可以看出是什么意思。這里講解一下play()方法,soundID參數為資源ID;leftVolume和rightVolume個參數為左右聲道的音量,從大到小取0.0f~1.0f之間的值;priority為音頻質量,暫時沒有實際意義,傳0即可;loop為循環次數,0為播放一次,-1為無線循環,其他正數+1為播放次數,如傳遞3,循環播放4次;rate為播放速率,從大到小取0.0f~2.0f,1.0f為正常速率播放。
在使用load()裝載音頻的時候需要注意,load()方法是一個異步的方法,也就是說,在播放音頻的時候,很可能此段音頻還沒有裝載到音頻池中,這里可以借助SoundPool的一個裝載完成的監聽事件SoundPool.setOnLoadCompleteListener來保證裝載完成在播放聲音。SoundPool.setOnLoadCompleteListener()需要實現一個SoundPool.OnLoadCompleteListener接口,其中需要實現onLoadComplete()方法,一下是onLoadComplete()方法的完整簽名:
onLoadComplete(SoundPool soundPool, int sampleId, int status)
- soundPool:當前觸發事件的聲音池。
- sampleId:當前裝載完成的音頻資源在音頻池中的ID。
- status:狀態碼,展示沒有意義,為預留參數,會傳遞0。
上面已經介紹了SoundPool的使用所涉及到的內容,下面通過一個簡單的示例來演示一下SoundPool的使用,播放的音頻資源都是我從其他app中拷貝出來的,沒有實際意義。示例中的注釋寫的比較全,這里不再累述了。
1 package cn.bgxt.soundpooldemo; 2 3 import java.util.HashMap; 4 import java.util.Map; 5 6 import android.media.AudioManager; 7 import android.media.SoundPool; 8 import android.media.SoundPool.OnLoadCompleteListener; 9 import android.os.Bundle; 10 import android.app.Activity; 11 import android.util.Log; 12 import android.view.View; 13 import android.view.View.OnClickListener; 14 import android.widget.Button; 15 import android.widget.Toast; 16 17 public class MainActivity extends Activity { 18 private Button btn_newqqmsg, btn_newweibontf, btn_newweibotoast; 19 private SoundPool pool; 20 private Map<String, Integer> poolMap; 21 22 @Override 23 protected void onCreate(Bundle savedInstanceState) { 24 super.onCreate(savedInstanceState); 25 setContentView(R.layout.activity_main); 26 btn_newqqmsg = (Button) findViewById(R.id.btn_newqqmsg); 27 btn_newweibontf = (Button) findViewById(R.id.btn_newweibontf); 28 btn_newweibotoast = (Button) findViewById(R.id.btn_newweibotoast); 29 30 poolMap = new HashMap<String, Integer>(); 31 // 實例化SoundPool,大小為3 32 pool = new SoundPool(3, AudioManager.STREAM_MUSIC, 0); 33 // 裝載音頻進音頻池,並且把ID記錄在Map中 34 poolMap.put("newqqmsg", pool.load(this, R.raw.qqmsg, 1)); 35 poolMap.put("newweibontf", pool.load(this, R.raw.notificationsound, 1)); 36 poolMap.put("newweibotoast", pool.load(this, R.raw.newblogtoast, 1)); 37 38 pool.setOnLoadCompleteListener(new OnLoadCompleteListener() { 39 40 @Override 41 public void onLoadComplete(SoundPool soundPool, int sampleId, 42 int status) { 43 // 每次裝載完成均會回調 44 Log.i("main", "音頻池資源id為:" + sampleId + "的資源裝載完成"); 45 // 當前裝載完成ID為map的最大值,即為最后一次裝載完成 46 if (sampleId == poolMap.size()) { 47 Toast.makeText(MainActivity.this, "加載聲音池完成!", 48 Toast.LENGTH_SHORT).show(); 49 btn_newqqmsg.setOnClickListener(click); 50 btn_newweibontf.setOnClickListener(click); 51 btn_newweibotoast.setOnClickListener(click); 52 // 進入應用播放四次聲音 53 pool.play(poolMap.get("newweibotoast"), 1.0f, 1.0f, 0, 3, 54 1.0f); 55 } 56 } 57 }); 58 } 59 60 private View.OnClickListener click = new OnClickListener() { 61 62 @Override 63 public void onClick(View v) { 64 65 switch (v.getId()) { 66 case R.id.btn_newqqmsg: 67 if (pool != null) { 68 pool.play(poolMap.get("newqqmsg"), 1.0f, 1.0f, 0, 0, 1.0f); 69 } 70 break; 71 case R.id.btn_newweibontf: 72 if (pool != null) { 73 pool.play(poolMap.get("newweibontf"), 1.0f, 1.0f, 0, 0, 74 1.0f); 75 } 76 break; 77 case R.id.btn_newweibotoast: 78 if (pool != null) { 79 pool.play(poolMap.get("newweibotoast"), 1.0f, 1.0f, 0, 0, 80 1.0f); 81 } 82 break; 83 default: 84 break; 85 } 86 } 87 }; 88 89 @Override 90 protected void onDestroy() { 91 // 銷毀的時候釋放SoundPool資源 92 if (pool != null) { 93 pool.release(); 94 pool = null; 95 } 96 super.onDestroy(); 97 } 98 }
效果展示:

因為SoundPool的一些設計上的BUG,從固件版本1.0開始就有些沒有修復的,以后應該會慢慢修復。這里簡單提一下:
- 雖然SoundPool可以裝載多個音頻資源,但是最大只能申請1MB的內存空間,這就意味着只能用使用它播放一些很短的聲音片段,而不是用它來播放歌曲或者做游戲背景音樂。
- SoundPool提供的pause()、resume()、stop()最好不要輕易使用,因為它有時候會使程序莫名其妙的終止,如果使用,最好做大量的測試。而且有時候也不會立即終止播放聲音,而是會等緩沖區的音頻數據播放完才會停止。
- 雖然SoundPool比MediaPlayer的效率好,但也不是絕對不存在延遲的問題,尤其在那些性能不太好的手機中,SoundPool的延遲問題會更嚴重,但是現在一般的手機配置,那一點的延遲還是可以接受的。
總結
本篇博客介紹了SoundPool的使用,雖然SoundPool還有一些不足的地方,當時對於應用中一些簡短的特效音,如按鍵音、短消息音,或者一些游戲中密集的聲音,如射擊的槍聲,爆破的聲音等。都是可以使用SoundPool的,效率會比使用MediaPlayer要高的多。

