android開發播放聲音檔案
阿新 • • 發佈:2019-02-10
有兩種播放音訊形式,第一個: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")這樣不行)。
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) { } }