Android -- MediaPlayer内部实现简析 mark~ 巨详细!!!

Android MediaPlayer深入解析:从创建到播放的全程剖析
本文详细分析了Android系统中MediaPlayer的内部实现,从创建、设置音频类型、设置资源、准备及启动播放的全过程。通过源码解析,揭示了MediaPlayer如何与Native层交互,涉及MediaPlayerService、StagefrightPlayer、 AwesomePlayer等多个关键组件的工作原理。通过了解这些,有助于开发者更深入地理解和使用MediaPlayer。

Android -- MediaPlayer内部实现简析

转载:https://www.cnblogs.com/zhchoutai/p/8715660.html

在之前的博客中,已经介绍了使用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业务的整个框架如图所看到的:

Native MediaPlayer的构造函数较为简单,就是一些字段的初始化操作,重要的是设置了默认的音频流类型,并将MediaPlayer的状态初始化为Idle:

MediaPlayer::MediaPlayer()
{
    ALOGV("constructor");
    mListener = NULL;
    mCookie = NULL;
    mStreamType = AUDIO_STREAM_MUSIC;//默认音频流类型
    mAudioAttributesParcel = NULL;
    mCurrentPosition = -1;
    mSeekPosition = -1;
    mCurrentState = MEDIA_PLAYER_IDLE;//MediaPlayer的初始状态
    mPrepareSync = false;
    mPrepareStatus = NO_ERROR;
    mLoop = false;//是否循环播放
    mLeftVolume = mRightVolume = 1.0;
    mVideoWidth = mVideoHeight = 0;
    mLockThreadId = 0;
    mAudioSessionId = AudioSystem::newAudioUniqueId();
    AudioSystem::acquireAudioSessionId(mAudioSessionId, -1);
    mSendLevel = 0;
    mRetransmitEndpointValid = false;
}

另外,还有默认同步状态的设置等等。

 

 

JNIMediaPlayerListener类继承自MediaPlayerListener,它声明了一个notify()函数。该函数主要用来向上层抛出事件或其它异常:

class JNIMediaPlayerListener: public MediaPlayerListener
{
public:
    JNIMediaPlayerListener(JNIEnv* env, jobject thiz, jobject weak_thiz);
    ~JNIMediaPlayerListener();
    virtual void notify(int msg, int ext1, int ext2, const Parcel *obj = NULL);
private:
    JNIMediaPlayerListener();
    jclass      mClass;     // Reference to MediaPlayer class
    jobject     mObject;    // Weak ref to MediaPlayer Java object to call on
};

JNIMediaPlayerListener的构造函数中用之前传入的MediaPlayer弱引用实例构造了一个Native层全局的变量mObject。而且也保存了一份MediaPlayer.java的类型实例:

JNIMediaPlayerListener::JNIMediaPlayerListener(JNIEnv* env, jobject thiz, jobject weak_thiz)
{

    // Hold onto the MediaPlayer class for use in calling the static method
    // that posts events to the application thread.
    jclass clazz = env->GetObjectClass(thiz);
    if (clazz == NULL) {
        ALOGE("Can't find android/media/MediaPlayer");
        jniThrowException(env, "java/lang/Exception", NULL);
        return;
    }
	
    mClass = (jclass)env->NewGlobalRef(clazz);//代表MediaPlayer.java类型的实例

    // We use a weak reference so the MediaPlayer object can be garbage collected.
    // The reference is only used as a proxy for callbacks.
    
    mObject  = env->NewGlobalRef(weak_thiz);//weak_thiz是MediaPlayer.java实例的一个弱引用
}

JNIMediaPlayerListener::notify()函数用来向上层抛出事件或异常:

//回调MediaPlayer.java中的postEventFromNative()方法,反馈Native层发生的事件;postEventFromNative()会EventHandler(运行在MediaPalyer的线程中)
//发送附带msg參数的消息,EventHandler推断当前的事件类型,如MEDIA_PREPARED等,最后调用应用程序设置的相关回调处理相应的事件信息
void JNIMediaPlayerListener::notify(int msg, int ext1, int ext2, const Parcel *obj)
{
    JNIEnv *env = AndroidRuntime::getJNIEnv();
    if (obj && obj->dataSize() > 0) {
        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);
        }
    } else {
        env->CallStaticVoidMethod(mClass, fields.post_event, mObject,
                msg, ext1, ext2, NULL);
    }
    if (env->ExceptionCheck()) {
        ALOGW("An exception occurred while notifying an event.");
        LOGW_EX(env);
        env->ExceptionClear();
    }
}

JNIMediaPlayerListener实例创建后,会将它保存到Native MediaPlayer::mListener字段中。最后掉setMediaPlayer()方法将mp对象转换成地址值的形式保存到上层MediaPlayer中:

static sp<MediaPlayer> setMediaPlayer(JNIEnv* env, jobject thiz, const sp<MediaPlayer>& player)
{
    Mutex::Autolock l(sLock);
    sp<MediaPlayer> old = (MediaPlayer*)env->GetLongField(thiz, fields.context);
    if (player.get()) {
        player->incStrong((void*)setMediaPlayer);
    }
    if (old != 0) {
        old->decStrong((void*)setMediaPlayer);
    }
    env->SetLongField(thiz, fields.context, (jlong)player.get());
    return old;
}

二、设置音频类型

 

