Android游戏编程之音频编程

如果你有一台Android设备,就会注意到当你按下增大或降低音量按钮时,你所控制的不同音量设置取决于你正在运行的应用程序。在通话中,你控制的是输入语音流的音量;在视频播放器中,你控制的是视频音频的音量;在主屏幕上,你控制的是铃声的音量。

Android为不同的目的提供不同音频流。当我们在游戏中播放音频时,可使用类来输出音效和音乐到特定的音乐流。不过,在我们想播放音效或音乐之前,需要确定音量按钮控制了正确的音频流。为此,我们使用Context接口的另一个方法:

context.setVolumeControlStream(AudioManager.STREAM_MUSIC);

一如既往,Context的实现仍然由我们的活动来负责。调用该方法之后,音量按钮就控制了该音乐流,后面我们就可使用它来输出我们的音效和音乐。在活动的生命周期内我们只需要调用该方法一次,最好是在Activity.onCreate()方法中调用它。

首先我们要分清音乐流和音效的不同。后者一般是存储在内存中且其长度不会超过几秒钟。Android系统给我们提供了一个SoundPool类,使用它可以很容易实现音效播放。

我们可以很简单地初始化一个新的SoundPool实例,如下所示:

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

第一个参数指定在同一时刻我们最多能播放多少个音效。这并不是说我们不能加载更多的音效文件,它只不过是限制可同时播放的音效个数。第二个参数指定了SoundPool使用什么音频流来输出该音频。我们在这里选择音乐流,同时也已经为它设置好音量控件。最后一个参数现在没有使用,它应该为默认值0.

为了从一个音频文件加载音效到堆内存中,我们可使用SoundPool.load()方法。所有的文件都存储在assets/目录下,因此我们需要重载SoundPool.load()方法。所有的文件都存储在assets/目录下,因此我们需要重载SoundPool.load()方法来获得一个AssetFileDescriptor。我们怎么获得AssetFileDescriptor呢?使用AssetManager。这里我们使用SoundPool从assets/目录加载一个名为explosion.ogg的OGG文件:

AssetFileDescriptor descriptor = assetManager.openFd("explosion.ogg");

int explosionId = soundPool.load(descriptor, 1);

通过AssetManager.openFd()方法可直接获得AssetFileDescriptor,而通过SoundPool可很容易地加载音效,第二个参数用于指定该音效的优先级。这个参数目前未使用,为了以后的兼容应设置为1.

SoundPool.load()方法将返回一个整型值,它将作为一个句柄用于加载的音效。当我们想播放音效时,只需要指定该句柄,SoundPool就知道该播放哪个音频。

soundPool.play(explosionId, 1.0f, 1.0f, 0, 0, 1);

第一个参数是从SoundPool.load()方法接受句柄。接下来两个参数分别用于指定左右通道的音量,其值应该从0(静音)到1(最大)

接下来两个参数我们很少使用,其中第一个参数是优先级,目前没有使用,并且应该设置为0.而另一个参数用于指定音效循环播放的频率,一般不建议循环播放音效,因此设置为0。最后一个参数是播放速率,将其设置为大于1时,音效播放的速度将会比其在录制时快;而将它设置为小于1时,播放该音效就会比较慢。

当我们不再需要一个音效并希望释放内存时,可使用SoundPool.unload()方法:

soundPool.unload(explosionId);

我们只需要将从SoundPool.load()方法接收的音效句柄传入即可,该方法会将音效从内存卸载。

当我们完成所有的音效输出且不再需要SoundPool时,需要调用SoundPool.release()方法来释放SoundPool所占用的所有资源。当然,在释放之后,我们不能再使用SoundPool,而且SoundPool所加载的所有音效也会被释放。

现在编写一个简单的测试活动,每当单击屏幕时它就播放一个爆炸音效。代码如下:

 

