NuPlayer源碼分析二:解封裝模塊

NuPlayer解封裝模塊


系列文章分爲如下幾個模塊:

解封裝模塊的重要作用,是將封裝好的音視頻源文件,通過不同的封裝協議,解析成碼流後,送到解碼器解碼。

NuPlayer中和解封裝相關的類有:

  • NuPlayer::Source:解封裝模塊的基類,定義瞭解封裝的基本接口。
  • GenericSource:本地文件相關。
  • HTTPLiveSource:HLS流媒體使用的解封裝類。
  • RTSPSource:SDP協議媒體流使用的解封裝類。

此外,還需要DataSource等配合操作。類圖如下:
在這裏插入圖片描述

篇幅有限,本文主要介紹本地媒體文件的例子。也就是GenericSource播放本地文件爲例。

Android播放器的一般步驟

一個Android播放器典型的播放步驟一般是:

  1. 播放器創建。
  2. 設置媒體源文件(本地文件路徑、或者Uri)。
  3. 準備媒體數據。
  4. 播放視頻。
  5. 停止播放。

爲了方便分析解封裝模塊,也從該順序逐步分析解封裝過程。對應的播放器調用接口如下:

  1. GenericSource:創建

  2. setDataSource:設置媒體源數據

  3. prepareAsync:準備媒體數據

  4. start:播放視頻

  5. stop&pause:停止播放

GenericSource:創建

先來分析第一個步驟:GenericSource的創建,即播放器創建部分。在流程中的位置是:

  1. GenericSource的創建

  2. setDataSource:設置媒體源數據

  3. prepareAsync:準備媒體數據

  4. start:啓動

  5. stop&pause&resume:停止&暫停&恢復

上一篇文章中,我們提到,在NuPlayersetDataSourceAsync函數中創建了GenericSource對象。並調用了setDataSource函數。用一張圖回憶一下:
在這裏插入圖片描述
再來看看對應代碼:

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); // 新建消息,用於和解封裝模塊通信,類似於一種listener的功能。

    sp<GenericSource> source = new GenericSource(notify, mUIDValid, mUID); // 創建解封裝器
    status_t err = source->setDataSource(fd, offset, length);  // 爲GenericSource設置媒體源
    msg->setObject("source", source);
    msg->post(); // 將創建並設置好的setDataSource,post給下一個流程處理
    mDataSourceType = DATA_SOURCE_TYPE_GENERIC_FD;
}

這段代碼中,首次創建了一個GenericSource實例,先來看看實例化過程。

NuPlayer::GenericSource::GenericSource(
        const sp<AMessage> &notify,
        bool uidValid,
        uid_t uid)
    : Source(notify), // 將一個AMessage對象存放在父類Source的mNotify字段中,這是個通用操作,用來通知調用者,當前資源狀態的。
      mAudioTimeUs(0),
      mAudioLastDequeueTimeUs(0),
      mVideoTimeUs(0),
      mVideoLastDequeueTimeUs(0),
      mFetchSubtitleDataGeneration(0),
      mFetchTimedTextDataGeneration(0),
      mDurationUs(-1ll),
      mAudioIsVorbis(false), // 音頻是否爲Vorbis壓縮格式,默認爲false
      mIsSecure(false),
      mIsStreaming(false),
      mFd(-1), // 文件句柄
      mBitrate(-1ll), // 比特率
      mPendingReadBufferTypes(0) {
    mBufferingMonitor = new BufferingMonitor(notify); // 新建一個BufferingMonitor實例
    resetDataSource(); // 重置一些DataSource數據到初始狀態。
}

從構造函數默認初始化列表中的字段含義來看,GenericSource包含了除了Buffer以外幾乎所有的解封裝相關數據,如文件句柄(mFd)、媒體時長(mDurationUs)等。

而關於Buffer狀態的管理和監聽使用的是BufferingMonitor類來實現。

  • BufferingMonitor:協助監控Buffer的狀態,每秒輪詢一次,必要時會將Buffer的狀態通過AMessage通知Player。

可見其重要性,來簡單看一下該結構體和部分函數,間接感受一下它的功能:

struct BufferingMonitor : public AHandler {
    public:
    explicit BufferingMonitor(const sp<AMessage> &notify);
    // 重新啓動監視任務。
    void restartPollBuffering();
    // 停止緩衝任務併發送相應的事件。
    void stopBufferingIfNecessary();
    // 確保數據源正在獲取數據。
    void ensureCacheIsFetching();
    // 更新從DataSource剛剛提取的緩衝區的媒體時間。
    void updateQueuedTime(bool isAudio, int64_t timeUs);
    // 更新發送到解碼器的最後出隊緩衝區的媒體時間。
    void updateDequeuedBufferTime(int64_t mediaUs);
    protected:
    virtual ~BufferingMonitor();
    virtual void onMessageReceived(const sp<AMessage> &msg);
}

setDataSource:設置媒體源數據

setDataSource在播放流程中的位置爲:

  1. GenericSource的創建

  2. setDataSource:設置媒體源數據

  3. prepareAsync:準備媒體數據

  4. start:啓動

  5. stop&pause&resume:停止&暫停&恢復

status_t NuPlayer::GenericSource::setDataSource(int fd, int64_t offset, int64_t length) {
    ALOGV("setDataSource %d/%lld/%lld", fd, (long long)offset, (long long)length);
    resetDataSource(); // 重置一些DataSource數據到初始狀態。
    mFd = dup(fd); // 將文件的句柄複製一份給mFd字段
    mOffset = offset; // 數據的偏移量
    mLength = length; // 文件長度

    // delay data source creation to prepareAsync() to avoid blocking
    // the calling thread in setDataSource for any significant time.
    return OK;
}

dup(fd)是什麼?該函數定義在/frameworks/base/core/java/android/os/ParcelFileDescriptor.java中,函數原型爲:public static ParcelFileDescriptor dup(FileDescriptor orig)

作用:創建一個新的ParcelFileDescriptor,它是現有FileDescriptor的副本。 這遵循標準POSIX語義,其中新文件描述符共享狀態,例如文件位置與原始文件描述符。

可以看到,setDataSource除了將媒體文件相關參數保存下來外,並沒有做其他的工作。順便看一看resetDataSource函數吧:

void NuPlayer::GenericSource::resetDataSource() {
   mUri.clear();
   mUriHeaders.clear();
   if (mFd >= 0) {
       close(mFd);
       mFd = -1;
   }
   mOffset = 0;
   mLength = 0;
   mStarted = false;
   mStopRead = true;

   if (mBufferingMonitorLooper != NULL) { // 讓BufferingMonitor停止循環監聽buffer
       mBufferingMonitorLooper->unregisterHandler(mBufferingMonitor->id());
       mBufferingMonitorLooper->stop();
       mBufferingMonitorLooper = NULL;
   }
   mBufferingMonitor->stop();
   mMimes.clear();
}

主要有兩個方面作用:

  1. 將一些媒體資源文件相關索引(值),以及解析器狀態重置爲默認狀態。
  2. 停止使用讓BufferingMonitor停止循環監聽buffer。

下面來看看如何準備資源的

prepareAsync:準備媒體數據

prepareAsync在播放流程中的位置爲:

  1. GenericSource的創建

  2. setDataSource:設置媒體源數據

  3. prepareAsync:準備媒體數據

  4. start:啓動

  5. stop&pause&resume:停止&暫停&恢復

void NuPlayer::GenericSource::prepareAsync() {
    ALOGV("prepareAsync: (looper: %d)", (mLooper != NULL));
    if (mLooper == NULL) { // 創建looper並啓動AHandler循環
        mLooper = new ALooper;
        mLooper->setName("generic");
        mLooper->start();
        mLooper->registerHandler(this);
    }
    sp<AMessage> msg = new AMessage(kWhatPrepareAsync, this);
    msg->post();
}

雖然代碼少,但這是一個很重要的調用:創建ALooper並且讓Looper 循環起來了。這個信息告訴我們,GenericSource本身組成了一個NativeHandler體系,用於傳遞自身消息。

GenericSource類通過繼承NuPlayer::Source間接繼承了AHandler,用於處理消息。

這些,都說明GenericSource的函數會有部分是異步的,函數名中prepareAsync中的Async也表明了這一點。

不熟悉的朋友可以翻一翻這篇文章:Android媒體底層通信框架Native Handler

啓動了looper循環處理消息後,發送了一個kWhatPrepareAsync的消息,給looper線程來處理。

熟悉NativeHandler的朋友應該知道,GenericSource函數作爲AHandler,必然要重寫onMessageReceived函數,用於處理數據:

void NuPlayer::GenericSource::onMessageReceived(const sp<AMessage> &msg) {
    switch (msg->what()) {
      case kWhatPrepareAsync:
      {
          onPrepareAsync();
          break;
      }
      case kWhatStart:
      case kWhatResume:
      {
          mBufferingMonitor->restartPollBuffering();
          break;
      }
      // .......省略一萬行.......
    }
}

AMessage的標誌是kWhatPrepareAsync,在onMessageReceived並沒有做什麼處理,直接調用了onPrepareAsync函數。

void NuPlayer::GenericSource::onPrepareAsync() { // 該函數運行在looper所在的子線程中
    // delayed data source creation
    if (mDataSource == NULL) { // 第一次進來,mDataSource肯定爲空
        mIsSecure = false; // 先設置爲false,如果extractor返回爲安全,再設置爲true.
        if (!mUri.empty()) { // 因爲是本地文件,所以mUri不用初始化,自然爲空。
			// 略掉網絡媒體源創建DataSource相關代碼。
        } else { // 處理本地媒體文件源
            // media.stagefright.extractremote屬性一般不會設置,
            if (property_get_bool("media.stagefright.extractremote", true) &&
                    !FileSource::requiresDrm(mFd, mOffset, mLength, nullptr /* mime */)) {
                sp<IBinder> binder =
                        defaultServiceManager()->getService(String16("media.extractor"));
                if (binder != nullptr) {
                    ALOGD("FileSource remote");
                    sp<IMediaExtractorService> mediaExService(
                            interface_cast<IMediaExtractorService>(binder));
                    sp<IDataSource> source =
                            mediaExService->makeIDataSource(mFd, mOffset, mLength);
                    ALOGV("IDataSource(FileSource): %p %d %lld %lld",
                            source.get(), mFd, (long long)mOffset, (long long)mLength);
                    if (source.get() != nullptr) {
                        mDataSource = DataSource::CreateFromIDataSource(source);
                        if (mDataSource != nullptr) { // 過河拆遷,初始化mDataSource成功後
                            // Close the local file descriptor as it is not needed anymore.
                            close(mFd);
                            mFd = -1;	
                        }
                    }
                }
            }
            if (mDataSource == nullptr) { // 如果沒有從extractor服務中成功獲取DataSource就自己創建
                ALOGD("FileSource local");
                mDataSource = new FileSource(mFd, mOffset, mLength);
            }
            mFd = -1;
        }

        if (mDataSource == NULL) { // 到這裏基本上是不可能爲NULL了
            ALOGE("Failed to create data source!");
            notifyPreparedAndCleanup(UNKNOWN_ERROR);
            return;
        }
    }

    if (mDataSource->flags() & DataSource::kIsCachingDataSource) {
        mCachedSource = static_cast<NuCachedSource2 *>(mDataSource.get());
    }

    // For cached streaming cases, we need to wait for enough
    // buffering before reporting prepared.
    mIsStreaming = (mCachedSource != NULL);

    // init extractor from data source
    status_t err = initFromDataSource();
	// ...
    finishPrepareAsync();
    ALOGV("onPrepareAsync: Done");
}

從函數代碼中可以看出,該函數唯一的目的就是爲了初始化mDataSource,主要的初始化方式有兩個:

  1. 從MediaExtractorService服務中獲取。
  2. 如果第一步未能初始化成功,直接自己創建一個new FileSource

這裏沒有想到的是,Android底層框架爲了解封裝的通用性,直接提供了一個解封裝相關的服務:MediaExtractorService,服務名稱爲:“media.extractor”,NuPlayer作爲衆多播放器的一種,也是可以直接享受該服務的。在這裏就通過該服務,創建了一個DataSource對象。

這裏有個問題,最終NuPLayer使用的到底是通過ExtractorService獲取DataSource對象,還是直接自己new FileSource呢。

