Android中播放音樂的幾種方式

前言

前幾天一直在研究RxJava2,也寫了記錄了幾篇博客,但由於工做任務緣由,須要研究音頻相關的知識,暫時放下Rxjava,本文的demo中,MediaPalyer 部分使用RxJava編寫一點邏輯,其中涉及,RxJava2的被壓、解除訂閱等知識點,雖然簡單,最起碼沒有丟了RxJava,後續Rxjava會繼續研究,作記錄.java

andorid提供了對聲音和視頻處理的api包android.media.本文編寫了針對這幾種方式播放的Demo,文章最後貼出。android

1、MediaPlayer播放音頻

對於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中方式緩存

  • setDataSource (String path)
//從sd卡中加載音樂
mMediaPlayer.setDataSource("../music/samsara.mp3") ;
//從網路加載音樂
mMediaPlayer.setDataSource("http://..../xxx.mp3") ;
//需使用異步緩衝
mMediaPlayer.prepareAsync() ;
  • setDataSource (FileDescriptor fd)
    //需將資源文件放在assets文件夾
     AssetFileDescriptor fd = getAssets().openFd("samsara.mp3");
     mMediaPlayer.setDataSource(fd)
     mMediaPlayer.prepare() ;
     
     Ps:此方法系統需大於等於android 
  • setDataSource (Context context, Uri uri)
    這個方法沒什麼好說的,通常經過ContentProvider獲取Android系統提供
    的共享music獲取uri,而後設置數據播放
  • setDataSource (FileDescriptor fd, long offset, long length)
    //需將資源文件放在assets文件夾
     AssetFileDescriptor fd = getAssets().openFd("samsara.mp3");
     mMediaPlayer.setDataSource(fd, fd.getStartOffset(), fd.getLegth())
     mMediaPlayer.prepare() ;

    4. 注意點網絡

  • 設置完數據源,不要忘記prepare(),儘可能使用異步prepareAync(),這樣不會阻塞UI線程。
  • 播放完畢即便釋放資源 

    mediaPlayer.stop(); 
    mediaPlayer.release(); 
    mediaPlayer = null; 
  • 不足

    資源佔用量較高、延遲時間較長、不支持多個音頻同時播放等dom

  • ### MeidaPlayer demo片斷
//建立播放時間格式化工具
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();
        }
 
 
    }

2、SoundPool播放音頻

SoundPool支持多個音頻文件同時播放(組合音頻也是有上限的),延時短,比較適合短促、密集的場景,是遊戲開發中音效播放的福音。異步

SoundPool實例化方式

1. new SoundPool(適用與5.0如下)
SoundPool(int maxStreams, int streamType, int srcQuality)
從android5.0開始此方法被標記爲過期,稍微說如下幾個參數。
1.maxStreams :容許同時播放的流的最大值
 
2.streamType :音頻流的類型描述,
               在Audiomanager中有種類型聲明,遊戲應用一般會使用流媒體音樂。
3. srcQuality:採樣率轉化質量
2. SoundPool.Builder(從5.0開始支持)
//設置描述音頻流信息的屬性
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() ;
3. 幾個重要的方法
// 幾個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

4. 區分2個概念

看了Sounpool的api,是否是感受對 streamID 和 soundID 一臉懵逼? 
1. soundID:加載音樂資源時的返回值,int load(String path, int priority),這個int返回值就是soundID 
2. streamID:播放時返回的值,即play()方法的返回值ide

這兩個值都很重要,須要緩存下來

5. SoundPool Demo片斷
注:我把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) ;
    }

3、AudioTrack播放音頻

AudioTrack屬於更偏底層的音頻播放,MediaPlayerService的內部就是使用了AudioTrack。
 
AudioTrack用於單個音頻播放和管理,相比於MediaPlayer具備:精煉、高效的優勢。
更適合實時產生播放數據的狀況,如加密的音頻,
MediaPlayer是一籌莫展的,AudioTrack卻能夠。
 
AudioTrack用於播放PCM(PCM無壓縮的音頻格式)音樂流的回放,
若是須要播放其它格式音頻,須要響應的解碼器,
這也是AudioTrack用的比較少的緣由,須要本身解碼音頻。

AudioTreack的2種播放模式

靜態模式—static

靜態的言下之意就是數據一次性交付給接收方。好處是簡單高效,只須要進行一次操做就完成了數據的傳遞;缺點固然也很明顯,對於數據量較大的音頻回放,顯然它是沒法勝任的,於是一般只用於播放鈴聲、系統提醒等對內存小的操做

流模式streaming流模式和網絡上播放視頻是相似的,即數據是按照必定規律不斷地傳遞給接收方的。理論上它可用於任何音頻播放的場景,不過咱們通常在如下狀況下采用:

  • 音頻文件過大

  • 音頻屬性要求高,好比採樣率高、深度大的數據

  • 音頻數據是實時產生的,這種狀況就只能用流模式了
經過write(byte[], int, int), write(short[], int, int)
write(float[], int, int, int)等方法推送解碼數據到AudioTrack

使用Demo

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/

4、AsyncPlayer播放音頻

1.介紹

從名字就可看出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();
            ....
 
    }

2.簡單demo

JetPlayer播放音頻

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() ;
    }

5、Ringtone

Ringtone爲鈴聲、通知和其餘相似聲音提供快速播放的方法,這裏還不得不提到一個管理類」RingtoneManager」,提供系統鈴聲列表檢索方法,而且,Ringtone實例須要從RingtoneManager獲取。

1. 獲取實例

獲取實例方法,均爲RingtoneManager類提供
 
//經過鈴聲uri獲取
static Ringtone getRingtone(Context context, Uri ringtoneUri)
 
//經過鈴聲檢索位置獲取
Ringtone getRingtone(int position)

其實,Rington這個類比較簡單,只須要掌握,播放、中止(paly(),stop())等方法就能夠了,而RingtoneManager倒是比較重要的。

2. 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"/>

總結

以上介紹了幾種播放方式,能夠說各有優劣。 
本文寫的不夠詳細,只是大概的介紹一下幾種播放方式。在此作下簡單的總結。

  1. 對於延遲度要求不高,而且但願可以更全面的控制音樂的播放,MediaPlayer比較適合
  2. 聲音短小,延遲度小,而且須要幾種聲音同時播放的場景,適合使用SoundPool
  3. 對於簡單的播放,不須要複雜控制的播放,能夠給使用AsyncPlayer,全部操做均在子線程不阻塞UI
  4. 播放大文件音樂,如WAV無損音頻和PCM無壓縮音頻,可以使用更底層的播放方式AudioTrack。它支持流式播放,能夠讀取(可來自本地和網絡)音頻流,卻播放延遲較小。 
    ps:據我測試AudioTrack直接支持WAV和PCM,其餘音頻須要解碼成PCM格式才能播放。(其餘無損格式沒有嘗試,有興趣可使本文提供的例子測試一下)
  5. .jet的音頻比較少見(有的遊戲中在使用),可以使用專門的播放器JetPlayer播放
  6. 對於系統類聲音的播放和操做,Ringtone更適合(主要是掌握好RingtoneManager)

android.media包中提供的播放音頻的方式,遠不止這些,本文只是參考api和其餘大牛的博客作一些研究和記錄,android.media種還有不少只是等着咱們探索……

相關文章
相關標籤/搜索