Android Media Player 框架分析-Nuplayer(1)

由于工作岗位调整,开始接触Media相关部分的代码,起初希望在网络上找一下大神们分析软文学习一下就好,由于Google在新的Android版本上修改了Nuplayer的地位,原本NuPlayer主要在系统中负责流媒体的播放,但现在Android基本已经全面弃用AwesomePlayer,很多网络文章介绍的多为之前的AwesomePlayer,所以最终没能找到需要的东西,只好自己入手分析。本次分析主要侧重于对Android中NuPlayer在播放本地文件时的工作流程的分析,由于本人初次接触Media模块很多基本的概念不全,加之对代码也只看了大约两周左右,所以可能存在诸多错误,若有发现,请及时指正,多谢,闲话不多说,切入正题。


Android中,我们通常使用SDK中的MediaPlayer来播放一个media资源,对于一个Application的开发者而言,可以十分容易的通过几个简单的接口来对此进行操作,之所以能如此完全依靠Google在Framework中的强大支持才得以实现,本文重在分析Framework中的Server端流程,但SDK中的使用流程便是对框架分析的一个很好的切入口,所以首先看一下在App中如何播放一个媒体文件。

Android在Java层中提供了一个MediaPlayer的类来作为播放媒体资源的接口,在使用中我们通常会编写以下的代码:

MediaPlayer mp = new MediaPlayer()
mp.setDataSource("/sdcard/filename.mp3");
mp.prepare();
mp.start();
只需寥寥的几行代码我们就可以在Android播放一个媒体文件,当然MediaPlayer的接口远不止示例中的几个,其他的接口使用上都比较简单,不做过多描述,下面放上一张MediaPlayer的状态图,以供参考,好吧,我承认因为懒,我从度娘上盗了张图。

Android Media Player 框架分析-Nuplayer(1)_第1张图片

我们的分析主要从刚才的示例代码开始,来看看我们简单的几个步骤会在内部执行什么样的操作。

1.创建MediaPlayer:

首先来看看MediaPlayer的构造函数:

    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();
        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(this));
    }

函数中最重要的一句是最后的一句native_setup这个JNI的函数,之上的代码主要是创建了一个Handler和一下辅助类,其中这个Handler的主要作用是接收mediaserver端发来的一些状态消息,这一点我们在之后遇到了再说。先来说说这个native函数,直接到android_media_MediaPlayer.cpp中来看看这个函数的实现。

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

    // create new listener and give it to MediaPlayer
    sp 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函数在C++端创建了一个MediaPlayer对象,之后创建了JNIMediaPlayerListener,然后调用setListener将其设置到创建的mp对象中,之后通过setMediaPlayer将mp的地址回传给Java端的MediaPlayer保存为一个long对象。JNI中的相互调用就不一一说了,要不写一周也写不完了。目前我们知道了其实Java端的MediaPlayer对象相当于一个代理,实际上在C++端我们也创建了一个同名的对象,如果大家对Android熟悉,就能知道真正干活的通常都是这个C++的对象,那么我们就得看看这个C++的MediaPlayer类究竟干了些啥, JNIMediaPlayerListener又是什么。

JNIMediaPlayerListener是干嘛用的,先来看看定义

// ----------------------------------------------------------------------------
// ref-counted object for callbacks
class MediaPlayerListener: virtual public RefBase
{
public:
    virtual void notify(int msg, int ext1, int ext2, const Parcel *obj) = 0;
};


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
};


该类继承与MediaPlayerListener,但实际上基类只是一个接口,从定义的函数notify来看是通知Java层一些消息的,看一下参数中有一个Parcel的对象,于是基本可以猜想这个notify的消息是从其他进程发过来的,至于是不是我们在后边遇到了再看,其他的三个参数比较容易理解,第一个是message的类型,后两个是附加的extra。

那么通过调用setListener将该对象设置给MediaPlayer的意图就很明显了,在C++端遇到一些消息后回传给Java层。

看一下notify函数的具体定义,看看猜测的是否正确。

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();
    }
}

很明显在notify中调用了Java端post_event所指向的一个函数,而该函数即为postEventFromNative,这部分就不再赘述了,简单说一下,Java端的MediaPlayer有一个静态代码段,其中调用了native_init,而该函数实际是C++中的android_media_MediaPlayer_native_init,此函数中对post_event等其他Java端的成员做了索引。如果不熟悉可以自行学习一下JNI的知识,网上资料很全。