我們當然可以通過日誌來判斷,但這會失去對播放邏輯的學習。所以,我一般都通過代碼來判斷。

因爲代碼調用的先後順序,我們先來看通過服務獲取的過程。

MediaExtractor服務獲取DataSource

// media.stagefright.extractremote屬性一般不會設置,
if (property_get_bool("media.stagefright.extractremote", true) &&
    !FileSource::requiresDrm(mFd, mOffset, mLength, nullptr /* mime */)) {
    // 通過Binder機制,獲取"media.extractor"服務的遠程代理
    sp<IBinder> binder =
        defaultServiceManager()->getService(String16("media.extractor"));
    if (binder != nullptr) { // 獲取失敗時爲空指針
        ALOGD("FileSource remote");
        // 強轉爲IMediaExtractorService對象指針
        sp<IMediaExtractorService> mediaExService(
            interface_cast<IMediaExtractorService>(binder));
        // 調用服務的代理對象接口,獲取IDataSource對象指針
        sp<IDataSource> source =
            mediaExService->makeIDataSource(mFd, mOffset, mLength);
        ALOGV("IDataSource(FileSource): %p %d %lld %lld",
              source.get(), mFd, (long long)mOffset, (long long)mLength);
        if (source.get() != nullptr) {
            // 通過獲取IDataSource對象指針初始化mDataSource
            mDataSource = DataSource::CreateFromIDataSource(source);
            if (mDataSource != nullptr) { // 過河拆遷,初始化mDataSource成功後
                // Close the local file descriptor as it is not needed anymore.
                close(mFd);
                mFd = -1;	
            }
        }
    }
}

這一段代碼,比較重要的函數調用,都加上了註釋,這裏再囉嗦得總結一下吧:

  • getService(String16("media.extractor")):熟悉binder機制的同學都知道,這是Binder遠端獲取指定服務的基本操作了。有時間整理一份文章出來,敬請期待吧。
  • mediaExService->makeIDataSource:調用服務接口,創建IDataSource對象。
  • DataSource::CreateFromIDataSource:調用CreateFromIDataSource通過前面創建的IDataSource初始化mDataSource。

基本上就這麼回事兒。第一點就不說了,東西太多。這裏稍微展開一下第二、第三點的調用。

makeIDataSource

該函數是通過Binder的遠端調用,最終會調用到服務端的代碼,也就是MediaExtractorService中:

代碼路徑:/frameworks/av/services/mediaextractor/MediaExtractorService.cpp

sp<IDataSource> MediaExtractorService::makeIDataSource(int fd, int64_t offset, int64_t length)
{
    sp<DataSource> source = DataSource::CreateFromFd(fd, offset, length);
    return source.get() != nullptr ? source->asIDataSource() : nullptr;
}

再看CreateFromFd幹了啥:

sp<DataSource> DataSource::CreateFromFd(int fd, int64_t offset, int64_t length) {
    sp<FileSource> source = new FileSource(fd, offset, length); // 也是直接new了FileSource
    return source->initCheck() != OK ? nullptr : source; // 檢查是否有sp時候爲有效指針,有效把指針丟回去
}

咦~這代碼看着耳熟啊!!!也是直接new FileSource

那個initCheck()函數,就不說了,說多了又是長篇大論。

CreateFromIDataSource

sp<DataSource> DataSource::CreateFromIDataSource(const sp<IDataSource> &source) {
    return new TinyCacheSource(new CallbackDataSource(source));
}

我去,又來了兩個陌生的類,其實他們都和DataSource有千絲萬縷的聯繫,看看一下類圖:
在這裏插入圖片描述
有關CallbackDataSourceTinyCacheSource的定義,都在CallbackDataSource.h頭文件中。它們的定義都比較簡單,就不貼代碼了,有興趣自己去看,源碼路徑如下:

\frameworks\av\include\media\stagefright\CallbackDataSource.h

\frameworks\av\media\libstagefright\CallbackDataSource.cpp

下面來稍微總結一下前面的類圖:

  • DataSource:該類規定了媒體源文件基本的操作接口。

  • IDataSource:它是實現遠程調用stagefright DataSource的Binder接口。Android媒體相關的各種服務中,創建的DataSource對象,都通過這個client的遠程接口句柄來調用。

  • CallbackDataSource:實現了DataSource接口(實現關係),但它的私有字段mIDataSource中,保留了IDataSource(服務端DataSource)的引用(組合關係),讓Client端程序可以回調到server端的DataSource對象,從而具備了”回調“功能。

  • TinyCacheSource:該類實現了DataSource接口(實現關係),在私有字段mSource中可以持有DataSource的引用,這個引用通常是用來存放CallbackDataSource對象的,所以和CallbackDataSource形成了組合關係。另外,該類中還有一個用於緩存的數組mCache[kCacheSize],對於小於kCacheSize的讀取,它將提前讀取並緩存在mCache中,這不僅極大減少了Client端到Server端的數據讀取操作,對提高數據類型嗅探和元數據(metadata)的提取也有較高效率。

回頭來看代碼:

return new TinyCacheSource(new CallbackDataSource(source));

也就稀鬆平常了,不過是將server端的FileSource對象,通過IDataSource接口傳遞到client端後,依次通過CallbackDataSourceTinyCacheSource對象包起來,已達到後續可以通過IDataSource對象調用遠端FileSource對象的目的。

new FileSource

整個onPrepareAsync函數執行的前一部分,都在想法設法的通過"media.extractor"服務,獲取初始化mDataSource字段,如果初始化失敗,那麼這個這個邏輯不會執行,如果失敗,那麼mDataSource的值爲NULL

if (mDataSource == nullptr) { // 如果沒有從extractor服務中成功獲取DataSource就自己創建
    ALOGD("FileSource local");
    mDataSource = new FileSource(mFd, mOffset, mLength);
}
mFd = -1;

這段代碼就比較簡潔,直接創建一個FileSource,將文件句柄和偏移量,長度等信息作出構造參數傳遞過去。這裏就先不展開FileSource源碼的分析,後面涉及到的時候再聊。

小結

