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

为了了解 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. 双向链表的基本实现【数据结构与算法—TypeScript 实现】

    笔记整理自 coderwhy 『TypeScript 高阶数据结构与算法』课程 双向链表:拥有两个指针方向的链表 DoublyNode 结构: prev:指向上一个节点 value:节点值 next: ...

  2. 力扣670(java)-最大交换(中等)

    题目: 给定一个非负整数,你至多可以交换一次数字中的任意两位.返回你能得到的最大值. 示例 1 : 输入: 2736输出: 7236解释: 交换数字2和数字7.示例 2 : 输入: 9973输出: 9 ...

  3. POJ4151:电影节

    4151:电影节 总时间限制:  1000ms 内存限制:  65536kB 描述 大学生电影节在北大举办! 这天,在北大各地放了多部电影,给定每部电影的放映时间区间,区间重叠的电影不可能同时看(端点 ...

  4. 阿里云容器服务差异化 SLO 混部技术实践

    ​简介:阿里巴巴在"差异化 SLO 混合部署"上已经有了多年的实践经验,目前已达到业界领先水平.所谓"差异化 SLO",就是将不同类型的工作负载混合运行在同一节 ...

  5. Duang,您的钉钉应用已上线!云开发5分钟快速打造钉钉会议室预定系统

    简介: 5分钟可以干什么?喝一杯咖啡,回一封邮件,还是开发上线一个钉钉应用.云开发平台联合钉钉开发平台推出0门槛打造你的第一个钉钉应用的活动,完成相应任务后,即可领取精美奖品.春暖花开,领个背包去踏春 ...

  6. 阿里云实时数仓Hologres年度发布,解读数仓新趋势

    ​简介:阿里云实时数仓Hologres年度发布,解读数仓新趋势. 1月7日,阿里云实时数仓Hologres发布最新版本,在成本.可用性.在线高可用等多方面进行了能力升级,行存吞吐提升100%,列存吞吐 ...

  7. 多任务学习模型之ESMM介绍与实现

    ​简介:本文介绍的是阿里巴巴团队发表在 SIGIR'2018 的论文<Entire Space Multi-Task Model: An Effective Approach for Estima ...

  8. 7张图揭晓RocketMQ存储设计的精髓

    ​简介: RocketMQ 作为一款基于磁盘存储的中间件,具有无限积压能力,并提供高吞吐.低延迟的服务能力,其最核心的部分必然是它优雅的存储设计. 存储概述 RocketMQ 存储的文件主要包括 Co ...

  9. 阿里云日志服务SLS,打造云原生时代智能运维

    ​2021年10月21日,阿里云针对企业运维难题,在云栖大会为大家带来了一场<智能运维论坛>的主题演讲.在会上,阿里云资深技术专家.日志服务技术负责人简志提出"云原生时代,企业业 ...

  10. 漫画 | 一口气搞懂 Serverless !

    ​简介: 第二届云原生编程挑战赛为热爱技术的年轻人提供一个挑战世界级技术问题的舞台,希望用技术为全社会创造更大价值. 作者 | 刘欣 呃,我可能是别人眼中所说的不用奋斗的一代. 大家喜欢听的什么多姿多 ...