分析到这里。我们能够推測:上层MediaPlayer和Native MediaPlayer之间的关系就相当于Client - Server结构。前者是Client,后者是Server;前者向后者发送服务请求。接着。我们调用了MediaPlayer::setAudioStreamType()函数设置了当前的音频流类型:

    /**
     * Sets the audio stream type for this MediaPlayer. See {@link AudioManager}
     * for a list of stream types. Must call this method before prepare() or
     * prepareAsync() in order for the target stream type to become effective
     * thereafter.
     *
     * @param streamtype the audio stream type
     * @see android.media.AudioManager
     */
    public void setAudioStreamType(int streamtype) {
        _setAudioStreamType(streamtype);
        mStreamType = streamtype;
    }

    private native void _setAudioStreamType(int streamtype);

从函数凝视能够。这里设置的音频流类型必须是AudioManager中定义过的,当前版本号中所支持的类型有:

    /** The audio stream for phone calls */
    public static final int STREAM_VOICE_CALL = AudioSystem.STREAM_VOICE_CALL;
    /** The audio stream for system sounds */
    public static final int STREAM_SYSTEM = AudioSystem.STREAM_SYSTEM;
    /** The audio stream for the phone ring */
    public static final int STREAM_RING = AudioSystem.STREAM_RING;
    /** The audio stream for music playback */
    public static final int STREAM_MUSIC = AudioSystem.STREAM_MUSIC;
    /** The audio stream for alarms */
    public static final int STREAM_ALARM = AudioSystem.STREAM_ALARM;
    /** The audio stream for notifications */
    public static final int STREAM_NOTIFICATION = AudioSystem.STREAM_NOTIFICATION;
    /** @hide The audio stream for phone calls when connected to bluetooth */
    public static final int STREAM_BLUETOOTH_SCO = AudioSystem.STREAM_BLUETOOTH_SCO;
    /** @hide The audio stream for enforced system sounds in certain countries (e.g camera in Japan) */
    public static final int STREAM_SYSTEM_ENFORCED = AudioSystem.STREAM_SYSTEM_ENFORCED;
    /** The audio stream for DTMF Tones */
    public static final int STREAM_DTMF = AudioSystem.STREAM_DTMF;
    /** @hide The audio stream for text to speech (TTS) */
    public static final int STREAM_TTS = AudioSystem.STREAM_TTS;

最后调用native函数_setAudioStreamType()将类型值设置下去,看它的实现:

static void
android_media_MediaPlayer_setAudioStreamType(JNIEnv *env, jobject thiz, jint streamtype)
{
    ALOGV("setAudioStreamType: %d", streamtype);
    sp<MediaPlayer> mp = getMediaPlayer(env, thiz);//获取创建时设置到MediaPlayer.java实例中的Native MediaPlayer实例
    if (mp == NULL ) {
        jniThrowException(env, "java/lang/IllegalStateException", NULL);
        return;
    }
    process_media_player_call( env, thiz, mp->setAudioStreamType((audio_stream_type_t) streamtype) , NULL, NULL );
}

首先获取到之前保存到MediaPlayer.java::mNativeContext字段中的Native MediaPlayer对象;最后调用process_media_player_call()函数,当中包括了通过mp实例设置音频类型的调用:MediaPlayer::setAudioStreamType()。

我们分解来分析,process_media_player_call()函数会依据函数的运行结果opStatus,以及附带的exception和message信息;推断是否须要向上反馈状态事件或抛出异常信息:

// If exception is NULL and opStatus is not OK, this method sends an error
// event to the client application; otherwise, if exception is not NULL and
// opStatus is not OK, this method throws the given exception to the client
// application.
//依据函数的运行结果opStatus,以及附带的exception和message信息;推断是否须要反馈操作失败事件或抛出异常信息
static void process_media_player_call(JNIEnv *env, jobject thiz, status_t opStatus, const char* exception, const char *message)
{
    if (exception == NULL) {  // Don't throw exception. Instead, send an event.
        if (opStatus != (status_t) OK) {//假设无需抛出异常,但存在函数处理错误,则向上层抛出事件
            sp<MediaPlayer> mp = getMediaPlayer(env, thiz);
            if (mp != 0) mp->notify(MEDIA_ERROR, opStatus, 0);//重要
        }
    } else {  // Throw exception! 假设须要抛出异常,则构建相应的异常并抛出
        if ( opStatus == (status_t) INVALID_OPERATION ) {
            jniThrowException(env, "java/lang/IllegalStateException", NULL);
        } else if ( opStatus == (status_t) BAD_VALUE ) {
            jniThrowException(env, "java/lang/IllegalArgumentException", NULL);
        } else if ( opStatus == (status_t) PERMISSION_DENIED ) {
            jniThrowException(env, "java/lang/SecurityException", NULL);
        } else if ( opStatus != (status_t) OK ) {
            if (strlen(message) > 230) {
               // if the message is too long, don't bother displaying the status code
               jniThrowException( env, exception, message);
            } else {
               char msg[256];
                // append the status code to the message
               sprintf(msg, "%s: status=0x%X", message, opStatus);
               jniThrowException( env, exception, msg);
            }
        }
    }
}

假设我们的操作无需抛出异常,而且当前的函数调用有错误,就须要通过Native MediaPlayer调用notify()向上层抛出错误:

//向应用程序反馈当前状态变化的回调事件,设置当前MediaPlayer的状态
void MediaPlayer::notify(int msg, int ext1, int ext2, const Parcel *obj)
{
    ALOGV("message received msg=%d, ext1=%d, ext2=%d", msg, ext1, ext2);
    bool send = true;
    bool locked = false;

    // TODO: In the future, we might be on the same thread if the app is
    // running in the same process as the media s
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值