扯了这么多,我们来看看C++端的MediaPlayer做了些什么事情,先看看构造函数:

MediaPlayer::MediaPlayer()
{
    ALOGV("constructor");
    mListener = NULL;
    mCookie = NULL;
    mStreamType = AUDIO_STREAM_MUSIC;
    mAudioAttributesParcel = NULL;
    mCurrentPosition = -1;
    mSeekPosition = -1;
    mCurrentState = MEDIA_PLAYER_IDLE;
    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;
}

由此可见,其涉及到的东西还是挺多的,构造函数中没太多的东西,主要是一些常规初始化,并且分配了一个唯一的ID号给mAudioSessionId,然后通过AudioSystem报了个到罢了。其他的成员变量从名字上也能了解大概,先不一一赘述,遇到再说。到此创建MediaPlayer的工作算是已经圆满完成了,接下来就是setDataSource了。

    public void setDataSource(String path)
            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
        Log.d("MediaPlayer", "setDataSource path = " + path);
        setDataSource(path, null, null);
    }

中间步骤就算了,直接到C++来看看吧:

status_t MediaPlayer::setDataSource(int fd, int64_t offset, int64_t length)
{
    ALOGV("setDataSource(%d, %" PRId64 ", %" PRId64 ")", fd, offset, length);
    status_t err = UNKNOWN_ERROR;
    const sp& service(getMediaPlayerService());
    if (service != 0) {
        sp player(service->create(this, mAudioSessionId));
        if ((NO_ERROR != doSetRetransmitEndpoint(player)) ||
            (NO_ERROR != player->setDataSource(fd, offset, length))) {
            player.clear();
        }

        err = attachNewPlayer(player);
    }
    return err;
}
从语义上来看通过调用服务进程的create函数创建了一个IMediaPlayer代理对象,先看看IMediaPlayerService的Stub端。

具体怎么查找stub端是哪个类就不赘述了,要不写不完了,烦人的步骤直接跳过去,看看stub类MediaPlayerService的create方法:

sp MediaPlayerService::create(const sp& client,
        int audioSessionId)
{
    pid_t pid = IPCThreadState::self()->getCallingPid();
    int32_t connId = android_atomic_inc(&mNextConnId);

    sp c = new Client(
            this, pid, connId, client, audioSessionId,
            IPCThreadState::self()->getCallingUid());

    ALOGV("Create new client(%d) from pid %d, uid %d, ", connId, pid,
         IPCThreadState::self()->getCallingUid());

    wp w = c;
    {
        Mutex::Autolock lock(mLock);
        mClients.add(w);
    }
    return c;
}

在服务端创建了一个内部类Client的对象,将其加入到mClients中,其实现在不需要看这些类的具体定义,只需要看看刚刚C++的MediaPlayer类的定义,是一个IMediaPlayerClient接口的Bn端子类,所以目的就很明确了,MediaPlayerServer创建一个Client对象,而该对象用于保存客户端MediaPlayer的代理对象,用于将server端的一些消息和状态通知给Client端的MediaPlayer。而Client这个类又是IMediaPlayer这个Binder接口的Bn端,这样一来client和server端就建立起了双向的连接,client(MediaPlayer)端可以请求server(MediaPlayerService::Client)端做自己需要的工作,而server端会返回client端需要的状态信息等,而MediaPlayerService::Client对象究竟是不是真正做事情的,还是另一个转发者,我们现在不需要关心,总之到目前为止,两端算是已经搭上线了,它们之间都是通过Binder接口进行回调来实现的,这样一来,刚刚我们说过的JNIMediaPlayerListener的作用也就明确了,将C++的client端收到的消息反馈给最终的Java端,也就是player的使用者。

那么我们再来看看setDataSource剩下的工作,通过得到的代理对象调用了server端的setDataSource方法,之后调用attachNewPlayer方法保存得到的代理对象:

