Android遊戲開發教程—-遊戲中的聲音

Android SDK為我們提供瞭SoundPool 用於播放遊戲音效,還提供瞭MediaPlayer為我們提供遊戲音樂的播放。SoundPool(android.media.SoundPool),顧名思義是聲音池的意思,主要用於播放一些較短的聲音片段,支持從程序的資源或文件系統加載。

SoundPool技術

     在遊戲開發中我們經常需要播放一些遊戲音效(比如:子彈爆炸,物體撞擊等),這些音效的共同特點是短促、密集、延遲程度小。在這樣的場景下我們可以使用SoundPool。

SoundPool特性:

 1. SoundPool最大隻能申請1M的內存空間,這就意味著我們隻能使用一些很短的聲音片段,而不是用它來播放歌曲或者遊戲背景音樂。

 2. SoundPool提供瞭pause和stop方法,但這些方法建議最好不要輕易使用。

 3. 音頻格式建議使用OGG格式。

SoundPool使用方法:

1. 創建一個SoundPool

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

  maxStream —— 同時播放的流的最大數量

  streamType —— 流的類型,一般為STREAM_MUSIC(具體在AudioManager類中列出)

  srcQuality —— 采樣率轉化質量,當前無效果,使用0作為默認值

  eg.

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

    創建瞭一個最多支持3個流同時播放的,類型標記為音樂的SoundPool。 

2 soundpool的加載:

  int  load(Context context, int resId, int priority)  //從APK資源載入

  int  load(FileDescriptor fd, long offset, long length, int priority)  //從FileDescriptor對象載入

  int  load(AssetFileDescriptor afd, int priority)  //從Asset對象載入

  int  load(String path, int priority)  //從完整文件路徑名載入

最後一個參數為優先級。

3 播放

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

其中leftVolume和rightVolume表示左右音量 取值范圍(range = 0.0 to 1.0)

priority表示優先級

loop表示循環次數

rate表示速率,如

//速率最低0.5最高為2,1代表正常速度

sp.play(soundId, 1, 1, 0, 0, 1);

關於音頻流:

    在Android系統中有多種音頻流,通過Activity中的函數 setVolumeControlStream(int streamType)可以設置該Activity中音量控制鍵控制的音頻流,一般在onCreate函數中設置。

Android中有如下幾種音頻流(streamType是需要調整音量的類型):

AudioManager.STREAM_MUSIC  /音樂回放即媒體音量/

AudioManager.STREAM_RING /鈴聲/

AudioManager.STREAM_ALARM  /警報/

AudioManager.STREAM_NOTIFICATION /窗口頂部狀態欄通知聲/

AudioManager.STREAM_SYSTEM  /系統/

AudioManager.STREAM_VOICECALL /通話 /

AudioManager.STREAM_DTMF /雙音多頻/

仔細分析瞭SoundPool 的API後,毫無疑問我們應該讓事情簡單些。

編寫AndroidSoundPool類簡化SoundPool的使用

