Android引入Treble架构后,OpenMAX框架以HIDL Service的形式为System分区提供服务,本文将探讨该服务是如何启动,服务提供了什么内容,以及服务是如何被应用层所使用的。
1 概述
在Android的Treble架构中,为了确保系统的稳定性和模块化,厂商的实现通常被放置在Vendor分区。因此,之前提到的由厂商实现的库libstagefrighthw.so和组件实现libvdec_xxx.so需要被编译到Vendor分区。由于System分区和Vendor分区之间的资源是隔离的,不能直接互通,所以当系统或应用需要访问Vendor分区中的资源时,就需要使用HIDL(Hardware Interface Definition Language)来定义接口,并通过hwbinder机制来实现跨分区的通信。
为了让System分区能够调用到Vendor分区中厂商实现的OMX组件,Android定义了相关的HIDL接口,并实现了HIDL Service。相关文件路径如下:
HIDL接口定义位于:hardware/interfaces/media/omx/1.0,编译生成的C++文件位于out/soong/.intermediates/hardware/interfaces/media/1.0
HIDL接口实现位于:frameworks/av/media/libstagefright/omx/1.0
服务启动文件位于:/frameworks/av/services/mediacodec/main_codecservice.cpp
实现HIDL Service除了需要代码实现外,还需要以下支持文件:
兼容性矩阵:hardware/interfaces/compatibility_matrices/compatibility_matrix.3.xml
清单文件:device/google/cuttlefish/shared/config/android.hardware.media.omx@1.0.xml
启动脚本:frameworks/av/services/mediacodec/android.hardware.media.omx@1.0-service.rc
2 服务启动
service vendor.media.omx /vendor/bin/hw/android.hardware.media.omx@1.0-service
class main
user mediacodec
group camera drmrpc mediadrm
ioprio rt 4
task_profiles ProcessCapacityHigh
开机启动时,程序将加载以上脚本,执行二进制文件/vendor/bin/hw/android.hardware.media.omx@1.0-service。bin文件由main_codecservice.cpp编译而成:
int main(int argc __unused, char** argv)
{
strcpy(argv[0], "media.codec");
LOG(INFO) << "mediacodecservice starting";
signal(SIGPIPE, SIG_IGN);
android::ProcessState::initWithDriver("/dev/vndbinder");
android::ProcessState::self()->startThreadPool();
// 配置HIDL Service线程池
::android::hardware::configureRpcThreadpool(64, false);
// Default codec services
using namespace ::android::hardware::media::omx::V1_0;
sp<IOmx> omx = new implementation::Omx();
if (omx == nullptr) {
LOG(ERROR) << "Cannot create IOmx HAL service.";
} else if (omx->registerAsService() != OK) {
LOG(ERROR) << "Cannot register IOmx HAL service.";
} else {
LOG(INFO) << "IOmx HAL service created.";
}
sp<IOmxStore> omxStore = new implementation::OmxStore(
property_get_int64("vendor.media.omx", 1) ? omx : nullptr);
if (omxStore == nullptr) {
LOG(ERROR) << "Cannot create IOmxStore HAL service.";
} else if (omxStore->registerAsService() != OK) {
LOG(ERROR) << "Cannot register IOmxStore HAL service.";
}
::android::hardware::joinRpcThreadpool();
}
main函数创建了一个Omx对象和一个OmxStore对象,之后将这两个对象注册到hwbinder驱动当中,也就是说进程提供了两个服务。在Android手机或电视上执行ps -A
可以看到该进程正在运行,服务名称为media.codec:
console:/ $ ps -A | grep media.codec
mediacodec 683 1 56196 11684 0 0 S media.codec
configureRpcThreadpool设定服务线程数量最大为64,理论上来说,我们最多能够同时创建32路组件。
2、IOmx
IOmx.hal
interface IOmx {
listNodes(
) generates (
Status status,
vec<ComponentInfo> nodeList
);
allocateNode(
string name,
IOmxObserver observer
) generates (
Status status,
IOmxNode omxNode
);
createInputSurface(
) generates (
Status status,
IGraphicBufferProducer producer,
IGraphicBufferSource source
);
}
IOmx.hal声明了三个接口,我们暂且先了解前面两个:
listNodes
:从OMXStore中获取组件信息,该方法不是给ACodec使用,而是给OmxStore使用;allocateNode
:根据组件名称创建一个组件;
构造函数
struct Omx : public IOmx, public hidl_death_recipient {
Omx();
virtual ~Omx();
// Methods from IOmx
Return<void> listNodes(listNodes_cb _hidl_cb) override;
Return<void> allocateNode(
const hidl_string& name,
const sp<IOmxObserver>& observer,
allocateNode_cb _hidl_cb) override;
Return<void> createInputSurface(createInputSurface_cb _hidl_cb) override;
// Method from hidl_death_recipient
void serviceDied(uint64_t cookie, const wp<IBase>& who) override;
// Method for OMXNodeInstance
status_t freeNode(sp<OMXNodeInstance> const& instance);
protected:
OMXStore* mStore;
Mutex mLock;
KeyedVector<wp<IBase>, sp<OMXNodeInstance> > mLiveNodes;
KeyedVector<OMXNodeInstance*, wp<IBase> > mNode2Observer;
MediaCodecsXmlParser mParser;
};
Omx继承于IOmx,除了实现了IOmx的三个接口,Omx还有新增一个接口freeNode。细想一下,IOmx服务只提供了创建组件的接口,但是没有提供销毁组件的接口,是不是有些奇怪,组件应该如何销毁呢?Omx提供的freeNode方法该如何被调用呢?先把疑问留在这里,后续的文章将解决这个问题。
Omx::Omx() :
mStore(new OMXStore()),
mParser() {
(void)mParser.parseXmlFilesInSearchDirs();
(void)mParser.parseXmlPath(mParser.defaultProfilingResultsXmlPath);
}
Omx的构造函数实例化了两个成员:
- OMXStore:该对象在前一篇文章已经做过了解,它同于加载平台提供的软硬件编解码组件信息,管理组件的创建与销毁,是不是可以猜到allocateNode通过调用makeComponentInstance创建组件。
- MediaCodecsXmlParser:xml解析工具类,对于Omx服务来说该类没有太大作用,我们放到OmxStore一节再做了解。
listNodes
Return<void> Omx::listNodes(listNodes_cb _hidl_cb) {
std::list<::android::IOMX::ComponentInfo> list;
char componentName[256];
for (OMX_U32 index = 0;
mStore->enumerateComponents(
componentName, sizeof(componentName), index) == OMX_ErrorNone;
++index) {
list.push_back(::android::IOMX::ComponentInfo());
::android::IOMX::ComponentInfo& info = list.back();
info.mName = componentName;
::android::Vector<::android::String8> roles;
OMX_ERRORTYPE err =
mStore->getRolesOfComponent(componentName, &roles);
if (err == OMX_ErrorNone) {
for (OMX_U32 i = 0; i < roles.size(); ++i) {
info.mRoles.push_back(roles[i]);
}
}
}
hidl_vec<ComponentInfo> tList;
tList.resize(list.size());
size_t i = 0;
for (auto const& info : list) {
convertTo(&(tList[i++]), info);
}
_hidl_cb(toStatus(OK), tList);
return Void();
}
listNodes方法循环调用OMXStore的enumerateComponents和getRolesOfComponent方法,获取平台提供的所有组件信息,将这些信息以ComponentInfo的形式回传给调用者。要注意的是,这个方法中用到了两种ComponentInfo,一种是::android::IOMX::ComponentInfo,它是一个普通的结构体;另一种是ComponentInfo,它是可以通过HIDL调用传递的类型,在IOmx.hal中定义。
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;
{
// 1.
Mutex::Autolock autoLock(mLock);
if (mLiveNodes.size() == kMaxNodeInstances) {
_hidl_cb(toStatus(NO_MEMORY), nullptr);
return Void();
}
// 2. 创建OMXNodeInstance实例
instance = new OMXNodeInstance(
this, new LWOmxObserver(observer), name.c_str());
// 2. 创建OMX组件
OMX_COMPONENTTYPE *handle;
OMX_ERRORTYPE err = mStore->makeComponentInstance(
name.c_str(), &OMXNodeInstance::kCallbacks,
instance.get(), &handle);
// 3. 将OMX组件绑定到OMXNodeInstance实例
instance->setHandle(handle);
// 4. 寻找组件的quirks并且设定给OMXNodeInstance
// 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;
}
}
instance->setQuirks(quirks);
}
// 5. 将创建的OMXNodeInstance实例存储到键值列表中
mLiveNodes.add(observer.get(), instance);
mNode2Observer.add(instance.get(), observer.get());
}
observer->linkToDeath(this, 0);
// 6. 将创建的OMXNodeInstance实例返回给应用层
_hidl_cb(toStatus(OK), new TWOmxNode(instance));
return Void();
}
allocateNode方法是本篇重点了解内容之一,客户端将利用该方法来创建组件:
- 正式创建组件之前要先检查当前已经运行了多少个组件实例,IOmx定义的kMaxNodeInstances值为16,也就是说最大支持16个组件同时运行;
- 创建OMXNodeInstance实例,将客户端创建的IOmxObserver(callback)对象传递给它;
- 使用OMXStore的makeComponentInstance方法创建OMX组件,传入OMXNodeInstance句柄和OMXNodeInstance::kCallbacks;
- 将创建的OMX组件句柄绑定到OMXNodeInstance实例;
- 在解析的xml文件中搜寻组件相关的quirks并且设定给OMXNodeInstance实例;
- 将创建的OMXNodeInstance实例存储到键值列表中;
- 将创建的OMXNodeInstance实例返回给应用层;
OMXNodeInstance就是我们在Spec阅读中提到的IL Client,它在整个OpenMAX框架中起着承上启下的作用,向下它封装了OMX Core API的调用,屏蔽了Core API的调用细节;向上重新封装了更为简单的API给应用层使用。
整体架构参考上图,OMXNodeInstance和OMX Component互相持有对方的句柄,OMXNodeInstance调用OMX Core API,传入组件句柄即可操作指定组件;OMX Component调用OMXNodeInstance实现的callback,传入OMXNodeInstance句柄即可向指定客户端实例发送消息。
关注公众号《青山渺渺》阅读全文