status_t MediaPlayerService::Client::setDataSource(int fd, int64_t offset, int64_t length)
{
    ALOGV("setDataSource fd=%d, offset=%lld, length=%lld", fd, offset, length);
    struct stat sb;
    int ret = fstat(fd, &sb);
    if (ret != 0) {
        ALOGE("fstat(%d) failed: %d, %s", fd, ret, strerror(errno));
        return UNKNOWN_ERROR;
    }

    ALOGV("st_dev  = %llu", static_cast(sb.st_dev));
    ALOGV("st_mode = %u", sb.st_mode);
    ALOGV("st_uid  = %lu", static_cast(sb.st_uid));
    ALOGV("st_gid  = %lu", static_cast(sb.st_gid));
    ALOGV("st_size = %llu", sb.st_size);

    if (offset >= sb.st_size) {
        ALOGE("offset error");
        ::close(fd);
        return UNKNOWN_ERROR;
    }

    if (offset + length > sb.st_size) {
        length = sb.st_size - offset;
        ALOGV("calculated length = %lld", length);
    }

    player_type playerType = MediaPlayerFactory::getPlayerType(this,
                                                               fd,
                                                               offset,
                                                               length);
    sp p = setDataSource_pre(playerType);
    if (p == NULL) {
        return NO_INIT;
    }
    // now set data source
    setDataSource_post(p, p->setDataSource(fd, offset, length));
    return mStatus;
}
好吧,路漫漫,看来事情远没有看起来那么容易。

该函数前面一段是检验参数用的,包括通过Linux系统调用fstat来检查fd,忘了说了,在Binder调用中fd也是可以在不同进程中传递的,具体的请自行Google。到此为止,我们还没有看到任何一个真正的用于播放文件的player,那么此时主角应该要登场了,先看看MediaPlayerFactory::getPlayerType,这个函数返回一个index值来决定是创建哪种player,这个要创建的player就是真正用来播放文件的,它的任务是把数据取出正确的送给Codec,然后将解码数据拿出来送到相应的输出设备,player_type有三种,分别如下:

enum player_type {
    STAGEFRIGHT_PLAYER = 3,
    NU_PLAYER = 4,
    // Test players are available only in the 'test' and 'eng' builds.
    // The shared library with the test player is passed passed as an
    // argument to the 'test:' url in the setDataSource call.
    TEST_PLAYER = 5,
};
getPlayerType函数通过一个宏来展开,具体的内容不看了,如果有兴趣自己研究下,值得一提的是,这个函数在传入fd这种资源的时候,读代码很容易产生幻觉,注意其中StagefrightPlayerFactory的scoreFactory有两个if语句,这两个在新的android版本上默认是不会走的,所以最终通过调用getDefaultType返回了NU_PLAYER的enum值。

static player_type getDefaultPlayerType() {
    char value[PROPERTY_VALUE_MAX];
    if (property_get("media.stagefright.use-awesome", value, NULL)
            && (!strcmp("1", value) || !strcasecmp("true", value))) {
        return STAGEFRIGHT_PLAYER;
    }

    return NU_PLAYER;
}
于是,顺理成章的,我们要创建的Player也就是最终的NuPlayerDriver了。最后通过createPlayer创建了NuPlayerDriver对象,过程不再赘述,都很简单。


值得注意的是在setDataSource_pre中因为NuPlayerDriver的hardwareOutput方法返回false,所以创建了一个AudioOutput对象,而这个对象包含了一个AudioTrack,老司机一看就能想到,这玩意最后用来播放音频了,至于是不是,我们以后再说吧。

好,接下来通过setDataSource_post的第二个参数调用了NuPlayerDriver的setDataSource函数。

status_t NuPlayerDriver::setDataSource(int fd, int64_t offset, int64_t length) {
    ALOGV("setDataSource(%p) file(%d)", this, fd);
    Mutex::Autolock autoLock(mLock);

    if (mState != STATE_IDLE) {
        return INVALID_OPERATION;
    }

    mState = STATE_SET_DATASOURCE_PENDING;
    mPlayer->setDataSourceAsync(fd, offset, length);

    while (mState == STATE_SET_DATASOURCE_PENDING) {
        mCondition.wait(mLock);
    }
    return mAsyncResult;
}

本来看起来以为就要结束了,这时候又杀出来个mPlayer,头瞬间就炸了,看看定义。