[java] package com.gaofeng.game; 
   
  import java.io.IOException; 
  import java.util.*; 
  import android.app.Activity; 
  import android.content.res.AssetFileDescriptor; 
  import android.content.res.AssetManager; 
  import android.media.AudioManager; 
  import android.media.SoundPool; 
   
  public class AndroidSoundPool { 
   
    AssetManager assets; 
      SoundPool soundPool; 
     HashMap<String,Integer> soundMap=new HashMap<String,Integer>(); 
    public AndroidSoundPool(Activity activity) { 
        activity.setVolumeControlStream(AudioManager.STREAM_MUSIC); 
          this.assets = activity.getAssets(); 
          this.soundPool = new SoundPool(20, AudioManager.STREAM_MUSIC, 0); 
    } 
    /** 從asset中加載聲音文件
     * @param filename 聲音文件名
     * @param identifer 聲音標識名,在播放聲音時調用
     */ 
    public void load(String filename,String identifer) { 
          try { 
              AssetFileDescriptor assetDescriptor = assets.openFd(filename); 
              int soundId = soundPool.load(assetDescriptor, 0); 
              soundMap.put(identifer, soundId);  
          } catch (IOException e) { 
              throw new RuntimeException("Couldn't load sound '" + filename + "'"); 
          } 
      } 
       /**根據聲音標識名播放聲音
     * @param identifer 聲音標識名,在加載聲音load方法中被設置的
     * @param volume 播放音量
     */ 
    public void play(String identifer,float volume) { 
            soundPool.play(soundMap.get(identifer), volume, volume, 0, 0, 1); 
        } 
       /**根據聲音標識名播放聲音
     * @param identifer 聲音標識名,在加載聲音load方法中被設置的
     */ 
    public void play(String identifer) { 
            soundPool.play(soundMap.get(identifer), 1, 1, 0, 0, 1); 
        } 
       /**卸載聲音
     * @param identifer 聲音標識名,在加載聲音load方法中被設置的
     * @return
     */ 
    public boolean unload(String identifer) { 
           return soundPool.unload(soundMap.get(identifer)); 
        } 
   
  } 

  package com.gaofeng.game;
  
  import java.io.IOException;
  import java.util.*;
  import android.app.Activity;
  import android.content.res.AssetFileDescriptor;
  import android.content.res.AssetManager;
  import android.media.AudioManager;
  import android.media.SoundPool;
  
  public class AndroidSoundPool {
  
   AssetManager assets;
      SoundPool soundPool;
     HashMap<String,Integer> soundMap=new HashMap<String,Integer>();
   public AndroidSoundPool(Activity activity) {
    activity.setVolumeControlStream(AudioManager.STREAM_MUSIC);
          this.assets = activity.getAssets();
          this.soundPool = new SoundPool(20, AudioManager.STREAM_MUSIC, 0);
   }
    /** 從asset中加載聲音文件
     * @param filename 聲音文件名
     * @param identifer 聲音標識名,在播放聲音時調用
     */
   public void load(String filename,String identifer) {
          try {
              AssetFileDescriptor assetDescriptor = assets.openFd(filename);
              int soundId = soundPool.load(assetDescriptor, 0);
              soundMap.put(identifer, soundId);
          } catch (IOException e) {
              throw new RuntimeException("Couldn't load sound '" + filename + "'");
          }
      }
      /**根據聲音標識名播放聲音
     * @param identifer 聲音標識名,在加載聲音load方法中被設置的
     * @param volume 播放音量
     */
   public void play(String identifer,float volume) {
           soundPool.play(soundMap.get(identifer), volume, volume, 0, 0, 1);
       }
      /**根據聲音標識名播放聲音
     * @param identifer 聲音標識名,在加載聲音load方法中被設置的
     */
   public void play(String identifer) {
           soundPool.play(soundMap.get(identifer), 1, 1, 0, 0, 1);
       }
      /**卸載聲音
    * @param identifer 聲音標識名,在加載聲音load方法中被設置的
    * @return
    */
   public boolean unload(String identifer) {
          return soundPool.unload(soundMap.get(identifer));
       }
  
  }
AndroidSoundPool 通過內置一個 HashMap<String,Integer> 實現瞭音效和聲音標識的映射,使用的時候需要首先調用load方法加載音效文件,指定聲音標識,然後再通過play方法根據音效對應的聲音標識進行播放。

 

下面是AndroidSoundPool 的使用方法:

[java]  import com.gaofeng.game.AndroidSoundPool; 
  import android.app.Activity; 
  import android.os.Bundle; 
  import android.util.Log; 
  import android.view.MotionEvent; 
  import android.view.View; 
  import android.view.View.OnTouchListener; 
  import android.widget.TextView; 
   
  public class AudioTest extends Activity implements OnTouchListener { 
    AndroidSoundPool andoridsoundpool; 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        TextView textView = new TextView(this); 
        textView.setOnTouchListener(this); 
        setContentView(textView); 
           //創建AndroidSoundPool  
        andoridsoundpool = new AndroidSoundPool(this); 
 //加載聲音文件explode.ogg,指定該聲音標識為:sExplode                                
           andoridsoundpool.load("explode.ogg", "sExplode"); 
    } 
    @Override 
    public boolean onTouch(View v, MotionEvent event) { 
        if (event.getAction() == MotionEvent.ACTION_UP) { 
            //根據聲音標識sExplode播放聲音文件  
            andoridsoundpool.play("sExplode"); 
        } 
        return true; 
    } 
  } 

  import com.gaofeng.game.AndroidSoundPool;
  import android.app.Activity;
  import android.os.Bundle;
  import android.util.Log;
  import android.view.MotionEvent;
  import android.view.View;
  import android.view.View.OnTouchListener;
  import android.widget.TextView;
  
  public class AudioTest extends Activity implements OnTouchListener {
   AndroidSoundPool andoridsoundpool;
   @Override
   public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    TextView textView = new TextView(this);
    textView.setOnTouchListener(this);
    setContentView(textView);
           //創建AndroidSoundPool
     andoridsoundpool = new AndroidSoundPool(this);
 //加載聲音文件explode.ogg,指定該聲音標識為:sExplode                            
           andoridsoundpool.load("explode.ogg", "sExplode");
   }
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_UP) {
            //根據聲音標識sExplode播放聲音文件
      andoridsoundpool.play("sExplode");
    }
    return true;
   }
  }

