Android Media Framework(十四)ACodec - Ⅱ

这一节我们一起来分析ACodec的创建、组件分配和组件配置过程,内容较为简单,轻松阅读吧。

1、ACodec创建

ACodec::ACodec()
    : mIsVideo(false),
      mIsImage(false),
      mIsEncoder(false) {
    // ...
    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);

    // ...
    mPortMode[kPortIndexInput] = IOMX::kPortModePresetByteBuffer;
    mPortMode[kPortIndexOutput] = IOMX::kPortModePresetByteBuffer;

    changeState(mUninitializedState);
}

ACodec的构造函数主要做了三件事,分别为初始化成员变量,实例化State对象,以及将状态切换至UninitializedState。切换状态时会调用UninitializedState的stateEntered方法:

void ACodec::UninitializedState::stateEntered() {
    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();
    // 释放OMXNodeInstance
    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();
}

ACodec在两种情况下会切换至UninitializedState状态。第一种情况是当ACodec实例被创建,但组件还未被分配时。在这种情况下,进入UninitializedState状态并不需要执行任何操作,所以stateEntered中的内容可以忽略。

第二种情况则发生在组件被销毁之后,此时ACodec也会进入UninitializedState状态。在这种情况下,上一次播放用到的相关资源还未被释放,因此需要在stateEntered中做释放动作。

在 Android Media Framework(十二)OMXNodeInstance - Ⅴ 一文最后讲到,释放组件需要调用OMXNodeInstance::freeNode方法,但是该方法只能释放OMX组件实例,不能释放OMXNodeInstance实例本身。只有在ACodec解除对OMXNodeInstance实例的引用后,OMXNodeInstance才会被销毁。解除引用的动作在UninitializedState::stateEntered中执行,OMXNodeInstance实例在此刻被释放。

2、initiateAllocateComponent

ACodec创建完成后,MediaCodec需要调用initiateAllocateComponent方法来实例化OMX组件:

bool ACodec::UninitializedState::onMessageReceived(const sp<AMessage> &msg) {
    switch (msg->what()) {
        case ACodec::kWhatAllocateComponent:
        {
            onAllocateComponent(msg);
            handled = true;
            break;
        }
    }
}

kWhatAllocateComponent这条消息是在UninitializedState状态中被处理,最后调用到onAllocateComponent方法创建OMX组件实例。

bool ACodec::UninitializedState::onAllocateComponent(const sp<AMessage> &msg) {
    // 1. omx callback message
    sp<AMessage> notify = new AMessage(kWhatOMXMessageList, mCodec);
    notify->setInt32("generation", mCodec->mNodeGeneration + 1);

    // 2. 获取MediaCodecInfo
    sp<RefBase> obj;
    CHECK(msg->findObject("codecInfo", &obj));
    sp<MediaCodecInfo> info = (MediaCodecInfo *)obj.get();
    if (info == nullptr) {
        mCodec->signalError(OMX_ErrorUndefined, UNKNOWN_ERROR);
        return false;
    }
    AString owner = (info->getOwnerName() == nullptr) ? "default" : info->getOwnerName();

    AString componentName;
    CHECK(msg->findString("componentName", &componentName));
    // 3. 创建CodecObserver
    sp<CodecObserver> observer = new CodecObserver(notify);
    sp<IOMX> omx;
    sp<IOMXNode> omxNode;

    status_t err = NAME_NOT_FOUND;
    // 4. 获取media.codec service
    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);
    // 5. 创建OMXNode
    err = omx->allocateNode(componentName.c_str(), observer, &omxNode);
    androidSetThreadPriority(tid, prevPriority);

    if (err != OK) {
        mCodec->signalError((OMX_ERRORTYPE)err, makeNoSideEffectStatus(err));
        return false;
    }
    // 6. 注册死亡通知
    mDeathNotifier = new DeathNotifier(new AMessage(kWhatOMXDied, mCodec));
    auto tOmxNode = omxNode->getHalInterface<IOmxNode>();
    if (tOmxNode && !tOmxNode->linkToDeath(mDeathNotifier, 0)) {
        mDeathNotifier.clear();
    }

    ++mCodec->mNodeGeneration;

    mCodec->mComponentName = componentName;
    mCodec->mRenderTracker.setComponentName(componentName);
    mCodec->mFlags = 0;
    // 7. 设定flags
    if (componentName.endsWith(".secure")) {
        mCodec->mFlags |= kFlagIsSecure;
        mCodec->mFlags |= kFlagIsGrallocUsageProtected;
        mCodec->mFlags |= kFlagPushBlankBuffersToNativeWindowOnShutdown;
    }

    mCodec->mOMX = omx;
    mCodec->mOMXNode = omxNode;
    // 8. 发送callback给MediaCodec
    mCodec->mCallback->onComponentAllocated(mCodec->mComponentName.c_str());
    // 9. 切换状态至LoadedState
    mCodec->changeState(mCodec->mLoadedState);

    return true;
}

onAllocateComponent中有如下几个操作需要了解:

  • 创建OMX Callback Message,OMXNodeInstance通过CodecObserver将消息回传给ACodec,ACodec需要重新将消息组织称为AMessage,最终送到ALooper中;

  • 调用initiateAllocateComponent传入的AMessage要有两个内容,一个是MediaCodecInfo,另一个是组件名称。这里有一个写法要注意,使用AMessage传递对象时,要将对象转化为sp传递,不能直接传递sp类型的对象,具体原因可以阅读AMessage实现;

  • OMXClient、IOMX、IOMXNode是对HIDL调用的封装,在之前的文章中已经对相关的内容做了描述,这里不再重复;

  • 代码中有一个mNodeGeneration,这个成员在ACodec中没有起实际的作用,可以忽略;

  • 如果组件的名称以 .secure 结尾,说明需要创建一个secure组件,ACodec会用mFlags成员记录下此信息,注意这里有三个标志位被拉起,后续需要用到它们;

  • 组件创建完成后,会调用MediaCodec给ACodec注册的Callback函数onComponentAllocated,通知上层组件创建完成,ACodec进入LoadedState了。与UninitializedState相同,ACodec也会有两种情况会进入LoadedState,一种是组件创建完成,另一种是组件被销毁,状态会从IdleToLoaded切换到LoadedState。这里只要看组件创建完成的情况,此时什么都不需要做。

3、initiateConfigureComponent

组件创建完成后就进入到了配置环节:

bool ACodec::LoadedState::onMessageReceived(const sp<AMessage> &msg) {
    switch (msg->what()) {
        case ACodec::kWhatConfigureComponent:
        {
            onConfigureComponent(msg);
            handled = true;
            break;
        }
    }
}

bool ACodec::LoadedState::onConfigureComponent(
        const sp<AMessage> &msg) {
    CHECK(mCodec->mOMXNode != NULL);

    status_t err = OK;
    AString mime;
    if (!msg->findString("mime", &mime)) {
        err = BAD_VALUE;
    } else {
        err = mCodec->configureCodec(mime.c_str(), msg);
    }
    if (err != OK) {
        mCodec->signalError(OMX_ErrorUndefined, makeNoSideEffectStatus(err));
        return false;
    }

    mCodec->mCallback->onComponentConfigured(mCodec->mInputFormat, mCodec->mOutputFormat);

    return true;
}

onConfigureComponent首先会检查传入的格式信息是否包含mime,如果没有就会返回error;接着调用ACodec的configureCodec方法配置组件;最后调用onComponentConfigured结束MediaCodec的阻塞调用,同时回传input/output format。

4、configureCodec

configureCodec方法很长,本文不会对全部代码进行详细分析,我们将了解其中比较重要的format,并对PortMode设置进行深入研究。

    int32_t encoder;
    if (!msg->findInt32("encoder", &encoder)) {
        encoder = false;
    }

    mIsEncoder = encoder;
    mIsVideo = !strncasecmp(mime, "video/", 6);
    mIsImage = !strncasecmp(mime, "image/", 6);

    mPortMode[kPortIndexInput] = IOMX::kPortModePresetByteBuffer;
    mPortMode[kPortIndexOutput] = IOMX::kPortModePresetByteBuffer;

    status_t err = setComponentRole(encoder /* isEncoder */, mime);

进入函数体,首先会根据配置信息判断要配置的是否为encoder,这个信息也可以从组件名中获取。接着,根据mime类型判断要配置的是video、audio还是image。然后,会调用setComponentRole。

status_t ACodec::setComponentRole(
        bool isEncoder, const char *mime) {
    const char *role = GetComponentRole(isEncoder, mime);
    if (role == NULL) {
        return BAD_VALUE;
    }
    status_t err = SetComponentRole(mOMXNode, role);
    if (err != OK) {
    }
    return err;
}

const char *GetComponentRole(bool isEncoder, const char *mime) {
    struct MimeToRole {
        const char *mime;
        const char *decoderRole;
        const char *encoderRole;
    };

    static const MimeToRole kMimeToRole[] = {
        { MEDIA_MIMETYPE_AUDIO_MPEG,
            "audio_decoder.mp3", "audio_encoder.mp3" },
        // ...
        { MEDIA_MIMETYPE_VIDEO_AV1,
            "video_decoder.av1", "video_encoder.av1" },
    };

    static const size_t kNumMimeToRole =
        sizeof(kMimeToRole) / sizeof(kMimeToRole[0]);

    size_t i;
    for (i = 0; i < kNumMimeToRole; ++i) {
        if (!strcasecmp(mime, kMimeToRole[i].mime)) {
            break;
        }
    }

    if (i == kNumMimeToRole) {
        return NULL;
    }

    return isEncoder ? kMimeToRole[i].encoderRole
                  : kMimeToRole[i].decoderRole;  

setComponentRole方法会从MimeToRole列表中查找指定的role,如果找到了,就将其设定给组件;如果没有找到合适的role,则返回错误。

MimeToRole列表在Framework中是预先设置好的,包含了一系列常见的媒体类型。若需新增一个解码器,比如H.266(VVC)解码器,除了要在OMXStore和相应的XML文件做修改外,还必须在MimeToRole列表中添加该媒体类型对应的role。如果忽略这一步,创建新的解码器时会在这里返回error。

接着往下看,configureCodec会先对Encoder的format进行解析:

关注公众号《青山渺渺》阅读全文
请添加图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青山渺渺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值