[java]  view plain copy
  1. package org.example.ch04_android_basics;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import android.app.Activity;  
  6. import android.content.res.AssetFileDescriptor;  
  7. import android.content.res.AssetManager;  
  8. import android.media.AudioManager;  
  9. import android.media.SoundPool;  
  10. import android.os.Bundle;  
  11. import android.view.MotionEvent;  
  12. import android.view.View;  
  13. import android.view.View.OnTouchListener;  
  14. import android.widget.TextView;  
  15.   
  16. public class SoundPoolTest extends Activity implements OnTouchListener{  
  17.     SoundPool soundPool;  
  18.     int explosionId = -1;  
  19.   
  20.     @Override  
  21.     protected void onCreate(Bundle savedInstanceState) {  
  22.         // TODO Auto-generated method stub  
  23.         super.onCreate(savedInstanceState);  
  24.         TextView textView = new TextView(this);  
  25.         textView.setOnTouchListener(this);  
  26.         setContentView(textView);  
  27.           
  28.         setVolumeControlStream(AudioManager.STREAM_MUSIC);  
  29.         soundPool = new SoundPool(20, AudioManager.STREAM_MUSIC, 0);  
  30.           
  31.         try{  
  32.             AssetManager assetManager = getAssets();  
  33.             AssetFileDescriptor descriptor = assetManager  
  34.                     .openFd("explosion.ogg");  
  35.             explosionId = soundPool.load(descriptor, 1);  
  36.         }catch(IOException e){  
  37.             textView.setText("Couldn't load sound effect from asset, " +  
  38.                     e.getMessage());  
  39.         }  
  40.     }  
  41.   
  42.     @Override  
  43.     public boolean onTouch(View v, MotionEvent event) {  
  44.         // TODO Auto-generated method stub  
  45.         if(event.getAction() == MotionEvent.ACTION_UP){  
  46.             if(explosionId != -1){  
  47.                 soundPool.play(explosionId, 11001);  
  48.             }  
  49.         }  
  50.         return true;  
  51.     }  
  52.   
  53.     @Override  
  54.     protected void onPause() {  
  55.         // TODO Auto-generated method stub  
  56.         super.onPause();  
  57.         soundPool.release();  
  58.     }  
  59.   
  60. }  


SoundPool在处理MP3文件或长的音频文件时会有问题,长文件的定义超过5或6秒钟。一般建议使用OGG音频文件来代替MP3文件,并尽可能使用低的采样率和持续时间,同时保持音效质量。

 

 

短小的音效很时候放在Android应用程序从操作系统分配到的堆内存中,而包含较长音乐文件的大音频文件就很不适合了。为此,我们就需要将音乐以流的方式输出到音频硬件上,这就意味着每次我们只能读入一小块数据,该数据足于解码成原生的PCM数据并输出到音频芯片上。

这听起来挺吓人的。不过幸运的是,我们有MediaPlayer类,它能处理的所有事情。初始化MediaPlayer类:

MediaPlayer mediaPlayer = new MediaPlayer();

接下来我们需要告诉MediaPlayer播放什么文件,这同样通过AssetFileDescriptor来实现:

AssetFileDescriptor descriptor = assetManager.openFd("music.ogg");

mediaPlayer.setDataSource(descriptor.getFileDescriptor(), descriptor.getStartOffset(), descriptor.getLength());

这里稍微比SoundPool中复杂一点。MediaPlayer.setDataSource()方法并没有直接获取AssetFileDescriptor,而是使用一个FileDescriptor,通过AssetFileDescriptor().getFileDescriptor()方法获取该描述符,除此之外我们还需要指定音频文件的偏移量和长度。为什么是偏移量?实际上资源是以单个文件形式存储的,为了让MediaPlayer获得文件的起始地址,我们需要将asset文件夹中的文件的偏移量提供给它。

在播放该音乐文件之前,需要再调用一个方法为MediaPlayer的播放做准备:

MediaPlayer.prepare();

这将实际地打开文件,检查它是否可以读取并用MediaPlayer实例来进行播放。从这里开始,我们就可以随意地播放、暂停和停止音频文件,也可以设置循环播放和改变音量。

启动播放可通过调用下面的方法进行:

mediaPlayer.start();

注意,该方法必须在成功调用MediaPlayer.prepare()方法之后才能调用(你将注意到它是否会抛出一个运行时异常)。

开始播放后,我们可以通过调用pause()方法来暂停播放:

MediaPlayer.pause();

只有我们成功准备好MediaPlayer并已启动播放,调用此方法才会生效。为了恢复一个暂停的MediaPlayer,可再次调用MediaPlayer.start()方法而无需任何准备。

通过调用下面的方法可停止播放:

MediaPlayer.stop();

注意,当我们想启动一个停止的MediaPlayer时,需要先再次调用MediaPlayer.prepare()方法。

我们可通过下面方法设置循环播放:

