当前位置: 首页 > news >正文

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

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

1、创建 ACodec

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

    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 looperif (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 的消息处理在不同线程中。

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 方法:

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

bool ACodec::UninitializedState::onAllocateComponent(const sp<AMessage> &msg) {ALOGV("onAllocateComponent");CHECK(mCodec->mOMXNode == NULL);mCodec->mFatalError = false;// 创建 Callback 消息,并且设置好 notifysp<AMessage> notify = new AMessage(kWhatOMXMessageList, mCodec);// notify 的 generation 为 nodegeneration + 1,这是因为进入 loaded 状态后,mNodeGeneration 会 + 1notify->setInt32("generation", mCodec->mNodeGeneration + 1);// 需要检查 codecInfo 才能创建 OMXNodesp<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;// 创建 OMXClientOMXClient 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 服务代理,并且封装为 IOMXNodeerr = 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());// 切换状态到 LoadedStatemCodec->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,说明状态机发生错误,这和之前看到的部分是不一样的,具体什么情况会出现不一样我们后续再做了解。

void ACodec::BaseState::stateExited() {++mCodec->mStateGeneration;
}

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

接下来看 LoadedState 的 stateEntered

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

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;}// 调用 CallbackmCodec->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 再回传回来。

http://www.lryc.cn/news/256815.html

相关文章:

  • TCP单聊和UDP群聊
  • 智能优化算法应用:基于鲸鱼算法3D无线传感器网络(WSN)覆盖优化 - 附代码
  • TortoiseGit 小乌龟svn客户端软件查看仓库地址
  • uniapp微信小程序分包,小程序分包
  • 『Linux升级路』进度条小程序
  • 使用rust slint开发桌面应用
  • Flutter桌面应用程序定义系统托盘Tray
  • docker:安装mysql以及最佳实践
  • uniapp实战 —— 自定义顶部导航栏
  • 中国移动频段划分
  • 《PySpark大数据分析实战》-01.关于数据
  • Qt/C++视频监控拉流显示/各种rtsp/rtmp/http视频流/摄像头采集/视频监控回放/录像存储
  • Vue.js - 界面设计工具和UI组件库
  • 【贪心算法】 Opponents
  • 【git 相关操作】
  • 流媒体音视频/安防视频云平台/可视化监控平台EasyCVR无法启动且打印panic报错,是什么原因?
  • H264之NALU结构详解
  • 快速整合EasyExcel实现Excel的上传下载
  • MongoDB的条件操作符
  • 【Linux】探索Linux进程状态 | 僵尸进程 | 孤儿进程
  • 大数据股票简单分析
  • 从零开始搭建链上dex自动化价差套利程序(11)
  • 2023.12面试题汇总小结
  • Linux权限命令详解
  • 【Android】Glide的简单使用(下)
  • TCP对数据的拆分
  • 面试问题--计算机网络:二层转发、三层转发与osi模型
  • kubectl获取ConfigMap导出YAML时如何忽略某些字段
  • 复制粘贴——QT实现原理
  • (一)五种最新算法(SWO、COA、LSO、GRO、LO)求解无人机路径规划MATLAB