Android音视频 MediaCodec框架-创建流程
简述
之前我们介绍并且演示了MediaCodec的接口使用方法,我们这一节来看一下MediaCodec进行编解码的创建流程。
java层的MediaCodec只是提供接口,实际的逻辑是通过jni层实现的,java层的MediaCodec通过jni创建管理一个C++层JMediaCodec,而JMediaCodec会创建C++层MediaCodec,C++层的MediaCodec会根据配置创建CCodec和OMX,新版本的Android一般使用CCodec,CCodec层会通过Codec2Client访问hal层的service,一般会有多个hal层的service,每个hal层的service又支持多个Component,不同Component会支持不同的编解码,且有不同是的实现方式,比如软解码和硬解码。
创建MediacCodec

1.1 MediaCodec.createDecoderByType
调用createDecoderByType创建MediaCodec,调用MediaCodec的构造函数,我们type传入的是H264,type就是name。
public static MediaCodec createDecoderByType(@NonNull String type)
throws IOException {
return new MediaCodec(type, true /* nameIsType */, false /* encoder */);
}
1.2 MediaCodec构造函数
构造函数调用一个重载构造函数,主要是构造了一个EventHandler,然后调用native_setup初始化native的MediaCodec。
private MediaCodec(@NonNull String name, boolean nameIsType, boolean encoder) {
this(name, nameIsType, encoder, -1 /* pid */, -1 /* uid */);
}
private MediaCodec(@NonNull String name, boolean nameIsType, boolean encoder, int pid,
int uid) {
Looper looper;
if ((looper = Looper.myLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else if ((looper = Looper.getMainLooper()) != null) {
mEventHandler = new EventHandler(this, looper);
} else {
mEventHandler = null;
}
mCallbackHandler = mEventHandler;
mOnFirstTunnelFrameReadyHandler = mEventHandler;
mOnFrameRenderedHandler = mEventHandler;
mBufferLock = new Object();
// 保存type作为名字
mNameAtCreation = nameIsType ? null : name;
// 调用native函数初始化MediaCodec
native_setup(name, nameIsType, encoder, pid, uid);
}
1.3 native_setup
通过jni调用native_setup。
构造了一个JMediaCodec,每个java层的MediaCodec在native侧会对应一个JMediaCodec,后续还会创建一个native层的MediaCodec。
注册了消息监听。
setMediaCodec回调java层记录JMediaCodec的地址。
static void android_media_MediaCodec_native_setup(
JNIEnv *env, jobject thiz,
jstring name, jboolean nameIsType, jboolean encoder, int pid, int uid) {
// ... 非空判断
// 构造JMediaCodec,详见1.4
sp<JMediaCodec> codec = new JMediaCodec(env, thiz, tmp, nameIsType, encoder, pid, uid);
const status_t err = codec->initCheck();
// ... 返回值错误的处理,权限异常/内存不足/编解码器类型不存在
// 注册消息监听
codec->registerSelf();
// 回调java侧记录JMediaCodec
setMediaCodec(env, thiz, codec);
}
1.4 JMediaCodec构造函数
这里会构造一个ALooper,这个的功能类似于Looper。
通过MediaCodec::CreateByType创建一个Native层的MediaCodec。
JMediaCodec::JMediaCodec(
JNIEnv *env, jobject thiz,
const char *name, bool nameIsType, bool encoder, int pid, int uid)
: mClass(NULL),
mObject(NULL) {
// ... 记录Java层MediaCodec信息
// 类似于Looper的功能,post消息,然后由对应的方法处理消息
mLooper = new ALooper;
mLooper->setName("MediaCodec_looper");
mLooper->start(
false, // runOnCallingThread
true, // canCallJava
ANDROID_PRIORITY_VIDEO);
// 我们传的nameIsType为true,name就是编码类型(比如H264)
if (nameIsType) {
// 详见1.5
mCodec = MediaCodec::CreateByType(mLooper, name, encoder, &mInitStatus, pid, uid);
if (mCodec == nullptr || mCodec->getName(&mNameAtCreation) != OK) {
mNameAtCreation = "(null)";
}
} else {
mCodec = MediaCodec::CreateByComponentName(mLooper, name, &mInitStatus, pid, uid);
mNameAtCreation = name;
}
CHECK((mCodec != NULL) != (mInitStatus != OK));
}
1.5 MediaCodec::CreateByType
调用一个重载CreateByType。
CreateByType会通过findMatchingCodecs根据名字查找对应的Codec的名字,返回的列表存储在了matchingCodecs。
遍历matchingCodecs,创建MediaCodec,然后初始化MediaCodec,初始化成功后就返回。
sp<MediaCodec> MediaCodec::CreateByType(
const sp<ALooper> &looper, const AString &mime, bool encoder, status_t *err, pid_t pid,
uid_t uid) {
sp<AMessage> format;
return CreateByType(looper, mime, encoder, err, pid, uid, format);
}
sp<MediaCodec> MediaCodec::CreateByType(
const sp<ALooper> &looper, const AString &mime, bool encoder, status_t *err, pid_t pid,
uid_t uid, sp<AMessage> format) {
Vector<AString> matchingCodecs;
// 根据mime编码名字,查询支持的编码器,值存在matchingCodecs。
// 详见1.5.1
MediaCodecList::findMatchingCodecs(
mime.c_str(),
encoder,
0,
format,
&matchingCodecs);
if (err != NULL) {
*err = NAME_NOT_FOUND;
}
for (size_t i = 0; i < matchingCodecs.size(); ++i) {
// 构建MediaCodec,根据componentName初始化Codec,初始化成功就返回。
sp<MediaCodec> codec = new MediaCodec(looper, pid, uid);
AString componentName = matchingCodecs[i];
// 初始化MediaCodec,详见1.6
status_t ret = codec->init(componentName);
if (err != NULL) {
*err = ret;
}
if (ret == OK) {
return codec;
}
// ...
}
return NULL;
}
1.5.1 MediaCodecList::findMatchingCodecs
void MediaCodecList::findMatchingCodecs(
const char *mime, bool encoder, uint32_t flags, const sp<AMessage> &format,
Vector<AString> *matches) {
matches->clear();
// 通过MediaPlayerService构建获取MediaCodecList,详见1.5.2
const sp<IMediaCodecList> list = getInstance();
if (list == nullptr) {
return;
}
size_t index = 0;
for (;;) {
// 从MediaCodecList的mCodecInfos查询对应索引,mCodecInfos记录了所有支持的Codec信息。
// 我们主要来看一下mCodecInfos是哪里初始化的,详见1.5.2。
ssize_t matchIndex =
list->findCodecByType(mime, encoder, index);
// ...
const sp<MediaCodecInfo> info = list->getCodecInfo(matchIndex);
CHECK(info != nullptr);
AString componentName = info->getCodecName();
// ...
// 将获取的组件名放入matches
matches->push(componentName);
ALOGV("matching '%s'", componentName.c_str());
}
if (flags & kPreferSoftwareCodecs ||
property_get_bool("debug.stagefright.swcodec", false)) {
matches->sort(compareSoftwareCodecsFirst);
}
// ...如果什么都没找到,从format信息里获取profile,调用findMatchingCodecs重试一下
}
1.5.2 MediaCodecList::getInstance
通过getLocalInstance构造获取MediaCodecList。
sp<IMediaCodecList> MediaCodecList::getInstance() {
Mutex::Autolock _l(sRemoteInitMutex);
if (sRemoteList == nullptr) {
sMediaPlayer = defaultServiceManager()->getService(String16("media.player"));
sp<IMediaPlayerService> service =
interface_cast<IMediaPlayerService>(sMediaPlayer);
if (service.get() != nullptr) {
// 这里通过MediaPlayerService的getCodecList也是通过getLocalInstance获取sRemoteList
sRemoteList = service->getCodecList();
if (sRemoteList != nullptr) {
sBinderDeathObserver = new BinderDeathObserver();
sMediaPlayer->linkToDeath(sBinderDeathObserver.get());
}
}
if (sRemoteList == nullptr) {
// 通过getLocalInstance构造MediaCodecList,详见1.5.3
sRemoteList = getLocalInstance();
}
}
return sRemoteList;
}
1.5.3 MediaCodecList::getLocalInstance
构造MediaCodecList,参数通过GetBuilders获取。
sp<IMediaCodecList> MediaCodecList::getLocalInstance() {
Mutex::Autolock autoLock(sInitMutex);
if (sCodecList == nullptr) {
// 构造MediaCodecList,详见1.5.4
MediaCodecList *codecList = new MediaCodecList(GetBuilders());
if (codecList->initCheck() == OK) {
sCodecList = codecList;
// ...
} else {
// failure to initialize may be temporary. retry on next call.
delete codecList;
}
}
return sCodecList;
}
1.5.4 MediaCodecList构造函数
遍历所有builders,调用它的buildMediaCodecList,一般buildMediaCodecList会调用MediaCodecListWriter::findMediaCodecInfo
而findMediaCodecInfo则会将MediaCodecInfo信息存储到MediaCodecListWriter
最后会调用writer.writeCodecInfos(&mCodecInfos)将所有write里面的信息存储到mCodecInfos变量中
所以可以看出来mCodecInfos最终有哪些数据取决于构造函数入参GetBuilders(),我们来看下GetBuilders()的实现,详见1.5.5。
MediaCodecList::MediaCodecList(std::vector<MediaCodecListBuilderBase*> builders) {
mGlobalSettings = new AMessage();
mCodecInfos.clear();
MediaCodecListWriter writer;
for (MediaCodecListBuilderBase *builder : builders) {
// ...
// buildMediaCodecList会调用MediaCodecListWriter::findMediaCodecInfo
// 而findMediaCodecInfo则会将MediaCodecInfo信息存储到MediaCodecListWriter
// 最后会调用writer.writeCodecInfos(&mCodecInfos)将所有write里面的信息存储到mCodecInfos变量中
auto currentCheck = builder->buildMediaCodecList(&writer);
if (currentCheck != OK) {
ALOGD("ignored failed builder");
continue;
} else {
mInitCheck = currentCheck;
}
}
writer.writeGlobalSettings(mGlobalSettings);
// 将MediaCodecListWriter里的CodecInfos存储到mCodecInfos列表里。
writer.writeCodecInfos(&mCodecInfos);
std::stable_sort(
mCodecInfos.begin(),
mCodecInfos.end(),
[](const sp<MediaCodecInfo> &info1, const sp<MediaCodecInfo> &info2) {
// null is lowest
return info1 == nullptr
|| (info2 != nullptr && info1->getRank() < info2->getRank());
});
// ...删除重复的
}
1.5.5 GetBuilders
构建OMX build和Codec2InfoBuilder。
每个builder会支持一系列的编码类型。Codec2是Android Q引入的,而之前使用的是OMX。
Codec2相比于OMX,状态更加简化。
std::vector<MediaCodecListBuilderBase *> GetBuilders() {
std::vector<MediaCodecListBuilderBase *> builders;
// 主要是Codec2InfoBuilder和OMX两种。
sp<PersistentSurface> surfaceTest = CCodec::CreateInputSurface();
if (surfaceTest == nullptr) {
ALOGD("Allowing all OMX codecs");
builders.push_back(&sOmxInfoBuilder);
} else {
ALOGD("Allowing only non-surface-encoder OMX codecs");
builders.push_back(&sOmxNoSurfaceEncoderInfoBuilder);
}
builders.push_back(GetCodec2InfoBuilder());
return builders;
}
1.6 MediaCodec::init
除了初始化ResourceManager,通过mGetCodecInfo查找CodecInfo,然后根据CodecInfo和name,使用mGetCodecBase创建实际的Codec,比如CCodec活着ACodec。
后续发送kWhatInit给ALooper来进行初始化。
status_t MediaCodec::init(const AString &name) {
// 初始化ResourceManager,service的key为media.resource_manager。
status_t err = mResourceManagerProxy->init();
if (err != OK) {
// ...如果初始化异常,直接返回
}
mInitName = name;
mCodecInfo.clear();
bool secureCodec = false;
const char *owner = "";
if (!name.startsWith("android.filter.")) {
// mGetCodecInfo是在MediaCodec构造函数时赋值的,是一个根据name查找CodecInfo的方法。
err = mGetCodecInfo(name, &mCodecInfo);
// ...查找失败直接返回
secureCodec = name.endsWith(".secure");
Vector<AString> mediaTypes;
mCodecInfo->getSupportedMediaTypes(&mediaTypes);
for (size_t i = 0; i < mediaTypes.size(); ++i) {
if (mediaTypes[i].startsWith("video/")) {
mDomain = DOMAIN_VIDEO;
break;
} else if (mediaTypes[i].startsWith("audio/")) {
mDomain = DOMAIN_AUDIO;
break;
} else if (mediaTypes[i].startsWith("image/")) {
mDomain = DOMAIN_IMAGE;
break;
}
}
owner = mCodecInfo->getOwnerName();
}
// 根据名称以及owner获取实际的Codec,详见1.6.1
mCodec = mGetCodecBase(name, owner);
// ...没有获取到Codec直接返回。
if (mDomain == DOMAIN_VIDEO) {
// 启动mCodecLooper消息接收处理。
if (mCodecLooper == NULL) {
status_t err = OK;
mCodecLooper = new ALooper;
mCodecLooper->setName("CodecLooper");
err = mCodecLooper->start(false, false, ANDROID_PRIORITY_AUDIO);
// ...启动失败直接返回
}
mCodecLooper->registerHandler(mCodec);
} else {
mLooper->registerHandler(mCodec);
}
mLooper->registerHandler(this);
// 配置Codec的Callback
mCodec->setCallback(
std::unique_ptr<CodecBase::CodecCallback>(
new CodecCallback(new AMessage(kWhatCodecNotify, this))));
mBufferChannel = mCodec->getBufferChannel();
mBufferChannel->setCallback(
std::unique_ptr<CodecBase::BufferCallback>(
new BufferCallback(new AMessage(kWhatCodecNotify, this))));
// 初始化kWhatInit消息,发送消息由ALooper处理。
sp<AMessage> msg = new AMessage(kWhatInit, this);
if (mCodecInfo) {
msg->setObject("codecInfo", mCodecInfo);
}
msg->setString("name", name);
if (mMetricsHandle != 0) {
mediametrics_setCString(mMetricsHandle, kCodecCodec, name.c_str());
mediametrics_setCString(mMetricsHandle, kCodecMode, toCodecMode(mDomain));
}
if (mDomain == DOMAIN_VIDEO) {
mBatteryChecker = new BatteryChecker(new AMessage(kWhatCheckBatteryStats, this));
}
std::vector<MediaResourceParcel> resources;
resources.push_back(MediaResource::CodecResource(secureCodec, toMediaResourceSubType(mDomain)));
// If the ComponentName is not set yet, use the name passed by the user.
if (mComponentName.empty()) {
mResourceManagerProxy->setCodecName(name.c_str());
}
for (int i = 0; i <= kMaxRetry; ++i) {
if (i > 0) {
if (!mResourceManagerProxy->reclaimResource(resources)) {
break;
}
}
sp<AMessage> response;
// 提交kwhatInit的msg,处理方法详见1.7
err = PostAndAwaitResponse(msg, &response);
if (!isResourceError(err)) {
break;
}
}
if (OK == err) {
mResourceManagerProxy->notifyClientCreated();
}
return err;
}
1.6.1 MediaCodec::GetCodecBase
可以看出来,这里只会返回两种Codec,一种是ACodec,一种是CCodec。
CCodec配合Codec2使用,而ACodec配置OMX使用,我们来看新一些的架构CCOdec。
sp<CodecBase> MediaCodec::GetCodecBase(const AString &name, const char *owner) {
if (owner) {
if (strcmp(owner, "default") == 0) {
return new ACodec;
} else if (strncmp(owner, "codec2", 6) == 0) {
return CreateCCodec();
}
}
if (name.startsWithIgnoreCase("c2.")) {
return CreateCCodec();
} else if (name.startsWithIgnoreCase("omx.")) {
return new ACodec;
} else {
return NULL;
}
}
1.7 MediaCodec::onMessageReceived
这里是一个中间层,将init消息转发到CodecBase去。
我们这里来跟踪CCodec,调用CCodec到initiateAllocateComponent方法。
void MediaCodec::onMessageReceived(const sp<AMessage> &msg) {
// ...
case kWhatInit:
{
// ...检测当前状态是否是UNINITIALIZED
mReplyID = replyID;
// 修改状态至INITIALIZING
setState(INITIALIZING);
sp<RefBase> codecInfo;
(void)msg->findObject("codecInfo", &codecInfo);
AString name;
CHECK(msg->findString("name", &name));
sp<AMessage> format = new AMessage;
if (codecInfo) {
format->setObject("codecInfo", codecInfo);
}
format->setString("componentName", name);
// 将消息转发到Codec,这里我们来看CCodec,详见1.8
mCodec->initiateAllocateComponent(format);
break;
}
// ...
}
1.8 CCodec::initiateAllocateComponent
发出kWhatAllocate消息。
void CCodec::initiateAllocateComponent(const sp<AMessage> &msg) {
// 更新CCodec状态至ALLOCATING
auto setAllocating = [this] {
Mutexed<State>::Locked state(mState);
if (state->get() != RELEASED) {
return INVALID_OPERATION;
}
state->set(ALLOCATING);
return OK;
};
if (tryAndReportOnError(setAllocating) != OK) {
return;
}
// 发出kWhatAllocate消息。
sp<RefBase> codecInfo;
CHECK(msg->findObject("codecInfo", &codecInfo));
sp<AMessage> allocMsg(new AMessage(kWhatAllocate, this));
allocMsg->setObject("codecInfo", codecInfo);
详见1.9
allocMsg->post();
}
1.9 CCodec::onMessageReceived
调用allocate处理消息。
void CCodec::onMessageReceived(const sp<AMessage> &msg) {
// ...
case kWhatAllocate: {
// C2ComponentStore::createComponent() should return within 100ms.
setDeadline(now, 1500ms, "allocate");
sp<RefBase> obj;
CHECK(msg->findObject("codecInfo", &obj));
// 详见1.10
allocate((MediaCodecInfo *)obj.get());
break;
}
// ...
}
1.10 CCodec::allocate
Codec2框架有一个Codec2Client,而Codec2Client会访问hal层,这里Codec2Client组件会有多个service,例如V4L2ComponentStore里面会对应V4L2驱动,提供硬编解码组件,而GoldfishComponentStore提供软编解码组件。
同时会更新CCodec的状态到ALLOCATING。
创建组件后,会通过config->initialize初始化编解码配置,例如编解码的宽高。
void CCodec::allocate(const sp<MediaCodecInfo> &codecInfo) {
// ... 参数检测
mClientListener.reset(new ClientListener(this));
AString componentName = codecInfo->getCodecName();
std::shared_ptr<Codec2Client> client;
// Codec2Client会搜索ServiceManager所有句柄查找IComponentStore的服务
// 这里ComponentStore实现有多个,例如V4L2ComponentStore里的组件一般是实现硬编解码的,而GoldfishComponentStore里面的组件是软件编解码的。
// 我们主要看软件编解码构建组件过程
client = Codec2Client::CreateFromService("default");
// ...
std::shared_ptr<Codec2Client::Component> comp;
// 根据组件名创建组件,这里会调用到hal层来创建Component,详见1.11
c2_status_t status = Codec2Client::CreateComponentByName(
componentName.c_str(),
mClientListener,
&comp,
&client);
if (status != C2_OK) {
// 创建失败直接返回
}
ALOGI("Created component [%s]", componentName.c_str());
// 构造好的C2Component会存在mChannel中
mChannel->setComponent(comp);
auto setAllocated = [this, comp, client] {
// ...更新state,并且记录创建的组件到state
return OK;
};
if (tryAndReportOnError(setAllocated) != OK) {
return;
}
Mutexed<std::unique_ptr<Config>>::Locked configLocked(mConfig);
const std::unique_ptr<Config> &config = *configLocked;
// 初始化CodecConfig参数,和编码相关的参数会存储在CodecConfig
status_t err = config->initialize(mClient->getParamReflector(), comp);
// ...
config->queryConfiguration(comp);
mCallback->onComponentAllocated(componentName.c_str());
}
1.11 Codec2Client::CreateComponentByName
遍历所有IComponentStore service,通过组件名称创建组件。
c2_status_t Codec2Client::CreateComponentByName(
const char* componentName,
const std::shared_ptr<Listener>& listener,
std::shared_ptr<Component>* component,
std::shared_ptr<Codec2Client>* owner,
size_t numberOfAttempts) {
std::string key{"create:"};
key.append(componentName);
// 遍历所有IComponentStore
c2_status_t status = ForAllServices(
key,
numberOfAttempts,
[owner, component, componentName, &listener](
const std::shared_ptr<Codec2Client> &client)
-> c2_status_t {
// binder调用对端创建组件,详见1.12
c2_status_t status = client->createComponent(componentName,
listener,
component);
// ...
return status;
});
// ...
return status;
}
1.12 GoldfishComponentStore::createComponent
找到对应的ComponentModule,调用ComponentModule::createComponent
c2_status_t GoldfishComponentStore::createComponent(
C2String name, std::shared_ptr<C2Component> *const component) {
// This method SHALL return within 100ms.
component->reset();
std::shared_ptr<ComponentModule> module;
c2_status_t res = findComponent(name, &module);
if (res == C2_OK) {
// 调用ComponentModule::createComponent,详见1.13
res = module->createComponent(0, component);
}
return res;
}
1.13 GoldfishComponentStore::ComponentModule::createComponent
调用mComponentFactory的createComponent,这里的mComponentFactory是在ComponentModule的init的时候构建的,ComponentModule的init会传入一个lib库,然后动态加载lib库,搜索CreateCodec2Factory方法来进行Factory构造,所以如果想要实现一个编解码算法,只需要按照一个固定的写法,然后编译成一个lib库,在这里添加支持即可。
c2_status_t GoldfishComponentStore::ComponentModule::createComponent(
c2_node_id_t id, std::shared_ptr<C2Component> *component,
std::function<void(::C2Component *)> deleter) {
component->reset();
if (mInit != C2_OK) {
return mInit;
}
std::shared_ptr<ComponentModule> module = shared_from_this();
// 调用mComponentFactory的createComponent,我们这里看CCodec2的H264软编码,详见1.14
c2_status_t res = mComponentFactory->createComponent(
id, component, [module, deleter](C2Component *p) mutable {
deleter(p); // delete component first
module.reset(); // remove module ref (not technically needed)
});
ALOGI("created component");
return res;
}
1.14 C2SoftAvcEncFactory::createComponent
C2SoftAvcEncFactory创建C2SoftAvcEnc,我们这里看软编码。
virtual c2_status_t createComponent(
c2_node_id_t id,
std::shared_ptr<C2Component>* const component,
std::function<void(C2Component*)> deleter) override {
// 构建C2SoftAvcEnc,C2SoftAvcEnc为实际编解码的操作。
*component = std::shared_ptr<C2Component>(
new C2SoftAvcEnc(COMPONENT_NAME,
id,
std::make_shared<C2SoftAvcEnc::IntfImpl>(mHelper)),
deleter);
return C2_OK;
}
小结
我们本节介绍了MediaCodec接口创建流程,通过MediaCodec提供给应用的接口开始,jni调用构建JMediaCodec,JMediaCodec创建管理C++层的MediaCodec,而MediaCodec都创建管理CCodec2/OMX,旧版本的Android使用OMX框架,使用ACodec,而新版本Android一般是使用CCodec2,使用CCodec。CCodec会访问hal层创建编解码组件,一般会有hal层服务,每个服务也会支持一个或者多个组件,每个组件都实现了不同的编解码,以及软硬编码,我们下一节会介绍H264软编码的流程。