一、引入:
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 FLUSHED
setState(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)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