所以,總結一下prepareAsync函數:

  • 該函數是異步執行的,整整的prepare動作,是在子線程執行的onPrepareAsync函數中。
  • onPrepareAsync函數主要的作用就是初始化mDataSource字段。共有兩種方式,首相嘗試通過"media.extractor"服務獲取server端DataSource,失敗後嘗試直接自己new FileSource
  • 遠端服務實例化mDataSource能否成功,主要看該服務在系統中是否啓用(一般來說都是正常運行的)。
  • 如果無法通過"media.extractor"初始化mDataSource,就直接自己創建(new FileSource)。
  • 不管通過server端還是自己new的方式,mDataSource最終關聯的對象都是FileSource的實例。

initFromDataSource

想方設法將mDataSource字段初始化後,接着往下看(不考慮初始化失敗的場景)。

if (mDataSource->flags() & DataSource::kIsCachingDataSource) { // 16 & 4 = 0
   mCachedSource = static_cast<NuCachedSource2 *>(mDataSource.get());
}
mIsStreaming = (mCachedSource != NULL); // mIsStreaming = false

// 通過data source初始化extractorinit
status_t err = initFromDataSource();

mDataSource->flags()這段代碼,會經歷漫長的路程,大概是這樣:

mDataSource->flags() ==>> TinyCacheSource::flags() ==>> CallbackDataSource::flags() ==>> FileSource::flags()。

virtual uint32_t flags() {
    return kIsLocalFileSource;
}

最終返回一個固定的值kIsLocalFileSource也就是16。該值定義在一個DataSource的結構體中:

enum Flags {
    kWantsPrefetching      = 1,
    kStreamedFromLocalHost = 2,
    kIsCachingDataSource   = 4,
    kIsHTTPBasedSource     = 8,
    kIsLocalFileSource     = 16,
};

DataSource::kIsCachingDataSource的值爲4,16&4 = 0。結果可想而知,哎,走了步閒棋。

該幹正事了,分析一下這段調用中最重要的函數之一:initFromDataSource

status_t NuPlayer::GenericSource::initFromDataSource() {
    sp<IMediaExtractor> extractor;
    extractor = MediaExtractor::Create(mDataSource, NULL); // 創建

    mFileMeta = extractor->getMetaData();
    if (mFileMeta != NULL) {
        int64_t duration;
        if (mFileMeta->findInt64(kKeyDuration, &duration)) {
            mDurationUs = duration;
        }
    }

    int32_t totalBitrate = 0;
    size_t numtracks = extractor->countTracks();

    mMimes.clear();
    for (size_t i = 0; i < numtracks; ++i) {
        sp<IMediaSource> track = extractor->getTrack(i);
        sp<MetaData> meta = extractor->getTrackMetaData(i);
        const char *mime;
        CHECK(meta->findCString(kKeyMIMEType, &mime));
        ALOGV("initFromDataSource track[%zu]: %s", i, mime);

        if (!strncasecmp(mime, "audio/", 6)) {
            if (mAudioTrack.mSource == NULL) {
                mAudioTrack.mIndex = i;
                mAudioTrack.mSource = track;
                mAudioTrack.mPackets =
                    new AnotherPacketSource(mAudioTrack.mSource->getFormat());

                if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS)) {
                    mAudioIsVorbis = true;
                } else {
                    mAudioIsVorbis = false;
                }

                mMimes.add(String8(mime));
            }
        } else if (!strncasecmp(mime, "video/", 6)) {
            if (mVideoTrack.mSource == NULL) {
                mVideoTrack.mIndex = i;
                mVideoTrack.mSource = track;
                mVideoTrack.mPackets =
                    new AnotherPacketSource(mVideoTrack.mSource->getFormat());

                // video always at the beginning
                mMimes.insertAt(String8(mime), 0);
            }
        }

        mSources.push(track);
        int64_t durationUs;
        if (meta->findInt64(kKeyDuration, &durationUs)) {
            if (durationUs > mDurationUs) {
                mDurationUs = durationUs;
            }
        }

        int32_t bitrate;
        if (totalBitrate >= 0 && meta->findInt32(kKeyBitRate, &bitrate)) {
            totalBitrate += bitrate;
        } else {
            totalBitrate = -1;
        }
    }

    ALOGV("initFromDataSource mSources.size(): %zu  mIsSecure: %d  mime[0]: %s", mSources.size(),
            mIsSecure, (mMimes.isEmpty() ? "NONE" : mMimes[0].string()));

    mBitrate = totalBitrate;
    return OK;
}

IMediaExtractor創建

代碼挺長,先來看看MediaExtractor::Create(mDataSource, NULL):

// static
sp<IMediaExtractor> MediaExtractor::Create(
        const sp<DataSource> &source, const char *mime) {
    if (!property_get_bool("media.stagefright.extractremote", true)) {
        // 本地 extractor
        ALOGW("creating media extractor in calling process");
        return CreateFromService(source, mime);
    } else { // 使用遠程extractor
        ALOGV("get service manager");
        sp<IBinder> binder = defaultServiceManager()->getService(String16("media.extractor"));
        if (binder != 0) {
            sp<IMediaExtractorService> mediaExService(interface_cast<IMediaExtractorService>(binder));
            sp<IMediaExtractor> ex = mediaExService->makeExtractor(source->asIDataSource(), mime);
            return ex;
        } else {
            ALOGE("extractor service not running");
            return NULL;
        }
    }
    return NULL;
}

第一個條件判斷,獲取系統屬性“media.stagefright.extractremote”,通常該屬性都是默認未設置的。

media.stagefright.extractremote系統屬性,用於判斷是否之處遠程extractor服務。

所以property_get_bool調用返回默認值true!true則條件不成立。所以通過遠程服務“media.extractor”創建一個MediaExtractor返回。

MediaExtractorService::makeExtractor
sp<IMediaExtractor> MediaExtractorService::makeExtractor(
        const sp<IDataSource> &remoteSource, const char *mime) {
    ALOGV("@@@ MediaExtractorService::makeExtractor for %s", mime);
    sp<DataSource> localSource = DataSource::CreateFromIDataSource(remoteSource);
    sp<IMediaExtractor> ret = MediaExtractor::CreateFromService(localSource, mime);
    ALOGV("extractor service created %p (%s)", ret.get(), ret == NULL ? "" : ret->name());
    if (ret != NULL) {
        registerMediaExtractor(ret, localSource, mime);
    }
    return ret;
}

