Android遊戲編程之音頻編程

來源:互聯網
上載者:User

以下均轉自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所載入的所有音效也會被釋放。

現在編寫一個簡單的測試活動,每當單擊螢幕時它就播放一個爆炸音效。代碼如下:

package org.example.ch04_android_basics;import java.io.IOException;import android.app.Activity;import android.content.res.AssetFileDescriptor;import android.content.res.AssetManager;import android.media.AudioManager;import android.media.SoundPool;import android.os.Bundle;import android.view.MotionEvent;import android.view.View;import android.view.View.OnTouchListener;import android.widget.TextView;public class SoundPoolTest extends Activity implements OnTouchListener{SoundPool soundPool;int explosionId = -1;@Overrideprotected void onCreate(Bundle savedInstanceState) {// TODO Auto-generated method stubsuper.onCreate(savedInstanceState);TextView textView = new TextView(this);textView.setOnTouchListener(this);setContentView(textView);setVolumeControlStream(AudioManager.STREAM_MUSIC);soundPool = new SoundPool(20, AudioManager.STREAM_MUSIC, 0);try{AssetManager assetManager = getAssets();AssetFileDescriptor descriptor = assetManager.openFd("explosion.ogg");explosionId = soundPool.load(descriptor, 1);}catch(IOException e){textView.setText("Couldn't load sound effect from asset, " +e.getMessage());}}@Overridepublic boolean onTouch(View v, MotionEvent event) {// TODO Auto-generated method stubif(event.getAction() == MotionEvent.ACTION_UP){if(explosionId != -1){soundPool.play(explosionId, 1, 1, 0, 0, 1);}}return true;}@Overrideprotected void onPause() {// TODO Auto-generated method stubsuper.onPause();soundPool.release();}}

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/目錄下的一個音頻檔案。該音效將根據活動的生命週期實現暫停和恢複,當活動暫停時,音樂也要暫停;而活動恢複時,音樂也要從上次暫停地方開始播放。

代碼如下:

package org.example.ch04_android_basics;import java.io.IOException;import android.app.Activity;import android.content.res.AssetFileDescriptor;import android.content.res.AssetManager;import android.media.AudioManager;import android.media.MediaPlayer;import android.os.Bundle;import android.widget.TextView;public class MediaPlayerTest extends Activity {MediaPlayer mediaPlayer;@Overrideprotected void onCreate(Bundle savedInstanceState) {// TODO Auto-generated method stubsuper.onCreate(savedInstanceState);TextView textView = new TextView(this);setContentView(textView);setVolumeControlStream(AudioManager.STREAM_MUSIC);mediaPlayer = new MediaPlayer();try{AssetManager assetManager = getAssets();AssetFileDescriptor descriptor = assetManager.openFd("music.ogg");mediaPlayer.setDataSource(descriptor.getFileDescriptor(),descriptor.getStartOffset(), descriptor.getLength());mediaPlayer.prepare();mediaPlayer.setLooping(true);}catch(IOException e){textView.setText("Couldn't load music file, " + e.getMessage());mediaPlayer = null;}}@Overrideprotected void onPause() {// TODO Auto-generated method stubsuper.onPause();if(mediaPlayer != null){mediaPlayer.pause();if(isFinishing()){mediaPlayer.stop();mediaPlayer.release();}}}@Overrideprotected void onResume() {// TODO Auto-generated method stubsuper.onResume();if(mediaPlayer != null)mediaPlayer.start();}}

在onResume()方法中,我們只需啟動MediaPlayer(如果已經成功建立它)。onResume()方法是一個處理該操作的完美地方,因為它在onCreate()方法和onPause()方法之後被調用。在第一種情況下,它將第一次啟動播放;在第二種情況下,它將簡單地恢複已暫停MediaPlayer。

在onPause()方法中,我們暫停MediaPlayer。如果該活動被銷毀,我們還需要停止該MediaPlayer並釋放所有資源

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.