一、引入:
MediaCodec这条通路的调用逻辑是MediaCode->ACodec->OMX,因为OMX有自己的状态机,所以MediaCodec和ACodec也分别基于OMX组件的调用维护了其状态机,这篇博客就先分析MediaCodec的状态机是如何运转的。
二、MediaCodec与ACodec的联动:
1.MediaCodec的继承关系:
struct MediaCodec : public AHandler {...
}
MediaCodec实际上是一个AHandler ,用于维护native层的消息机制。
2.ACodec的继承关系:
struct ACodec : public AHierarchicalStateMachine, public CodecBase {...
}
ACodec继承自CodecBase ,而CodecBase 中含有一个内联函数:
inline void setCallback(std::unique_ptr<CodecCallback> &&callback) {mCallback = std::move(callback);
}
这表明,在实例化ACodec的时候,是可以设置一个回调函数下来的。
3.MediaCodec与ACodec的关联:
看一下MediaCodec创建ACodec的场景:
status_t MediaCodec::init(const AString &name) {.../* 1.实例化Acodec */mCodec = GetCodecBase(name);if (mCodec == NULL) {return NAME_NOT_FOUND;}.../* 2.设置ACodec的回调 */mCodec->setCallback(std::unique_ptr<CodecBase::CodecCallback>(new CodecCallback(new AMessage(kWhatCodecNotify, this))));/* 3.创建buffer channel,为fillbuffer和emptybuffer做准备 */mBufferChannel = mCodec->getBufferChannel();mBufferChannel->setCallback(std::unique_ptr<CodecBase::BufferCallback>(new BufferCallback(new AMessage(kWhatCodecNotify, this))));...
}
在MediaCodec的init函数中,首先会去创建ACodec,然后创建CodecCallback对象,这个对象专门用于处理ACodec中回调回来的各种消息,然后根据不同的消息去设置MediaCodec的状态机。
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
三、MediaCodec的各状态机:
MediaCodec有如下状态机:
enum State {UNINITIALIZED,INITIALIZING,INITIALIZED,CONFIGURING,CONFIGURED,STARTING,STARTED,FLUSHING,FLUSHED,STOPPING,RELEASING,};
1.UNINITIALIZED:
在构造MediaCodec对象的时候,会设置当前状态为UNINITIALIZED:
MediaCodec::MediaCodec(const sp<ALooper> &looper, pid_t pid, uid_t uid): mState(UNINITIALIZED),mReleasedByResourceManager(false),mLooper(looper),mCodec(NULL),...{...
}
当然,如果在其他的状态机中(INITIALIZING/STOPPING/FLUSHING),ACodec回调了kWhatError消息时,也会将MediaCodec设置为UNINITIALIZED。
2.INITIALIZING:
MediaCodec的init函数中,会发送kWhatInit消息,而消息的处理阶段,将会设置此状态机:
case kWhatInit:{sp<AReplyToken> replyID;CHECK(msg->senderAwaitsResponse(&replyID));if (mState != UNINITIALIZED) {PostReplyWithError(replyID, INVALID_OPERATION);break;}mReplyID = replyID;/* 设置状态机 */setState(INITIALIZING);sp<RefBase> codecInfo;CHECK(msg->findObject("codecInfo", &codecInfo));AString name;CHECK(msg->findString("name", &name));sp<AMessage> format = new AMessage;format->setObject("codecInfo", codecInfo);format->setString("componentName", name);/* 跟进到ACodec去初始化omx组件 */mCodec->initiateAllocateComponent(format);break;}
3.INITIALIZED:
在上面kWhatInit
的消息处理中,可以看到会跟进到ACodec去初始化omx组件,当底层组件初始化完成后,会回调给MediaCodec并设置当前状态:
void CodecCallback::onComponentAllocated(const char *componentName) {sp<AMessage> notify(mNotify->dup());notify->setInt32("what", kWhatComponentAllocated);notify->setString("componentName", componentName);notify->post();
}
看一下kWhatComponentAllocated消息的处理:
case kWhatComponentAllocated:
{CHECK_EQ(mState, INITIALIZING);setState(INITIALIZED);mFlags |= kFlagIsComponentAllocated;CHECK(msg->findString("componentName", &mComponentName));if (mComponentName.c_str()) {mAnalyticsItem->setCString(kCodecCodec, mComponentName.c_str());}...
}
当然,在其他一些场景下也会设置为此状态,这里不去赘述。
4.CONFIGURING:
MediaCodec的configure函数会发送kWhatConfigure
消息,在此消息的处理阶段,会去设置当前状态:
case kWhatConfigure:
{sp<AReplyToken> replyID;CHECK(msg->senderAwaitsResponse(&replyID));if (mState != INITIALIZED) {PostReplyWithError(replyID, INVALID_OPERATION);break;}sp<RefBase> obj;CHECK(msg->findObject("surface", &obj));sp<AMessage> format;CHECK(msg->findMessage("format", &format));int32_t push;if (msg->findInt32("push-blank-buffers-on-shutdown", &push) && push != 0) {mFlags |= kFlagPushBlankBuffersOnShutdown;}if (obj != NULL) {format->setObject("native-window", obj);status_t err = handleSetSurface(static_cast<Surface *>(obj.get()));if (err != OK) {PostReplyWithError(replyID, err);break;}} else {handleSetSurface(NULL);}mReplyID = replyID;/* 状态机设置 */setState(CONFIGURING);
...
}
5. CONFIGURED:
当底层OMX IL层完成上面的CONFIGURING
指令后,会先回调给ACodec:
bool ACodec::LoadedState::onConfigureComponent(const sp<AMessage> &msg) {ALOGV("onConfigureComponent");CHECK(mCodec->mOMXNode != NULL);status_t err = OK;AString mime;if (!msg->findString("mime", &mime)) {err = BAD_VALUE;} else {/* 根据msg配置codec */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;}/* 配置完成,通知至MediaCodec */mCodec->mCallback->onComponentConfigured(mCodec->mInputFormat, mCodec->mOutputFormat);return true;
}
MediaCodec会在onComponentConfigured
函数中发送msg,之后设置此状态机:
void CodecCallback::onComponentConfigured(const sp<AMessage> &inputFormat, const sp<AMessage> &outputFormat) {sp<AMessage> notify(mNotify->dup());notify->setInt32("what", kWhatComponentConfigured);notify->setMessage("input-format", inputFormat);notify->setMessage("output-format", outputFormat);notify->post();
}
case kWhatComponentConfigured:{if (mState == UNINITIALIZED || mState == INITIALIZED) {// In case a kWhatError message came in and replied with error,// we log a warning and ignore.ALOGW("configure interrupted by error, current state %d", mState);break;}/* 检查状态 */CHECK_EQ(mState, CONFIGURING);.../* 设置状态 */setState(CONFIGURED);....
}
6.STARTING:
状态机的发起由start()函数开始执行:
status_t MediaCodec::start() {sp<AMessage> msg = new AMessage(kWhatStart, this);.../* 发送kWhatStart消息 */err = PostAndAwaitResponse(msg, &response);...
}
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓
对应的消息处理:
case kWhatStart:
{sp<AReplyToken> replyID;CHECK(msg->senderAwaitsResponse(&replyID));if (mState == FLUSHED) {setState(STARTED);if (mHavePendingInputBuffers) {onInputBufferAvailable();mHavePendingInputBuffers = false;}mCodec->signalResume();PostReplyWithError(replyID, OK);break;} else if (mState != CONFIGURED) {PostReplyWithError(replyID, INVALID_OPERATION);break;}mReplyID = replyID;/* 设置STARTING状态 */setState(STARTING);/* 调用到底层进行codec初始化 */mCodec->initiateStart();break;
}
7.STARTED:
此状态的发起在ACodec的状态机中完成,ACodec在LoadedToIdleState
状态时,会去申请buffer同时回调至MediaCodec:
status_t ACodec::LoadedToIdleState::allocateBuffers() {/* 申请输入buffer */status_t err = mCodec->allocateBuffersOnPort(kPortIndexInput);if (err != OK) {return err;}/* 申请输出buffer */err = mCodec->allocateBuffersOnPort(kPortIndexOutput);if (err != OK) {return err;}/* 通知至MediaCodec */mCodec->mCallback->onStartCompleted();return OK;
}
看一下MediaCodec对onStartCompleted
函数的处理:
void CodecCallback::onStartCompleted() {sp<AMessage> notify(mNotify->dup());notify->setInt32("what", kWhatStartCompleted);notify->post();
}
case kWhatStartCompleted:
{CHECK_EQ(mState, STARTING);if (mIsVideo) {addResource(MediaResource::kGraphicMemory,MediaResource::kUnspecifiedSubType,getGraphicBufferSize());}/* 设置状态 */setState(STARTED);(new AMessage)->postReply(mReplyID);break;
}
8.FLUSHING:
此状态为MediaCodec主动发起:
status_t MediaCodec::flush() {sp<AMessage> msg = new AMessage(kWhatFlush, this);sp<AMessage> response;return PostAndAwaitResponse(msg, &response);
}
case kWhatFlush:
{sp<AReplyToken> replyID;CHECK(msg->senderAwaitsResponse(&replyID));if (!isExecuting()) {PostReplyWithError(replyID, INVALID_OPERATION);break;} else if (mFlags & kFlagStickyError) {PostReplyWithError(replyID, getStickyError());break;}mReplyID = replyID;// TODO: skip flushing if already FLUSHEDsetState(FLUSHING);mCodec->signalFlush();returnBuffersToCodec();break;
}
9.FLUSHED:
因为flush的使用很灵活,所以会有多个地方会回调至MediaCodec:
void CodecCallback::onFlushCompleted() {sp<AMessage> notify(mNotify->dup());notify->setInt32("what", kWhatFlushCompleted);notify->post();
}
case kWhatFlushCompleted:
{if (mState != FLUSHING) {ALOGW("received FlushCompleted message in state %d",mState);break;}if (mFlags & kFlagIsAsync) {setState(FLUSHED);} else {setState(STARTED);mCodec->signalResume();}(new AMessage)->postReply(mReplyID);break;
}
10.stop/release:
这两个状态都是由MediaCodec主动发起的:
status_t MediaCodec::stop() {sp<AMessage> msg = new AMessage(kWhatStop, this);sp<AMessage> response;return PostAndAwaitResponse(msg, &response);
}
status_t MediaCodec::release() {sp<AMessage> msg = new AMessage(kWhatRelease, this);sp<AMessage> response;return PostAndAwaitResponse(msg, &response);
}
对应的消息处理会设置其状态:
case kWhatStop:
case kWhatRelease:
{.../* 设置状态 */setState(msg->what() == kWhatStop ? STOPPING : RELEASING);/* 通知底层进行相应操作 */mCodec->initiateShutdown(msg->what() == kWhatStop /* keepComponentAllocated */);
}
四、状态机总览图:
如果你对音视频开发感兴趣,觉得文章对您有帮助,别忘了点赞、收藏哦!或者对本文的一些阐述有自己的看法,有任何问题,欢迎在下方评论区讨论!
本文福利, 免费领取C++音视频学习资料包、技术视频,内容包括(音视频开发,面试题,FFmpeg ,webRTC ,rtmp ,hls ,rtsp ,ffplay ,srs)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