1. 程式人生 > >Android Media Player 框架分析-Nuplayer(1)

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的狀態圖,以供參考,好吧,我承認因為懶,我從度娘上盜了張圖。

我們的分析主要從剛才的示例程式碼開始,來看看我們簡單的幾個步驟會在內部執行什麼樣的操作。

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 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函式在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<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呼叫,以後再說,流程圖還沒時間畫,以後有時間在補一張。