Android 13 - Media框架(20)- ACodec(二)

这一节开始我们就来学习 ACodec 的实现

1、创建 ACodec

ACodec 是在 MediaCodec 中创建的,这里先贴出创建部分的代码:

cpp 复制代码
    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 looper
        if (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 的消息处理在不同线程中。

cpp 复制代码
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 方法:

cpp 复制代码
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,所以消息最终在该状态中被处理:

cpp 复制代码
bool ACodec::UninitializedState::onAllocateComponent(const sp<AMessage> &msg) {
    ALOGV("onAllocateComponent");

    CHECK(mCodec->mOMXNode == NULL);
    mCodec->mFatalError = false;
	// 创建 Callback 消息,并且设置好 notify
    sp<AMessage> notify = new AMessage(kWhatOMXMessageList, mCodec);
    // notify 的 generation 为 nodegeneration + 1,这是因为进入 loaded 状态后,mNodeGeneration 会 + 1
    notify->setInt32("generation", mCodec->mNodeGeneration + 1);
	// 需要检查 codecInfo 才能创建 OMXNode
    sp<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;
    // 创建 OMXClient
    OMXClient 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 服务代理,并且封装为 IOMXNode
    err = 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());
    // 切换状态到 LoadedState
    mCodec->changeState(mCodec->mLoadedState);

    return true;
}

这里涉及的内容比较多:

  1. 创建 notify 对象,并且传入到 CodecObserver 对象中,CodecObserver 会把 OMX 发送回来的消息重新封装,再通过 notify message 转发给 ACodec,最后在不同状态中处理。这里有个是 mNodeGeneration 用于检查 OMX 消息及时性的,但是实际并未启用。这里有一点需要注意,这些 State 状态类都是 ACodec 的内部类,C++11之后内部类可以访问外部类的私有成员以及私有方法,所以虽然这些类并不是 ACodec 的友元,但是同样是可以调用 ACodec 所有方法的。
  2. 创建 OMXNode 之前,会先检查从 MediaCodec 层获取到的 MediaCodecInfo,如果没有这个信息将会报错,这里算是一个双重检查,防止强行越过 MediaCodecList 的检查;
  3. 调用 OMXClient 的方法获取 IOmx 的代理,并用该代理创建 IOmxNode 代理,传入参数为组件名称;
  4. 如果组件名称以 secure 结尾,那么说明需要创建安全组件,并且记录到 ACodec mFlags 成员中;
  5. 调用 onComponentAllocated 通知 MediaCodec 完成阻塞调用;
  6. 切换状态到 LoadedState

在看 LoadedState 的 stateEntered 方法之前,我们要先看下 BaseState 给出的 stateExited 方法,这里用到了 mStateGeneration,用来记录 ACodec 当前的状态变化,在处理消息时,如果传来的消息 generation 不等于当前的generation,说明状态机发生错误,这和之前看到的部分是不一样的,具体什么情况会出现不一样我们后续再做了解。

cpp 复制代码
void ACodec::BaseState::stateExited() {
    ++mCodec->mStateGeneration;
}

从上面的代码我们可以知道,每次状态切换,mStateGeneration数都会加 1 。

接下来看 LoadedState 的 stateEntered

cpp 复制代码
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,所以我们去这个状态下找对应的处理:

cpp 复制代码
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;
    }
	// 调用 Callback
    mCodec->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 再回传回来。

相关推荐
就爱六点起1 小时前
C/C++ 中的类型转换方式
c语言·开发语言·c++
_祝你今天愉快1 小时前
分析android :The binary version of its metadata is 1.8.0, expected version is 1.5.
android
暮志未晚Webgl1 小时前
109. UE5 GAS RPG 实现检查点的存档功能
android·java·ue5
召木1 小时前
C++小白实习日记——Day 2 TSCNS怎么读取当前时间
c++·职场和发展
麦田里的守望者江2 小时前
KMP 中的 expect 和 actual 声明
android·ios·kotlin
St_Ludwig2 小时前
C语言 蓝桥杯某例题解决方案(查找完数)
c语言·c++·后端·算法·游戏·蓝桥杯
Jack黄从零学c++2 小时前
opencv(c++)---自带的卷积运算filter2D以及应用
c++·人工智能·opencv
Dnelic-2 小时前
解决 Android 单元测试 No tests found for given includes:
android·junit·单元测试·问题记录·自学笔记
sweetheart7-72 小时前
LeetCode20. 有效的括号(2024冬季每日一题 11)
c++·算法·力扣··括号匹配
佛系小嘟嘟2 小时前
Android Studio不显示需要的tag日志解决办法《All logs entries are hidden by the filter》
android·ide·android studio