DataSource::CreateFromIDataSource前面已經詳細說明了,這裏就不贅述了。來看看更重要的函數

MediaExtractor::CreateFromService
sp<MediaExtractor> MediaExtractor::CreateFromService(
        const sp<DataSource> &source, const char *mime) {
    ALOGV("MediaExtractor::CreateFromService %s", mime);
    RegisterDefaultSniffers();

    sp<AMessage> meta;
    String8 tmp;
    if (mime == NULL) {
        float confidence;
        if (!sniff(source, &tmp, &confidence, &meta)) {
            ALOGW("FAILED to autodetect media content.");
            return NULL;
        }
        mime = tmp.string();
    }

    MediaExtractor *ret = NULL;
    if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG4)
            || !strcasecmp(mime, "audio/mp4")) {
        ret = new MPEG4Extractor(source);
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG)) {
        ret = new MP3Extractor(source, meta);
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)
            || !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {
        ret = new AMRExtractor(source);
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)) {
        ret = new FLACExtractor(source);
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WAV)) {
        ret = new WAVExtractor(source);
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_OGG)) {
        ret = new OggExtractor(source);
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MATROSKA)) {
        ret = new MatroskaExtractor(source);
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) {
        ret = new MPEG2TSExtractor(source);
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC_ADTS)) {
        ret = new AACExtractor(source, meta);
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2PS)) {
        ret = new MPEG2PSExtractor(source);
    } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MIDI)) {
        ret = new MidiExtractor(source);
    }
	// 略掉了一些跟蹤信息的代碼
    return ret;
}

天哪嚕,感覺又捅了一個馬蜂窩,哎,源碼真是輕易看不得啊啊啊啊啊啊。內容是真的多,感覺又可以單獨拎出來,另起一篇了。

來看看MediaExtractor的類圖吧。
在這裏插入圖片描述

MediaExtractor::RegisterDefaultSniffers
// static
void MediaExtractor::RegisterDefaultSniffers() {
    Mutex::Autolock autoLock(gSnifferMutex);
    if (gSniffersRegistered) { // 只註冊一次
        return;
    }
    RegisterSniffer_l(SniffMPEG4);
    RegisterSniffer_l(SniffMatroska);
    RegisterSniffer_l(SniffOgg);
    RegisterSniffer_l(SniffWAV);
    RegisterSniffer_l(SniffFLAC);
    RegisterSniffer_l(SniffAMR);
    RegisterSniffer_l(SniffMPEG2TS);
    RegisterSniffer_l(SniffMP3);
    RegisterSniffer_l(SniffAAC);
    RegisterSniffer_l(SniffMPEG2PS);
    RegisterSniffer_l(SniffMidi);
    gSniffersRegistered = true;
}

sniff的意思是“嗅、用鼻子吸“,sniffer可以翻譯爲”嗅探器“。所以,該函數是註冊默認嗅探器的意思。

在媒體框架中,嗅探器又是個什麼概念呢?

這裏的嗅探,實際上是對媒體輸入源進行文件頭的讀取,根據文件頭內容嗅探出需要什麼樣的解封裝組件,也就是不同的MediaExtractor實現。通過類圖,我們也可以知道MediaExtractor有大量的實現,分別針對MP3、AAC、OGG、WAV、MPEG4等格式輸入的解封裝操作。

回到代碼上,函數體中,通過RegisterSniffer_l函數,將不同解封裝器的嗅探函數指針保存到了列表gSniffers中。顯然,針對於不同封裝格式的解封裝器,嗅探函數也是不一樣的,當然需要對應的解封裝器自己實現。

具體註冊嗅探器的代碼如下:

List<MediaExtractor::SnifferFunc> MediaExtractor::gSniffers;
// static
void MediaExtractor::RegisterSniffer_l(SnifferFunc func) {
    for (List<SnifferFunc>::iterator it = gSniffers.begin();
         it != gSniffers.end(); ++it) {
        if (*it == func) {
            return;
        }
    }
    gSniffers.push_back(func);
}
MediaExtractor::sniff
// static
bool MediaExtractor::sniff(
        const sp<DataSource> &source, String8 *mimeType, float *confidence, sp<AMessage> *meta) {
    *mimeType = "";
    *confidence = 0.0f;
    meta->clear();
    {
        Mutex::Autolock autoLock(gSnifferMutex);
        if (!gSniffersRegistered) { // 在“嗅探”之前必須已經註冊了嗅探器
            return false;
        }
    }
    for (List<SnifferFunc>::iterator it = gSniffers.begin();
         it != gSniffers.end(); ++it) { // 遍歷所有嗅探器
        String8 newMimeType;
        float newConfidence;
        sp<AMessage> newMeta;
        if ((*it)(source, &newMimeType, &newConfidence, &newMeta)) { // 調用嗅探器的嗅探函數
            if (newConfidence > *confidence) {
                *mimeType = newMimeType;
                *confidence = newConfidence;
                *meta = newMeta;
            }
        }
    }
    return *confidence > 0.0;
}

嗅探器的實現原理基本上都是讀取媒體源文件的頭信息,不同格式都會有自己的特徵,嗅探器就是根據這些特徵,來判斷是否是需要找的類型。

嗅探函數的目的,就是判斷源文件(碼流)類型是否和當前格式匹配。

說到嗅探函數的實現,必然會涉及到各種編碼格式的特徵,鑑於這部分內容實在太多,就不進一步詳細分析了。

