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][/java] view plaincopy

  1. package com.gaofeng.game;
  2. import java.io.IOException;
  3. import java.util.*;
  4. import android.app.Activity;
  5. import android.content.res.AssetFileDescriptor;
  6. import android.content.res.AssetManager;
  7. import android.media.AudioManager;
  8. import android.media.SoundPool;
  9. public class AndroidSoundPool {
  10.     AssetManager assets;
  11.     SoundPool soundPool;
  12.    HashMap<String,Integer> soundMap=new HashMap<String,Integer>();
  13.     public AndroidSoundPool(Activity activity) {
  14.         activity.setVolumeControlStream(AudioManager.STREAM_MUSIC);
  15.         this.assets = activity.getAssets();
  16.         this.soundPool = new SoundPool(20, AudioManager.STREAM_MUSIC, 0);
  17.     }
  18.     /** 从asset中加载声音文件
  19.      * @param filename 声音文件名
  20.      * @param identifer 声音标识名,在播放声音时调用
  21.      */
  22.     public void load(String filename,String identifer) {
  23.         try {
  24.             AssetFileDescriptor assetDescriptor = assets.openFd(filename);
  25.             int soundId = soundPool.load(assetDescriptor, 0);
  26.             soundMap.put(identifer, soundId);
  27.         } catch (IOException e) {
  28.             throw new RuntimeException(“Couldn’t load sound ‘” + filename + “‘”);
  29.         }
  30.     }
  31.        /**根据声音标识名播放声音
  32.      * @param identifer 声音标识名,在加载声音load方法中被设置的
  33.      * @param volume 播放音量
  34.      */
  35.     public void play(String identifer,float volume) {
  36.             soundPool.play(soundMap.get(identifer), volume, volume, 0, 0, 1);
  37.         }
  38.        /**根据声音标识名播放声音
  39.      * @param identifer 声音标识名,在加载声音load方法中被设置的
  40.      */
  41.     public void play(String identifer) {
  42.             soundPool.play(soundMap.get(identifer), 1, 1, 0, 0, 1);
  43.         }
  44.        /**卸载声音
  45.      * @param identifer 声音标识名,在加载声音load方法中被设置的
  46.      * @return
  47.      */
  48.     public boolean unload(String identifer) {
  49.            return soundPool.unload(soundMap.get(identifer));
  50.         }
  51. }

 

AndroidSoundPool 通过内置一个 HashMap<String,Integer> 实现了音效和声音标识的映射,使用的时候需要首先调用load方法加载音效文件,指定声音标识,然后再通过play方法根据音效对应的声音标识进行播放。

 

下面是AndroidSoundPool 的使用方法:

[java][/java] view plaincopy

  1.  import com.gaofeng.game.AndroidSoundPool;
  2.  import android.app.Activity;
  3.  import android.os.Bundle;
  4.  import android.util.Log;
  5.  import android.view.MotionEvent;
  6.  import android.view.View;
  7.  import android.view.View.OnTouchListener;
  8.  import android.widget.TextView;
  9.  public class AudioTest extends Activity implements OnTouchListener {
  10.     AndroidSoundPool andoridsoundpool;
  11.     @Override
  12.     public void onCreate(Bundle savedInstanceState) {
  13.         super.onCreate(savedInstanceState);
  14.         TextView textView = new TextView(this);
  15.         textView.setOnTouchListener(this);
  16.         setContentView(textView);
  17.           //创建AndroidSoundPool
  18.         andoridsoundpool = new AndroidSoundPool(this);
  19. //加载声音文件explode.ogg,指定该声音标识为:sExplode
  20.           andoridsoundpool.load(“explode.ogg”, “sExplode”);
  21.     }
  22.     @Override
  23.     public boolean onTouch(View v, MotionEvent event) {
  24.         if (event.getAction() == MotionEvent.ACTION_UP) {
  25.            //根据声音标识sExplode播放声音文件
  26.             andoridsoundpool.play(“sExplode”);
  27.         }
  28.         return true;
  29.     }
  30.  }

 

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][/java] view plaincopy

  1.   import java.io.IOException;
  2.   import java.util.HashMap;
  3.   import java.util.Iterator;
  4.   import java.util.Map.Entry;
  5.   import android.app.Activity;
  6.   import android.content.res.AssetFileDescriptor;
  7.   import android.content.res.AssetManager;
  8.   import android.media.MediaPlayer;
  9.   public class AndroidMusicPool {
  10.        AssetManager assets;
  11. HashMap<String,AndroidMusic> musicMap=new HashMap<String,AndroidMusic>();
  12.     public AndroidMusicPool(Activity activity) {
  13.          this.assets = activity.getAssets();
  14.     }
  15.     /*
  16.      * 从asset中加载音乐文件
  17.      * filename:加载的文件名
  18.      * identifer:声音标志名,音乐标识名,播音乐时调用
  19.      * */
  20.     public void load(String filename,String identifer) {
  21.               AssetFileDescriptor assetDescriptor=null;
  22.             try {
  23.                 assetDescriptor = assets.openFd(filename);
  24.             } catch (IOException e) {
  25.                 e.printStackTrace();
  26.             }
  27.               AndroidMusic music = new AndroidMusic(assetDescriptor);
  28.               musicMap.put(identifer, music);
  29.       }
  30.      /**根据音乐标识符播放音乐
  31.      * @param identifer 音乐标识符
  32.      * @param volume 音量大小
  33.      */
  34.        public void play(String identifer,float volume) {
  35.            if(!musicMap.containsKey(identifer))return;
  36.            musicMap.get(identifer).setVolume(volume);
  37.            musicMap.get(identifer).play();
  38.         }
  39.        /*根据音乐标识符播放音乐
  40.         * */
  41.        public void play(String identifer) {
  42.            if(!musicMap.containsKey(identifer))return;
  43.            musicMap.get(identifer).play();
  44.         }
  45.       //根据音乐标识符停止音乐
  46.         public void stop(String identifer) {
  47.            if(!musicMap.containsKey(identifer))return;
  48.            musicMap.get(identifer).stop();
  49.         }
  50.        //根据音乐标识符设定循环播放
  51.        public void setLooping(String identifer,boolean isLooping) {
  52.            if(!musicMap.containsKey(identifer))return;
  53.            musicMap.get(identifer).setLooping(isLooping);
  54.         }
  55.         //根据音乐标识符暂停音乐
  56.        public void pause(String identifer) {
  57.            if(!musicMap.containsKey(identifer))return;
  58.            musicMap.get(identifer).pause();
  59.        }
  60.          //根据音乐标识符注销音乐
  61.        public void dispose(String identifer) {
  62.            if(!musicMap.containsKey(identifer))return;
  63.              musicMap.get(identifer).dispose();
  64.              musicMap.remove(identifer);
  65.         }
  66.          //根据音乐标识符获取音乐
  67.        public AndroidMusic getMusic(String identifer) throws Exception
  68.        {
  69.            if(!musicMap.containsKey(identifer))
  70.    throw new Exception(“no music for identifer found”) ;
  71.          return  musicMap.get(identifer);
  72.        }
  73.        /*清除所有的音乐*/
  74.        public void clear()
  75.        {
  76.            Iterator<Entry<String, AndroidMusic>> iter
  77.              = musicMap.entrySet().iterator();
  78.            while (iter.hasNext()) {
  79.                musicMap.get(iter.next().getKey()).dispose();
  80.            }
  81.            musicMap.clear();
  82.        }
  83.   }
  84.     //该类是对MediaPlayer的轻度封装
  85.    class AndroidMusic {
  86.       MediaPlayer mediaPlayer;
  87.       boolean isPrepared = false;
  88.       public AndroidMusic(AssetFileDescriptor assetDescriptor) {
  89.           mediaPlayer = new MediaPlayer();
  90.           try {
  91.          mediaPlayer.setDataSource(assetDescriptor.getFileDescriptor(),
  92.                       assetDescriptor.getStartOffset(),
  93.                       assetDescriptor.getLength());
  94.               mediaPlayer.prepare();
  95.               isPrepared = true;
  96.           } catch (Exception e) {
  97.               throw new RuntimeException(“Couldn’t load music”);
  98.           }
  99.       }
  100.       public void dispose() {
  101.           if (mediaPlayer.isPlaying())
  102.               mediaPlayer.stop();
  103.           mediaPlayer.release();
  104.       }
  105.       public boolean isLooping() {
  106.           return mediaPlayer.isLooping();
  107.       }
  108.       public boolean isPlaying() {
  109.           return mediaPlayer.isPlaying();
  110.       }
  111.       public boolean isStopped() {
  112.           return !isPrepared;
  113.       }
  114.       public void pause() {
  115.           if (mediaPlayer.isPlaying())
  116.               mediaPlayer.pause();
  117.       }
  118.       public void play() {
  119.           if (mediaPlayer.isPlaying())
  120.               return;
  121.           try {
  122.               synchronized (this) {
  123.                   if (!isPrepared)
  124.                       mediaPlayer.prepare();
  125.                   mediaPlayer.start();
  126.               }
  127.           } catch (IllegalStateException e) {
  128.               e.printStackTrace();
  129.           } catch (IOException e) {
  130.               e.printStackTrace();
  131.           }
  132.       }
  133.       public void setLooping(boolean isLooping) {
  134.           mediaPlayer.setLooping(isLooping);
  135.       }
  136.       public void setVolume(float volume) {
  137.           mediaPlayer.setVolume(volume, volume);
  138.       }
  139.       public void stop() {
  140.           mediaPlayer.stop();
  141.           synchronized (this) {
  142.            isPrepared = false;
  143.           }
  144.       }
  145.   }