NuPlayerDriver::NuPlayerDriver(pid_t pid)
    : mState(STATE_IDLE),
      mIsAsyncPrepare(false),
      mAsyncResult(UNKNOWN_ERROR),
      mSetSurfaceInProgress(false),
      mDurationUs(-1),
      mPositionUs(-1),
      mSeekInProgress(false),
      mLooper(new ALooper),
      mPlayerFlags(0),
      mAtEOS(false),
      mLooping(false),
      mAutoLoop(false),
      mStartupSeekTimeUs(-1) {
    ALOGV("NuPlayerDriver(%p)", this);
    mLooper->setName("NuPlayerDriver Looper");

    mLooper->start(
            false, /* runOnCallingThread */
            true,  /* canCallJava */
            PRIORITY_AUDIO);

    mPlayer = new NuPlayer(pid);
    mLooper->registerHandler(mPlayer);

    mPlayer->setDriver(this);
}
此时弄出来了个NuPlayer,为啥?翻翻设计模式吧。Google的代码就是这么傲娇。

看看NuPlayer的setDataSourceAsync方法,一看到这个后缀Async就有一种不翔的预感------消息机制:

void NuPlayer::setDataSourceAsync(int fd, int64_t offset, int64_t length) {
    sp msg = new AMessage(kWhatSetDataSource, this);
    sp notify = new AMessage(kWhatSourceNotify, this);
    sp source =
            new GenericSource(notify, mUIDValid, mUID);
    status_t err = source->setDataSource(fd, offset, length);

    if (err != OK) {
        ALOGE("Failed to set data source!");
        source = NULL;
    }
    msg->setObject("source", source);
    msg->post();
}

满满的消息机制,这玩意倒是不复杂,不过当消息漫天飞来飞去的时候,对代码阅读有点阻碍,不过算不上大问题,先不管这个消息了,看看具体做了些啥,先是new了两个Message然后创建了一个GenericSource对象,创建后调用source的setDataSource,通过传给GenericSource构造函数的参数可以知道这个notify的消息是用来让source发回调消息给Player的,而msg这个消息,显然是一个通知。那么这个Source是啥呢,简单来说,就是对media资源的一个封装而已。看看函数把。

status_t NuPlayer::GenericSource::setDataSource(
        int fd, int64_t offset, int64_t length) {
    resetDataSource();

    mFd = dup(fd);
    mOffset = offset;
    mLength = length;

    // delay data source creation to prepareAsync() to avoid blocking
    // the calling thread in setDataSource for any significant time.
    return OK;
}
好嘛,简单直接,是吧,存下来就好了,因为还没确定你是不是真的要播放呢,没必要搞那么多事,万一你是撩完就跑呢。那就省点精力,能下一步做的事情就下一步做好了。

现在来看看这个msg消息是干嘛的好了,直接看看处理的地方,至于这个消息机制的具体知识看看下一篇再介绍好了,写微博好累,写微博好累,写微博好累,重要的事情说三次。。。

        case kWhatSetDataSource:
        {
            ALOGV("kWhatSetDataSource");

            CHECK(mSource == NULL);
            status_t err = OK;
            sp obj;
            CHECK(msg->findObject("source", &obj));
            if (obj != NULL) {
                mSource = static_cast(obj.get());
            } else {
                err = UNKNOWN_ERROR;
            }

            CHECK(mDriver != NULL);
            sp driver = mDriver.promote();
            if (driver != NULL) {
                driver->notifySetDataSourceCompleted(err);
            }
            break;
        }
都是类型转换,重点最后driver->notifySetDataSourceCompleted(err);
void NuPlayerDriver::notifySetDataSourceCompleted(status_t err) {
    Mutex::Autolock autoLock(mLock);
    CHECK_EQ(mState, STATE_SET_DATASOURCE_PENDING);
    mAsyncResult = err;
    mState = (err == OK) ? STATE_UNPREPARED : STATE_IDLE;
    mCondition.broadcast();
}

改了下状态而已,刚才在调用NuPlayerDriver::setDataSource方法时,使用了异步机制,既然是异步的,那想知道调用的结果如何,只好等通知了,通知的方式就是Condition,也就是条件变量,这玩意很常见了,不明白的话Google一下pthread_cond_t就明白了。

好了,到此setDataSource就结束了,过程简单明了,真正麻烦的在后面的start调用,以后再说,流程图还没时间画,以后有时间在补一张。














你可能感兴趣的:(Android Media Player 框架分析-Nuplayer(1))