Android 13 - Media框架(14)- OpenMax(二)

全新系列文章已更新:


这一节我们将来解析 media.codec 这个 HIDL service 究竟提供了什么服务,服务是如何启动的。

1、main 函数

我们先来看 frameworks/av/services/mediacodec/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();

    ::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 函数中创建了 IOmx 和 IOmxStore 两个对象,说明这一个进程有两个服务。

我会对这里的代码有一点疑问,media.codec 这个进程作为 HIDL service 应该使用 /dev/hwbinder,main函数一开始为什么打开的是 /dev/vndbinder 呢?

观察可以看到 IOmx 会调用 registerAsService 方法,这个方法可以在 HIDL 编译生成文件中找到:

::android::status_t IOmx::registerAsService(const std::string &serviceName) {
    return ::android::hardware::details::registerAsServiceInternal(this, serviceName);
}

内部调用了 registerAsServiceInternal 方法将服务对象注册到了 /dev/hwbinder,至于为什么要打开 /dev/vndbinder 我猜测可能是为了给 vendor 进程调用吧…


2、IOmx

Omx 的构造函数创建了一个 OMXStore 用于加载、创建、管理所有的 OMX 组件,以及一个 MediaCodecsXmlParser 用于加载 Media 相关的 xml 配置文件。

Omx::Omx() :
    mStore(new OMXStore()),
    mParser() {
    (void)mParser.parseXmlFilesInSearchDirs();
    (void)mParser.parseXmlPath(mParser.defaultProfilingResultsXmlPath);
}

接下来对 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);
  • listNodes:给 IOmxStore 使用,列出所有可用的组件;
  • allocateNode:根据组件名创建 OMX 组件;
  • createInputSurface:暂未使用到,后期碰到了再来记录;
  • freeNode:释放创建的 OMX 组件。

OMXStore 是 IOmx 服务的大管家,创建销毁组件最终都由 OMXStore 来完成。这里不会去了解具体的如何创建销毁的过程,重在先了解设计结构。


3、OMXStore

OMXStore 的构造函数加载了两个 lib(libstagefrighthw.so, libstagefright_softomx_plugin.so),第一个lib 是硬件平台需要实现的,也就是我们所说的硬件解码实现,第二个 lib 是Android平台提供的默认的软件编解码实现,现在它已经被移除,并且用 CCodec 来替代。

我们这一系列笔记重点要研究的是硬件编解码的框架,所以只研究 libstagefrighthw.so 的部分。

这里之所以用 dlopen 和 dlsym 加载库是因为,不是所有的平台都会实现硬件编解码库,如果用动态链接这里就会出错了。

OMXStore::OMXStore() {
	......
    addVendorPlugin();
    addPlatformPlugin();
}

void OMXStore::addVendorPlugin() {
    addPlugin("libstagefrighthw.so");
}

void OMXStore::addPlatformPlugin() {
    addPlugin("libstagefright_softomx_plugin.so");
}

在进入 OMXStore 了解之前,我们先对这部分的实现结构做一个简单了解,这样学习起来会更轻松。Android 源码中已经有了高通的 demo 实现可供我们参考,位于:

hardware/qcom/media/msm8998/libstagefrighthw
hardware/qcom/media/msm8998/mm-core/src/common

这里主要涉及了两个库,libstagefrighthw.so 已经在 OMXStore 中看到过了,还有另一个重要的库 libOmxCore.so 它实现了获取调用 OMX 服务的标准接口。

请添加图片描述

这里会由下自上来描述每一层的作用:

  1. 编解码库实现:厂商会提供多个硬件编解码实现,这些库以 OMX 开头,接下来的问题是如何使用这些库?;
  2. libOmxCore.so:所有的硬件编解码库实现都会以列表的形式存储在 libOmxCore.so 中,libOmxCore.so 库实现了 OMX 框架提供的标准接口,上层可以通过这些接口获取底层硬件编解码库的实现;当然,libOmxCore这个库的名字可以由 vendor 自己定义;
  3. libstagefrighthw.so:libstagefrighthw 对 libOmxCore 提供的接口调用进行了封装,并且提供标准接口给上层使用,这一层同样由 vendor 来实现,如果上面的 libOmxCore 名称发生变化,那么 libstagefrighthw 加载的库名也要变化;
  4. OMXStore:如果可以成功加载 libstagefrighthw.so,那么就调用它的标准接口获取底层 OMX 提供的服务细节。

3.1、addPlugin

接下来一起了解 libstagefrighthw 的加载过程:

void OMXStore::addPlugin(const char *libname) {
	// 1. 获取 vendor.media.omx ,如果是0则退出
    if (::android::base::GetIntProperty("vendor.media.omx", int64_t(1)) == 0) {
        return;
    }
	// 2. 加载 libstagefrighthw.so
    void *libHandle = android_load_sphal_library(libname, RTLD_NOW);

    if (libHandle == NULL) {
        return;
    }
	// 3. 获取lib 中的方法
    typedef OMXPluginBase *(*CreateOMXPluginFunc)();
    CreateOMXPluginFunc createOMXPlugin =
        (CreateOMXPluginFunc)dlsym(
                libHandle, "createOMXPlugin");
    if (!createOMXPlugin)
        createOMXPlugin = (CreateOMXPluginFunc)dlsym(
                libHandle, "_ZN7android15createOMXPluginEv");
	// 4. 调用方法创建实例
    OMXPluginBase *plugin = nullptr;
    if (createOMXPlugin) {
        plugin = (*createOMXPlugin)();
    }
	// 5. 存储创建的实例
    if (plugin) {
        mPlugins.push_back({ plugin, libHandle });
        // 6. 从实例中读取提供的服务内容
        addPlugin(plugin);
    } else {
        android_unload_sphal_library(libHandle);
    }
}
  1. 首先获取 vendor.media.omx 属性,默认是 1,如果设置了0,则硬件编解码将不会再走 OMX 一路,这应该是在为切换到 CCodec 做准备;
  2. 加载libstagefrighthw.so,dlsym 获取 createOMXPlugin 接口;
  3. 调用 createOMXPlugin 创建 OMXPluginBase 实例;
  4. 存储实例,从实例中获取提供的服务内容。

从上面的流程中我们可以知道,libstagefrighthw 需要实现 createOMXPlugin 接口,该接口会创建一个 OMXPluginBase 对象。

createOMXPlugin 接口声明位于
frameworks/native/headers/media_plugin/media/hardware/HardwareAPI.h

OMXPluginBase 声明位于 frameworks/native/headers/media_plugin/media/hardware/OMXPluginBase.h


struct OMXPluginBase {
    OMXPluginBase() {}
    virtual ~OMXPluginBase() {}
	// 创建组件实例
    virtual OMX_ERRORTYPE makeComponentInstance(
            const char *name,
            const OMX_CALLBACKTYPE *callbacks,
            OMX_PTR appData,
            OMX_COMPONENTTYPE **component) = 0;
	// 销毁组件实例
    virtual OMX_ERRORTYPE destroyComponentInstance(
            OMX_COMPONENTTYPE *component) = 0;
	// 列出组件信息
    virtual OMX_ERRORTYPE enumerateComponents(
            OMX_STRING name,
            size_t size,
            OMX_U32 index) = 0;
	// 获取组件 role
    virtual OMX_ERRORTYPE getRolesOfComponent(
            const char *name,
            Vector<String8> *roles) = 0;

private:
    OMXPluginBase(const OMXPluginBase &);
    OMXPluginBase &operator=(const OMXPluginBase &);
};

3.2、addPlugin Inner

这里又有一个 addPlugin,但是它的功能和上面3.1节中的是完全不同的,这里的 addPlugin 参数为 OMXPluginBase,用于加载 OMX 组件信息的。

void OMXStore::addPlugin(OMXPluginBase *plugin) {
    Mutex::Autolock autoLock(mLock);
	// 获取当前设备类型,获取设备api level
    bool typeTV = isTV();
    int firstApiLevel = getFirstApiLevel();

    OMX_U32 index = 0;

    char name[128];
    OMX_ERRORTYPE err;
    // 循环读取 OMXPluginBase 中的组件信息,传出参数为字符串
    while ((err = plugin->enumerateComponents(
                    name, sizeof(name), index++)) == OMX_ErrorNone) {
        String8 name8(name);

        Vector<String8> roles;
        // 根据字符串再从 OMXPluginBase 解析出 role
        OMX_ERRORTYPE err = plugin->getRolesOfComponent(name, &roles);
        if (err == OMX_ErrorNone) {
            bool skip = false;
            for (String8 role : roles) {
            	// 根据当前的 API level 来判断是否要加载当前组件
                if (role.find("video_decoder") != -1 || role.find("video_encoder") != -1) {
                    if (firstApiLevel >= __ANDROID_API_T__) {
                        skip = true;
                        break;
                    } else if (!typeTV && firstApiLevel >= __ANDROID_API_S__) {
                        skip = true;
                        break;
                    }
                }
                if (role.find("audio_decoder") != -1 || role.find("audio_encoder") != -1) {
                    if (firstApiLevel >= __ANDROID_API_T__) {
                        skip = true;
                        break;
                    }
                }
            }
            if (skip) {
                continue;
            }
        }
		// 判断是否有重复
        if (mPluginByComponentName.indexOfKey(name8) >= 0) {
            ALOGE("A component of name '%s' already exists, ignoring this one.",
                 name8.string());

            continue;
        }

        mPluginByComponentName.add(name8, plugin);
    }
}
  1. 获取设备类型以及api level;
  2. 调用 OMXPluginBase 的方法读取组件信息(组件名称);
  3. 获取组件名称对应的 role,如果是 video 组件,并且满足 api level 高于 Android T 或者 不是电视并且 api level 高于 Android S 的条件,则不会将该组件加载到 OMXStore 当中;如果是 audio 组件并且 api level 高于 Android T,则同样不会将该组件加载到 OMXStore 当中;
  4. 否则将组件名称存储到容器当中。

从这里我们可以了解到,从 Android 13 开始要弃用 OMX 框架了,如果 vendor 还没有实现 CCodec,则需要修改这边的内容,我们切到 Android S 上来看 OMXStore 的代码,是没有这部分的判断的。

虽然说从 Android T 开始要弃用 OMX 框架了,但是我们仍然用该版本的代码来学习它。


4、QComOMXPlugin

关注公众号《青山渺渺》阅读完整内容; 如有问题可在公众号后台私信,也可进入音视频开发技术分享群一起讨论!

在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青山渺渺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值