這裏就簡單的說一下幾個參數的意義:

  • source:這是個DataSource類型的指針,該類型通過層層包裹包含了一系列讀取媒體源文件的功能。嗅探函數通過該指針通源文件中讀取頭信息,來判斷源文件的類型。

  • newMimeType:String8類型的指針,一旦嗅探函數通過頭信息探測出源文件屬於當前類型,該變量會通過指針賦值。這些類型定義在MediaDefs.cpp中如:

    const char *MEDIA_MIMETYPE_IMAGE_JPEG = "image/jpeg";
    const char *MEDIA_MIMETYPE_VIDEO_HEVC = "video/hevc";
    const char *MEDIA_MIMETYPE_VIDEO_MPEG2 = "video/mpeg2";
    
    const char *MEDIA_MIMETYPE_AUDIO_MPEG = "audio/mpeg";
    const char *MEDIA_MIMETYPE_AUDIO_FLAC = "audio/flac";
    const char *MEDIA_MIMETYPE_AUDIO_AC3 = "audio/ac3";
    const char *MEDIA_MIMETYPE_AUDIO_EAC3 = "audio/eac3";
    
    const char *MEDIA_MIMETYPE_CONTAINER_MPEG4 = "video/mp4";
    const char *MEDIA_MIMETYPE_CONTAINER_AVI = "video/avi";
    // ......................此處略去一萬字
    
  • newConfidence:float類型指針,一旦嗅探函數通過頭信息探測出源文件屬於當前類型,該變量會通過指針賦值。該值的意思是**“信心”,每個判斷了是自己類型的函數,都會給出對於源文件類型的判斷的信心值**,然後通過比較,信心值最大的類型判斷獲勝,該源文件便會被判定爲該類型。例如:SniffAAC對自己的信心值爲:0.2、SniffMPEG4:0.4、SniffMatroska:0.6、SniffOgg:0.2等。

  • newMeta:這是一個AMessage對象,用於將嗅探結果(一些和格式相關的頭信息)傳遞給調用者。對於不同格式傳遞的類型會不一樣。

    SniffMPEG4傳遞的是:文件頭結束的偏移量

    if (moovAtomEndOffset >= 0) { mpeg4
        *meta = new AMessage;
        (*meta)->setInt64("meta-data-size", moovAtomEndOffset);
    }
    

    SniffAAC傳遞:也是文件頭結束的偏移位置。

    *meta = new AMessage; aac
    (*meta)->setInt64("offset", pos);
    

    SniffMP3:有文件頭結束的偏移量,也有特有的格式位置信息。

    *meta = new AMessage;
    (*meta)->setInt64("offset", pos);
    (*meta)->setInt32("header", header);
    (*meta)->setInt64("post-id3-offset", post_id3_pos);
    

最後說一下整個嗅探函數的返回值return *confidence > 0.0;只需要信息之大於0.0就返回true,說明已經嗅探到格式信息。

一般來說,支持嗅探的格式越多,失敗的可能越小。Android默認支持的類型已經足夠普通使用了,所以,分析的時候我就當它返回true了。

回到MediaExtractor::CreateFromService中,經過嗅探函數對源文件進行嗅探後,基本能夠確定源文件的類型,並把嗅探出來的newMimeType字符串的指針賦值給mime,最終通過在CreateFromService對該類型進行比較,創建對應類型的XXXExtractor。代碼如下:

創建指定格式的Extractor
MediaExtractor *ret = NULL;
if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG4)
    || !strcasecmp(mime, "audio/mp4")) {
    ret = new MPEG4Extractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MPEG)) {
    ret = new MP3Extractor(source, meta);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)
           || !strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) {
    ret = new AMRExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_FLAC)) {
    ret = new FLACExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_WAV)) {
    ret = new WAVExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_OGG)) {
    ret = new OggExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MATROSKA)) {
    ret = new MatroskaExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2TS)) {
    ret = new MPEG2TSExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC_ADTS)) {
    ret = new AACExtractor(source, meta);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2PS)) {
    ret = new MPEG2PSExtractor(source);
} else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_MIDI)) {
    ret = new MidiExtractor(source);
}

創建完指定的MediaExtractor之後,還需要將剛剛創建的XXXExtractor註冊一下:

if (ret != NULL) {
    registerMediaExtractor(ret, localSource, mime);
}
registerMediaExtractor
void IMediaExtractor::registerMediaExtractor(
        const sp<IMediaExtractor> &extractor,
        const sp<DataSource> &source,
        const char *mime) {
    ExtractorInstance ex;
    ex.mime = mime == NULL ? "NULL" : mime;
    ex.name = extractor->name();
    ex.sourceDescription = source->toString();
    ex.owner = IPCThreadState::self()->getCallingPid();
    ex.extractor = extractor;
    // ...
        sExtractors.push_front(ex);
    // ...
}

該函數很短,作用也很簡單,直接將闖入的XXXExtractor實例用ExtractorInstance包裝一下,存放在sExtractors(一個vector)隊首中。方便以後查詢。

至此IMediaExtractor創建工作纔算完成。

小結

簡單小結一下創建過程中都做了那些值得注意的事:

  1. 將之前流程中創建的FileSource對象,通過包裝成了一個DataSource對象。
  2. 註冊各種格式Extractor的嗅探函數。
  3. 通過調用嗅探函數,利用DataSource讀取媒體文件頭,並分析媒體文件是何種格式。
  4. 根據媒體文件格式,創建對應格式的XXXExtractor

####初始化媒體源基本參數:mFileMeta、mDurationUs

initFromDataSource函數通過千辛萬苦創建了Extractor後,任務其實已經完成了一大半了。後面都是一些從Extractor實例後的對象中拿數據進行填充的過程。

接着看看後面初始化mFileMetamDurationUs的調用

mFileMeta = extractor->getMetaData();
if (mFileMeta != NULL) {
    int64_t duration;
    if (mFileMeta->findInt64(kKeyDuration, &duration)) {
        mDurationUs = duration;
    }
}

第一行代碼,通過extractor調用getMetaData獲取文件的元數據(metadata)。對於getMetaData函數的實現,不同類型的Extractor會有不同的實現手段,例如:

sp<MetaData> MPEG4Extractor::getMetaData() {
    status_t err;
    if ((err = readMetaData()) != OK) { // 從源文件中讀取MetaData信息,初始化mFileMetaData
        return new MetaData;
    }
    return mFileMetaData; // 將MetaData 返回給調用者
}
sp<MetaData> MP3Extractor::getMetaData() {
    sp<MetaData> meta = new MetaData; // 直接new
    if (mInitCheck != OK) {
        return meta;
    }
	// ...
    meta->setCString(kKeyMIMEType, "audio/mpeg");
    // ...
        meta->setCString(kMap[i].key, s);
    // ...
        meta->setData(kKeyAlbumArt, MetaData::TYPE_NONE, data, dataSize);
        meta->setCString(kKeyAlbumArtMIME, mime.string());
    return meta;
}

MPEG4ExtractorMP3ExtractorgetMetaData函數實現就大爲不同,不能再展開了。這裏只順便提及一下什麼是元數據(MetaData):

對於媒體文件而言,元數據一般有:音頻採樣率、視頻幀率、視頻尺寸、比特率、編解碼、播放時長等基本信息,此外也可能含有其它雜七雜八的信息:名稱、版權、專輯、時間、藝術家等。

初始化媒體源基本參數:mMimes、mSources、mBitrate

下面這些代碼就不一一解讀了,啥都不用說,都在註釋裏。

int32_t totalBitrate = 0;
size_t numtracks = extractor->countTracks(); // 獲取媒體源中的軌道數量,通常爲三個,音頻、視頻、字幕各一個
mMimes.clear(); // 清理掉mMime信息,準備裝新的。
for (size_t i = 0; i < numtracks; ++i) { // 遍歷軌道,將音視頻軌道信息的mime添加到mMimes中
    sp<IMediaSource> track = extractor->getTrack(i); // 獲取各軌道
    sp<MetaData> meta = extractor->getTrackMetaData(i); // 獲取各軌道的元數據
    const char *mime;
    CHECK(meta->findCString(kKeyMIMEType, &mime));
    ALOGV("initFromDataSource track[%zu]: %s", i, mime);
    if (!strncasecmp(mime, "audio/", 6)) { // 音頻軌道
        if (mAudioTrack.mSource == NULL) { // 初始化各種音頻軌道信息
            mAudioTrack.mIndex = i;
            mAudioTrack.mSource = track;
            mAudioTrack.mPackets =
                new AnotherPacketSource(mAudioTrack.mSource->getFormat());
            if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_VORBIS)) {
                mAudioIsVorbis = true;
            } else {
                mAudioIsVorbis = false;
            }
            mMimes.add(String8(mime)); // 將音頻軌道mime信息,添加到mMimes中
        }
    } else if (!strncasecmp(mime, "video/", 6)) { // 視頻軌道
        if (mVideoTrack.mSource == NULL) { // 初始化各種視頻軌道信息
            mVideoTrack.mIndex = i;
            mVideoTrack.mSource = track;
            mVideoTrack.mPackets =
                new AnotherPacketSource(mVideoTrack.mSource->getFormat());
            // video always at the beginning
            mMimes.insertAt(String8(mime), 0); // 將視頻軌道mime信息,添加到mMimes隊首
        }
    }
    mSources.push(track); // 將各軌道信息統一保存在保存在mSources中
    int64_t durationUs;
    if (meta->findInt64(kKeyDuration, &durationUs)) { // 獲取媒體播放時長
        if (durationUs > mDurationUs) { // 將個軌道中最大的播放時長作爲媒體文件的播放時長
            mDurationUs = durationUs;
        }
    }
	// 通比特率爲各軌道比特率之和
    int32_t bitrate;
    if (totalBitrate >= 0 && meta->findInt32(kKeyBitRate, &bitrate)) {
        totalBitrate += bitrate;
    } else {
        totalBitrate = -1;
    }
}
mBitrate = totalBitrate; // 初始化比特率

扯了這麼多,才把initFromDataSource搞完,趕緊看一下最後一個函數:

finishPrepareAsync

void NuPlayer::GenericSource::finishPrepareAsync() {
    ALOGV("finishPrepareAsync");
    status_t err = startSources(); // 啓動各資源對象
    // ....
    if (mIsStreaming) { // 通常爲false
		// ....
    } else {
        notifyPrepared(); // 該函數幾乎啥都沒做。
    }
}
status_t NuPlayer::GenericSource::startSources() {
    // 在我們開始緩衝之前,立即啓動所選的A / V曲目。
    // 如果我們將它延遲到start(),那麼在準備期間緩衝的所有數據都將被浪費。
    // (並不是在start()開始執行後,纔開始讀取數據)
    if (mAudioTrack.mSource != NULL && mAudioTrack.mSource->start() != OK) { // 啓動音頻
        ALOGE("failed to start audio track!");
        return UNKNOWN_ERROR;
    }

    if (mVideoTrack.mSource != NULL && mVideoTrack.mSource->start() != OK) { // 啓動視頻
        ALOGE("failed to start video track!");
        return UNKNOWN_ERROR;
    }
    return OK;
}

除了註釋部分的信息,關於mSource->start()我也沒什麼準備在本文補充了,如果以後有機會,我會寫n篇來盡未盡之事。

小結prepareSync

好了,prepareSync函數算是告一段落。花了巨大的篇幅來描述,總要總結一波的。

  1. 不管是通過直接創建,還是通過服務創建,總之拐彎抹角的創建了一個FileSource對象。並通過各種封裝,達到不一樣的用途。
  2. 通過各種封裝好的FileSource對象,讀取並嗅探源文件的頭信息,判斷文件格式,並創建對應格式的XXXExtractor
  3. 通過創建好的XXXExtractor,初始化各種媒體相關字段,如:mFileMetamDurationUsmMimesmSourcesmBitrate
  4. mSources中包含所有track信息,track中又包含了對應的流信息,通過這些信息,啓動了音視頻數據的讀取。

start:啓動

start在播放流程中的位置爲:

  1. GenericSource的創建

  2. setDataSource:設置媒體源數據

  3. prepareAsync:準備媒體數據

  4. start:啓動

  5. stop&pause&resume:停止&暫停&恢復

現在我們來看看GenericSource是怎麼參與到播放流程中的。

void NuPlayer::GenericSource::start() {
    ALOGI("start");
    mStopRead = false; // 啓動播放時,自然要不暫停讀取數據false掉
    if (mAudioTrack.mSource != NULL) { // 在prepareAsync中,已經賦值,自然不能爲空
        postReadBuffer(MEDIA_TRACK_TYPE_AUDIO); 
    }
    if (mVideoTrack.mSource != NULL) { // 在prepareAsync中,已經賦值,自然不能爲空
        postReadBuffer(MEDIA_TRACK_TYPE_VIDEO);
    }
    mStarted = true;
    (new AMessage(kWhatStart, this))->post();
}

postReadBuffer函數其實做的事情不多,就是把trackType一路向下異步傳遞,最後讓NuPlayer::GenericSource::readBuffer摘桃子,調用鏈如下:

postReadBuffer ==> onMessageReceived ==> onReadBuffer ==> readBuffer

基本上沒啥看頭,跳過,直接來readBuffer

NuPlayer::GenericSource::readBuffer

void NuPlayer::GenericSource::readBuffer(
        media_track_type trackType, int64_t seekTimeUs, MediaPlayerSeekMode mode,
        int64_t *actualTimeUs, bool formatChange) 
    if (mStopRead) {
        return;
    }
    Track *track;
    size_t maxBuffers = 1;
    switch (trackType) { // 根據track類型分配最大buffer,並初始化track
        case MEDIA_TRACK_TYPE_VIDEO: // 音頻
            track = &mVideoTrack;
            maxBuffers = 8;  // 最大buffer值爲64,太大的buffer值會導致不能流暢的執行seek操作。
            break;
        case MEDIA_TRACK_TYPE_AUDIO: // 視頻
            track = &mAudioTrack;
            maxBuffers = 64; // 最大buffer值爲64
            break;
        case MEDIA_TRACK_TYPE_SUBTITLE: // 字幕
            track = &mSubtitleTrack;
            break;
        // 篇幅有限,能省一行是一行
    }
	// 篇幅有限,能省一行是一行
    for (size_t numBuffers = 0; numBuffers < maxBuffers; ) {
        Vector<MediaBuffer *> mediaBuffers;
        status_t err = NO_ERROR;
		// 從文件中讀取媒體數據,用於填充mediaBuffers
        if (couldReadMultiple) { // 這個值一般爲true
            err = track->mSource->readMultiple(
                    &mediaBuffers, maxBuffers - numBuffers, &options);
        } else { // read函數其實最終也是調用了readMultiple,只是read的最大buffer數爲1
            MediaBuffer *mbuf = NULL;
            err = track->mSource->read(&mbuf, &options); 
            if (err == OK && mbuf != NULL) {
                mediaBuffers.push_back(mbuf);
            }
        }
        size_t id = 0;
        size_t count = mediaBuffers.size();
        for (; id < count; ++id) { // 將所有剛纔讀到的MediaBuffer中的數據摘出來封裝到mPackets中
            int64_t timeUs;
            MediaBuffer *mbuf = mediaBuffers[id];
            // 根據類型,通過mBufferingMonitor監視器更新狀態
            if (trackType == MEDIA_TRACK_TYPE_AUDIO) { 
                mAudioTimeUs = timeUs;
                mBufferingMonitor->updateQueuedTime(true /* isAudio */, timeUs);
            } else if (trackType == MEDIA_TRACK_TYPE_VIDEO) {
                mVideoTimeUs = timeUs;
                mBufferingMonitor->updateQueuedTime(false /* isAudio */, timeUs);
            }
            // 根據類型,將MediaBuffer轉換爲ABuffer
            sp<ABuffer> buffer = mediaBufferToABuffer(mbuf, trackType);
			// 篇幅有限,能省一行是一行
            track->mPackets->queueAccessUnit(buffer); // 將buffer入隊,等待播放
            formatChange = false;
            seeking = false;
            ++numBuffers;
        }
    }
}

除了trackType參數外,其它都是有默認參數的,在start調用鏈中,readBuffer只傳入了這個參數。其它參數可以控制seek功能。代碼其實比這個長多了,我刪掉了些暫時不重要的seek、異常中斷等邏輯。

能說的代碼註釋裏說了,繼續看一下start函數接下來發送的kWhatResume消息幹了啥

case kWhatResume:
{
    mBufferingMonitor->restartPollBuffering(); // 只是讓buffer監視器重新循環起來
    break;
}

start小結

  • start函數調用鏈中,最終的的就是readBuffer函數,該函數最終要的功能就是將各種類型的數據讀取並解析到track的buffer隊列中,等待播放。
  • 需要注意的是:解封裝模塊的start函數和NuPlayer的start功能並不相同,NuPlayer的start函數是播放,而解封裝模塊的start函數則是加載數據,後者是前者的子集。

stop&pause&resume:停止&暫停&恢復

start在播放流程中的位置爲:

  1. GenericSource的創建

  2. setDataSource:設置媒體源數據

  3. prepareAsync:準備媒體數據

  4. start:播放視頻

  5. stop&pause&resume:停止&暫停&恢復

void NuPlayer::GenericSource::stop() { // 停止
    mStarted = false;
}

void NuPlayer::GenericSource::pause() { // 暫停
    mStarted = false;
}

void NuPlayer::GenericSource::resume() { // 恢復
    mStarted = true;
    (new AMessage(kWhatResume, this))->post();
}

停止、暫停、恢復幾個動作,相關函數中僅是改變mStarted,其它幾乎什麼事情都沒做。

這有提醒了我解封裝模塊和播放器的區別:

  • 播放器的暫停:表示的是暫停播放
  • 解封裝模塊的暫停:表示暫停將讀取並緩存好的數據提供給播放器,這一點同樣適用於停止,回覆和start則相反。

所以,不管是停止、暫停還是回覆的函數,關鍵都不在函數本身,而在於mStarted變量對於向外提供數據的函數的影響,也就是dequeueAccessUnit

status_t NuPlayer::GenericSource::dequeueAccessUnit(
        bool audio, sp<ABuffer> *accessUnit) {
    if (audio && !mStarted) { // 如果是音頻,並且mStarted爲false,則不提供數據,返回block
        return -EWOULDBLOCK;
    }
    // ...
}

該函數用於爲播放器提供原始媒體數據,audio表示是否爲音頻,accessUnit則是需要填充的buffer指針。

可以看到,如果GenericSource::stop()或者GenericSource::pause()函數調用後,mStarted變爲了false,那麼播放器將無法得到媒體數據,也就無法播放了。

那麼,有人問,如果是視頻不就可以了麼。是的,視頻還是可以從該函數中獲取數據,但對於播放器而言,視頻和音頻肯定是同時播放,如果沒了音頻,視頻也不會獨活的。

好了,解封裝模塊終於搞定了。媽呀!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章