這一節開始我們就來學習 ACodec 的實現
1、創建 ACodec
ACodec 是在 MediaCodec 中創建的,這里先貼出創建部分的代碼:
mCodec = mGetCodecBase(name, owner);if (mCodec == NULL) {ALOGE("Getting codec base with name '%s' (owner='%s') failed", name.c_str(), owner);return NAME_NOT_FOUND;}if (mDomain == DOMAIN_VIDEO) {// video codec needs dedicated looperif (mCodecLooper == NULL) {status_t err = OK;mCodecLooper = new ALooper;mCodecLooper->setName("CodecLooper");err = mCodecLooper->start(false, false, ANDROID_PRIORITY_AUDIO);if (OK != err) {ALOGE("Codec Looper failed to start");return err;}}mCodecLooper->registerHandler(mCodec);} else {mLooper->registerHandler(mCodec);}
從前面的學習我們可以知道,MediaCodec 使用的是異步消息處理的機制,創建MediaCodec 時需要傳入一個 ALooper 對象用于處理發送給 MediaCodec 的消息。同樣的 ACodec 也是用的異步消息處理機制,它也需要一個 ALooper,這個 ALooper 應該由上一級 MediaCodec 傳遞,從上面的代碼我們可以知道,如果創建的是音頻解碼器,那么 ACodec 將會復用 MediaCodec 的 ALooper,也就是它們的消息處理會在相同線程當中;如果是視頻解碼器,那么 MediaCodec 會創建一個專門的 ALooper 給 ACodec 使用,ACodec 和 MediaCodec 的消息處理在不同線程中。
ACodec::ACodec(): mSampleRate(0),mNodeGeneration(0),mUsingNativeWindow(false),mNativeWindowUsageBits(0),mLastNativeWindowDataSpace(HAL_DATASPACE_UNKNOWN),mIsVideo(false),mIsImage(false),mIsEncoder(false),mFatalError(false),mShutdownInProgress(false),mExplicitShutdown(false),mIsLegacyVP9Decoder(false),mIsStreamCorruptFree(false),mIsLowLatency(false),mEncoderDelay(0),mEncoderPadding(0),mRotationDegrees(0),mChannelMaskPresent(false),mChannelMask(0),mDequeueCounter(0),mMetadataBuffersToSubmit(0),mNumUndequeuedBuffers(0),mRepeatFrameDelayUs(-1LL),mMaxPtsGapUs(0LL),mMaxFps(-1),mFps(-1.0),mCaptureFps(-1.0),mCreateInputBuffersSuspended(false),mTunneled(false),mDescribeColorAspectsIndex((OMX_INDEXTYPE)0),mDescribeHDRStaticInfoIndex((OMX_INDEXTYPE)0),mDescribeHDR10PlusInfoIndex((OMX_INDEXTYPE)0),mStateGeneration(0),mVendorExtensionsStatus(kExtensionsUnchecked) {memset(&mLastHDRStaticInfo, 0, sizeof(mLastHDRStaticInfo));mUninitializedState = new UninitializedState(this);mLoadedState = new LoadedState(this);mLoadedToIdleState = new LoadedToIdleState(this);mIdleToExecutingState = new IdleToExecutingState(this);mExecutingState = new ExecutingState(this);mOutputPortSettingsChangedState =new OutputPortSettingsChangedState(this);mExecutingToIdleState = new ExecutingToIdleState(this);mIdleToLoadedState = new IdleToLoadedState(this);mFlushingState = new FlushingState(this);mPortEOS[kPortIndexInput] = mPortEOS[kPortIndexOutput] = false;mInputEOSResult = OK;mPortMode[kPortIndexInput] = IOMX::kPortModePresetByteBuffer;mPortMode[kPortIndexOutput] = IOMX::kPortModePresetByteBuffer;memset(&mLastNativeWindowCrop, 0, sizeof(mLastNativeWindowCrop));changeState(mUninitializedState);
}
ACodec 的構造函數主要是初始化了成員對象,實例化了各個狀態對象
,并且將狀態切換到了 UninitializedState
,這時候我們就要去查看它的 stateEntered 方法:
void ACodec::UninitializedState::stateEntered() {ALOGV("Now uninitialized");if (mDeathNotifier != NULL) {if (mCodec->mOMXNode != NULL) {auto tOmxNode = mCodec->mOMXNode->getHalInterface<IOmxNode>();if (tOmxNode) {tOmxNode->unlinkToDeath(mDeathNotifier);}}mDeathNotifier.clear();}mCodec->mUsingNativeWindow = false;mCodec->mNativeWindow.clear();mCodec->mNativeWindowUsageBits = 0;mCodec->mOMX.clear();mCodec->mOMXNode.clear();mCodec->mFlags = 0;mCodec->mPortMode[kPortIndexInput] = IOMX::kPortModePresetByteBuffer;mCodec->mPortMode[kPortIndexOutput] = IOMX::kPortModePresetByteBuffer;mCodec->mConverter[0].clear();mCodec->mConverter[1].clear();mCodec->mComponentName.clear();
}
UninitializedState::stateEntered 主要是將與 OMX 組件相關的成員對象重置初始化。
2、initiateAllocateComponent
創建 MediaCodec 時,ACodec 也就被創建了,隨后就會調用 initiateAllocateComponent 方法創建 OMX 組件,ACodec 創建之后處在 UninitializedState,所以消息最終在該狀態中被處理:
bool ACodec::UninitializedState::onAllocateComponent(const sp<AMessage> &msg) {ALOGV("onAllocateComponent");CHECK(mCodec->mOMXNode == NULL);mCodec->mFatalError = false;// 創建 Callback 消息,并且設置好 notifysp<AMessage> notify = new AMessage(kWhatOMXMessageList, mCodec);// notify 的 generation 為 nodegeneration + 1,這是因為進入 loaded 狀態后,mNodeGeneration 會 + 1notify->setInt32("generation", mCodec->mNodeGeneration + 1);// 需要檢查 codecInfo 才能創建 OMXNodesp<RefBase> obj;CHECK(msg->findObject("codecInfo", &obj));sp<MediaCodecInfo> info = (MediaCodecInfo *)obj.get();if (info == nullptr) {ALOGE("Unexpected nullptr for codec information");mCodec->signalError(OMX_ErrorUndefined, UNKNOWN_ERROR);return false;}AString owner = (info->getOwnerName() == nullptr) ? "default" : info->getOwnerName();AString componentName;CHECK(msg->findString("componentName", &componentName));// 創建 callback 對象sp<CodecObserver> observer = new CodecObserver(notify);sp<IOMX> omx;sp<IOMXNode> omxNode;status_t err = NAME_NOT_FOUND;// 創建 OMXClientOMXClient client;// 獲取 IOmx 服務if (client.connect(owner.c_str()) != OK) {mCodec->signalError(OMX_ErrorUndefined, NO_INIT);return false;}// 將獲取到的 IOmx 服務代理封裝為 Legacy 模式omx = client.interface();pid_t tid = gettid();int prevPriority = androidGetThreadPriority(tid);androidSetThreadPriority(tid, ANDROID_PRIORITY_FOREGROUND);// 創建 IOmxNode 服務代理,并且封裝為 IOMXNodeerr = omx->allocateNode(componentName.c_str(), observer, &omxNode);androidSetThreadPriority(tid, prevPriority);mDeathNotifier = new DeathNotifier(new AMessage(kWhatOMXDied, mCodec));auto tOmxNode = omxNode->getHalInterface<IOmxNode>();if (tOmxNode && !tOmxNode->linkToDeath(mDeathNotifier, 0)) {mDeathNotifier.clear();}// 記錄新的狀態下的 ACodec 狀態++mCodec->mNodeGeneration;mCodec->mComponentName = componentName;mCodec->mRenderTracker.setComponentName(componentName);mCodec->mFlags = 0;// 記錄是否創建的是 secure 組件if (componentName.endsWith(".secure")) {mCodec->mFlags |= kFlagIsSecure;mCodec->mFlags |= kFlagIsGrallocUsageProtected;mCodec->mFlags |= kFlagPushBlankBuffersToNativeWindowOnShutdown;}mCodec->mOMX = omx;mCodec->mOMXNode = omxNode;// 調用 callback 通知 MediaCodec 完成阻塞調用mCodec->mCallback->onComponentAllocated(mCodec->mComponentName.c_str());// 切換狀態到 LoadedStatemCodec->changeState(mCodec->mLoadedState);return true;
}
這里涉及的內容比較多:
- 創建 notify 對象,并且傳入到 CodecObserver 對象中,CodecObserver 會把 OMX 發送回來的消息重新封裝,再通過 notify message 轉發給 ACodec,最后在不同狀態中處理。這里有個是 mNodeGeneration 用于檢查 OMX 消息及時性的,但是實際并未啟用。這里有一點需要注意,這些 State 狀態類都是 ACodec 的內部類,C++11之后內部類可以訪問外部類的私有成員以及私有方法,所以雖然這些類并不是 ACodec 的友元,但是同樣是可以調用 ACodec 所有方法的。
- 創建 OMXNode 之前,會先檢查從 MediaCodec 層獲取到的 MediaCodecInfo,如果沒有這個信息將會報錯,這里算是一個雙重檢查,防止強行越過 MediaCodecList 的檢查;
- 調用 OMXClient 的方法獲取 IOmx 的代理,并用該代理創建 IOmxNode 代理,傳入參數為組件名稱;
- 如果組件名稱以 secure 結尾,那么說明需要創建安全組件,并且記錄到 ACodec mFlags 成員中;
- 調用 onComponentAllocated 通知 MediaCodec 完成阻塞調用;
- 切換狀態到
LoadedState
;
在看 LoadedState 的 stateEntered 方法之前,我們要先看下 BaseState 給出的 stateExited
方法,這里用到了 mStateGeneration
,用來記錄 ACodec 當前的狀態變化,在處理消息時,如果傳來的消息 generation 不等于當前的generation,說明狀態機發生錯誤,這和之前看到的部分是不一樣的,具體什么情況會出現不一樣我們后續再做了解。
void ACodec::BaseState::stateExited() {++mCodec->mStateGeneration;
}
從上面的代碼我們可以知道,每次狀態切換,mStateGeneration數都會加 1 。
接下來看 LoadedState 的 stateEntered
:
void ACodec::LoadedState::stateEntered() {ALOGV("[%s] Now Loaded", mCodec->mComponentName.c_str());mCodec->mPortEOS[kPortIndexInput] =mCodec->mPortEOS[kPortIndexOutput] = false;mCodec->mInputEOSResult = OK;mCodec->mDequeueCounter = 0;mCodec->mMetadataBuffersToSubmit = 0;mCodec->mRepeatFrameDelayUs = -1LL;mCodec->mInputFormat.clear();mCodec->mOutputFormat.clear();mCodec->mBaseOutputFormat.clear();mCodec->mGraphicBufferSource.clear();if (mCodec->mShutdownInProgress) {bool keepComponentAllocated = mCodec->mKeepComponentAllocated;mCodec->mShutdownInProgress = false;mCodec->mKeepComponentAllocated = false;onShutdown(keepComponentAllocated);}mCodec->mExplicitShutdown = false;mCodec->processDeferredMessages();
}
LoadedState::stateEntered 會對編解碼過程中記錄信息的成員變量進行重置,后面是關于 shutdown 的處理流程,這里暫時不看。
到這里我們先做一個小結:MediaCodec 創建完成后,ACodec 最終會進入到 LoadedState,這個狀態代表了內部的 OMX 組件已經創建完成,UninitializedState 則表示OMX組件還未創建或者是已經銷毀的狀態。
3、initiateConfigureComponent
組件創建完成后,就要開始配置組件了,這時候狀態在 LoadedState,所以我們去這個狀態下找對應的處理:
bool ACodec::LoadedState::onConfigureComponent(const sp<AMessage> &msg) {ALOGV("onConfigureComponent");CHECK(mCodec->mOMXNode != NULL);status_t err = OK;// 檢查 mime type,調用 configureCodec 方法AString mime;if (!msg->findString("mime", &mime)) {err = BAD_VALUE;} else {err = mCodec->configureCodec(mime.c_str(), msg);}if (err != OK) {ALOGE("[%s] configureCodec returning error %d",mCodec->mComponentName.c_str(), err);mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));return false;}// 調用 CallbackmCodec->mCallback->onComponentConfigured(mCodec->mInputFormat, mCodec->mOutputFormat);return true;
}
onConfigureComponent 會先去檢查 mime type,如果沒有將會返回error,如果有則會再調用 ACodec 的 configureCodec 方法做組件的配置,這個方法會比較復雜,我們留到下一節來講,最后會調用 callback 完成 MediaCodec 阻塞調用,同時把 input format 和 output format 回傳給 MediaCodec,需要注意的是這里的 output format 并不一定是準確的,可能是 omx 設定的默認值,在decoder解出相關序列信息之后會把真正的 output format 再回傳回來。