Android 下音頻播放 MediaPlayer


最近在Android遊戲開發中,需要處理WAV和OGG的播放。其中背景音樂需求爲一路WAV或者OGG。在Android.media Package中。選中了MediaPlayer作爲背景音樂的播放。


0. 簡介:
android.media.MediaPlayer class用來控制播放Audio/Video 文件和流。(不光是文件,還支持流)


1. Playback狀態機:
可以將播放文件和流看作狀態機管理。下圖詳細描述了各狀態以及如何在各狀態間轉移。
注:單箭頭表示同步調用。雙箭頭表示異步調用。
Android <wbr>下音頻播放<一> <wbr> <wbr>MediaPlayer


現在詳細分析每個狀態以及狀態間轉移。

1.1:Idle狀態
當MediaPlayer object剛被使用new 創建,或者調用reset()後,此MediaPlayer object處於Idle狀態。

兩種方法進入Idle狀態,有個細小卻重要的區別:當程序在Idle狀態錯誤的調用了類似getCurrentPosition(),GetDuration(),getVideoight(),setAudioStreamType(), setLooping(),setVolume(),pause(),start(),stop(),seekTo(),prepare()等方法。
如果MediaPlayer object剛被new 出來,處於idle狀態。用戶使用OnErrorListener.onError()註冊的callback函數不會被內在palyer Engine調用,且狀態不會發生改變,繼續爲idle.
如果是reset() 後處於idle狀態,則註冊的callback函數被調用。且MediaPlayer object會轉爲Error狀態。




1.2:End狀態:
當MediaPlayer object調用release() 後,它處於End 狀態。

如果MediaPlayer object不再使用時,請立即調用release(),這樣,內部player engine可以釋放掉他們所佔用的資源。 這類資源,不光包括內存資源,還包括Audio,Video硬件解碼設備。
當用戶進入End狀態後,沒有任何渠道去轉回其它狀態。


1.3:Initialized狀態
當MediaPlayer Object爲Idle狀態時,調用setDataSource(FileDescriptor), or setDataSource(String), or setDataSource(Context, Uri), or setDataSource(FileDescriptor, long, long)轉變其狀態到Initialized狀態。

如果setDataSource()在非Idle狀態調用, IllegalStateException會被拋出。
所以,setDataSource()時必須捕獲IllegalArgumentException and IOException異常。


1.4:Prepared狀態
MediaPlayer Object 必須首先進入Prepared狀態,之後才能開始start.

有兩種方式進入Prepared狀態:
調用prepare() 則以同步方式進入Prepared狀態。當prepare()返回時,MediaPlayer Object 已經進入Prepared狀態。
調用prepareAsync()則以異步方式進入Prepared狀態。

當在錯誤狀態調用prepare() or prepareAsync() ,則拋出異常IllegalStateException。


1.5: Started狀態
要播放,則需要調用start(),當start()返回successfully時,MediaPlayer Object則進入Started狀態。
isPlaying()可以測試是否位於Started 狀態。

在MediaPlayer Object處於Started狀態時,調用start()沒有任何效果。


1.6:Paused狀態
調用pause(),當其返回時,MediaoPlayer Object進入Paused狀態。從started狀態到pasued狀態,Play Engine有缺陷。所以導致isPlaying()...

MediaPlayer進入Paused狀態後,調用start()重新開始播放(Resume).但Position是從剛纔暫停處開始播放,而不是從頭。且狀態變化爲Started。

在Paused狀態,再次調用pause()沒有反應 。

1.7 Stop狀態
在Started,Paused,Prepared或者PlaybackCompleted狀態,調用stop(),則進入Stopped狀態。

在Stopped狀態,MediaPlayer Object不能再次調用start()去播放,除非調用prepare()或prepareAsync()使其進入Prepared狀態。

在Stopped狀態下再次調用stop.無效。


1.8 PlaybackCompleted狀態
當Video/Audio播放完畢後,進入PlaybackCompleted模式。

但如果使用setLooping(true),則播放完畢後,立刻重新播放。且狀態繼續爲started.
如果setLooping(false).播放完畢後,MediaPlayer Object進入PlaybackCompleted模式,並調用setOnCompletionListener(OnCompletionListener)註冊的callback函數。

如在 PlaybackCompleted狀態下,調用start(),會進入started模式,並從頭播放。






2. 回調機制
MediaPlayer使用一些方法來設置回調函數,當發生某種特定情況時,內部palyer Engine會調用之。
使用方法,可以在稍後的例子中看到。


3. 播放指針
可以調用 seekTo()來調整當前播放指針。


下表列出了各種方法在哪些狀態下調用合法,在哪些狀態下非法以及導致的後果:

Valid and invalid states

Method Name

 

Valid Sates

 

Invalid States

 

Comments

 

attachAuxEffect

 

{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted}

 

{Idle, Error}

 

This method must be called after setDataSource. Calling it does not change the object state.

 

getAudioSessionId

 

