Android -- MediaPlayer内部实现简析

Posted 第一序列丶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android -- MediaPlayer内部实现简析相关的知识,希望对你有一定的参考价值。

android -- MediaPlayer内部实现简析


在之前的博客中,已经介绍了使用MediaPlayer时要注意的内容。现在,这里就通过一个MediaPlayer代码实例,来进一步分析MediaPlayer内部是如何运作、实现的;当然这里的分析只截止到底层调用播放器之前,因为播放器这块实在是没搞懂。

我们使用的例子来源于之前MediaPlayer 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();

代码中主要通过5个步骤实现了媒体的播放过程,我们一步步来分析。


一、创建MediaPlayer


从MediaPlayer模块的实现层次来说,它其实只是一个暴露给外部调用的工具类;真正的媒体操作都通过JNI调用到底层Media服务,由它们真正实现。MediaPlayer类要使用一个libmedia_jni.so库,它的加载过程如下:

   static {
        System.loadLibrary("media_jni");
        native_init();
    }
libmedia_jni.so提供了MediaPlayer需要调用的各个JNI函数,它对应的文件是android_media_MediaPlayer.cpp。load该so库的同时,会调用native_init()函数进行一些前期的初始化工作:
// This function gets some field IDs, which in turn causes class initialization.
// It is called from a static block in MediaPlayer, which won't run until the
// first time an instance of this class is used.
static void
android_media_MediaPlayer_native_init(JNIEnv *env)//初始化一些Field和Method域ID
{
    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;
    }

    env->DeleteLocalRef(clazz);

    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;");

    env->DeleteLocalRef(clazz);

    gPlaybackParamsFields.init(env);
    gSyncParamsFields.init(env);
}
struct fields_t {
    jfieldID    context;
    jfieldID    surface_texture;

    jmethodID   post_event;

    jmethodID   proxyConfigGetHost;
    jmethodID   proxyConfigGetPort;
    jmethodID   proxyConfigGetExclusionList;
};
static fields_t fields;
从代码可知,native_init()函数主要保存了一些MediaPlayer.java中定义的一些字段或方法的ID;其中获取的mNativeContext字段,用于将初始化的本地MediaPlayer对象的地址保存到该变量中,这也就给每一个MediaPlayer.java实例绑定了一个Native层的MediaPlayer;另外,post_event保存了MediaPlayer::postEventFromNative()函数的ID值,它会被用来在Native层中向上层抛出事件或异常。


加载完要使用的动态库,我们就可以开始创建MediaPlayer实例了,首先看它的默认构造函数:

    /**
     * Default constructor. Consider using one of the create() methods for
     * synchronously instantiating a MediaPlayer from a Uri or resource.
     * <p>When done with the MediaPlayer, you should call  {@link #release()},
     * to free the resources. If not released, too many MediaPlayer instances may
     * result in an exception.</p>
     */
    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);
        mOpenSubtitleSources = new Vector<InputStream>();
        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需要运行在消息循环中,EventHandler是MediaPlayer的一个内部类,它专门处理来自Native层的事件,这些事件一般都表明了MediaPlayer现在转移到了某个状态,我们可以在该状态处理什么回调操作。EventHandler的功能较为单一,就是根据底层上抛的事件,进行对应的回调或事件处理,这里就不再细看。接着,调用了native_setup()函数,并传入了一个MediaPlayer类型的弱引用实例,我们看该函数的实现:
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
    //JNIMediaPlayerListener类继承自MediaPlayer.h中声明的MediaPlayerListener,并实现了notify()方法
    sp<JNIMediaPlayerListener> listener = new JNIMediaPlayerListener(env, thiz, weak_this);
    mp->setListener(listener);//在Native MediaPlayer实例中保存这个JNIMediaPlayerListener监听对象

    // Stow our new C++ MediaPlayer in an opaque field in the Java object.
    setMediaPlayer(env, thiz, mp);//将创建的Native MediaPlayer对象转化成Long型值(地址),保存到MediaPlayer.java::mNativeContext变量中
}
该函数中主要做了三个操作:

  • 创建了一个Native MediaPlayer对象
  • 创建了一个JNIMediaPlayerListener对象,它主要用于向上层MediaPlayer(.java)对象通知事件或抛出异常
  • 将创建的Native MediaPlayer实例保存到MediaPlayer.java::mNativeContext字段中
Native MediaPlayer类继承自BnMediaPlayerClient,它是IMediaPlayerClient业务的服务端。IMediaPlayerClient业务的整个框架如图所示:

以上是关于Android -- MediaPlayer内部实现简析的主要内容,如果未能解决你的问题,请参考以下文章

当系统音量发生变化时,Android MediaPlayer 会忽略它的内部音量

Android MediaPlayer播放视频详细步骤

android中怎么样能获取正在播放的音乐名?

Android MediaPlayer 播放音频

Android 音频开发之 MediaPlayer

Android使用VideoView播放视频