现在的位置: 首页 > 综合 > 正文

Android 下音频播放<一> MediaPlayer

2018年02月10日 ⁄ 综合 ⁄ 共 11100字 ⁄ 字号 评论关闭


最近在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没来的及加入)

抱歉!评论已关闭.