以上代码中包含两个类AndroidMusic 和 AndroidMusicPool ,AndroidMusic 其实是对MediaPlayer的轻度封装,在AndroidMusicPool中会调用AndroidMusic 进行音乐播放,而AndroidMusicPool可以看作音乐容器, 通过内置一个 HashMap 实现了音乐和音乐标识的映射,使用的时候需要首先调用load方法加载音乐文件,指定音乐标识,然后再通过play方法根据音乐标识进行播放。

如果上述代码让你感到疑惑,也不必太纠结了,因为重要的是知道如何去使用,请看下面的实例代码:

使用AndroidMusicPool 播放音乐

以下代码演示:当我们轻触屏幕时,音乐就会响起。

[java][/java] view plaincopy

  1. public class AudioTest extends Activity implements OnTouchListener {
  2.     AndroidMusicPool musicpool;
  3.     @Override
  4.     public void onCreate(Bundle savedInstanceState) {
  5.         super.onCreate(savedInstanceState);
  6.         TextView textView = new TextView(this);
  7.         textView.setOnTouchListener(this);
  8.         setContentView(textView);
  9.         musicpool = new AndroidMusicPool(this);
  10.          //从assets中加载音乐文件music.mp3,并设定其音乐标识为:mMusic
  11.         musicpool.load(“music.mp3”, “mMusic”);
  12.     }
  13.     @Override
  14.     public boolean onTouch(View v, MotionEvent event) {
  15.         if (event.getAction() == MotionEvent.ACTION_UP) {
  16.         //根据音乐标识mMusic播放音乐
  17.             musicpool.play(“mMusic”);
  18.         }
  19.         return true;
  20.     }
  21. }

 

AndroidMusicPool通过短短两行代码就实现了音乐的加载和播放,简单吧,但是请记住,在调用播放方法play之前,务必先调用load方法加载音乐文件,并指定音乐标识。

标签