解碼模塊
NuPlayer的解碼模塊相對比較簡單,統一使用了一個基類NuPlayerDecoderBase管理,該類中包含了一個MediaCodec的對象,實際解碼工作全靠MediaCodec。
如果你不會知道MediaCodec是什麼,推薦去官網看看:MediaCodec
儘管解碼工作都被MediaCodec
接管,我還是會按照播放器的一般步驟,來分析一下NuPlayerDecoderBase
。步驟如下:
一個Android播放器典型的播放步驟一般是:
- 播放器創建。
- 設置媒體源文件(本地文件路徑、或者Uri)。
- 準備媒體數據。
- 播放視頻。
- 停止播放。
對應於解碼模塊,會稍微簡單一些:
NuPlayerDecoderBase
:解碼器創建onInputBufferFetched
:填充數據到解碼隊列onRenderBuffer
:渲染解碼後的數據~Decoder
:釋放解碼器
解碼器創建:NuPlayerDecoderBase
當前位置:
NuPlayerDecoderBase
:解碼器創建onInputBufferFetched
:填充數據到解碼隊列onRenderBuffer
:渲染解碼後的數據~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> ¬ify,
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> ¬ify);
void configure(const sp<AMessage> &format);
void init();
void setParameters(const sp<AMessage> ¶ms);
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> ¶ms) = 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> ¬ify,
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
,所以這裏執行的應該是DecoderBase
的init
函數:
void NuPlayer::DecoderBase::init() {
mDecoderLooper->registerHandler(this);
}
DecoderBase
的構造函數中,已經創建了一套NativeHandler
體系,並將Looper
啓動,只是沒有將AHandler
的子類對象和ALooper
綁定,知道init()
函數執行後,這種綁定關係纔算結束。也只有這樣,DecoderBase
中的NativeHandle
r體系才能夠正常工作。有關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
繼承於DecoderBase
,DecoderBas
e基本上接管了解碼工作所有的操作,通過純虛構(抽象)函數來讓子類,也就是Decoder
來實現一些具體的操作。DecoderBase
解碼體系,都是通過MediaCodec
來實現解碼流程。有鑑於此,它的基本操作函數都是爲了MediaCodec
的服務。
填充數據到解碼隊列:onInputBufferFetched
當前位置:
NuPlayerDecoderBase
:解碼器創建onInputBufferFetched
:填充數據到解碼隊列onRenderBuffer
:渲染解碼後的數據~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;
}
這個函數的核心,就是調用MediaCodec
的queueInputBuffer
函數,將填充好的MediaCodecBuffer
添加到MediaCodec
的輸入隊列中,等待解碼。解釋都放在註釋裏了。來看一下如何取數據的。
渲染解碼後的數據:onRenderBuffer
當前位置:
NuPlayerDecoderBase
:解碼器創建onInputBufferFetched
:填充數據到解碼隊列onRenderBuffer
:渲染解碼後的數據~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", ×tampNs));
// 觸發播放音頻數據
err = mCodec->renderOutputBufferAndRelease(bufferIx, timestampNs);
} else { // 播放視頻數據
mNumOutputFramesDropped += !mIsAudio;
err = mCodec->releaseOutputBuffer(bufferIx);
}
// ...
}
釋放解碼器:~Decoder
當前位置:
NuPlayerDecoderBase
:解碼器創建onInputBufferFetched
:填充數據到解碼隊列onRenderBuffer
:渲染解碼後的數據~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
}