MediaCodec(native)状态机分析

一、引入:

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↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

猜你喜欢

转载自blog.csdn.net/m0_60259116/article/details/127303585
今日推荐