any

 

{}

 

This method can be called in any state and calling it does not change the object state.

 

getCurrentPosition

 

{Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted}

 

{Error}

 

Successful invoke of this method in a valid state does not change the state. Calling this method in an invalid state transfers the object to the Error state.

 

getDuration

 

{Prepared, Started, Paused, Stopped, PlaybackCompleted}

 

{Idle, Initialized, Error}

 

Successful invoke of this method in a valid state does not change the state. Calling this method in an invalid state transfers the object to the Error state.

 

getVideoHeight

 

{Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted}

 

{Error}

 

Successful invoke of this method in a valid state does not change the state. Calling this method in an invalid state transfers the object to the Error state.

 

getVideoWidth

 

{Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted}

 

{Error}

 

Successful invoke of this method in a valid state does not change the state. Calling this method in an invalid state transfers the object to the Error state.

 

isPlaying

 

{Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted}

 

{Error}

 

Successful invoke of this method in a valid state does not change the state. Calling this method in an invalid state transfers the object to the Error state.

 

pause

 

{Started, Paused}

 

{Idle, Initialized, Prepared, Stopped, PlaybackCompleted, Error}

 

Successful invoke of this method in a valid state transfers the object to thePaused state. Calling this method in an invalid state transfers the object to the Error state.

 

prepare

 

{Initialized, Stopped}

 

{Idle, Prepared, Started, Paused, PlaybackCompleted, Error}

 

Successful invoke of this method in a valid state transfers the object to thePrepared state. Calling this method in an invalid state throws an IllegalStateException.

 

prepareAsync

 

{Initialized, Stopped}

 

{Idle, Prepared, Started, Paused, PlaybackCompleted, Error}

 

Successful invoke of this method in a valid state transfers the object to thePreparing state. Calling this method in an invalid state throws an IllegalStateException.

 

release

 

any

 

{}

 

After release(), the object is no longer available.

 

reset

 

{Idle, Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, Error}

 

{}

 

After reset(), the object is like being just created.

 

seekTo

 

{Prepared, Started, Paused, PlaybackCompleted}

 

{Idle, Initialized, Stopped, Error}

 

Successful invoke of this method in a valid state does not change the state. Calling this method in an invalid state transfers the object to the Error state.

 

setAudioSessionId

 

{Idle}

 

{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, Error}

 

This method must be called in idle state as the audio session ID must be known before calling setDataSource. Calling it does not change the object state.

 

setAudioStreamType

 

{Idle, Initialized, Stopped, Prepared, Started, Paused, PlaybackCompleted}

 

{Error}

 

Successful invoke of this method does not change the state. In order for the target audio stream type to become effective, this method must be called before prepare() or prepareAsync().

 

setAuxEffectSendLevel

 

any

 

{}

 

Calling this method does not change the object state.

 

setDataSource

 

{Idle}

 

{Initialized, Prepared, Started, Paused, Stopped, PlaybackCompleted, Error}

 

Successful invoke of this method in a valid state transfers the object to theInitialized state. Calling this method in an invalid state throws an IllegalStateException.

 

setDisplay

 

any

 

{}

 

This method can be called in any state and calling it does not change the object state.

 

setSurface

 

any

 

{}

 

This method can be called in any state and calling it does not change the object state.

 

setLooping

 

{Idle, Initialized, Stopped, Prepared, Started, Paused, PlaybackCompleted}

 

{Error}

 

Successful invoke of this method in a valid state does not change the state. Calling this method in an invalid state transfers the object to the Error state.

 

isLooping

 

any

 

{}

 

This method can be called in any state and calling it does not change the object state.

 

setOnBufferingUpdateListener

 

any

 

{}

 

This method can be called in any state and calling it does not change the object state.

 

setOnCompletionListener

 

any

 

{}

 

This method can be called in any state and calling it does not change the object state.

 

setOnErrorListener

 

any

 

{}

 

This method can be called in any state and calling it does not change the object state.

 

setOnPreparedListener

 

any

 

{}

 

This method can be called in any state and calling it does not change the object state.

 

setOnSeekCompleteListener

 

any

 

{}

 

This method can be called in any state and calling it does not change the object state.

 

setScreenOnWhilePlaying any

 

{}

 

This method can be called in any state and calling it does not change the object state.

 

setVolume

 

{Idle, Initialized, Stopped, Prepared, Started, Paused, PlaybackCompleted}

 

{Error}

 

Successful invoke of this method does not change the state.
setWakeMode

 

any

 

{}

 

This method can be called in any state and calling it does not change the object state.

 

start

 

{Prepared, Started, Paused, PlaybackCompleted}

 

{Idle, Initialized, Stopped, Error}

 

Successful invoke of this method in a valid state transfers the object to theStarted state. Calling this method in an invalid state transfers the object to the Error state.

 

stop

 

{Prepared, Started, Stopped, Paused, PlaybackCompleted}

 

{Idle, Initialized, Error}

 

