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

来源:互联网 发布:axure mac汉化教程 编辑:程序博客网 时间:2024/04/20 05:45

由于工作岗位调整,开始接触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的状态图,以供参考,好吧,我承认因为懒,我从度娘上盗了张图。

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

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

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

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

// ----------------------------------------------------------------------------// ref-counted object for callbacksclass 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<IMediaPlayerService>& service(getMediaPlayerService());    if (service != 0) {        sp<IMediaPlayer> 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<IMediaPlayer> MediaPlayerService::create(const sp<IMediaPlayerClient>& client,        int audioSessionId){    pid_t pid = IPCThreadState::self()->getCallingPid();    int32_t connId = android_atomic_inc(&mNextConnId);    sp<Client> 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<Client> 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<uint64_t>(sb.st_dev));    ALOGV("st_mode = %u", sb.st_mode);    ALOGV("st_uid  = %lu", static_cast<unsigned long>(sb.st_uid));    ALOGV("st_gid  = %lu", static_cast<unsigned long>(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<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;}
好吧,路漫漫,看来事情远没有看起来那么容易。

该函数前面一段是检验参数用的,包括通过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<AMessage> msg = new AMessage(kWhatSetDataSource, this);    sp<AMessage> notify = new AMessage(kWhatSourceNotify, this);    sp<GenericSource> 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<RefBase> obj;            CHECK(msg->findObject("source", &obj));            if (obj != NULL) {                mSource = static_cast<Source *>(obj.get());            } else {                err = UNKNOWN_ERROR;            }            CHECK(mDriver != NULL);            sp<NuPlayerDriver> 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调用,以后再说,流程图还没时间画,以后有时间在补一张。














0 0