1. 程式人生 > >android開發播放聲音檔案

android開發播放聲音檔案

有兩種播放音訊形式,第一個:MediaPlayer 類 ;第二個:SoundPool 類

MediaPlayer mediaPlayer01;
mediaPlayer01 = MediaPlayer.create(YouActivity.this, R.raw.xxxx);
mediaPlayer01.start();

網上有很多介紹mp播放聲音檔案的幾種方式就是:
1.用R.raw.x來播放raw目錄下的檔案,
2.用setDataSource("/sdcard/s.mp4"),但是沒有說如果想用setDataSource()這樣的方式播放raw目錄下的檔案(setDataSource("/raw/s.mp4")這樣不行)。 


一、 MediaPlayer 播放音訊的實現步驟:
1. 呼叫MediaPlayer.create(context, R.raw.himi); 利用MediaPlayer類呼叫create方法並且傳入通過id索引的資源音訊檔案,得到例項;
2. 得到的例項就可以呼叫 MediaPlayer.star();
簡單吧、其實MediaPlayer還有幾個構造方法,大家有興趣可以去嘗試和實現,這裡主要是簡單的向大家介紹基本的,畢竟簡單實用最好!
  二、 SoundPlayer 播放音訊的實現步驟:
1.   new出一個例項 ;   new SoundPool(4, AudioManager.STREAM_MUSIC, 100);第一個引數是允許有多少個聲音流同時播放,第2個引數是聲音型別,第三個引數是聲音的品質;
2.loadId = soundPool.load(context, R.raw.himi_ogg, 1);
3. 使用例項呼叫play方法傳入對應的音訊檔案id即可!
下面講下兩個播放形式的利弊:
        使用MediaPlayer來播放音訊檔案存在一些不足:
例如:資源佔用量較高、延遲時間較長、不支援多個音訊同時播放等。
這些缺點決定了MediaPlayer在某些場合的使用情況不會很理想,例如在對時間精準度要求相對較高的遊戲開發中。
最開始我使用的也是普通的MediaPlayer的方式,但這個方法不適合用於遊戲開發,因為遊戲裡面同時播放多個音效是常有的事,用過MediaPlayer的朋友都該知道,它是不支援實時播放多個聲音的,會出現或多或少的延遲,而且這個延遲是無法讓人忍受的,尤其是在快速連續播放聲音(比如連續猛點按鈕)時,會非常明顯,長的時候會出現3~5秒的延遲,【使用MediaPlayer.seekTo() 這個方法來解決此問題】;
        相對於使用SoundPool存在的一些問題:
1. SoundPool最大隻能申請1M的記憶體空間,這就意味著我們只能使用一些很短的聲音片段,而不是用它來播放歌曲或者遊戲背景音樂(背景音樂可以考慮使用JetPlayer來播放)。
2. SoundPool提供了pause和stop方法,但這些方法建議最好不要輕易使用,因為有些時候它們可能會使你的程式莫名其妙的終止。還有些朋友反映它們不會立即中止播放聲音,而是把緩衝區裡的資料播放完才會停下來,也許會多播放一秒鐘。 
3. 音訊格式建議使用OGG格式。使用WAV格式的音訊檔案存放遊戲音效,經過反覆測試,在音效播放間隔較短的情況下會出現異常關閉的情況(有說法是SoundPool目前只對16bit的WAV檔案有較好的支援)。後來將檔案轉成OGG格式,問題得到了解決。
4.在使用SoundPool播放音訊的時候,如果在初始化中就呼叫播放函式進行播放音樂那麼根本沒有聲音,不是因為沒有執行,而是SoundPool需要一準備時間!囧。當然這個準備時間也很短,不會影響使用,只是程式一執行就播放會沒有聲音罷了,所以我把SoundPool播放寫在了按鍵中處理了、備註4的地方
大概看完了利弊解釋,那麼來看我的程式碼備註的地方:
備註1:
 這裡我定義了一個 HashMap ,這個是雜湊表,如果大家不是很瞭解這個類,那建議百度 google學習下,它與Hashtable很常用的,它倆的主要區別是: HashMap   不同步、空鍵值、效率高;  Hashtable   同步、非空鍵值、效率略低 ;而在J2ME中不支援HashMap ,因為me中不支援空鍵值,所以在me中只能使用hashtable、咳咳、言歸正傳,我這裡使用hashmap主要是為了存入多個音訊的ID,播放的時候可以同時播放多個音訊。
上面也介紹了,SoundPool可以支援多個音訊同時播放,而且SoundPool在播放的時候呼叫的這個方法(備註3)soundPool.play(loadId, currentVol, currentVol, 1, 0, 1f); 第一個引數指的就是之前的loadId !是通過 soundPool.load(context, R.raw.himi_ogg, 1);方法取出來的,
那麼除此之外還要注意一點的就是定義hashmap的時候一定要定義成這種形式HashMap<Integer, Integer> hm = new Hash<Integer, Integer>,宣告此雜湊表就是一個key和volue值都是Integer的雜湊表! 為什麼要這麼做,因為如果你只是簡單的定義成 HashMap hm =new HashMap(),那麼當你在播放的時候,也就是備註4方法這裡的第一個id引數使用Hashmap.get()這個方法的時候總會出現錯誤的提示!
《SoundPool最大隻能申請1M的記憶體空間,這就意味著我們只能使用一些很短的聲音片段》為什麼只能使用一些很短的聲音呢?
大家還是看備註4方法的第一個引數,這裡要求傳入的Id型別是個int值,那麼這個int其實對應的是通過load()方法返回的音訊id,而且這個id會因音訊檔案的大小而變大變小,那麼一旦我們的音訊檔案超過int最大值,那麼就會報記憶體錯誤的異常。所以為什麼用SoundPool只能播放一些簡短的音訊這就是其原因了。當然os 裡為什麼這麼定義 我也無從查證和說明。
備註4 :此方法中引數的解釋
第一個引數是我通過SoundPool.load()方法返回的音訊對應id,第二個第三個引數表示左右聲道大小,第四個引數是優先順序,第五個引數是迴圈次數,最後一個是播放速率(1.0 =正常播放,範圍是0.5至2.0)
備註2:
 這裡是通過媒體服務得到一個音訊管理器,從而來對音量大小進行調整。這裡要強調一下,調整音訊是用這個音訊管理器呼叫setStreamVolume()的方式去調整,而不是MediaPlayer.setVolue(int LeftVolume,int RightVolume);這個方法的兩個引數也是調正左右聲道而不是調節聲音大小。
   好了,對此我們對遊戲開發中到底需要用什麼來做進行了分析,總結就是SoundPool適合做特效聲,其實播放背景音樂我感覺還是用MediaPlayer比較好,當然啦,用什麼都看大家喜好和選擇啦!下面附上專案下載地址:(專案10+MB因為含有res音訊檔案)
有人問  怎麼才知道一首歌曲播放完了,那麼這裡給說下:
PlaybackCompleted狀態:檔案正常播放完畢,而又沒有設定迴圈播放的話就進入該狀態,並會觸發OnCompletionListener的onCompletion()方法。此時可以呼叫start()方法重新從頭播放檔案,也可以stop()停止MediaPlayer,或者也可以seekTo()來重新定位播放位置。
注意:1、 別忘記繫結操作! mp.setOnCompletionListener(this);
2、如果你設定了迴圈播放  mp.setLooping(true); 的話,那麼永遠都不會監聽到播放完成的狀態!!!!這裡一定要注意!
ndroid中音訊和視訊的播放我們最先想到的就是MediaPlayer類了,該類提供了播放、暫停、停止、和重複播放等方法。該類位於android.media包下,詳見API文件。其實除了這個類還有一個音樂播放類那就是SoundPool,這兩個類各有不同分析一下便於大家理解
MediaPlayer:
   此類適合播放較大檔案,此類檔案應該儲存在SD卡上,而不是在資原始檔裡,還有此類每次只能播放一個音訊檔案。
此類用法如下:
    1、從資原始檔中播放
              MediaPlayer   player  =   new MediaPlayer.create(this,R.raw.test);
              player.stare();
    2、從檔案系統播放
              MediaPlayer   player  =   new MediaPlayer();
              String  path   =  "/sdcard/test.mp3";
               player.setDataSource(path);
               player.prepare();
               player.start();
    3、從網路播放
        (1)通過URI的方式:
              String path="http://**************.mp3";     //這裡給一個歌曲的網路地址就行了
                Uri  uri  =  Uri.parse(path);
                MediaPlayer   player  =   new MediaPlayer.create(this,uri);
                player.start();
        (2)通過設定資料來源的方式:
             MediaPlayer   player  =   new MediaPlayer.create();
             String path="http://**************.mp3";          //這裡給一個歌曲的網路地址就行了
             player.setDataSource(path);
             player.prepare();
             player.start();
 SoundPool:
  此類特點就是低延遲播放,適合播放實時音實現同時播放多個聲音,如遊戲中炸彈的爆炸音等小資原始檔,此類音訊比較適合放到資原始檔夾 res/raw下和程式一起打成APK檔案。
  用法如下:
        SoundPool soundPool = new SoundPool(4, AudioManager.STREAM_MUSIC, 100);
        HashMap<Integer, Integer> soundPoolMap = new HashMap<Integer, Integer>();  
        soundPoolMap.put(1, soundPool.load(this, R.raw.dingdong1, 1));        
        soundPoolMap.put(2, soundPool.load(this, R.raw.dingdong2, 2));      
        public void playSound(int sound, int loop) {
            AudioManager mgr = (AudioManager)this.getSystemService(Context.AUDIO_SERVICE);   
            float streamVolumeCurrent = mgr.getStreamVolume(AudioManager.STREAM_MUSIC);   
            float streamVolumeMax = mgr.getStreamMaxVolume(AudioManager.STREAM_MUSIC);       
           float volume = streamVolumeCurrent/streamVolumeMax;   
           soundPool.play(soundPoolMap.get(sound), volume, volume, 1, loop, 1f);
           //引數:1、Map中取值   2、當前音量     3、最大音量  4、優先順序   5、重播次數   6、播放速度
}   
      this.playSound(1, 0);
 