MediaPlayer技術

MediaPlayer使用簡單,適合做遊戲的背景音樂,但資源占用量較高、延遲時間較長、不支持多個音頻同時播放等。

下面介紹MediaPlayer的相關技術:

1.獲取MediaPlayer實例

MediaPlayer.create(Context context,int resId); 

//參數一:上下文對象,參數二:音樂資源ID

2.MediaPlayer常用的函數

prepare();//為播放音樂文件做準備工作

start();//播放音樂

pause();//暫停音樂播放

stop();//停止音樂播放

pause()和stop()主要的區別在於:暫停播放後可以調用start()繼續播放,停止音樂播放後,需要調用prepare()再調用start()進行播放音樂。

3.MediaPlayer其它常用函數

setLooping(boolean looping);//設置音樂是否循環播放,true為循環播放

seekTo(int msec);//將音樂播放跳轉到某一時間點,以毫秒為單位

getDuration();//獲取播放的音樂文件總時間長度

getCurrentPosition();//獲取當前播放音樂時間點

仔細分析瞭MediaPlayer的API後,毫無疑問我們應該讓事情簡單些。

編寫AndroidMusicPool簡化音樂播放

[java] import java.io.IOException; 
  import java.util.HashMap; 
  import java.util.Iterator; 
  import java.util.Map.Entry; 
  import android.app.Activity; 
  import android.content.res.AssetFileDescriptor; 
  import android.content.res.AssetManager; 
  import android.media.MediaPlayer; 
   
  public class AndroidMusicPool { 
       AssetManager assets; 
HashMap<String,AndroidMusic> musicMap=new HashMap<String,AndroidMusic>(); 
    public AndroidMusicPool(Activity activity) { 
         this.assets = activity.getAssets(); 
    } 
    /*
     * 從asset中加載音樂文件
     * filename:加載的文件名
     * identifer:聲音標志名,音樂標識名,播音樂時調用
     * */ 
    public void load(String filename,String identifer) {      
              AssetFileDescriptor assetDescriptor=null; 
            try { 
                assetDescriptor = assets.openFd(filename); 
            } catch (IOException e) { 
                e.printStackTrace(); 
            } 
              AndroidMusic music = new AndroidMusic(assetDescriptor); 
              musicMap.put(identifer, music); 
            
      } 
     /**根據音樂標識符播放音樂
     * @param identifer 音樂標識符
     * @param volume 音量大小
     */ 
       public void play(String identifer,float volume) { 
           if(!musicMap.containsKey(identifer))return; 
           musicMap.get(identifer).setVolume(volume); 
           musicMap.get(identifer).play(); 
        } 
       /*根據音樂標識符播放音樂
        * */ 
       public void play(String identifer) { 
           if(!musicMap.containsKey(identifer))return; 
           musicMap.get(identifer).play(); 
        } 
      //根據音樂標識符停止音樂  
        public void stop(String identifer) { 
           if(!musicMap.containsKey(identifer))return; 
           musicMap.get(identifer).stop(); 
        } 
       //根據音樂標識符設定循環播放  
       public void setLooping(String identifer,boolean isLooping) { 
           if(!musicMap.containsKey(identifer))return; 
           musicMap.get(identifer).setLooping(isLooping); 
        } 
        //根據音樂標識符暫停音樂  
       public void pause(String identifer) { 
           if(!musicMap.containsKey(identifer))return; 
           musicMap.get(identifer).pause(); 
       } 
         //根據音樂標識符註銷音樂  
       public void dispose(String identifer) { 
           if(!musicMap.containsKey(identifer))return; 
             musicMap.get(identifer).dispose(); 
             musicMap.remove(identifer); 
        } 
         //根據音樂標識符獲取音樂  
       public AndroidMusic getMusic(String identifer) throws Exception 
       {   
           if(!musicMap.containsKey(identifer))  
   throw new Exception("no music for identifer found") ; 
         return  musicMap.get(identifer); 
       }   
       /*清除所有的音樂*/ 
       public void clear() 
       { 
           Iterator<Entry<String, AndroidMusic>> iter  
             = musicMap.entrySet().iterator();  
           while (iter.hasNext()) {  
               musicMap.get(iter.next().getKey()).dispose(); 
           }  
           musicMap.clear(); 
       } 
  } 
    //該類是對MediaPlayer的輕度封裝  
   class AndroidMusic { 
      MediaPlayer mediaPlayer; 
      boolean isPrepared = false; 
   
      public AndroidMusic(AssetFileDescriptor assetDescriptor) { 
          mediaPlayer = new MediaPlayer(); 
          try { 
         mediaPlayer.setDataSource(assetDescriptor.getFileDescriptor(), 
                      assetDescriptor.getStartOffset(), 
                      assetDescriptor.getLength()); 
              mediaPlayer.prepare(); 
              isPrepared = true; 
          } catch (Exception e) { 
              throw new RuntimeException("Couldn't load music"); 
          } 
      } 
   
      public void dispose() { 
          if (mediaPlayer.isPlaying()) 
              mediaPlayer.stop(); 
          mediaPlayer.release(); 
      } 
   
      public boolean isLooping() { 
          return mediaPlayer.isLooping(); 
      } 
   
      public boolean isPlaying() { 
          return mediaPlayer.isPlaying(); 
      } 
      public boolean isStopped() { 
          return !isPrepared; 
      } 
      public void pause() { 
          if (mediaPlayer.isPlaying()) 
              mediaPlayer.pause(); 
      } 
      public void play() { 
          if (mediaPlayer.isPlaying()) 
              return;  
          try { 
              synchronized (this) { 
                  if (!isPrepared) 
                      mediaPlayer.prepare(); 
                  mediaPlayer.start(); 
              } 
          } catch (IllegalStateException e) { 
              e.printStackTrace(); 
          } catch (IOException e) { 
              e.printStackTrace(); 
          } 
      } 
      public void setLooping(boolean isLooping) { 
          mediaPlayer.setLooping(isLooping); 
      } 
   
      public void setVolume(float volume) { 
          mediaPlayer.setVolume(volume, volume); 
      } 
      public void stop() { 
          mediaPlayer.stop(); 
          synchronized (this) { 
           isPrepared = false; 
          } 
      } 
  } 

  import java.io.IOException;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.Map.Entry;
  import android.app.Activity;
  import android.content.res.AssetFileDescriptor;
  import android.content.res.AssetManager;
  import android.media.MediaPlayer;
  
  public class AndroidMusicPool {
      AssetManager assets;
HashMap<String,AndroidMusic> musicMap=new HashMap<String,AndroidMusic>();
   public AndroidMusicPool(Activity activity) {
     this.assets = activity.getAssets();
   }
   /*
    * 從asset中加載音樂文件
    * filename:加載的文件名
    * identifer:聲音標志名,音樂標識名,播音樂時調用
    * */
   public void load(String filename,String identifer) { 
              AssetFileDescriptor assetDescriptor=null;
     try {
      assetDescriptor = assets.openFd(filename);
     } catch (IOException e) {
      e.printStackTrace();
     }
              AndroidMusic music = new AndroidMusic(assetDescriptor);
              musicMap.put(identifer, music);
          
      }
     /**根據音樂標識符播放音樂
    * @param identifer 音樂標識符
    * @param volume 音量大小
    */
      public void play(String identifer,float volume) {
       if(!musicMap.containsKey(identifer))return;
       musicMap.get(identifer).setVolume(volume);
       musicMap.get(identifer).play();
       }
      /*根據音樂標識符播放音樂
       * */
      public void play(String identifer) {
       if(!musicMap.containsKey(identifer))return;
       musicMap.get(identifer).play();
       }
     //根據音樂標識符停止音樂
        public void stop(String identifer) {
       if(!musicMap.containsKey(identifer))return;
       musicMap.get(identifer).stop();
       }
       //根據音樂標識符設定循環播放
      public void setLooping(String identifer,boolean isLooping) {
       if(!musicMap.containsKey(identifer))return;
       musicMap.get(identifer).setLooping(isLooping);
       }
        //根據音樂標識符暫停音樂
      public void pause(String identifer) {
       if(!musicMap.containsKey(identifer))return;
       musicMap.get(identifer).pause();
      }
         //根據音樂標識符註銷音樂
      public void dispose(String identifer) {
       if(!musicMap.containsKey(identifer))return;
         musicMap.get(identifer).dispose();
         musicMap.remove(identifer);
       }
         //根據音樂標識符獲取音樂
      public AndroidMusic getMusic(String identifer) throws Exception
      { 
       if(!musicMap.containsKey(identifer))
   throw new Exception("no music for identifer found") ;
     return  musicMap.get(identifer);
      } 
      /*清除所有的音樂*/
      public void clear()
      {
       Iterator<Entry<String, AndroidMusic>> iter
             = musicMap.entrySet().iterator();
       while (iter.hasNext()) {
           musicMap.get(iter.next().getKey()).dispose();
       }
       musicMap.clear();
      }
  }
    //該類是對MediaPlayer的輕度封裝
   class AndroidMusic {
      MediaPlayer mediaPlayer;
      boolean isPrepared = false;
  
      public AndroidMusic(AssetFileDescriptor assetDescriptor) {
          mediaPlayer = new MediaPlayer();
          try {
         mediaPlayer.setDataSource(assetDescriptor.getFileDescriptor(),
                      assetDescriptor.getStartOffset(),
                      assetDescriptor.getLength());
              mediaPlayer.prepare();
              isPrepared = true;
          } catch (Exception e) {
              throw new RuntimeException("Couldn't load music");
          }
      }
  
      public void dispose() {
          if (mediaPlayer.isPlaying())
              mediaPlayer.stop();
          mediaPlayer.release();
      }
  
      public boolean isLooping() {
          return mediaPlayer.isLooping();
      }
  
      public boolean isPlaying() {
          return mediaPlayer.isPlaying();
      }
      public boolean isStopped() {
          return !isPrepared;
      }
      public void pause() {
          if (mediaPlayer.isPlaying())
              mediaPlayer.pause();
      }
      public void play() {
          if (mediaPlayer.isPlaying())
              return;
          try {
              synchronized (this) {
                  if (!isPrepared)
                      mediaPlayer.prepare();
                  mediaPlayer.start();
              }
          } catch (IllegalStateException e) {
              e.printStackTrace();
          } catch (IOException e) {
              e.printStackTrace();
          }
      }
      public void setLooping(boolean isLooping) {
          mediaPlayer.setLooping(isLooping);
      }
  
      public void setVolume(float volume) {
          mediaPlayer.setVolume(volume, volume);
      }
      public void stop() {
          mediaPlayer.stop();
          synchronized (this) {
           isPrepared = false;
          }
      }
  }以上代碼中包含兩個類AndroidMusic 和 AndroidMusicPool ,AndroidMusic 其實是對MediaPlayer的輕度封裝,在AndroidMusicPool中會調用AndroidMusic 進行音樂播放,而AndroidMusicPool可以看作音樂容器, 通過內置一個 HashMap 實現瞭音樂和音樂標識的映射,使用的時候需要首先調用load方法加載音樂文件,指定音樂標識,然後再通過play方法根據音樂標識進行播放。

