前幾天一直在研究RxJava2,也寫了記錄了幾篇博客,但由於工做任務緣由,須要研究音頻相關的知識,暫時放下Rxjava,本文的demo中,MediaPalyer 部分使用RxJava編寫一點邏輯,其中涉及,RxJava2的被壓、解除訂閱等知識點,雖然簡單,最起碼沒有丟了RxJava,後續Rxjava會繼續研究,作記錄.java
andorid提供了對聲音和視頻處理的api包android.media.本文編寫了針對這幾種方式播放的Demo,文章最後貼出。android
對於android音頻的播放,這個類多是你們最熟悉的了,從入門就一直想編寫一個本身的音樂播放器,有木有?MediaPlayer確實強大,提供了對音頻播放的各類控制,生命週期:git
1. MediaPlayer支持:AAC、AMR、FLAC、MP三、MIDI、OGG、PCM等格式
2. 播放Raw下的元數據api
//直接建立,不須要設置setDataSource mMediaPlayer=MediaPlayer.create(this, R.raw.audio); mMediaPlayer.start();
3. MediaPlayer設置播放源的4中方式緩存
//從sd卡中加載音樂 mMediaPlayer.setDataSource("../music/samsara.mp3") ; //從網路加載音樂 mMediaPlayer.setDataSource("http://..../xxx.mp3") ; //需使用異步緩衝 mMediaPlayer.prepareAsync() ;
//需將資源文件放在assets文件夾 AssetFileDescriptor fd = getAssets().openFd("samsara.mp3"); mMediaPlayer.setDataSource(fd) mMediaPlayer.prepare() ; Ps:此方法系統需大於等於android
這個方法沒什麼好說的,通常經過ContentProvider獲取Android系統提供
的共享music獲取uri,而後設置數據播放
//需將資源文件放在assets文件夾 AssetFileDescriptor fd = getAssets().openFd("samsara.mp3"); mMediaPlayer.setDataSource(fd, fd.getStartOffset(), fd.getLegth()) mMediaPlayer.prepare() ;
4. 注意點網絡
mediaPlayer.stop();
mediaPlayer.release();
mediaPlayer = null;
資源佔用量較高、延遲時間較長、不支持多個音頻同時播放等dom
//建立播放時間格式化工具 mFormat = new SimpleDateFormat("mm:ss"); //建立MediaPlayer和設置監聽 mPlayer = new MediaPlayer() ; mSeekBar.setOnSeekBarChangeListener(new MySeekBarChangeListener()); mPlayer.setOnPreparedListener(new MyOnPrepareListener()); mPlayer.setOnCompletionListener(new MyOnCompletionListener()); /** * 從assets資源文件夾中播放 * @param name */ private void playSoundFromA(String name) { if (mPlayer.isPlaying()) { mPlayer.stop(); } // 設置當前播放歌曲的名字 title.setText(names[current]); mPlayer.reset(); AssetFileDescriptor afd = getAssetFileDescriptor(name); try { mPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength()); hasResource = true; mPlayer.prepareAsync(); } catch (IOException e) { e.printStackTrace(); } }
SoundPool支持多個音頻文件同時播放(組合音頻也是有上限的),延時短,比較適合短促、密集的場景,是遊戲開發中音效播放的福音。異步
SoundPool(int maxStreams, int streamType, int srcQuality) 從android5.0開始此方法被標記爲過期,稍微說如下幾個參數。 1.maxStreams :容許同時播放的流的最大值 2.streamType :音頻流的類型描述, 在Audiomanager中有種類型聲明,遊戲應用一般會使用流媒體音樂。 3. srcQuality:採樣率轉化質量
//設置描述音頻流信息的屬性 AudioAttributes abs = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .build() ; SoundPool mSoundPoll = new SoundPool.Builder() .setMaxStreams(100) //設置容許同時播放的流的最大值 .setAudioAttributes(abs) //徹底能夠設置爲null .build() ;
// 幾個load方法和上文提到的MediaPlayer基本一致,不作多的解釋 //------------------------------------------------------------ int load(AssetFileDescriptor afd, int priority) int load(Context context, int resId, int priority) int load(String path, int priority) int load(FileDescriptor fd, long offset, long length, int priority) //------------------------------------------------------------- // 經過流id暫停播放 final void pause(int streamID) // 播放聲音,soundID:音頻id; left/rightVolume:左右聲道(默認1,1);loop:循環次數(-1無限循環);rate:播放速率(1爲標準) final int play(int soundID, float leftVolume, float rightVolume, int priority, int loop, float rate) //釋放資源(很重要) final void release() //恢復播放 final void resume(int streamID) //設置指定id的音頻循環播放次數 final void setLoop(int streamID, int loop) //設置加載監聽(由於加載是異步的,須要監聽加載,完成後再播放) void setOnLoadCompleteListener(SoundPool.OnLoadCompleteListener listener) //設置優先級(同時播放個數超過最大值時,優先級低的先被移除) final void setPriority(int streamID, int priority) //設置指定音頻的播放速率,0.5~2.0(rate>1:加快播放,反之慢速播放) final void setRate(int streamID, float rate) //中止指定音頻播放 final void stop(int streamID) //卸載指定音頻 final boolean unload(int soundID) //暫停全部音頻的播放 final void autoPause() //恢復全部暫停的音頻播放 final void autoResum()
以上方法基本上是SoundPool的全部方法了,也都很經常使用。async
看了Sounpool的api,是否是感受對 streamID 和 soundID 一臉懵逼?
1. soundID:加載音樂資源時的返回值,int load(String path, int priority),這個int返回值就是soundID
2. streamID:播放時返回的值,即play()方法的返回值ide
這兩個值都很重要,須要緩存下來
注:我把SoundPool作了簡單封裝,SoundPoolUtil,會在文末上傳, 有興趣可下載看一下,時間比較急,還有不少不足的地方 //初始化SoundPool if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){ AudioAttributes aab = new AudioAttributes.Builder() .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .setUsage(AudioAttributes.USAGE_MEDIA) .build() ; mSoundPool = new SoundPool.Builder() .setMaxStreams(10) .setAudioAttributes(aab) .build() ; }else{ mSoundPool = new SoundPool(60, AudioManager.STREAM_MUSIC,8) ; } mSoundPool = new SoundPool(60, AudioManager.STREAM_MUSIC,8) ; //設置資源加載監聽 mSoundPool.setOnLoadCompleteListener(new MyOnLoadCompleteListener()); //加載資源 /** * 加載指定路徑列表的資源 * @param map */ public void loadR(Map<String, String> map){ Set<Map.Entry<String, String>> entries = map.entrySet(); for(Map.Entry<String, String> entry : entries){ String key = entry.getKey() ; if(checkSoundPool()){ if(!idCache.containsKey(key)){ idCache.put(key, mSoundPool.load(entry.getValue(),1)) ; } } } } /** * 播放指定音頻,並返用於中止、暫停、恢復的StreamId * @param name * @param times * @return */ public int play(String name, int times){ return this.play(name,1,1,1,times,1) ; }
AudioTrack屬於更偏底層的音頻播放,MediaPlayerService的內部就是使用了AudioTrack。
AudioTrack用於單個音頻播放和管理,相比於MediaPlayer具備:精煉、高效的優勢。
更適合實時產生播放數據的狀況,如加密的音頻,
MediaPlayer是一籌莫展的,AudioTrack卻能夠。
AudioTrack用於播放PCM(PCM無壓縮的音頻格式)音樂流的回放,
若是須要播放其它格式音頻,須要響應的解碼器,
這也是AudioTrack用的比較少的緣由,須要本身解碼音頻。
靜態模式—static
靜態的言下之意就是數據一次性交付給接收方。好處是簡單高效,只須要進行一次操做就完成了數據的傳遞;缺點固然也很明顯,對於數據量較大的音頻回放,顯然它是沒法勝任的,於是一般只用於播放鈴聲、系統提醒等對內存小的操做
流模式streaming流模式和網絡上播放視頻是相似的,即數據是按照必定規律不斷地傳遞給接收方的。理論上它可用於任何音頻播放的場景,不過咱們通常在如下狀況下采用:
音頻文件過大
音頻屬性要求高,好比採樣率高、深度大的數據
經過write(byte[], int, int), write(short[], int, int) write(float[], int, int, int)等方法推送解碼數據到AudioTrack
private void jetPlayStream(){ new Thread(new Runnable() { @RequiresApi(api = Build.VERSION_CODES.M) @Override public void run() { // 獲取最小緩衝區 int bufSize = AudioTrack.getMinBufferSize(44100, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT); // 實例化AudioTrack(設置緩衝區爲最小緩衝區的2倍,至少要等於最小緩衝區) AudioTrack audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 44100, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, bufSize*2, AudioTrack.MODE_STREAM); // 設置音量 audioTrack.setVolume(2f) ; // 設置播放頻率 audioTrack.setPlaybackRate(10) ; audioTrack.play(); // 獲取音樂文件輸入流 InputStream is = getResources().openRawResource(R.raw.zbc); byte[] buffer = new byte[bufSize*2] ; int len ; try { while((len=is.read(buffer,0,buffer.length)) != -1){ System.out.println("讀取數據中..."); // 將讀取的數據,寫入Audiotrack audioTrack.write(buffer,0,buffer.length) ; } is.close(); } catch (Exception e) { e.printStackTrace(); } } }).start(); }
更深刻的研究,請參考如下博客
http://blog.csdn.net/edmond999/article/details/18600323
http://blog.csdn.net/conowen/article/details/7799155/
從名字就可看出AsyncPlayer屬於異步播放器,官方給出的說明是:全部工做都在子線程進行,不影響調用線程任何操做。
AsyncPlayer就是對MediaPlayer的一次簡單的封裝,對MediaPlaer全部的操做都在新開線程中執行。
AsyncPlayer只適合簡單的異步播放,不能控制進度,只能開始或中止播放。若是播放在此調用play()方法,AsyncPlayer會中止當前播放,開始新的播放。
播放源碼
/** *內部線程類 **/ private final class Thread extends java.lang.Thread { public void run() { while (true) { Command cmd = null; synchronized (mCmdQueue) { //取出鏈表中新加入的cmd cmd = mCmdQueue.removeFirst(); } switch (cmd.code) { case PLAY: if (mDebug) Log.d(mTag, "PLAY"); //調用MediaPlayer播放 startSound(cmd); break; } .... } } } private void startSound(Command cmd) { // Preparing can be slow, so if there is something else // is playing, let it continue until we're done, so if (mDebug) Log.d(mTag, "Starting playback"); MediaPlayer player = new MediaPlayer(); player.setAudioStreamType(cmd.stream); player.setDataSource(cmd.context, cmd.uri); player.setLooping(cmd.looping); player.prepare(); player.start(); .... }
Jet是由OHA聯盟成員SONiVOX開發的一個交互音樂引擎。其包括兩部分:JET播放器和JET引擎。JET經常使用於控制遊戲的聲音特效,採用MIDI(Musical Instrument Digital Interface)格式。
ps:之後遇到手機中的」.jet」文件,就只到它到底是什麼東東了。。。
//獲取JetPlayer播放器 JetPlayer mJet = JetPlayer.getJetPlayer() ;
// 清空分段隊列,並清除全部要進行播放的剪輯。 1. boolean clearQueue() //每次播放前,記得作一次清空操做 // 加載jet文件的方法 2. boolean loadJetFile(String path) boolean loadJetFile(AssetFileDescriptor afd) // 開始播放 3. boolean play() // 暫停播放 4. boolean pause() // 釋放資源 5. void release() // 指定jet隊列的播放序列(調用play()前須要調用此方法) 6. boolean queueJetSegment(int segmentNum, int libNum, int repeatCount, int transpose, int muteFlags, byte userID)
JetPlayer Demo
private void jetPlayer(){ // 獲取JetPlayer播放器 JetPlayer mJet = JetPlayer.getJetPlayer() ; //清空播放隊列 mJet.clearQueue() ; //綁定事件監聽 mJet.setEventListener(new JetPlayer.OnJetEventListener() { //播放次數記錄 int playNum = 1 ; @Override public void onJetEvent(JetPlayer player, short segment, byte track, byte channel, byte controller, byte value) { Log.i(TAG,"----->onJetEvent") ; } @Override public void onJetUserIdUpdate(JetPlayer player, int userId, int repeatCount) { Log.i(TAG,"----->onJetUserIdUpdate") ; } @Override public void onJetNumQueuedSegmentUpdate(JetPlayer player, int nbSegments) { Log.i(TAG,"----->onJetNumQueuedSegmentUpdate") ; } @Override public void onJetPauseUpdate(JetPlayer player, int paused) { Log.i(TAG,"----->onJetPauseUpdate") ; if(playNum == 2){ playNum = -1 ; //釋放資源,並關閉jet文件 player.release(); player.closeJetFile() ; }else{ playNum++ ; } } }); //加載資源 mJet.loadJetFile(getResources().openRawResourceFd(R.raw.level1)) ; byte sSegmentID = 0 ; //指定播放序列 mJet.queueJetSegment(0, 0, 0, 0, 0, sSegmentID); mJet.queueJetSegment(1, 0, 1, 0, 0, sSegmentID); //開始播放 mJet.play() ; }
Ringtone爲鈴聲、通知和其餘相似聲音提供快速播放的方法,這裏還不得不提到一個管理類」RingtoneManager」,提供系統鈴聲列表檢索方法,而且,Ringtone實例須要從RingtoneManager獲取。
獲取實例方法,均爲RingtoneManager類提供 //經過鈴聲uri獲取 static Ringtone getRingtone(Context context, Uri ringtoneUri) //經過鈴聲檢索位置獲取 Ringtone getRingtone(int position)
其實,Rington這個類比較簡單,只須要掌握,播放、中止(paly(),stop())等方法就能夠了,而RingtoneManager倒是比較重要的。
1. // 兩個構造方法 RingtoneManager(Activity activity) RingtoneManager(Context context) 2. // 獲取指定聲音類型(鈴聲、通知、鬧鈴等)的默認聲音的Uri static Uri getDefaultUri(int type) 3. // 獲取系統全部Ringtone的cursor Cursor getCursor() 4. // 獲取cursor指定位置的Ringtone uri Uri getRingtoneUri(int position) 5. // 判斷指定Uri是否爲默認鈴聲 static boolean isDefault(Uri ringtoneUri) 6. //獲取指定uri的所屬類型 static int getDefaultType(Uri defaultRingtoneUri) 7. //將指定Uri設置爲指定聲音類型的默認聲音 static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri)
從api看,Ringtone和RingtoneManager仍是比較簡單的,很少作解釋了,直接放上一段使用代碼。
/** * 播放來電鈴聲的默認音樂 */ private void playRingtoneDefault(){ Uri uri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_RINGTONE) ; Ringtone mRingtone = RingtoneManager.getRingtone(this,uri); mRingtone.play(); } /** * 隨機播放一個Ringtone(有多是提示音、鈴聲等) */ private void ShufflePlayback(){ RingtoneManager manager = new RingtoneManager(this) ; Cursor cursor = manager.getCursor(); int count = cursor.getCount() ; int position = (int)(Math.random()*count) ; Ringtone mRingtone = manager.getRingtone(position) ; mRingtone.play(); } //記得添加下面兩個權限 <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
以上介紹了幾種播放方式,能夠說各有優劣。
本文寫的不夠詳細,只是大概的介紹一下幾種播放方式。在此作下簡單的總結。
- 對於延遲度要求不高,而且但願可以更全面的控制音樂的播放,MediaPlayer比較適合
- 聲音短小,延遲度小,而且須要幾種聲音同時播放的場景,適合使用SoundPool
- 對於簡單的播放,不須要複雜控制的播放,能夠給使用AsyncPlayer,全部操做均在子線程不阻塞UI
- 播放大文件音樂,如WAV無損音頻和PCM無壓縮音頻,可以使用更底層的播放方式AudioTrack。它支持流式播放,能夠讀取(可來自本地和網絡)音頻流,卻播放延遲較小。
ps:據我測試AudioTrack直接支持WAV和PCM,其餘音頻須要解碼成PCM格式才能播放。(其餘無損格式沒有嘗試,有興趣可使本文提供的例子測試一下)- .jet的音頻比較少見(有的遊戲中在使用),可以使用專門的播放器JetPlayer播放
- 對於系統類聲音的播放和操做,Ringtone更適合(主要是掌握好RingtoneManager)
android.media包中提供的播放音頻的方式,遠不止這些,本文只是參考api和其餘大牛的博客作一些研究和記錄,android.media種還有不少只是等着咱們探索……