package com.robert.sound;    
import java.util.HashMap;    
import android.content.Context;    
import android.graphics.Canvas;    
import android.graphics.Color;    
import android.graphics.Paint;    
import android.media.AudioManager;    
import android.media.MediaPlayer;    
import android.media.SoundPool;    
import android.view.KeyEvent;    
import android.view.MotionEvent;    
import android.view.SurfaceHolder;    
import android.view.SurfaceView;    
import android.view.SurfaceHolder.Callback;    
public class MySurfaceView extends SurfaceView implements Callback, Runnable {    
    private Thread th;    
    private SurfaceHolder sfh;    
    private Canvas canvas;    
    private MediaPlayer player;    
    private Paint paint;    
    private boolean ON = true;    
    private int currentVol, maxVol;    
    private AudioManager am;     
    private HashMap<Integer, Integer> soundPoolMap;//備註1    
    private int loadId;    
    private SoundPool soundPool;    
    public MySurfaceView(Context context) {    
        super(context);    
// 獲取音訊服務然後強轉成一個音訊管理器,後面方便用來控制音量大小用    
        am = (AudioManager) MainActivity.instance    
                .getSystemService(Context.AUDIO_SERVICE);    
        maxVol = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);    
        // 獲取最大音量值(15最大! .不是100!)    
        sfh = this.getHolder();    
        sfh.addCallback(this);    
        th = new Thread(this);    
        this.setKeepScreenOn(true);    
        setFocusable(true);    
        paint = new Paint();    
        paint.setAntiAlias(true);    
        //MediaPlayer的初始化    
        player = MediaPlayer.create(context, R.raw.himi);     
        player.setLooping(true);//設定迴圈播放    
        //SoundPool的初始化    
        soundPool = new SoundPool(4, AudioManager.STREAM_MUSIC, 100);    
        soundPoolMap = new HashMap<Integer, Integer>();    
        soundPoolMap.put(1, soundPool.load(MainActivity.content,    
                R.raw.himi_ogg, 1));    
        loadId = soundPool.load(context, R.raw.himi_ogg, 1);    
//load()方法的最後一個引數他標識優先考慮的聲音。目前沒有任何效果。使用了也只是對未來的相容性價值。    
    }    
    public void surfaceCreated(SurfaceHolder holder) {    
        /*  
         * Android OS中,如果你去按手機上的調節音量的按鈕,會分兩種情況,  
         * 一種是調整手機本身的鈴聲音量,一種是調整遊戲,軟體,音樂播放的音量  
         * 當我們在遊戲中的時候 ,總是想調整遊戲的音量而不是手機的鈴聲音量,  
         * 可是煩人的問題又來了,我在開發中發現,只有遊戲中有聲音在播放的時候  
         * ,你才能去調整遊戲的音量,否則就是手機的音量,有沒有辦法讓手機只要是  
         * 在運行遊戲的狀態就只調整遊戲的音量呢?試試下面這段程式碼吧!  
         */    
        MainActivity.instance.setVolumeControlStream(AudioManager.STREAM_MUSIC);    
        // 設定調整音量為媒體音量,當暫停播放的時候調整音量就不會再預設調整鈴聲音量了,娃哈哈    
            
        player.start();    
        th.start();    
    }    
    public void draw() {    
        canvas = sfh.lockCanvas();    
        canvas.drawColor(Color.WHITE);    
        paint.setColor(Color.RED);    
        canvas.drawText("當前音量: " + currentVol, 100, 40, paint);    
        canvas.drawText("當前播放的時間" + player.getCurrentPosition() + "毫秒", 100,    
                70, paint);    
        canvas.drawText("方向鍵中間按鈕切換 暫停/開始", 100, 100, paint);    
        canvas.drawText("方向鍵←鍵快退5秒 ", 100, 130, paint);    
        canvas.drawText("方向鍵→鍵快進5秒 ", 100, 160, paint);    
        canvas.drawText("方向鍵↑鍵增加音量 ", 100, 190, paint);    
        canvas.drawText("方向鍵↓鍵減小音量", 100, 220, paint);    
        sfh.unlockCanvasAndPost(canvas);    
    }    
    private void logic() {    
        currentVol = am.getStreamVolume(AudioManager.STREAM_MUSIC);// 不斷獲取當前的音量值    
    }    
    @Override    
    public boolean onKeyDown(int key, KeyEvent event) {    
        if (key == KeyEvent.KEYCODE_DPAD_CENTER) {    
            ON = !ON;    
            if (ON == false)    
                player.pause();    
            else    
                player.start();     
        } else if (key == KeyEvent.KEYCODE_DPAD_UP) {// 按鍵這裡本應該是RIGHT,但是因為當前是橫屏模式,以下雷同    
            player.seekTo(player.getCurrentPosition() + 5000);    
        } else if (key == KeyEvent.KEYCODE_DPAD_DOWN) {    
            if (player.getCurrentPosition() < 5000) {    
                player.seekTo(0);    
            } else {    
                player.seekTo(player.getCurrentPosition() - 5000);    
            }    
        } else if (key == KeyEvent.KEYCODE_DPAD_LEFT) {    
            currentVol += 1;    
            if (currentVol > maxVol) {    
                currentVol = 100;    
            }    
            am.setStreamVolume(AudioManager.STREAM_MUSIC, currentVol,// 備註2    
                    AudioManager.FLAG_PLAY_SOUND);    
        } else if (key == KeyEvent.KEYCODE_DPAD_RIGHT) {    
            currentVol -= 1;    
            if (currentVol <= 0) {    
                currentVol = 0;    
            }    
            am.setStreamVolume(AudioManager.STREAM_MUSIC, currentVol,    
                    AudioManager.FLAG_PLAY_SOUND);    
        }    
        soundPool.play(loadId, currentVol, currentVol, 1, 0, 1f);// 備註3    
//      soundPool.play(soundPoolMap.get(1), currentVol, currentVol, 1, 0, 1f);//備註4    
//      soundPool.pause(1);//暫停SoundPool的聲音     
        return super.onKeyDown(key, event);    
    }     
    @Override    
    public boolean onTouchEvent(MotionEvent event) {    
        return true;    
    }     
    public void run() {    
        // TODO Auto-generated method stub    
        while (true) {    
            draw();    
            logic();    
            try {    
                Thread.sleep(100);    
            } catch (Exception ex) {    
            }    
        }    
    }     
    public void surfaceChanged(SurfaceHolder holder, int format, int width,    
            int height) {      
    }     
    public void surfaceDestroyed(SurfaceHolder holder) {      
    }     
}