Successful invoke of this method in a valid state transfers the object to theStopped state. Calling this method in an invalid state transfers the object to the Error state.

 




例子程序如下:
package com.Android.AudioPlayer;

import android.media.MediaPlayer;
import android.util.Log;

import java.io.IOException;
import java.lang.String;


public class MusicPlayer {
static public int Object_Num = 0;
static String TAG = "MusicPlayer";
static MediaPlayer mMP;
static int seekend = 0;
//0: idle.  1: Initialized    2: Prepared  3: Started    4:Paused   5:Stoped   6:PlaybackComplete
static int mode = -1; // -1: End 
private static class BufferUpdateListener implements MediaPlayer.OnBufferingUpdateListener
{
public void onBufferingUpdate(MediaPlayer mp, int percent)
{
Log.w(TAG, "updateBuffer.");
return;
}
}
private static class CompletionListener implements MediaPlayer.OnCompletionListener
{
public void onCompletion(MediaPlayer mp)
{
Log.w(TAG, "Mediaplay completion");
return ;
}
}
private static class ErrorListener implements MediaPlayer.OnErrorListener
{
public boolean onError(MediaPlayer mp, int what, int extra)
{
Log.w(TAG, "Mediaplay Error");
return false;
}
}
private static class InfoListener implements MediaPlayer.OnInfoListener
{
public boolean onInfo(MediaPlayer mp, int what, int extra)
{
Log.w(TAG, String.format("MediaPlayer Info:[%d]", what));
return false;
}
}
private static class PreparedListener implements MediaPlayer.OnPreparedListener
{
public void onPrepared(MediaPlayer mp)
{
Log.w(TAG, "Mediaplay onPrepared");
return;
}
}
private static class SeekCompleteListener implements MediaPlayer.OnSeekCompleteListener
{
public void onSeekComplete(MediaPlayer mp)
{
Log.w(TAG, "Mediaplay SeekComplete");
seekend = 1;
return;
}
}
public void LoadMusic(String MusicFile)
{
if(Object_Num > 0)
{
unLoadMusic();
}
MediaPlayer mp = new MediaPlayer();
mode = 0;  //idle
// set 
mp.setOnBufferingUpdateListener(new BufferUpdateListener());
mp.setOnCompletionListener(new CompletionListener());
mp.setOnErrorListener(new ErrorListener());
mp.setOnPreparedListener(new PreparedListener());
mp.setOnSeekCompleteListener(new SeekCompleteListener());
mp.setOnInfoListener(new InfoListener());
//int id = mp.getAudioSessionId();
int position = mp.getCurrentPosition();
Log.w(TAG, String.format("Create MediaPlayer. Position :[%d]", position));
mMP = mp;
try {
mp.setDataSource(MusicFile);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
Log.w(TAG, "setDataSource:IllegalArgumentException");
e.printStackTrace();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
Log.w(TAG, "setDataSource:IllegalStateException");
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
Log.w(TAG, "setDataSource:IOException");
e.printStackTrace();
}
mode = 1;  //Initialized
try {
mp.prepare();
} catch (IllegalStateException e) {
// TODO Auto-generated catch block
Log.w(TAG, "prepare:IllegalStateException");
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
Log.w(TAG, "prepare:IOException");
e.printStackTrace();
}
mode = 2; //prepared
Object_Num++;
return;
}
public void unLoadMusic()
{
if(Object_Num == 1)
{
mMP.stop();
mMP.release();
mode = -1;  //end
Object_Num--;
}
return;
}
public void PlayMusic(boolean looping)
{
if(Object_Num == 1)
{
//Prepared  Started    Paused   PlaybackComplete
if(mode == 2 || mode == 4 || mode == 6 || mode == 3)  
{
mMP.seekTo(0);
mMP.setLooping(looping);
mMP.start();
mode = 3;
}
}
return;
}
public void StopMusic()
{
if(Object_Num == 1)
{
// started or paused
if(mode == 3 || mode == 4)
{
mMP.pause();
mMP.seekTo(0);
mode = 4;
}
}
return;
}
public void PauseMusic()
{
if(Object_Num == 1)
{
if(mode == 3 || mode == 4)
mMP.pause();
}
return;
}
public void ResumeMusic()
{
if(Object_Num == 1)
{
if(mode == 4)
mMP.start();
}
return;
}
}


MediaPlayer所支持格式:
這個問題,看到網上有不少人回答。但個人覺得回答並不正確。
MediaPlayer底層具體實現決定了所支持格式。畢竟JAVA層不太可能直接操作硬件去播放Video/Audio. 它也是通過JNI與底層C打交道播放Audio/Video. 那麼底層的實現纔是決定MediaPlayer支持格式的關鍵。

例如:Sam使用Hi3716C來測試,發現其底層是用HiPlayer來實現,支持WAV,OGG等。
但MTK5502平臺,則只支持WAV.(可能OGG沒來的及加入)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章