如果上述代碼讓你感到疑惑,也不必太糾結瞭,因為重要的是知道如何去使用,請看下面的實例代碼:

使用AndroidMusicPool 播放音樂

以下代碼演示:當我們輕觸屏幕時,音樂就會響起。

[java]  public class AudioTest extends Activity implements OnTouchListener { 
    AndroidMusicPool musicpool; 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        TextView textView = new TextView(this); 
        textView.setOnTouchListener(this); 
        setContentView(textView); 
        musicpool = new AndroidMusicPool(this); 
           //從assets中加載音樂文件music.mp3,並設定其音樂標識為:mMusic  
        musicpool.load("music.mp3", "mMusic"); 
    } 
    @Override 
    public boolean onTouch(View v, MotionEvent event) { 
        if (event.getAction() == MotionEvent.ACTION_UP) { 
          //根據音樂標識mMusic播放音樂  
            musicpool.play("mMusic"); 
        } 
        return true; 
    } 
  } 

  public class AudioTest extends Activity implements OnTouchListener {
   AndroidMusicPool musicpool;
   @Override
   public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    TextView textView = new TextView(this);
    textView.setOnTouchListener(this);
    setContentView(textView);
    musicpool = new AndroidMusicPool(this);
           //從assets中加載音樂文件music.mp3,並設定其音樂標識為:mMusic
     musicpool.load("music.mp3", "mMusic");
   }
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_UP) {
          //根據音樂標識mMusic播放音樂
     musicpool.play("mMusic");
    }
    return true;
   }
  }

AndroidMusicPool通過短短兩行代碼就實現瞭音樂的加載和播放,簡單吧,但是請記住,在調用播放方法play之前,務必先調用load方法加載音樂文件,並指定音樂標識。

 

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *