GVKun编程网logo

Android MediaPlayer架构 -- 前言小知识点(一)(android的mediaplayer)

25

在本文中,我们将给您介绍关于AndroidMediaPlayer架构--前言小知识点的详细内容,并且为您解答一的相关问题,此外,我们还将为您提供关于AndroidMediaPlayer、android

在本文中,我们将给您介绍关于Android MediaPlayer架构 -- 前言小知识点的详细内容,并且为您解答的相关问题,此外,我们还将为您提供关于Android MediaPlayer、android MediaPlayer API、Android Mediaplayer MediaController超时、Android MediaPlayer Playback流程分析的知识。

本文目录一览:

Android MediaPlayer架构 -- 前言小知识点(一)(android的mediaplayer)

Android MediaPlayer架构 -- 前言小知识点(一)(android的mediaplayer)

  在Android中可以使用MediaPlayer+SurfaceView来实现一个简单的多媒体播放器。

一  构造函数

  java MediaPlayer class 的源码位置:frameworks\base\media\java\android\media\MediaPlayer.java

  首先看一下其构造函数:

 1     public MediaPlayer() {
 2 
 3         Looper looper;
 4         if ((looper = Looper.myLooper()) != null) {
 5             mEventHandler = new EventHandler(this,looper);
 6         } else if ((looper = Looper.getMainLooper()) !=  7             mEventHandler =  8         } else {
 9             mEventHandler = ;
10         }
11 
12         mTimeProvider = new TimeProvider();
13         mOpenSubtitleSources = new Vector<InputStream>();
14         IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
15         mAppOps = IAppOpsService.Stub.asInterface(b);
16 
17         /* Native setup requires a weak reference to our object.
18          * It's easier to create it here than in C++.
19          */
20         native_setup(new WeakReference<MediaPlayer>()); // 在实例化MediaPlayer对象时,会调用一个native_setup方法
21     }

  在实例化MediaPlayer对象时,会调用一个native_setup方法:

1 private native final void native_setup(Object mediaplayer_this);

  在JNI代码(frameworks\base\media\jni\android_media_MediaPlayer.cpp)中可以找到对应的native函数:

 1 static void
 2 android_media_MediaPlayer_native_setup(jnienv *env,jobject thiz,jobject weak_this)
{
 4     ALOGV("native_setup" 5     sp<MediaPlayer> mp = new MediaPlayer(); // 实例化一个native MediaPlayer(frameworks\av\media\libmedia\mediaplayer.cpp)
 6     if (mp == NULL) {
 7         jniThrowException(env,java/lang/RuntimeException",1)">Out of memory 8         return 9     }
10 
11     // create new listener and give it to MediaPlayer
12     sp<JNIMediaPlayerListener> listener =  JNIMediaPlayerListener(env,thiz,weak_this);
13     mp->setListener(listener);
14 
15      Stow our new C++ MediaPlayer in an opaque field in the Java object.
16     setMediaPlayer(env,mp);
17 }

  在JNI可以看到,sp<MediaPlayer> mp = new MediaPlayer() 这个native MediaPlayer会去和media service进行交互实现真正的播放功能。

 

 二  设置Listener

  在JNI 函数 android_media_MediaPlayer_native_setup 中有为MediaPlayer设置listener,目的就是通过callback的方式将player的事件上传至java层,以便用户做出对应的处理

1     2     sp<JNIMediaPlayerListener> listener = 3     mp->setListener(listener);

 

  从上面这两行代码可以看到,首先我们实例化一个JNIMediaPlayerListener对象,然后将这个对象传递给了C++ MediaPlayer,在frameworks\av\include\media\mediaplayer.h中定义了listener的接口形式:

 ref-counted object for callbacks
2 class MediaPlayerListener: virtual  RefBase
3 4 :
5     void notify(int msg,int ext1,1)">int ext2,1)">const Parcel *obj) = 06 };

  在JNI代码中,JNIMediaPlayerListener class 正是一个MediaPlayerListener的子类,实现了notify函数,其定义如下:

class JNIMediaPlayerListener:  MediaPlayerListener
{
:
    JNIMediaPlayerListener(jnienv* env,jobject weak_thiz);
    ~JNIMediaPlayerListener();
    const Parcel *obj = NULL);
private:
    JNIMediaPlayerListener();
    jclass      mClass;      Reference to MediaPlayer class
    jobject     mObject;     Weak ref to MediaPlayer Java object to call on
};

 

   callback事件的传递主要是通过notify函数来完成的,下面看一下这个函数的具体实现过程:

void JNIMediaPlayerListener::notify(const Parcel *obj)
{
    jnienv *env = AndroidRuntime::getjnienv();
    if (obj && obj->dataSize() > ) {
        jobject jParcel = createJavaParcelObject(env);
        if (jParcel != NULL) {
            Parcel* nativeParcel = parcelForJavaObject(env,jParcel);
            nativeParcel->setData(obj->data(),obj->dataSize());
            env->CallStaticVoidMethod(mClass,fields.post_event,mObject,msg,ext1,ext2,jParcel);
            env->DeleteLocalRef(jParcel);
        }
    }  {
        env->if (env->ExceptionCheck()) {
        ALOGW(An exception occurred while notifying an event.);
        LOGW_EX(env);
        env->ExceptionClear();
    }
}
  • msg,obj 携带着事件相关信息及数据;

  • 通过env->CallStaticVoidMethod()方法去回调MediaPlayer(java)的 fields.post_event 方法,并将事件信息及数据传递过去;

  • fields.post_event对应于MediaPlayer的java 方法 postEventFromNative;

  • fields.post_event是在MediaPlayer第一次加载时在android_media_MediaPlayer_native_init中初始化的;

三  小结

  这一篇介绍的内容很简单,主要是对java层的MediaPlayer, JNI层,及native层的MediaPlayer如何关联起来的有个基本的认识与了解。

  简单两点概述:

  1. java层的MediaPlayer提供了java API供用户调用实现playback功能,但其功能的实现还会串接到JNI层,在JNI层会去实例化一个native MediaPlayer,这个native MediaPlayer会和MediaPlayerService进行交互实现真正的播放功能;
  2. 设置listener的目的就是通过回调的方式将来自native的事件进行上传,以便用户在java层做出处理,就这篇而言可以暂时这样理解流程: MediaPlayerService-->native MediaPlayer-->JNI->java MediaPlayer ;

 

Android MediaPlayer

Android MediaPlayer

1、实现点击屏幕出现控制按钮,再次点击隐藏按钮。并可以自动隐藏按钮。

public void show(int timeout) {

if (!mShowing) {

top_framelayout.setVisibility(View.VISIBLE);

bottom_framelayout.setVisibility(View.VISIBLE);

mShowing = true;

}

if (timeout != 0) {

Message msg = mHandler.obtainMessage(FADE_OUT);

mHandler.removeMessages(FADE_OUT);

mHandler.sendMessageDelayed(msg, timeout);

}

}


public void hide() {

if (mShowing) {

top_framelayout.setVisibility(View.GONE);

bottom_framelayout.setVisibility(View.GONE);

mShowing = false;

}

}


private Handler mHandler = new Handler() {

@Override

public void handleMessage(Message msg) {

switch (msg.what) {

case FADE_OUT:

hide();

break;

}

}

};


private void toggleMediaControlsVisiblity() {

if (mShowing) {

hide();

} else {

show(sDefaultTimeout);

}

}


// 手势类

public class MySimpleOnGestureListener extends SimpleOnGestureListener {

@Override

public boolean onSingleTapUp(MotionEvent e) {

toggleMediaControlsVisiblity();

return super.onSingleTapUp(e);

}

}

@Override

public boolean onTouchEvent(MotionEvent event) {

return mGestureDetector.onTouchEvent(event);

}

以上实现方式参考了:VideoView源码及其MediaController的源码。

2、

使用MediaPlayer视频播放的时候,直接把拖动条拖动到最后,然后再往回拖动,这时奇葩的事情发生了:视频界面更换了,但是不能播放了,这是怎么回事呢?用VideoView就不会出现这个问题。差距在哪里?

关于点击seekbar可以看到画面但是不能播放的问题, 我认为你使用seekto是需要为mediaPlayer添加OnSeekCompleteListener,因为seekto是个异步方法
mediaPlayer.setOnSeekCompleteListener(new OnSeekCompleteListener() {  
    public void onSeekComplete(MediaPlayer m) {  
        m.start();  
        //isPlaying = true;  
    }  
});  
















android MediaPlayer API

android MediaPlayer API

OSC 请你来轰趴啦!1028 苏州源创会,一起寻宝 AI 时代
MediaPlayer 类可用于控制音频 / 视频文件或流的播放。关于如何使用这个类的方法还可以阅读 VideoView 类的文档。


1.状态图

对播放音频 / 视频文件和流的控制是通过一个状态机来管理的。下图显示一个 MediaPlayer 对象被支持的播放控制操作驱动的生命周期和状态。椭圆代表 MediaPlayer 对象可能驻留的状态。弧线表示驱动 MediaPlayer 在各个状态之间迁移的播放控制操作。这里有两种类型的弧线。由一个箭头开始的弧代表同步的方法调用,而以双箭头开头的代表的弧线代表异步方法调用

 



通过这张图,我们可以知道一个 MediaPlayer 对象有以下的状态:

1)当一个 MediaPlayer 对象被刚刚用 new 操作符创建或是调用了 reset() 方法后,它就处于 Idle 状态。当调用了 release() 方法后,它就处于 End 状态。这两种状态之间是 MediaPlayer 对象的生命周期。

1.1) 在一个新构建的 MediaPlayer 对象和一个调用了 reset() 方法的 MediaPlayer 对象之间有一个微小的但是十分重要的差别。在处于 Idle 状态时,调用 getCurrentPosition()getDuration()getVideoHeight()getVideoWidth()setAudioStreamType(int)setLooping(boolean)setVolume(float, float)pause()start()stop()seekTo(int)prepare() 或者 prepareAsync() 方法都是编程错误。当一个 MediaPlayer 对象刚被构建的时候,内部的播放引擎和对象的状态都没有改变,在这个时候调用以上的那些方法,框架将无法回调客户端程序注册的 OnErrorListener.onError() 方法;但若这个 MediaPlayer 对象调用了 reset() 方法之后,再调用以上的那些方法,内部的播放引擎就会回调客户端程序注册的 OnErrorListener.onError() 方法了,并将错误的状态传入。

1.2) 我们建议,一旦一个 MediaPlayer 对象不再被使用,应立即调用 release() 方法来释放在内部的播放引擎中与这个 MediaPlayer 对象关联的资源。资源可能包括如硬件加速组件的单态组件,若没有调用 release() 方法可能会导致之后的 MediaPlayer 对象实例无法使用这种单态硬件资源,从而退回到软件实现或运行失败。一旦 MediaPlayer 对象进入了 End 状态,它不能再被使用,也没有办法再迁移到其它状态。

1.3) 此外,使用 new 操作符创建的 MediaPlayer 对象处于 Idle 状态,而那些通过重载的 create() 便利方法创建的 MediaPlayer 对象却不是处于 Idle 状态。事实上,如果成功调用了重载的 create() 方法,那么这些对象已经是 Prepare 状态了。 


2) 在一般情况下,由于种种原因一些播放控制操作可能会失败,如不支持的音频 / 视频格式,缺少隔行扫描的音频 / 视频,分辨率太高,流超时等原因,等等。因此,错误报告和恢复在这种情况下是非常重要的。有时,由于编程错误,在处于无效状态的情况下调用了一个播放控制操作可能发生。在所有这些错误条件下,内部的播放引擎会调用一个由客户端程序员提供的 OnErrorListener.onError() 方法。客户端程序员可以通过调用 MediaPlayer.setOnErrorListener(android.media.MediaPlayer.OnErrorListener)方法来注册 OnErrorListener.

2.1) 一旦发生错误,MediaPlayer 对象会进入到 Error 状态。

2.2) 为了重用一个处于 Error 状态的 MediaPlayer 对象,可以调用 reset() 方法来把这个对象恢复成 Idle 状态。

2.3) 注册一个 OnErrorListener 来获知内部播放引擎发生的错误是好的编程习惯。

2.4) 在不合法的状态下调用一些方法,如 prepare()prepareAsync() setDataSource() 方法会抛出 IllegalStateException 异常。 


3) 调用 setDataSource(FileDescriptor) 方法,或 setDataSource(String) 方法,或 setDataSource(Context,Uri) 方法,或 setDataSource(FileDescriptor,long,long) 方法会使处于 Idle 状态的对象迁移到 Initialized 状态。


3.1) 若当此 MediaPlayer 处于其它的状态下,调用 setDataSource() 方法,会抛出 IllegalStateException 异常。

3.2) 好的编程习惯是不要疏忽了调用 setDataSource() 方法的时候可能会抛出的 IllegalArgumentException 异常和 IOException 异常。 


4) 在开始播放之前,MediaPlayer 对象必须要进入 Prepared 状态。

4.1) 有两种方法(同步和异步)可以使 MediaPlayer 对象进入 Prepared 状态:要么调用 prepare() 方法(同步),此方法返回就表示该 MediaPlayer 对象已经进入了 Prepared 状态;要么调用 prepareAsync() 方法(异步),此方法会使此 MediaPlayer 对象进入 Preparing 状态并返回,而内部的播放引擎会继续未完成的准备工作。当同步版本返回时或异步版本的准备工作完全完成时就会调用客户端程序员提供的 OnPreparedListener.onPrepared() 监听方法。可以调用 MediaPlayer.setOnPreparedListener(android.media.MediaPlayer.OnPreparedListener) 方法来注册 OnPreparedListener.

4.2) Preparing 是一个中间状态,在此状态下调用任何具备边影响的方法的结果都是未知的!

4.3) 在不合适的状态下调用 prepare() prepareAsync() 方法会抛出 IllegalStateException 异常。当 MediaPlayer 对象处于 Prepared 状态的时候,可以调整音频 / 视频的属性,如音量,播放时是否一直亮屏,循环播放等。 


5) 要开始播放,必须调用 start() 方法。当此方法成功返回时,MediaPlayer 的对象处于 Started 状态。isPlaying() 方法可以被调用来测试某个 MediaPlayer 对象是否在 Started 状态。

5.1) 当处于 Started 状态时,内部播放引擎会调用客户端程序员提供的 OnBufferingUpdateListener.onBufferingUpdate() 回调方法,此回调方法允许应用程序追踪流播放的缓冲的状态。

5.2) 对一个已经处于 Started 状态的 MediaPlayer 对象调用 start() 方法没有影响。


6) 播放可以被暂停,停止,以及调整当前播放位置。当调用 pause() 方法并返回时,会使 MediaPlayer 对象进入 Paused 状态。注意 Started Paused 状态的相互转换在内部的播放引擎中是异步的。所以可能需要一点时间在 isPlaying() 方法中更新状态,若在播放流内容,这段时间可能会有几秒钟。

6.1) 调用 start() 方法会让一个处于 Paused 状态的 MediaPlayer 对象从之前暂停的地方恢复播放。当调用 start() 方法返回的时候,MediaPlayer 对象的状态会又变成 Started 状态。

6.2) 对一个已经处于 Paused 状态的 MediaPlayer 对象 pause() 方法没有影响。


7) 调用 stop() 方法会停止播放,并且还会让一个处于 StartedPausedPrepared PlaybackCompleted 状态的 MediaPlayer 进入 Stopped 状态。

7.1) 对一个已经处于 Stopped 状态的 MediaPlayer 对象 stop() 方法没有影响。


8) 调用 seekTo() 方法可以调整播放的位置。

8.1) seekTo(int) 方法是异步执行的,所以它可以马上返回,但是实际的定位播放操作可能需要一段时间才能完成,尤其是播放流形式的音频 / 视频。当实际的定位播放操作完成之后,内部的播放引擎会调用客户端程序员提供的 OnSeekComplete.onSeekComplete() 回调方法。可以通过 setOnSeekCompleteListener(OnSeekCompleteListener) 方法注册。

8.2) 注意,seekTo(int) 方法也可以在其它状态下调用,比如 PreparedPaused PlaybackCompleted 状态。此外,目前的播放位置,实际可以调用 getCurrentPosition() 方法得到,它可以帮助如音乐播放器的应用程序不断更新播放进度


9) 当播放到流的末尾,播放就完成了。

9.1) 如果调用了 setLooping(boolean) 方法开启了循环模式,那么这个 MediaPlayer 对象会重新进入 Started 状态。

9.2) 若没有开启循环模式,那么内部的播放引擎会调用客户端程序员提供的 OnCompletion.onCompletion() 回调方法。可以通过调用 MediaPlayer.setOnCompletionListener(OnCompletionListener) 方法来设置。内部的播放引擎一旦调用了 OnCompletion.onCompletion() 回调方法,说明这个 MediaPlayer 对象进入了 PlaybackCompleted 状态。

9.3) 当处于 PlaybackCompleted 状态的时候,可以再调用 start() 方法来让这个 MediaPlayer 对象再进入 Started 状态。 

Android Mediaplayer MediaController超时

Android Mediaplayer MediaController超时

香港专业教育学院实现了流媒体的MP3 URL的mediaplayer和mediacontroller.但是,我在TMobile网络上的设备无法获得很好的3G信号,因此可以在EDGE上运行.我假设媒体播放器因流太慢或不完整而崩溃,是否可以设置超时?

解决方法:

MediaPlayer中没有超时方法,但是您可以自己实现-有多种方法可以执行.
我建议其中之一,我用了自己,对我有用-broadcastReceiver
代码如下所示:

public class ConnectivityCheckingReceiver extends WakefulbroadcastReceiver
{
  private AlarmManager alarmManager;
  private PendingIntent pendingIntent;

  @Override
  public void onReceive(Context context, Intent intent)
  {
    if (MusicService.mediaPlayer != null)
    {
        if (!MusicService.mediaPlayer.isPlaying())
            Log.v("Music", "Music is NOT playing"); 
            //stop service and notify user
        else
            Log.v("Music", "Music is playing");
    }
    else
    {
        Log.v("Music", "User stopped player");
    }
  }
  public void setAlarm (Context context, int hour, int minute, int second)
  {
    alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    Intent intent = new Intent(context, ConnectivityCheckingReceiver.class);
    pendingIntent = PendingIntent.getbroadcast(context, 0, intent, 0);
    Calendar calendar = Calendar.getInstance();
    calendar.set(Calendar.HOUR_OF_DAY, hour);
    calendar.set(Calendar.MINUTE, minute);
    calendar.set(Calendar.SECOND, second);
    alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), pendingIntent);      
  }
}

在活动/服务/片段中添加以下行:

ConnectivityCheckingReceiver conCheck = new ConnectivityCheckingReceiver();
conCheck.setAlarm(context, hour, min, second);

您将需要自己实现小时/分钟/秒检查逻辑,但是使用Joda Time之类的库就可以轻松实现.
并且不要忘记添加到清单中:

<uses-permission android:name="android.permission.WAKE_LOCK" />
<receiver android:name=".receivers.ConnectivityCheckingReceiver" />

附言:我的解决方案不是完美的,但是我没有看到关于此问题的任何好答案,因此,如果找到一个,请分享.

Android MediaPlayer Playback流程分析

Android MediaPlayer Playback流程分析

一个最简单的Media Playback过程如下,仅通过五个步骤就可以完成播放。

String url = "http://........"; // your URL here
MediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
mediaPlayer.setDataSource(url);
mediaPlayer.prepare(); // might take long! (for buffering, etc)
mediaPlayer.start();

初始化MediaPlayer

创建MediaPlayer对象,并完成Java层和Native层的初始化。

frameworks/base/media/java/android/media/MediaPlayer.java

public class MediaPlayer implements SubtitleController.Listener
{
......
    static {
        System.loadLibrary("media_jni");
        native_init(); // native 初始化
    }
    ......
    public MediaPlayer() {

        Looper looper;
        if ((looper = Looper.myLooper()) != null) {
            mEventHandler = new EventHandler(this, looper);
        } else if ((looper = Looper.getMainLooper()) != null) {
            mEventHandler = new EventHandler(this, looper);
        } else {
            mEventHandler = null;
        }

        mTimeProvider = new TimeProvider(this);
        mOutOfBandSubtitleTracks = new Vector<SubtitleTrack>();
        mOpenSubtitleSources = new Vector<InputStream>();
        mInbandSubtitleTracks = new SubtitleTrack[0];
        IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
        mAppOps = IAppOpsService.Stub.asInterface(b);

        /* Native setup requires a weak reference to our object.
         * It''s easier to create it here than in C++.
         */
        native_setup(new WeakReference<MediaPlayer>(this)); // native层setup
    }
    .......

可以看到,MediaPlayer通过静态初始化的方式对native层进行初始化。MediaPlayer对媒体控制的动作都是在底层实现的,所以必须在MediaPlayer创建时优先初始化底层。

frameworks/base/media/jni/android_media_MediaPlayer.cpp

static void
android_media_MediaPlayer_native_init(JNIEnv *env)                                                                                                                                                          
{
    jclass clazz;

    clazz = env->FindClass("android/media/MediaPlayer");
    if (clazz == NULL) {
        return;
    }

    fields.context = env->GetFieldID(clazz, "mNativeContext", "J");
    if (fields.context == NULL) {
        return;
    }

    fields.post_event = env->GetStaticMethodID(clazz, "postEventFromNative",
                                               "(Ljava/lang/Object;IIILjava/lang/Object;)V");
    if (fields.post_event == NULL) {
        return;
    }

    fields.surface_texture = env->GetFieldID(clazz, "mNativeSurfaceTexture", "J");
    if (fields.surface_texture == NULL) {
        return;
    }

    clazz = env->FindClass("android/net/ProxyInfo");
    if (clazz == NULL) {
        return;
    }

    fields.proxyConfigGetHost =
        env->GetMethodID(clazz, "getHost", "()Ljava/lang/String;");

    fields.proxyConfigGetPort =
        env->GetMethodID(clazz, "getPort", "()I");

    fields.proxyConfigGetExclusionList =
        env->GetMethodID(clazz, "getExclusionListAsString", "()Ljava/lang/String;");
}

native_init()获取Java类MediaPlayer和ProxyInfo中的一些方法和变量。

  • 将native MediaPlayer的对象保存到Java MediaPlayer的mNativeContext中。
  • 获取Java MediaPlayer的postEventFromNative()方法,用于发送事件。
  • 将native video surface保存到Java MediaPlayer的mNativeSurfaceTexture中。
  • 获取Java ProxyInfo的getHost()、getPort()、getExclusionListAsString()方法。

之后在MediaPlayer的构造函数中初始化一些媒体播放的必要元素,像消息、时间、字幕、权限等。最后调用native_setup()对native层进行设置。

frameworks/base/media/jni/android_media_MediaPlayer.cpp

static void 
android_media_MediaPlayer_native_setup(JNIEnv *env, jobject thiz, jobject weak_this)
{
    ALOGV("native_setup");
    sp<MediaPlayer> mp = new MediaPlayer();
    if (mp == NULL) {
        jniThrowException(env, "java/lang/RuntimeException", "Out of memory");
        return;
    }    

    // create new listener and give it to MediaPlayer
    sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener(env, thiz, weak_this);
    mp->setListener(listener);

    // Stow our new C++ MediaPlayer in an opaque field in the Java object.
    setMediaPlayer(env, thiz, mp);
}

Setup过程完成一下工作。

  • 创建Native MeidaPlayer对象。
  • 创建Listener上报Native事件,这里调用postEventFromNative()。
  • 存储Native MediaPlayer对象到mNativeContext中,以便Java层使用。

设置声音系统

Android系统将定义了不同的Audio Stream类型,用来适用不同的应用场景。像STREAM_VOICE_CALL用于打电话场景,STREAM_SYSTEM用于系统声音,STREAM_RING用于电话铃声,STREAM_MUSIC用于播放音乐等。每种Stream类型都是独立的,有自己的状态和音量。在使用Player前需要选择一个Stream类型,音乐播放和视频播放时基本上采用的都是STREAM_MUSIC。
Java层的setAudioStreamType()直接调用的Native的setAudioStreamType(),而Native的这个方法也很简单,就是赋值给mStreamType。

frameworks/av/media/libmedia/mediaplayer.cpp

status_t MediaPlayer::setAudioStreamType(audio_stream_type_t type)
{
    ALOGV("MediaPlayer::setAudioStreamType");
    Mutex::Autolock _l(mLock);
    if (mStreamType == type) return NO_ERROR;
    if (mCurrentState & ( MEDIA_PLAYER_PREPARED | MEDIA_PLAYER_STARTED |
                MEDIA_PLAYER_PAUSED | MEDIA_PLAYER_PLAYBACK_COMPLETE ) ) { 
        // Can''t change the stream type after prepare
        ALOGE("setAudioStream called in state %d", mCurrentState);
        return INVALID_OPERATION;
    }   
    // cache
    mStreamType = type;
    return OK; 
}

mStreamType会在Player Prepare过程中被设置生效,这也就是为什么必须在Player开始前设置Stream类型。

frameworks/av/media/libmedia/mediaplayer.cpp

status_t MediaPlayer::prepareAsync_l()
{
    if ( (mPlayer != 0) && ( mCurrentState & ( MEDIA_PLAYER_INITIALIZED | MEDIA_PLAYER_STOPPED) ) ) {
        mPlayer->setAudioStreamType(mStreamType);
        if (mAudioAttributesParcel != NULL) {
            mPlayer->setParameter(KEY_PARAMETER_AUDIO_ATTRIBUTES, *mAudioAttributesParcel);
        }
        mCurrentState = MEDIA_PLAYER_PREPARING;
        return mPlayer->prepareAsync();
    }
    ALOGE("prepareAsync called in state %d", mCurrentState);
    return INVALID_OPERATION;
}

上面的过程又远程调用了MediaPlayerService的setAudioStreamType(),真正的Audio系统设置就发生在这里。Audio系统比较复杂,这里不再展开。

frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp

status_t MediaPlayerService::Client::setAudioStreamType(audio_stream_type_t type)
{
    ALOGV("[%d] setAudioStreamType(%d)", mConnId, type);
    // TODO: for hardware output, call player instead
    Mutex::Autolock l(mLock);
    if (mAudioOutput != 0) mAudioOutput->setAudioStreamType(type);                                                                                                                                          
    return NO_ERROR;
}

初始化底层播放器

Android MediaPlayer使用”工厂模式“来管理底层播放器。只要按MediaPlayerFactory
定义接口进行实现,并注册到sFactoryMap中,就可以调用自己的Player。原生Android提供了四个可用的播放器:NuPlayer、StagefrightPlayer、SonivoxPlayer和TestPlayer,厂商也可以增加客制化的播放器。在播放前需要根据评分来选择使用播放器,这个选择过程就是在setDataSource()中完成的。
Java层MediaPlayer实现了多个setDataSource(),但最终都是通过两个JNI接口调用到native中。

  • nativeSetDataSource:处理网络流媒体。
  • _setDataSource:处理本地的媒体文件。
frameworks/base/media/jni/android_media_MediaPlayer.cpp

static void 
android_media_MediaPlayer_setDataSourceAndHeaders(
        JNIEnv *env, jobject thiz, jobject httpServiceBinderObj, jstring path,
        jobjectArray keys, jobjectArray values) {                                                                                                                                                           

sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
......
    sp<IMediaHTTPService> httpService;
    if (httpServiceBinderObj != NULL) {
        sp<IBinder> binder = ibinderForJavaObject(env, httpServiceBinderObj);
        httpService = interface_cast<IMediaHTTPService>(binder);
    }

    status_t opStatus =
        mp->setDataSource(
                httpService,
                pathStr,
                headersVector.size() > 0? &headersVector : NULL);
......
}

static void
android_media_MediaPlayer_setDataSourceFD(JNIEnv *env, jobject thiz, jobject fileDescriptor, jlong offset, jlong length)
{
    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
......
    int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
    ALOGV("setDataSourceFD: fd %d", fd);
    process_media_player_call( env, thiz, mp->setDataSource(fd, offset, length), "java/io/IOException", "setDataSourceFD failed." );
}

JNI中又调用Native层MediaPlayer的setDataSource()方法,最终的远程调用到MediaPlayerService的setDataSource()。

frameworks/av/media/libmediaplayerservice/MediaPlayerService.cpp

status_t MediaPlayerService::Client::setDataSource(
        const sp<IMediaHTTPService> &httpService,
        const char *url,
        const KeyedVector<String8, String8> *headers)
{
......
if (strncmp(url, "content://", 10) == 0) { 
    // get a filedescriptor for the content Uri and
    // pass it to the setDataSource(fd) method

    String16 url16(url);
    int fd = android::openContentProviderFile(url16);
    if (fd < 0) 
    {
        ALOGE("Couldn''t open fd for %s", url);
        return UNKNOWN_ERROR;
    }
    setDataSource(fd, 0, 0x7fffffffffLL); // this sets mStatus
    close(fd);
    return mStatus;
} else {
    player_type playerType = MediaPlayerFactory::getPlayerType(this, url);
    .....
    sp<MediaPlayerBase> p = setDataSource_pre(playerType);
    if (p == NULL) {
        return NO_INIT;
    }

    setDataSource_post(p, p->setDataSource(httpService, url, headers));
    return mStatus;
}
}

status_t MediaPlayerService::Client::setDataSource(int fd, int64_t offset, int64_t length)
{
    ......
    player_type playerType = MediaPlayerFactory::getPlayerType(this,
                                                               fd,
                                                               offset,
                                                               length);
    sp<MediaPlayerBase> p = setDataSource_pre(playerType);
    if (p == NULL) {
        return NO_INIT;
    }

    // now set data source
    setDataSource_post(p, p->setDataSource(fd, offset, length));
    return mStatus;
}

status_t MediaPlayerService::Client::setDataSource(
        const sp<IStreamSource> &source) {
    // create the right type of player
    player_type playerType = MediaPlayerFactory::getPlayerType(this, source);
    sp<MediaPlayerBase> p = setDataSource_pre(playerType);
    if (p == NULL) {
        return NO_INIT;
    }

    // now set data source
    setDataSource_post(p, p->setDataSource(source));
    return mStatus;
}

setDataSource()虽然具体实现不同,但最主要的步骤都是相同的。

  • getPlayerType:选择合适的底层播放器。
  • setDataSource_pre:创建底层播放器。
  • setDataSource_post:调用底层播放器的setDataSource(),设置数据源。

播放器的选择过程也比较简单,就是根据参数寻找评分最高的播放器。每个播放器都会实现scoreFactory()方法,为自己打分。分值最高的播放器有最高优先级。

frameworks/av/media/libmediaplayerservice/MediaPlayerFactory.cpp

#define GET_PLAYER_TYPE_IMPL(a...)                      \
    Mutex::Autolock lock_(&sLock);                      \
                                                        \
    player_type ret = STAGEFRIGHT_PLAYER;               \
    float bestScore = 0.0;                              \
                                                        \
    for (size_t i = 0; i < sFactoryMap.size(); ++i) {   \
                                                        \
        IFactory* v = sFactoryMap.valueAt(i);           \
        float thisScore;                                \
        CHECK(v != NULL);                               \
        thisScore = v->scoreFactory(a, bestScore);      \
        if (thisScore > bestScore) {                    \
            ret = sFactoryMap.keyAt(i);                 \
            bestScore = thisScore;                      \
        }                                               \
    }                                                   \
                                                        \
    if (0.0 == bestScore) {                             \
        ret = getDefaultPlayerType();                   \
    }                                                   \
                                                        \
    return ret;

player_type MediaPlayerFactory::getPlayerType(const sp<IMediaPlayer>& client,
                                              const char* url) {
    GET_PLAYER_TYPE_IMPL(client, url);
}

player_type MediaPlayerFactory::getPlayerType(const sp<IMediaPlayer>& client,
                                              int fd, 
                                              int64_t offset,
                                              int64_t length) {
    GET_PLAYER_TYPE_IMPL(client, fd, offset, length);
}

player_type MediaPlayerFactory::getPlayerType(const sp<IMediaPlayer>& client,
                                              const sp<IStreamSource> &source) {
    GET_PLAYER_TYPE_IMPL(client, source);
}

以StagefrightPlayer为例,看一下scoreFactory()的实现。

frameworks/av/media/libmediaplayerservice/MediaPlayerFactory.cpp

class StagefrightPlayerFactory :
    public MediaPlayerFactory::IFactory {
  public:
    virtual float scoreFactory(const sp<IMediaPlayer>& /*client*/,
                               int fd,
                               int64_t offset,
                               int64_t length,
                               float /*curScore*/) {
        if (legacyDrm()) {
            sp<DataSource> source = new FileSource(dup(fd), offset, length);
            String8 mimeType;
            float confidence;
            if (SniffWVM(source, &mimeType, &confidence, NULL /* format */)) {
                return 1.0;
            }
        }

        if (getDefaultPlayerType() == STAGEFRIGHT_PLAYER) {
            char buf[20];
            lseek(fd, offset, SEEK_SET);
            read(fd, buf, sizeof(buf));
            lseek(fd, offset, SEEK_SET);

            uint32_t ident = *((uint32_t*)buf);

            // Ogg vorbis?
            if (ident == 0x5367674f) // ''OggS''
                return 1.0;
        }

        return 0.0;
    }

    virtual float scoreFactory(const sp<IMediaPlayer>& /*client*/,
                               const char* url,
                               float /*curScore*/) {
        if (legacyDrm() && !strncasecmp("widevine://", url, 11)) {
            return 1.0;
        }
        return 0.0;
    }
......

可以看到,如果媒体为Widevine DRM格式,StagefrightPlayer会给出更高的打分。Ogg vorbis也会返回高分。

准备Player和播放

选择好底层播放器后,使用prepare()做播放前的准备,之后使用start()开始播发。这两个操作的调用流程类似,最终都是调用到底层播放器的相关接口。这里不分析播放器的具体操作,每个播放器的操作都不同,需要针对播放器做分析。调用流程如下图所示。
图片描述

关于Android MediaPlayer架构 -- 前言小知识点的问题就给大家分享到这里,感谢你花时间阅读本站内容,更多关于Android MediaPlayer、android MediaPlayer API、Android Mediaplayer MediaController超时、Android MediaPlayer Playback流程分析等相关知识的信息别忘了在本站进行查找喔。

本文标签: