关注公众号免费阅读全文,进入音视频开发技术分享群!

为了了解 ACodec 是如何与 OpenMAX 组件进行 buffer 流转的,我们有必要先来学习 OMXNodeInstance,在前面的章节中,我们已经了解了 media.codec 进程包含的内容,以及 OpenMAX 框架中的一些内容。这一节我们将来学习 OMXNode 与 media.codec 进程之间的关系,了解OMXNode是如何创建、使用、销毁的。

1、创建 OMXNodeInstance

我们回到 Omx.cpp 来看 allocateNode 方法:

Return<void> Omx::allocateNode(
const hidl_string& name,
const sp<IOmxObserver>& observer,
allocateNode_cb _hidl_cb) { using ::android::IOMXNode;
using ::android::IOMXObserver; sp<OMXNodeInstance> instance;
{
// 检查是否到达了 OMXNode 实例最大个数
Mutex::Autolock autoLock(mLock);
if (mLiveNodes.size() == kMaxNodeInstances) {
_hidl_cb(toStatus(NO_MEMORY), nullptr);
return Void();
}
// 创建 OMXNodeInstance 实例,传入参数为 IOmx 服务,IOmxObserver,以及组件名称
instance = new OMXNodeInstance(
this, new LWOmxObserver(observer), name.c_str());
// 调用 OMXStore 的方法创建 OMX_COMPONENTTYPE 对象
OMX_COMPONENTTYPE *handle;
OMX_ERRORTYPE err = mStore->makeComponentInstance(
name.c_str(), &OMXNodeInstance::kCallbacks,
instance.get(), &handle); if (err != OMX_ErrorNone) {
LOG(ERROR) << "Failed to allocate omx component "
"'" << name.c_str() << "' "
" err=" << asString(err) <<
"(0x" << std::hex << unsigned(err) << ")";
_hidl_cb(toStatus(StatusFromOMXError(err)), nullptr);
return Void();
}
// 将创建的 OMX_COMPONENTTYPE 对象与 OMXNodeInstance 进行绑定
instance->setHandle(handle);
// 获取 quirks 信息
// Find quirks from mParser
const auto& codec = mParser.getCodecMap().find(name.c_str());
if (codec == mParser.getCodecMap().cend()) {
LOG(WARNING) << "Failed to obtain quirks for omx component "
"'" << name.c_str() << "' "
"from XML files";
} else {
uint32_t quirks = 0;
for (const auto& quirk : codec->second.quirkSet) {
if (quirk == "quirk::requires-allocate-on-input-ports") {
quirks |= OMXNodeInstance::
kRequiresAllocateBufferOnInputPorts;
}
if (quirk == "quirk::requires-allocate-on-output-ports") {
quirks |= OMXNodeInstance::
kRequiresAllocateBufferOnOutputPorts;
}
}
// 如果有 quirks 信息则设置
instance->setQuirks(quirks);
}
// 将 IOmxObersver 和 OMXNodeInstance 以键值的形式存储
mLiveNodes.add(observer.get(), instance);
// 将 OMXNodeInstance 和 IOmxObersver 以键值的形式存储
mNode2Observer.add(instance.get(), observer.get());
}
observer->linkToDeath(this, 0);
// 返回 OMXNodeInstance 给 ACodec
_hidl_cb(toStatus(OK), new TWOmxNode(instance));
return Void();
}

allocateNode 方法中的内容还是比较清晰简洁的,主要做了如下几件事情:

  1. 检查 OMXNode 实例个数是否达到上限;
  2. 创建 OMXNodeInstance 实例,传入参数为 IOmx 服务,IOmxObserver,以及组件名称;
  3. 将创建的 OMX_COMPONENTTYPE 对象与 OMXNodeInstance 进行绑定;
  4. 获取 quirks 并给 OMXNodeInstance 设置相关信息;
  5. 将 IOmxObersver 和 OMXNodeInstance 以键值的形式双向绑定存储;
  6. 返回 OMXNodeInstance 返回给 ACodec。

以上是代码的解释,接下来是我们对这段代码的理解:

  • OMX_COMPONENTTYPE 是由 OMXStore 创建,这个指针由 OMXStore 来管理,所以最终也由 OMXStore 来释放;
  • OMX_COMPONENTTYPE 和 OMXNodeInstance 进行了绑定,OMXNodeInstance帮我们进行OMX组件方法的封装,之后上层调用 OMXNodeInstance 的方法,OMXNodeInstance 最终调用到组件的接口;
  • OMXNodeInstance 的引用计数为2,mLiveNodes 中存有一个计数,上层ACodec存有一个计数,mNode2Observer 中存储的是指针,所以不会有计数增加。

2、OMXNodeInstance 构造函数

OMXNodeInstance::OMXNodeInstance(
Omx *owner, const sp<IOMXObserver> &observer, const char *name)
: mOwner(owner),
mHandle(NULL),
mObserver(observer),
mDying(false),
mSailed(false),
mQueriedProhibitedExtensions(false),
mQuirks(0),
mBufferIDCount(0),
mRestorePtsFailed(false),
mMaxTimestampGapUs(0LL),
mPrevOriginalTimeUs(-1LL),
mPrevModifiedTimeUs(-1LL)
{
mName = ADebug::GetDebugName(name);
DEBUG = ADebug::GetDebugLevelFromProperty(name, "debug.stagefright.omx-debug");
ALOGV("debug level for %s is %d", name, DEBUG);
DEBUG_BUMP = DEBUG;
mNumPortBuffers[0] = 0;
mNumPortBuffers[1] = 0;
mDebugLevelBumpPendingBuffers[0] = 0;
mDebugLevelBumpPendingBuffers[1] = 0;
mMetadataType[0] = kMetadataBufferTypeInvalid;
mMetadataType[1] = kMetadataBufferTypeInvalid;
mPortMode[0] = IOMX::kPortModePresetByteBuffer;
mPortMode[1] = IOMX::kPortModePresetByteBuffer;
mSecureBufferType[0] = kSecureBufferTypeUnknown;
mSecureBufferType[1] = kSecureBufferTypeUnknown;
mGraphicBufferEnabled[0] = false;
mGraphicBufferEnabled[1] = false;
mIsSecure = AString(name).endsWith(".secure");
mLegacyAdaptiveExperiment = ADebug::isExperimentEnabled("legacy-adaptive");
}

OMXNodeInstance 的构造函数主要就初始化了几个数组,这个数组中都是由两个元素,索引0表示input port,索引 1 表示 output port,以下是一些数组的意义:

  • mNumPortBuffers:port 中buffer的数量;
  • mMetadataType:meta data 的类型,这个用于确定有surface的情况下ouput buffer的类型,以及input为camera或者是graphic(录屏)的情况下 input buffer的类型;
  • mPortMode:端口模式;
  • mSecureBufferType:secure buffer 的类型;
  • mGraphicBufferEnabled:是否使用graphic buffer;

看过前面章节的小伙伴应该大致可以揣摩出使用的枚举类型的意义。

void OMXNodeInstance::setHandle(OMX_HANDLETYPE handle) {
CLOG_LIFE(allocateNode, "handle=%p", handle);
CHECK(mHandle == NULL);
mHandle = handle;
if (handle != NULL) {
mDispatcher = new CallbackDispatcher(this);
}
}

setHandle方法可以把创建的OMX组件和OMXNodeInstance实例进行绑定,同时会创建一个CallbackDispatcher对象。

3、CallbackDispatcher

CallbackDispatcher 从名字上来看是回调的调度者,它的作用是开启一个线程,所有由OMX组件发上来的消息或者事件都会进入到该线程的队列当中,按照顺序一个一个通过 IOmxObserver 发回到 ACodec 层。

OMX callback 会把事件或者消息封装成为 omx_message,再通过IOmxObserver发送,具体如何封装的,以及ACodec如何解封装,参考OnEvent、OnEmptyBufferDone、OnFillBufferDone这三个方法的实现。

具体 CallbackDispatcher 和 CallbackDispatcherThread 是如何实现的我们这里不做过多的了解。

4、销毁 OMXNodeInstance

我们常常只关注对象是如何创建的,其实销毁的过程也很重要,这里我们就一起来了解OMXNode是如何被销毁的。

目光回到 ACodec 中来,当我们调用了 initiateShutdown 去释放组件时,ACodec 会调用 IOMXNode 的 freeNode 方法:

        case ACodec::kWhatReleaseCodecInstance:
{
ALOGI("[%s] forcing the release of codec",
mCodec->mComponentName.c_str());
status_t err = mCodec->mOMXNode->freeNode();
ALOGE_IF("[%s] failed to release codec instance: err=%d",
mCodec->mComponentName.c_str(), err);
mCodec->mCallback->onReleaseCompleted(); mCodec->changeState(mCodec->mUninitializedState);
break;
}

最终调用到 OMXNodeInstance 的 freeNode 方法中:

status_t OMXNodeInstance::freeNode() {
CLOG_LIFE(freeNode, "handle=%p", mHandle);
static int32_t kMaxNumIterations = 10; // Transition the node from its current state all the way down
// to "Loaded".
// This ensures that all active buffers are properly freed even
// for components that don't do this themselves on a call to
// "FreeHandle". // The code below may trigger some more events to be dispatched
// by the OMX component - we want to ignore them as our client
// does not expect them.
bool expected = false;
if (!mDying.compare_exchange_strong(expected, true)) {
// exit if we have already freed the node or doing so right now.
// NOTE: this ensures that the block below executes at most once.
ALOGV("Already dying");
return OK;
}
// 获取 OMX 组件当前的状态
OMX_STATETYPE state;
CHECK_EQ(OMX_GetState(mHandle, &state), OMX_ErrorNone);
switch (state) {
case OMX_StateExecuting:
{
// 将OMX状态置为 Idle
ALOGV("forcing Executing->Idle");
sendCommand(OMX_CommandStateSet, OMX_StateIdle);
OMX_ERRORTYPE err;
int32_t iteration = 0;
// 阻塞等待
while ((err = OMX_GetState(mHandle, &state)) == OMX_ErrorNone
&& state != OMX_StateIdle
&& state != OMX_StateInvalid) {
if (++iteration > kMaxNumIterations) {
CLOGW("failed to enter Idle state (now %s(%d), aborting.",
asString(state), state);
state = OMX_StateInvalid;
break;
} usleep(100000);
}
CHECK_EQ(err, OMX_ErrorNone); if (state == OMX_StateInvalid) {
break;
} FALLTHROUGH_INTENDED;
} case OMX_StateIdle:
{
// 将 OMX 组件状态设置为 Loaded
ALOGV("forcing Idle->Loaded");
sendCommand(OMX_CommandStateSet, OMX_StateLoaded);
// 销毁所有 buffer
freeActiveBuffers(); OMX_ERRORTYPE err;
int32_t iteration = 0;
// 阻塞等待
while ((err = OMX_GetState(mHandle, &state)) == OMX_ErrorNone
&& state != OMX_StateLoaded
&& state != OMX_StateInvalid) {
if (++iteration > kMaxNumIterations) {
CLOGW("failed to enter Loaded state (now %s(%d), aborting.",
asString(state), state);
state = OMX_StateInvalid;
break;
} ALOGV("waiting for Loaded state...");
usleep(100000);
}
CHECK_EQ(err, OMX_ErrorNone); FALLTHROUGH_INTENDED;
} case OMX_StateLoaded:
{
// 销毁所有 buffer
freeActiveBuffers();
FALLTHROUGH_INTENDED;
}
case OMX_StateInvalid:
break; default:
LOG_ALWAYS_FATAL("unknown state %s(%#x).", asString(state), state);
break;
} Mutex::Autolock _l(mLock);
// 调用 OMXNodeInstance 的另一个 freeNode 方法,传入参数为自身
status_t err = mOwner->freeNode(this);
// 关闭 Dispatcher 线程,销毁相关内容
mDispatcher.clear();
mOMXBufferSource.clear(); mHandle = NULL;
CLOG_IF_ERROR(freeNode, err, "");
free(mName);
mName = NULL; ALOGV("OMXNodeInstance going away."); return err;
}
  1. 获取 OMX 组件当前的状态,按照状态依次设定 OMX_StateIdle、OMX_StateLoaded,并且调用 freeActiveBuffers 释放所有的 buffer,这里的buffer指的是什么我们后面再看;
  2. 调用 IOmx 的 freeNode 方法,传入参数为自身;
  3. 关闭 Dispatcher 线程;

这里比较令人疑惑的可能就是第二点了,我们刚刚调用的 OMXNode freeNode 干了什么?为什么又要调用一个freeNode呢?

其实从上面的代码我们可以看出来,OMXNodeInstance 的 freeNode 方法是用于关闭或者销毁 OMX 组件所使用的一些资源,但是这时候 OMX 组件还是存在没有被销毁的。之所以把销毁资源的方法放在 OMXNodeInstance 中是为了保证 API 调用逻辑的统一,所有的关于组件的操作方法都放在 OMXNode 当中。

当OMX组件的资源全部释放完成,下一步就是要销毁OMX组件了,调用的方法就是 IOmx 的 freeNode 方法:

status_t Omx::freeNode(sp<OMXNodeInstance> const& instance) {
if (instance == NULL) {
return OK;
} {
Mutex::Autolock autoLock(mLock);
// 获取OMXNode指针
ssize_t observerIndex = mNode2Observer.indexOfKey(instance.get());
if (observerIndex >= 0) {
wp<IBase> observer = mNode2Observer.valueAt(observerIndex);
ssize_t nodeIndex = mLiveNodes.indexOfKey(observer);
// 移除引用计数,移除指针
if (nodeIndex >= 0) {
mNode2Observer.removeItemsAt(observerIndex);
mLiveNodes.removeItemsAt(nodeIndex);
sp<IBase> sObserver = observer.promote();
if (sObserver != nullptr) {
sObserver->unlinkToDeath(this);
}
} else {
LOG(WARNING) << "Inconsistent observer record";
}
}
} OMX_ERRORTYPE err = OMX_ErrorNone;
if (instance->handle() != NULL) {
// 调用destroyComponentInstance销毁OMX组件
err = mStore->destroyComponentInstance(
static_cast<OMX_COMPONENTTYPE*>(instance->handle()));
}
return StatusFromOMXError(err);
}
  1. 移除两个keyedVector 中的键值,减少OMXNode引用计数,减少IOmxObserver 的引用计数释放资源;
  2. 调用destroyComponentInstance销毁OMX组件,最终创建的组件还是由 OMXStore 来销毁;

Omx::freeNode 执行完成,OMX组件被销毁,这时候 OMXNodeInstance 有没有被销毁呢?答案是没有的,退出 Omx::freeNode 时,OMXNodeInstance 的引用计数为 1,通过 binder 被 ACodec 持有,当 ACodec 销毁时,OMXNodeInstance 自然就销毁了。

把之前的一幅图改改,来表示 OMXNodeInsatnce、OMX_HANDLE、IOmx、OMXStore 之间的关系:

Android 13 - Media框架(24)- OMXNodeInstance(一)的更多相关文章

  1. 简析Android 兼容性测试框架CTS使用

    一.什么是兼容性测试? 1)为用户提供最好的用户体验,让更多高质量的APP可以顺利的运行在此平台上 2)让程序员能为此平台写更多的高质量的应用程序 3)可以更好的利用Android应用市场 二.CTS ...

  2. 15类Android通用流行框架

    15类Android通用流行框架 Android流行框架 缓存 DiskLruCache Java实现基于LRU的磁盘缓存 图片加载 Android Universal Image Loader 一个 ...

  3. 25类Android常用开源框架

    1.图片加载,缓存,处理 框架名称 功能描述 Android Universal Image Loader 一个强大的加载,缓存,展示图片的库,已过时 Picasso 一个强大的图片下载与缓存的库 F ...

  4. 15 个 Android 通用流行框架大全(转)

    1. 缓存 DiskLruCache    Java实现基于LRU的磁盘缓存 2.图片加载 Android Universal Image Loader 一个强大的加载,缓存,展示图片的库 Picas ...

  5. Android 通用流行框架

    原文出处: http://android.jobbole.com/83028/ 1. 缓存 名称 描述 DiskLruCache Java实现基于LRU的磁盘缓存 2.图片加载 名称 描述 Andro ...

  6. 经受时间沉淀的15 个 Android 通用流行框架大全

    1. 缓存 名称描述 DiskLruCache: Java实现基于LRU的磁盘缓存 2.图片加载 名称描述 Android    Universal Image Loader 一个强大的加载,缓存,展 ...

  7. Android通用流行框架大全

    1. 缓存 名称 描述 DiskLruCache Java实现基于LRU的磁盘缓存 2.图片加载 名称 描述 Android Universal Image Loader 一个强大的加载,缓存,展示图 ...

  8. 60.Android通用流行框架大全

    转载:https://segmentfault.com/a/1190000005073746 Android通用流行框架大全 1. 缓存 名称 描述 DiskLruCache Java实现基于LRU的 ...

  9. 15 个 Android 通用流行框架大全

      1. 缓存 名称 描述 DiskLruCache Java实现基于LRU的磁盘缓存 2.图片加载 名称 描述 Android Universal Image Loader 一个强大的加载,缓存,展 ...

  10. Android 通用流行框架大全

    1. 缓存 DiskLruCache    Java实现基于LRU的磁盘缓存 2.图片加载 Android Universal Image Loader 一个强大的加载,缓存,展示图片的库 Picas ...

随机推荐

  1. Mysql之SQL语句初级用法

    前言 本文通过简单的示例去了解Mysql的DDL.DML.DCL的语句用法. 一.DDL语句 DDL(Data Definition Language)语句: 数据定义语言,主要是进行定义/改变表的结 ...

  2. Bill的挑战

    看数据范围就知道应该要状压,也不难看出应该压缩位数的状态.所以设f[i][j]为前i位,相互匹配的字符串的状态. 那么,就会有 f[i+1][j&a[i][ch]]=(f[i+1][j& ...

  3. [CentOS]压缩+解压+打包命令大全

    [CentOS]压缩+解压+打包命令大全 --------------- .tar 解包:tar xvf FileName.tar 打包:tar cvf FileName.tar DirName (注 ...

  4. Linux systemd 定时任务

    哈喽大家好,我是咸鱼. 说到 Linux 定时任务,大家用得最多的就是 crond 服务,但其实 systemd 也有类似的功能.我们不但可以通过 systemd 来管理服务,还能设置定时任务,那就是 ...

  5. @EnableDiscoveryClient 注解如何实现服务注册与发现

    @EnableDiscoveryClient 是如何实现服务注册的?我们首先需要了解 Spring-Cloud-Commons 这个模块,Spring-Cloud-Commons 是 Spring-C ...

  6. golang开发 深入理解 context

    context的历史 context包在Go 1.7版本正式加入Go标准库.在加入之前我们看看Go团队核心成员Sameer Ajmani在2014年发表的一篇关于context介绍博客,地址:http ...

  7. pycharm更换主题,pycharm更换皮肤,pycharm更换不同颜色

    1.首先  点击File→进入setting 2. 在settings里面找到appearance 3.选择 Theme,下拉即可修改 4.选择不同的主题,darcula是黑色,其他两个是白色

  8. 力扣1454(MySQL)-活跃用户(中等)

    (非会员进不去,看的其他博主的题目) 问题: 写一个 SQL 查询, 找到活跃用户的 id 和 name. 活跃用户是指那些至少连续 5 天登录账户的用户. 返回的结果表按照 id 排序.  解题思路 ...

  9. 力扣182(MySQL)-查找重复的电子邮箱(简单)

    题目: 编写一个 SQL 查询,查找 Person 表中所有重复的电子邮箱. 示例:  解题思路: 方法一: 使用group by 按Email来分组,然后使用having选择count(id)> ...

  10. P10160 [DTCPC 2024] Ultra 题解

    [题目描述] 给你一个 \(01\) 序列,你可以进行如下操作若干次(或零次): 将序列中形如 \(101\cdots01\) 的一个子串(即 \(1(01)^k\),\(k\ge 1\))替换成等长 ...