一、引言:
ACodec作为OMX的直接对接上级,其状态机的维护比MediaCodec要复杂一些,ACodec将状态机独立出来,确保与OMX处于同步状态,这篇文章将详细介绍下ACodec的状态机是如何维护的。
二、ACodec的状态机引入:
我们先看下OMX标准头文件(OMX_CORE.h
)的状态机:
typedef enum OMX_STATETYPE
{
OMX_StateInvalid, /**< component has detected that it's internal data
structures are corrupted to the point that
it cannot determine it's state properly */
OMX_StateLoaded, /**< component has been loaded but has not completed
initialization. The OMX_SetParameter macro
and the OMX_GetParameter macro are the only
valid macros allowed to be sent to the
component in this state. */
OMX_StateIdle, /**< component initialization has been completed
successfully and the component is ready to
to start. */
OMX_StateExecuting, /**< component has accepted the start command and
is processing data (if data is available) */
OMX_StatePause, /**< component has received pause command */
OMX_StateWaitForResources, /**< component is waiting for resources, either after
preemption or before it gets the resources requested.
See specification for complete details. */
OMX_StateKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */
OMX_StateVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */
OMX_StateMax = 0X7FFFFFFF
} OMX_STATETYPE;
这几个状态机中最重要的是OMX_StateLoaded
、OMX_StateIdle
和OMX_StateExecuting
,而ACodec就是根据这几个状态机来对OMX组件进行操控的。
下面看下ACodec的构造函数:
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),
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),
mLatency(0),
mTunneled(false),
mDescribeColorAspectsIndex((OMX_INDEXTYPE)0),
mDescribeHDRStaticInfoIndex((OMX_INDEXTYPE)0),
mStateGeneration(0),
mVendorExtensionsStatus(kExtensionsUnchecked) {
// Mediatek Android Patch Begin
sVerboseLog = (bool)property_get_int32("vendor.media.acodec.verbose.log", 0);
// Mediatek Android Patch End
memset(&mLastHDRStaticInfo, 0, sizeof(mLastHDRStaticInfo));
/* 1.实例化各个状态机 */
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));
/* 2.改变状态 */
changeState(mUninitializedState);
}
1.ACodec的状态机:
ACodec一共维护了9个状态机,分别如下:
sp<UninitializedState> mUninitializedState;
sp<LoadedState> mLoadedState;
sp<LoadedToIdleState> mLoadedToIdleState;
sp<IdleToExecutingState> mIdleToExecutingState;
sp<ExecutingState> mExecutingState;
sp<OutputPortSettingsChangedState> mOutputPortSettingsChangedState;
sp<ExecutingToIdleState> mExecutingToIdleState;
sp<IdleToLoadedState> mIdleToLoadedState;
sp<FlushingState> mFlushingState;
sp<SkipCutBuffer> mSkipCutBuffer;
以mUninitializedState
为例,看一下这个类的继承关系:
struct ACodec::UninitializedState : public ACodec::BaseState {
explicit UninitializedState(ACodec *codec);
protected:
virtual bool onMessageReceived(const sp<AMessage> &msg);
virtual void stateEntered();
private:
void onSetup(const sp<AMessage> &msg);
bool onAllocateComponent(const sp<AMessage> &msg);
sp<DeathNotifier> mDeathNotifier;
DISALLOW_EVIL_CONSTRUCTORS(UninitializedState);
};
可以看到,每个状态机都会继承自BaseState
,类中有两个重要的成员函数onMessageReceived
和stateEntered
,前者用于处理独立于每个状态的msg,后者则用于处理进入此状态时所做的一些必要的事情,而private中的成员函数则是每个状态所特有的,也从正面反映了每个状态机的特色。
2.BaseState分析:
struct ACodec::BaseState : public AState {
explicit BaseState(ACodec *codec, const sp<AState> &parentState = NULL);
protected:
enum PortMode {
KEEP_BUFFERS,
RESUBMIT_BUFFERS,
FREE_BUFFERS,
};
ACodec *mCodec;
virtual PortMode getPortMode(OMX_U32 portIndex);
virtual void stateExited();
virtual bool onMessageReceived(const sp<AMessage> &msg);
virtual bool onOMXEvent(OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2);
virtual void onOutputBufferDrained(const sp<AMessage> &msg);
virtual void onInputBufferFilled(const sp<AMessage> &msg);
void postFillThisBuffer(BufferInfo *info);
private:
// Handles an OMX message. Returns true iff message was handled.
bool onOMXMessage(const sp<AMessage> &msg);
// Handles a list of messages. Returns true iff messages were handled.
bool onOMXMessageList(const sp<AMessage> &msg);
// returns true iff this message is for this component and the component is alive
bool checkOMXMessage(const sp<AMessage> &msg);
bool onOMXEmptyBufferDone(IOMX::buffer_id bufferID, int fenceFd);
bool onOMXFillBufferDone(
IOMX::buffer_id bufferID,
size_t rangeOffset, size_t rangeLength,
OMX_U32 flags,
int64_t timeUs,
int fenceFd);
virtual bool onOMXFrameRendered(int64_t mediaTimeUs, nsecs_t systemNano);
void getMoreInputDataIfPossible();
DISALLOW_EVIL_CONSTRUCTORS(BaseState);
};
可以看到,ACodec::BaseState
是对OMX组件的具体操作,其中onOutputBufferDrained
和onInputBufferFilled
就是对buffer的操作。也就是说,ACodec的状态机是具备对OMX进行buffer操作的。
3.changeState分析:
所有状态的改变都需要通过changeState
来实现,看看这个函数的实现:
void AHierarchicalStateMachine::changeState(const sp<AState> &state) {
/* 1.确认设置的状态是否是当前状态 */
if (state == mState) {
// Quick exit for the easy case.
return;
}
/* 2.将当前状态推入容器A中 */
Vector<sp<AState> > A;
sp<AState> cur = mState;
for (;;) {
A.push(cur);
if (cur == NULL) {
break;
}
cur = cur->parentState();
}
/* 3.将下一个状态推入容器B中 */
Vector<sp<AState> > B;
cur = state;
for (;;) {
B.push(cur);
if (cur == NULL) {
break;
}
cur = cur->parentState();
}
// Remove the common tail.
while (A.size() > 0 && B.size() > 0 && A.top() == B.top()) {
A.pop();
B.pop();
}
/* 4.更新状态 */
mState = state;
/* 5.调用上一个状态的退出函数 */
for (size_t i = 0; i < A.size(); ++i) {
A.editItemAt(i)->stateExited();
}
/* 6.调用待设置状态的进入函数 */
for (size_t i = B.size(); i > 0;) {
i--;
B.editItemAt(i)->stateEntered();
}
}
三、ACodec状态机分析:
ACodec的各个状态机是紧密相连的,为了让他加不至于对下面每个状态机的讲解云里雾里,这里先贴出整个ACodec的状态机图示:
1.UninitializedState:
a.ACodec的构造:
在前面将ACodec构造的时候,已经看到,在实例化了9个状态之后,最后会调用changeState
将当前状态设置为UninitializedState
:
ACodec::ACodec()...{
...
changeState(mUninitializedState);
}
b.在LoadedState
状态时,如果此时调用onShundown
时,也会置于这个状态,看一下这个状态的stateEntered
实现:
void ACodec::LoadedState::onShutdown(bool keepComponentAllocated) {
/* release()函数会调用此处 */
if (!keepComponentAllocated) {
(void)mCodec->mOMXNode->freeNode();
mCodec->changeState(mCodec->mUninitializedState);
}
...
}
2.LoadedState:
a.当MediaCodec在配置codec时,会调用到onAllocateComponent
函数,这里会去申请omx组件,申请成功后会将状态改为LoadedState
:
bool ACodec::UninitializedState::onAllocateComponent(const sp<AMessage> &msg) {
...
sp<CodecObserver> observer = new CodecObserver;
sp<IOMX> omx;
sp<IOMXNode> omxNode;
status_t err = NAME_NOT_FOUND;
OMXClient client;
if (client.connect(owner.c_str()) != OK) {
mCodec->signalError(OMX_ErrorUndefined, NO_INIT);
return false;
}
omx = client.interface();
pid_t tid = gettid();
int prevPriority = androidGetThreadPriority(tid);
androidSetThreadPriority(tid, ANDROID_PRIORITY_FOREGROUND);
err = omx->allocateNode(componentName.c_str(), observer, &omxNode);
...
mCodec->mOMX = omx;
mCodec->mOMXNode = omxNode;
mCodec->mCallback->onComponentAllocated(mCodec->mComponentName.c_str());
mCodec->changeState(mCodec->mLoadedState);
return true;
}
b.进入LoadedToIdleState
状态后,会去申请buffer,如果buffer申请失败,会回退到此状态:
void ACodec::LoadedToIdleState::stateEntered() {
ALOGV("[%s] Now Loaded->Idle", mCodec->mComponentName.c_str());
status_t err;
/* 申请buffer */
if ((err = allocateBuffers()) != OK) {
ALOGE("Failed to allocate buffers after transitioning to IDLE state "
"(error 0x%08x)",
err);
mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));
mCodec->mOMXNode->sendCommand(
OMX_CommandStateSet, OMX_StateLoaded);
if (mCodec->allYourBuffersAreBelongToUs(kPortIndexInput)) {
mCodec->freeBuffersOnPort(kPortIndexInput);
}
if (mCodec->allYourBuffersAreBelongToUs(kPortIndexOutput)) {
mCodec->freeBuffersOnPort(kPortIndexOutput);
}
mCodec->changeState(mCodec->mLoadedState);
}
}
c.codec的release阶段,即IdleToLoadedState
收到OMX_EventCmdComplete
消息时:
bool ACodec::IdleToLoadedState::onOMXEvent(
OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) {
switch (event) {
case OMX_EventCmdComplete:
{
if (data1 != (OMX_U32)OMX_CommandStateSet
|| data2 != (OMX_U32)OMX_StateLoaded) {
ALOGE("Unexpected command completion in IdleToLoadedState: %s(%u) %s(%u)",
asString((OMX_COMMANDTYPE)data1), data1,
asString((OMX_STATETYPE)data2), data2);
mCodec->signalError(OMX_ErrorUndefined, FAILED_TRANSACTION);
return true;
}
mCodec->changeState(mCodec->mLoadedState);
return true;
}
default:
return BaseState::onOMXEvent(event, data1, data2);
}
}
3.LoadedToIdleState:
LoadedState
状态下,调用onStar
t函数时会去设置OMX状态为OMX_StateIdle
,如果设置状态成功,ACodec就会切换至此状态:
void ACodec::LoadedState::onStart() {
ALOGV("onStart");
status_t err = mCodec->mOMXNode->sendCommand(OMX_CommandStateSet, OMX_StateIdle);
if (err != OK) {
mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));
} else {
mCodec->changeState(mCodec->mLoadedToIdleState);
}
}
4.IdleToExecutingState:
LoadedToIdleState
状态下,接收到OMX_EventCmdComplete
消息时,表明此时OMX底层的状态是切换成功的,同时会向底层发送OMX_StateExecuting
的状态设置,同时会将此时的ACodec状态变更成IdleToExecutingState
:
bool ACodec::LoadedToIdleState::onOMXEvent(
OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) {
switch (event) {
case OMX_EventCmdComplete:
{
status_t err = OK;
if (data1 != (OMX_U32)OMX_CommandStateSet
|| data2 != (OMX_U32)OMX_StateIdle) {
ALOGE("Unexpected command completion in LoadedToIdleState: %s(%u) %s(%u)",
asString((OMX_COMMANDTYPE)data1), data1,
asString((OMX_STATETYPE)data2), data2);
err = FAILED_TRANSACTION;
}
if (err == OK) {
err = mCodec->mOMXNode->sendCommand(
OMX_CommandStateSet, OMX_StateExecuting);
}
if (err != OK) {
mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));
} else {
mCodec->changeState(mCodec->mIdleToExecutingState);
}
return true;
}
...
}
5.ExecutingState:
在IdleToExecutingState
状态下,接收到OMX_EventCmdComplete
消息时,表明底层OMX此时已处于OMX_StateExecuting
状态了,那么ACodec当同步改变状态为ExecutingState
:
bool ACodec::IdleToExecutingState::onOMXEvent(
OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) {
switch (event) {
case OMX_EventCmdComplete:
{
if (data1 != (OMX_U32)OMX_CommandStateSet
|| data2 != (OMX_U32)OMX_StateExecuting) {
ALOGE("Unexpected command completion in IdleToExecutingState: %s(%u) %s(%u)",
asString((OMX_COMMANDTYPE)data1), data1,
asString((OMX_STATETYPE)data2), data2);
mCodec->signalError(OMX_ErrorUndefined, FAILED_TRANSACTION);
return true;
}
mCodec->mExecutingState->resume();
mCodec->changeState(mCodec->mExecutingState);
return true;
}
default:
return BaseState::onOMXEvent(event, data1, data2);
}
}
当ACodec变更为此状态机时,表明此时与OMX在不停地进行buffer操作了。
6.OutputPortSettingsChangedState:
这是一个特殊的状态机,只会与ExecutingState
进行相互切换,通常用于输入或者输出port的参数变化,需要重新配置。
在ExecutingState
的事件接收中,如果收到OMX_EventPortSettingsChanged
事件,会将状态切换为OutputPortSettingsChangedState
:
bool ACodec::ExecutingState::onOMXEvent(
OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) {
switch (event) {
case OMX_EventPortSettingsChanged:
{
...
/* 给OMX组件发送OMX_CommandPortDisable指令 */
if (data2 == 0 || data2 == OMX_IndexParamPortDefinition) {
mCodec->mMetadataBuffersToSubmit = 0;
CHECK_EQ(mCodec->mOMXNode->sendCommand(
OMX_CommandPortDisable, kPortIndexOutput),
(status_t)OK);
mCodec->freeOutputBuffersNotOwnedByComponent();
mCodec->changeState(mCodec->mOutputPortSettingsChangedState);
}
...
}
...
}
上面的代码中,可以看到,当切换到此状态时,会给OMX组件发送OMX_CommandPortDisable
指令,而OMX组件执行指令完成后,会回调通知到ACodec:
bool ACodec::OutputPortSettingsChangedState::onOMXEvent(
OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) {
switch (event) {
case OMX_EventCmdComplete:
{
if (data1 == (OMX_U32)OMX_CommandPortDisable) {
if (data2 != (OMX_U32)kPortIndexOutput) {
ALOGW("ignoring EventCmdComplete CommandPortDisable for port %u", data2);
return false;
}
ALOGV("[%s] Output port now disabled.", mCodec->mComponentName.c_str());
status_t err = OK;
if (!mCodec->mBuffers[kPortIndexOutput].isEmpty()) {
ALOGE("disabled port should be empty, but has %zu buffers",
mCodec->mBuffers[kPortIndexOutput].size());
err = FAILED_TRANSACTION;
} else {
mCodec->mAllocator[kPortIndexOutput].clear();
}
/* OMX组件执行指令完成后将继续发送OMX_CommandPortEnable指令 */
if (err == OK) {
err = mCodec->mOMXNode->sendCommand(
OMX_CommandPortEnable, kPortIndexOutput);
}
if (err == OK) {
err = mCodec->allocateBuffersOnPort(kPortIndexOutput);
ALOGE_IF(err != OK, "Failed to allocate output port buffers after port "
"reconfiguration: (%d)", err);
mCodec->mCallback->onOutputBuffersChanged();
}
if (err != OK) {
mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));
ALOGE("Error occurred while disabling the output port");
}
return true;
} else if (data1 == (OMX_U32)OMX_CommandPortEnable) {
if (data2 != (OMX_U32)kPortIndexOutput) {
ALOGW("ignoring EventCmdComplete OMX_CommandPortEnable for port %u", data2);
return false;
}
ALOGV("[%s] Output port now reenabled.", mCodec->mComponentName.c_str());
if (mCodec->mExecutingState->active()) {
mCodec->mExecutingState->submitOutputBuffers();
}
/* 再次失能端口成功将切换至ExecutingState */
mCodec->changeState(mCodec->mExecutingState);
return true;
}
return false;
}
default:
return BaseState::onOMXEvent(event, data1, data2);
}
}
可以看到这个状态切换是ACodec自己主动完成的,通过向OMX发送指令-等待回调
,在所有的操作都完成后,最终会切换回ExecutingState
。
7.ExecutingToIdleState:
当应用决定不再需要解码器进行编解码时,此时会调用ACodec的initiateShutdown
函数:
void ACodec::initiateShutdown(bool keepComponentAllocated) {
/* 发送kWhatShutdown消息 */
sp<AMessage> msg = new AMessage(kWhatShutdown, this);
msg->setInt32("keepComponentAllocated", keepComponentAllocated);
msg->post();
if (!keepComponentAllocated) {
// ensure shutdown completes in 3 seconds
(new AMessage(kWhatReleaseCodecInstance, this))->post(3000000);
}
}
ExecutingState
的onMessageReceived
会接收到此消息:
bool ACodec::ExecutingState::onMessageReceived(const sp<AMessage> &msg) {
bool handled = false;
switch (msg->what()) {
case kWhatShutdown:
{
int32_t keepComponentAllocated;
CHECK(msg->findInt32(
"keepComponentAllocated", &keepComponentAllocated));
mCodec->mShutdownInProgress = true;
mCodec->mExplicitShutdown = true;
mCodec->mKeepComponentAllocated = keepComponentAllocated;
mActive = false;
/* 向OMX发送OMX_StateIdle状态的设置CMD */
status_t err = mCodec->mOMXNode->sendCommand(
OMX_CommandStateSet, OMX_StateIdle);
if (err != OK) {
if (keepComponentAllocated) {
mCodec->signalError(OMX_ErrorUndefined, FAILED_TRANSACTION);
}
// TODO: do some recovery here.
} else {
/* 设置成功将切换状态 */
mCodec->changeState(mCodec->mExecutingToIdleState);
}
handled = true;
break;
}
...
}
可以看到,ExecutingState
会向底层发送OMX_StateIdle
的状态设置,同时会将ACodec的状态切换为ExecutingToIdleState
。
8.IdleToLoadedState:
当OMX_StateIdle
状态设置成功会,会回调通知给ExecutingToIdleState
的onOMXEvent
:
bool ACodec::ExecutingToIdleState::onOMXEvent(
OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) {
switch (event) {
case OMX_EventCmdComplete:
{
if (data1 != (OMX_U32)OMX_CommandStateSet
|| data2 != (OMX_U32)OMX_StateIdle) {
ALOGE("Unexpected command completion in ExecutingToIdleState: %s(%u) %s(%u)",
asString((OMX_COMMANDTYPE)data1), data1,
asString((OMX_STATETYPE)data2), data2);
mCodec->signalError(OMX_ErrorUndefined, FAILED_TRANSACTION);
return true;
}
mComponentNowIdle = true;
/* 注意这个函数 */
changeStateIfWeOwnAllBuffers();
return true;
}
...
}
...
}
跟进看一下changeStateIfWeOwnAllBuffers
:
void ACodec::ExecutingToIdleState::changeStateIfWeOwnAllBuffers() {
if (mComponentNowIdle && mCodec->allYourBuffersAreBelongToUs()) {
/* 向OMX发送OMX_StateLoaded指令 */
status_t err = mCodec->mOMXNode->sendCommand(
OMX_CommandStateSet, OMX_StateLoaded);
if (err == OK) {
err = mCodec->freeBuffersOnPort(kPortIndexInput);
status_t err2 = mCodec->freeBuffersOnPort(kPortIndexOutput);
if (err == OK) {
err = err2;
}
}
...
}
...
/* 状态切换 */
mCodec->changeState(mCodec->mIdleToLoadedState);
}
当底层OMX状态切换成功后,ACodec又将通过回调切换至`LoadedState状态。
9.FlushingState:
这个状态也只能同ExecutingState
相互切换。
当ExecutingState
接收到kWhatFlush
消息时,会进行状态的切换:
case kWhatFlush:
{
ALOGV("[%s] ExecutingState flushing now "
"(codec owns %zu/%zu input, %zu/%zu output).",
mCodec->mComponentName.c_str(),
mCodec->countBuffersOwnedByComponent(kPortIndexInput),
mCodec->mBuffers[kPortIndexInput].size(),
mCodec->countBuffersOwnedByComponent(kPortIndexOutput),
mCodec->mBuffers[kPortIndexOutput].size());
mActive = false;
/* 向OMX底层发送OMX_CommandFlush指令 */
status_t err = mCodec->mOMXNode->sendCommand(OMX_CommandFlush, OMX_ALL);
if (err != OK) {
mCodec->signalError(OMX_ErrorUndefined, FAILED_TRANSACTION);
} else {
/* 同时切换状态 */
mCodec->changeState(mCodec->mFlushingState);
}
handled = true;
break;
}
当OMX底层完成了OMX_CommandFlush
后,会回调通知至ACodec:
bool ACodec::FlushingState::onOMXEvent(
OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) {
...
case OMX_EventCmdComplete:
{
...
changeStateIfWeOwnAllBuffers();
...
}
...
}
跟进到changeStateIfWeOwnAllBuffers()
,最终会回到ExecutingState
:
void ACodec::FlushingState::changeStateIfWeOwnAllBuffers() {
...
mCodec->changeState(mCodec->mExecutingState);
...
}
四、总结:
掌握ACodec的状态机,需要从以下几个方面入手:
1.关注ACodec的各个状态机的紧密联系,可以参考上面的总览图;
2.关注每个状态的stateEntered()
和stateExited()
实现;
3.关注ACodec是如何通过OMX的标准接口向OMX组件进行状态设置,OMX是如何通过回调通知至ACodec;