• 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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    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);
    }
    
    • 1
    • 2
    • 3

    内部调用了 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    接下来对 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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 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");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在进入 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    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 &);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    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

    从上面我们可以知道 OMXStore 会打开 libstagefrighthw.so,并且调用内部方法 createOMXPlugin 创建一个 OMXPluginBase 对象,接着会调用该对象的方法读取 OMX 组件信息。之前我们只看了 OMXPluginBase 的接口声明,这一小节,我们一起来了解下 OMXPluginBase 的高通实现,代码位于:
    hardware/qcom/media/msm8998/libstagefrighthw/QComOMXPlugin.cpp

    QComOMXPlugin::QComOMXPlugin()
        : mLibHandle(dlopen("libOmxCore.so", RTLD_NOW)),
          mInit(NULL),
          mDeinit(NULL),
          mComponentNameEnum(NULL),
          mGetHandle(NULL),
          mFreeHandle(NULL),
          mGetRolesOfComponentHandle(NULL) {
        if (mLibHandle != NULL) {
            mInit = (InitFunc)dlsym(mLibHandle, "OMX_Init");
            mDeinit = (DeinitFunc)dlsym(mLibHandle, "OMX_Deinit");
    
            mComponentNameEnum =
                (ComponentNameEnumFunc)dlsym(mLibHandle, "OMX_ComponentNameEnum");
    
            mGetHandle = (GetHandleFunc)dlsym(mLibHandle, "OMX_GetHandle");
            mFreeHandle = (FreeHandleFunc)dlsym(mLibHandle, "OMX_FreeHandle");
    
            mGetRolesOfComponentHandle =
                (GetRolesOfComponentFunc)dlsym(
                        mLibHandle, "OMX_GetRolesOfComponent");
    
            if (!mInit || !mDeinit || !mComponentNameEnum || !mGetHandle ||
                !mFreeHandle || !mGetRolesOfComponentHandle) {
                dlclose(mLibHandle);
                mLibHandle = NULL;
            } else
                (*mInit)();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    QComOMXPlugin 的构造函数里打开了 libOmxCore.so,并且动态加载了内部的方法,并且在最后调用了 OMX_Init 方法。

    看到这里,如果我们要实现自己的底层硬件编解码库,首先要写一个 libstagefrighthw.so,内部要包含 createOMXPlugin 方法,该方法要返回 OMXPluginBase 对象,该对象的构造函数里同样也要打开一个类似 libOmxCore.so 的库,库里面封装有 OMX_GetHandle,OMX_FreeHandle 等方法。


    5、IOmxStore

    我们这里不会去详细展开 IOmxStore 的内容,IOmxStore 我看到的只在一个地方有使用:在 MediaCodecList 加载时,加载 OMX 组件时会用到 IOmxStore。IOmxStore 里的信息怎么来的?

    IOmx 的构造函数中有三句和 xml 相关的内容,codec 信息就是在这里被加载的:

        mParser() {
        (void)mParser.parseXmlFilesInSearchDirs();
        (void)mParser.parseXmlPath(mParser.defaultProfilingResultsXmlPath);
    
    • 1
    • 2
    • 3

    我们这里主要看 parseXmlFilesInSearchDirs:

        status_t parseXmlFilesInSearchDirs(
                const std::vector<std::string> &xmlFiles = getDefaultXmlNames(),
                const std::vector<std::string> &searchDirs = getDefaultSearchDirs());
    
        static std::vector<std::string> getDefaultSearchDirs() {
            return { "/product/etc",
                     "/odm/etc",
                     "/vendor/etc",
                     "/system/etc" };
        }
    
    std::vector<std::string> MediaCodecsXmlParser::getDefaultXmlNames() {
        static constexpr char const* prefixes[] = {
                "media_codecs",
                "media_codecs_performance"
            };
        static std::vector<std::string> variants = {
                android::base::GetProperty("ro.media.xml_variant.codecs", ""),
                android::base::GetProperty("ro.media.xml_variant.codecs_performance", "")
            };
        static std::vector<std::string> names = {
                prefixes[0] + variants[0] + ".xml",
                prefixes[1] + variants[1] + ".xml",
    
                // shaping information is not currently variant specific.
                "media_codecs_shaping.xml"
            };
        return names;
    }	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    可以看到默认情况下,会到 “/product/etc”, “/odm/etc”, “/vendor/etc”, “/system/etc” 这四个目录下查找 media_codecs.xml 和 media_codecs_performance.xml 以及 media_codecs_shaping.xml。

    如果我们的文件名称不是使用的默认的,那么需要用属性 ro.media.xml_variant.codecs 和 ro.media.xml_variant.codecs_performance 来自定义文件名。

    具体如何加载以及解析 xml 文件的,可以参考:MediaCodecsXmlParser.cpp

    我们要注意的是,IOmxStore 这里存储的所有的 codec 信息均是来自于 xml 文件,并不会从 OMX 去读取。如果 xml 文件中没有某个组件相关的配置,则无法从 MediaCodecList 中获取到该组件信息,因此也就无法创建该组件;如果 OMX某个组件的信息没有加载到 OMXStore 中,那么就算可以从 MediaCodecList 中获取到组件信息,那么也是无法创建该组件的。所以 xml 文件需要和 libOmxCore 中的内容保持一致。

  • 相关阅读:
    Python学习基础笔记七十——模块和库1
    牛批 阿里P8熬夜冠军手码的Docker容器+k8s技术PDF,你还等啥呢
    mysql数据库的管理
    linux之权限
    Linux内核代码分析集合(持续更新中)
    spring boot3登录开发-微信小程序用户登录设计与实现
    ognl表达式和值栈
    layui引入百度地图
    肠道重要菌属——嗜胆菌属 (Bilophila)喜欢脂肪、耐胆汁的促炎菌
    03_瑞萨GUI(LVGL)移植实战教程之驱动触摸屏(I2C)
  • 原文地址:https://blog.csdn.net/qq_41828351/article/details/134171726