MediaPlayer.setLooping(true);

可通过下面的方法来调整音乐播放的音量:

MediaPlayer.setVolume(1, 1);

这会重新设置左右声道的音量,文档中没有指定这两个参数的设定范围。从多次尝试的结果看,有效值应该是0-1。

最后,我们需要一个方法来检查该播放是否完成,有两种方式可实现这一点。对于第一种方式,可向MediaPlayer注册一个OnCompletionListener,当播放完成时它就会被调用:

mediaPlayer.setOnCompletionListener(listener);

如果我们想轮询MediaPlayer的状态,则可使用下面方法:

boolean isPlaying = mediaPlayer.isPlaying();

注意,如果MediaPlayer被设置成循环播放,前面两个方法都无法指示该MediaPlayer已停止。

最后,如果MediaPlayer实例完成了所有的操作,就需要通过下面的方法来确保它所占用的资源得以释放:

mediaPlayer.release();

在丢弃一个实例前,执行这个操作应是很好的实践。

如果我们没有将MediaPlayer设置成循环且播放已结束,就可以通过MediaPlayer.prepare()和MediaPlayer.start()方法重启MediaPlayer。

大多数这些方法都是异步的,因此当调用MediaPlayer.stop()时,MediaPlayer.isPlaying()方法可能还需要一点时间才能返回。

现在编写一个测试活动,用循环模式播放assets/目录下的一个音频文件。该音效将根据活动的生命周期实现暂停和恢复,当活动暂停时,音乐也要暂停;而活动恢复时,音乐也要从上次暂停的地方开始播放。

代码如下:

 

[java]  view plain copy
  1. package org.example.ch04_android_basics;  
  2.   
  3. import java.io.IOException;  
  4.   
  5. import android.app.Activity;  
  6. import android.content.res.AssetFileDescriptor;  
  7. import android.content.res.AssetManager;  
  8. import android.media.AudioManager;  
  9. import android.media.MediaPlayer;  
  10. import android.os.Bundle;  
  11. import android.widget.TextView;  
  12.   
  13. public class MediaPlayerTest extends Activity {  
  14.     MediaPlayer mediaPlayer;  
  15.   
  16.     @Override  
  17.     protected void onCreate(Bundle savedInstanceState) {  
  18.         // TODO Auto-generated method stub  
  19.         super.onCreate(savedInstanceState);  
  20.         TextView textView = new TextView(this);  
  21.         setContentView(textView);  
  22.           
  23.         setVolumeControlStream(AudioManager.STREAM_MUSIC);  
  24.         mediaPlayer = new MediaPlayer();  
  25.         try{  
  26.             AssetManager assetManager = getAssets();  
  27.             AssetFileDescriptor descriptor = assetManager.openFd("music.ogg");  
  28.             mediaPlayer.setDataSource(descriptor.getFileDescriptor(),  
  29.                     descriptor.getStartOffset(), descriptor.getLength());  
  30.             mediaPlayer.prepare();  
  31.             mediaPlayer.setLooping(true);  
  32.         }catch(IOException e){  
  33.             textView.setText("Couldn't load music file, " + e.getMessage());  
  34.             mediaPlayer = null;  
  35.         }  
  36.     }  
  37.   
  38.     @Override  
  39.     protected void onPause() {  
  40.         // TODO Auto-generated method stub  
  41.         super.onPause();  
  42.         if(mediaPlayer != null){  
  43.             mediaPlayer.pause();  
  44.             if(isFinishing()){  
  45.                 mediaPlayer.stop();  
  46.                 mediaPlayer.release();  
  47.             }  
  48.         }  
  49.     }  
  50.   
  51.     @Override  
  52.     protected void onResume() {  
  53.         // TODO Auto-generated method stub  
  54.         super.onResume();  
  55.         if(mediaPlayer != null)  
  56.             mediaPlayer.start();  
  57.     }  
  58. }  


在onResume()方法中,我们只需启动MediaPlayer(如果已经成功创建它)。onResume()方法是一个处理该操作的完美地方,因为它在onCreate()方法和onPause()方法之后被调用。在第一种情况下,它将第一次启动播放;在第二种情况下,它将简单地恢复已暂停的MediaPlayer。

 

在onPause()方法中,我们暂停MediaPlayer。如果该活动被销毁,我们还需要停止该MediaPlayer并释放所有资源

你可能感兴趣的:(android)