这一节我们一起来分析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进行解析:
关注公众号《青山渺渺》阅读全文