NuPlayer源碼分析三:解碼模塊

解碼模塊

NuPlayer的解碼模塊相對比較簡單,統一使用了一個基類NuPlayerDecoderBase管理,該類中包含了一個MediaCodec的對象,實際解碼工作全靠MediaCodec。

如果你不會知道MediaCodec是什麼,推薦去官網看看:MediaCodec

儘管解碼工作都被MediaCodec接管,我還是會按照播放器的一般步驟,來分析一下NuPlayerDecoderBase。步驟如下:

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

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

對應於解碼模塊,會稍微簡單一些:

  1. NuPlayerDecoderBase:解碼器創建
  2. onInputBufferFetched:填充數據到解碼隊列
  3. onRenderBuffer:渲染解碼後的數據
  4. ~Decoder:釋放解碼器

解碼器創建:NuPlayerDecoderBase

當前位置:

  1. NuPlayerDecoderBase:解碼器創建
  2. onInputBufferFetched:填充數據到解碼隊列
  3. onRenderBuffer:渲染解碼後的數據
  4. ~Decoder:釋放解碼器

解碼器創建的入口在NuPlayer的NuPlayer::instantiateDecoder函數調用時。NuPlayer在執行start函數後,會通過一系列調用鏈,觸發該函數。來具體分析一下該函數。

// 參數部分:audio true調用者想要創建音頻解碼器, false 想要創建視頻解碼器
// 參數部分:*decoder 該函數最終會創建指定解碼器,使用該函數將解碼器對象地址提供給調用者
status_t NuPlayer::instantiateDecoder(
        bool audio, sp<DecoderBase> *decoder, bool checkAudioModeChange) {
    sp<AMessage> format = mSource->getFormat(audio); // 其實就是GenericSource中的MetaData
    format->setInt32("priority", 0 /* realtime */);

    if (!audio) { // 視頻
        // 總要丟掉一些代碼的
            mCCDecoder = new CCDecoder(ccNotify); // 創建字幕解碼器
    }
	// 創建音/視頻解碼器
    if (audio) { // 音頻
        // ...
            *decoder = new Decoder(notify, mSource, mPID, mUID, mRenderer);
        // ...
    } else { // 視頻
		// ...
        *decoder = new Decoder(
                notify, mSource, mPID, mUID, mRenderer, mSurface, mCCDecoder);
		// ...
    }
    (*decoder)->init();
    (*decoder)->configure(format);

    if (!audio) { // 視頻
        sp<AMessage> params = new AMessage();
        float rate = getFrameRate();
        if (rate > 0) {
            params->setFloat("frame-rate-total", rate);
        }
		// ...
        if (params->countEntries() > 0) {
            (*decoder)->setParameters(params);
        }
    }
    return OK;
}

刪刪減減去掉了大量代碼,留下來我感興趣的。

先來說說,Decoder實際上是繼承於DecoderBase的。

Decoder前者的定義如下:

struct NuPlayer::Decoder : public DecoderBase {
    Decoder(const sp<AMessage> &notify,
            const sp<Source> &source,
            pid_t pid,
            uid_t uid,
            const sp<Renderer> &renderer = NULL,
            const sp<Surface> &surface = NULL,
            const sp<CCDecoder> &ccDecoder = NULL);
    // 自然又是刪掉很多代碼
protected:
    virtual ~Decoder();

DecoderBase的定義如下:

struct ABuffer;
struct MediaCodec;
class MediaBuffer;
class MediaCodecBuffer;
class Surface;
struct NuPlayer::DecoderBase : public AHandler {
    explicit DecoderBase(const sp<AMessage> &notify);
    void configure(const sp<AMessage> &format);
    void init();
    void setParameters(const sp<AMessage> &params);
protected:
    virtual ~DecoderBase();
    void stopLooper();
    virtual void onMessageReceived(const sp<AMessage> &msg);
    virtual void onConfigure(const sp<AMessage> &format) = 0;
    virtual void onSetParameters(const sp<AMessage> &params) = 0;
    virtual void onSetRenderer(const sp<Renderer> &renderer) = 0;
    virtual void onResume(bool notifyComplete) = 0;
    virtual void onFlush() = 0;
    virtual void onShutdown(bool notifyComplete) = 0;
};

可以從DecoderBase的實現看到,它包含了所有解碼相關的接口,這些接口往往都和MediaCodec的接口直接相關。可見,它是處在解碼的前沿陣地上的。

instantiateDecoder函數中,創建音頻和視頻的解碼器,參數略有不同,創建視頻解碼器是會多出一個mSurface,提供給MediaCodec以顯示視頻,mCCDecoder則是字幕相關解碼器。來看一下解碼器構建函數:

NuPlayer::Decoder::Decoder(
        const sp<AMessage> &notify,
        const sp<Source> &source,
        pid_t pid,
        uid_t uid,
        const sp<Renderer> &renderer,
        const sp<Surface> &surface,
        const sp<CCDecoder> &ccDecoder)
    : DecoderBase(notify),
      mSurface(surface), // 視頻播放的surface實體
      mSource(source),
      mRenderer(renderer), // 渲染器
      mCCDecoder(ccDecoder), // 字幕解碼器
      mIsAudio(true) { // 是否爲音頻
    mCodecLooper = new ALooper;
    mCodecLooper->setName("NPDecoder-CL");
    mCodecLooper->start(false, false, ANDROID_PRIORITY_AUDIO);
    mVideoTemporalLayerAggregateFps[0] = mFrameRateTotal;
}

構造函數基本上就是將傳遞進來的參數,直接保存到自己的各類變量中,功能後續使用。繼續看一下接下來對解碼器來說比較重要的調用。

再來看看關於解碼器的第二各操作:(*decoder)->init();

直接在Decoder類中查找init()並不能找到,因爲Decoder繼承與DecoderBase,所以這裏執行的應該是DecoderBaseinit函數:

void NuPlayer::DecoderBase::init() {
    mDecoderLooper->registerHandler(this);
}

DecoderBase的構造函數中,已經創建了一套NativeHandler體系,並將Looper啓動,只是沒有將AHandler的子類對象和ALooper綁定,知道init()函數執行後,這種綁定關係纔算結束。也只有這樣,DecoderBase中的NativeHandler體系才能夠正常工作。有關NativeHandler的詳細信息,請參考:NativeHandler系列(一)

接下來看看和解碼相關的最重要的一步操作:(*decoder)->configure(format);

configure函數實際上是在DecoderBase中實現,最終調用了DecoderBase的純虛構函數:onConfigure,讓它的子類去實現具體的配置方法:

void NuPlayer::Decoder::onConfigure(const sp<AMessage> &format) {
    AString mime;
    CHECK(format->findString("mime", &mime));
	// 根據需要創建的解碼器類型創建解碼器
    mCodec = MediaCodec::CreateByType(
            mCodecLooper, mime.c_str(), false /* encoder */, NULL /* err */, mPid, mUid);
    err = mCodec->configure(format, mSurface, crypto, 0 /* flags */); // 配置解碼器
    sp<AMessage> reply = new AMessage(kWhatCodecNotify, this);
    mCodec->setCallback(reply); // 設置解碼器回調
    err = mCodec->start(); // 啓動解碼器
}

從簡化後的代碼可以看到, 在onConfigure函數中,有關MediaCodec的調用都是比較經典的調用方式。分別有,MediaCodec的創建、配置、設置回調通知、啓動解碼器。

關於MediaCodec還有buffer的入隊和出隊以及釋放函數,相信不久後在其它地方可以見到。

解碼器創建部分暫時就這麼多,小結一下:

小結解碼器創建

  • Decoder繼承於DecoderBaseDecoderBase基本上接管了解碼工作所有的操作,通過純虛構(抽象)函數來讓子類,也就是Decoder來實現一些具體的操作。
  • DecoderBase解碼體系,都是通過MediaCodec來實現解碼流程。有鑑於此,它的基本操作函數都是爲了MediaCodec的服務。

填充數據到解碼隊列:onInputBufferFetched

當前位置:

  1. NuPlayerDecoderBase:解碼器創建
  2. onInputBufferFetched:填充數據到解碼隊列
  3. onRenderBuffer:渲染解碼後的數據
  4. ~Decoder:釋放解碼器

MediaCode創建並執行了start函數後,就已經在通過mCodec->setCallback(reply)提供的回調,不斷地調用填充數據有關的邏輯,最後實現數據填充的地方是在onInputBufferFetched函數中:

bool NuPlayer::Decoder::onInputBufferFetched(const sp<AMessage> &msg) {
   size_t bufferIx;
   CHECK(msg->findSize("buffer-ix", &bufferIx));
   CHECK_LT(bufferIx, mInputBuffers.size());
   sp<MediaCodecBuffer> codecBuffer = mInputBuffers[bufferIx];

   sp<ABuffer> buffer;
   bool hasBuffer = msg->findBuffer("buffer", &buffer); // 填充通解封裝模塊獲取的數據
   bool needsCopy = true; // 是否需要將數據拷貝給MediaCodec

   if (buffer == NULL /* includes !hasBuffer */) { // 如果已經沒有buffer可以提供了。
       status_t err = mCodec->queueInputBuffer(bufferIx, 0, 0, 0, MediaCodec::BUFFER_FLAG_EOS);
		// ...
   } else { // 還有buffer
        if (needsCopy) { // 拷貝給MediaCodec
            // ...
            if (buffer->data() != NULL) {
                codecBuffer->setRange(0, buffer->size());
                // 拷貝到MediaCodec的buffer中
                memcpy(codecBuffer->data(), buffer->data(), buffer->size()); 
            }
        } // needsCopy

        status_t err;
        AString errorDetailMsg;
		// ...
            err = mCodec->queueInputBuffer( // 將buffer加入到MediaCodec的待解碼隊列中
                    bufferIx,
                    codecBuffer->offset(),
                    codecBuffer->size(),
                    timeUs,
                    flags,
                    &errorDetailMsg);
        // ...
    }   // buffer != NULL
    return true;
}

這個函數的核心,就是調用MediaCodecqueueInputBuffer函數,將填充好的MediaCodecBuffer添加到MediaCodec的輸入隊列中,等待解碼。解釋都放在註釋裏了。來看一下如何取數據的。

渲染解碼後的數據:onRenderBuffer

當前位置:

  1. NuPlayerDecoderBase:解碼器創建
  2. onInputBufferFetched:填充數據到解碼隊列
  3. onRenderBuffer:渲染解碼後的數據
  4. ~Decoder:釋放解碼器

onRenderBuffer的執行時機,和onInputBufferFetched幾乎是同時的,當MediaCodec的解碼outputBuffer隊列中有數據時,就會通過回調通知播放器,執行對應的回調函數渲染數據。在NuPlayer這樣的回調函數執行鏈條爲:NuPlayer::Decoder::onMessageReceived ==> handleAnOutputBuffer ==> NuPlayer::Decoder::onRenderBuffer

最終執行取出解碼數據並渲染的函數便是onRenderBuffer

void NuPlayer::Decoder::onRenderBuffer(const sp<AMessage> &msg) {
    int32_t render;
    size_t bufferIx;
    CHECK(msg->findSize("buffer-ix", &bufferIx));
	ALOGE("onRenderBuffer");

    if (msg->findInt32("render", &render) && render) {
        int64_t timestampNs;
        CHECK(msg->findInt64("timestampNs", &timestampNs));
        // 觸發播放音頻數據
        err = mCodec->renderOutputBufferAndRelease(bufferIx, timestampNs);
    } else { // 播放視頻數據
        mNumOutputFramesDropped += !mIsAudio;
        err = mCodec->releaseOutputBuffer(bufferIx);
    }
	// ...
}

釋放解碼器:~Decoder

當前位置:

  1. NuPlayerDecoderBase:解碼器創建
  2. onInputBufferFetched:填充數據到解碼隊列
  3. onRenderBuffer:渲染解碼後的數據
  4. ~Decoder:釋放解碼器
NuPlayer::Decoder::~Decoder() {
    // Need to stop looper first since mCodec could be accessed on the mDecoderLooper.
    stopLooper(); // 停止looper
    if (mCodec != NULL) {
        mCodec->release(); // release掉MediaCodec
    }
    releaseAndResetMediaBuffers(); // 清理buffer
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章