文章目录

  • 一、OpenMax的设计理念与特性点
    • 设计理念---媒体框架的抽象/可移植性/异步处理/组件组合
    • 设计特性---组件化API/方便新增解码器/方便扩展/支持动态链接/可配置
  • 二、OpenMax的设计点分析
    • 2.1 【兼容性】版本兼容性设计---组件版本号\指针函数\入参void指针
    • 2.2 【扩展性】新增组件设计---动态库新增组件
    • 2.3 【扩展性】新增组件新增私有功能 设计---扩展参数设置类型枚举
    • 2.4 【性能】异步处理性能设计---配置异步通知 / buffer轮转回调通知
    • 2.5 【编解码领域】组件间tunnel模式建立buffer自动轮转通路 设计点
    • 2.6 【编解码领域】组件基础抽象设计---handle封装 / port抽象 / 内外buffer /state状态
    • 2.7 【性能】0拷贝的buffer share与buffer透传(待更新)
  • 三、关于多媒体框架思考
    • 为什么编解码这么需要框架支持---编解码需求/硬解私有/用户隔离
    • 如何才能建立一个好的通用多媒体框架(待更新)
    • 私有多媒体通路能从OpenMax吸取哪些好设计(待更新)
  • 参考

OpenMax相关设计非常优秀,本文主要从设计角度对OpenMax的IL层进行个人分析总结。
OpenMax的基础概念及IL层相关基础介绍请参考 个人另一篇总结编解码抽象层OpenMax简介:https://blog.csdn.net/runafterhit/article/details/119961868

一、OpenMax的设计理念与特性点

我们先简单梳理一下OpenMax的设计理念和特性,好以此来分析后续设计点。(这部分上一篇博文已总结)

设计理念—媒体框架的抽象/可移植性/异步处理/组件组合

为了提升多媒体的可移植性,同时又能兼顾对接当前设备媒体多样性方案,OpenMAX IL相对这些方案抽象到更高的层次,通常意味着一个多媒体框架。OpenMax IL层的接口来源于媒体框架层次,设计实现上 要更容易集成新的解码器,其他比如文件处理也可以进行方便添加,考虑到各种可扩展性。同时设计上使用高度异步通信方式,者可以是处理通过一个或多个执行线程、或 专用硬件IP(硬件加速),通过多组件链接处理的方式 也给了架构更大的灵活性和效率。

设计特性—组件化API/方便新增解码器/方便扩展/支持动态链接/可配置

基于组件compoment化的核心API接口设计,保持灵活性。
能方便的新增集成新解码器codec的能力。
同时给Khronos Group和供应商提供针对音频、视频、图像领域良好扩展性;
可以被实现为静态库 或者 动态链接库。
提供对父平台(如多媒体frameworks)的关键特性和配置可选项。
保持client和解码器codec、解码器codec之间通信的简易性;

二、OpenMax的设计点分析

梳理openmax真正的设计点,按个人的思路总结如下:

2.1 【兼容性】版本兼容性设计—组件版本号\指针函数\入参void指针

(1)组件版本号:由于Omx的组件Component可以动态加载,每个组件的.so由芯片vender方实现方案,他们可能存在不同的版本,也可能替换方案,因此omx提供了OMX_GetComponentVersion接口,来查询组件的版本号,调用者需要根据版本号判断使用策略(通常framework可能只适配一种组件,就只做校验用,当有多种版本组件就需要参考跑策略)。版本号一般都是静态编译阶段 通过一组8bit字段构成,如下:

// omx的版本号定义数据结构
typedef union OMX_VERSIONTYPE {struct {OMX_U8 nVersionMajor;   /**< Major version accessor element */OMX_U8 nVersionMinor;   /**< Minor version accessor element */OMX_U8 nRevision;       /**< Revision version accessor element */OMX_U8 nStep;           /**< Step version accessor element */} s;OMX_U32 nVersion;           /**< 32 bit value to make accessing the version easily done in a single word size copy/compare operation */
} OMX_VERSIONTYPE;
// 典型的omx版本号举例
#define OMX_VERSION_MAJOR 1
#define OMX_VERSION_MINOR 2
#define OMX_VERSION_REVISION 0
#define OMX_VERSION_STEP 0#define OMX_VERSION ((OMX_VERSION_STEP<<24) | (OMX_VERSION_REVISION<<16) | (OMX_VERSION_MINOR<<8) | OMX_VERSION_MAJOR)

(2)指针函数:为了统一对外接口,封装不同芯片厂商实现差异,omx接口基本都是通过函数指针调用,如下SetParameter,用户通过core的API宏函数OMX_SetParameter,实际调用的是组件的SetParameter函数指针,SetParameter函数指针 通常在组件初始化OMX_ComponentInit时候注册,实现为自己芯片硬解方案对应封装如xxx_SetParameter。

// core接口
#define OMX_SetParameter(                                   \hComponent,                                         \nParamIndex,                                        \pComponentParameterStructure)                        \((OMX_COMPONENTTYPE*)hComponent)->SetParameter(         \hComponent,                                         \nParamIndex,                                        \pComponentParameterStructure)
// 组件接口OMX_ERRORTYPE (*SetParameter)(OMX_IN  OMX_HANDLETYPE hComponent, OMX_IN  OMX_INDEXTYPE nIndex,OMX_IN  OMX_PTR pComponentParameterStructure);
// 初始化组件时 注册函数指针
OMX_ERRORTYPE OMX_ComponentInit(OMX_HANDLETYPE phandle){// 略phandle->SetParameter = xxx_SetParameter; // 对接真正的接口实现// 略
}

(3)入参void指针 :为了保证各种组件实现的兼容性,omx入参基本都是通过 空指针传递,配合参数类型index 或者 入参size来描述 参数数据结构类型;比如上面的setParameter函数,core接口是一个宏函数,实际调用的component组件的接口,入参通过枚举OMX_INDEXTYPE描述是什么类型的参数,参数数据通过void*类型指针传入。这样可以通过一个setParam接口设置各种类型配置(包括可扩展的私有数据结构)。

typedef enum OMX_INDEXTYPE {OMX_IndexComponentStartUnused = 0x01000000,OMX_IndexParamPriorityMgmt,             /**< reference: OMX_PRIORITYMGMTTYPE */OMX_IndexParamAudioInit,                /**< reference: OMX_PORT_PARAM_TYPE */OMX_IndexParamImageInit,                /**< reference: OMX_PORT_PARAM_TYPE */OMX_IndexParamVideoInit,                /**< reference: OMX_PORT_PARAM_TYPE */OMX_IndexParamOtherInit,                /**< reference: OMX_PORT_PARAM_TYPE */OMX_IndexParamNumAvailableStreams,      /**< reference: OMX_PARAM_U32TYPE */// 略} OMX_INDEXTYPE;
typedef void* OMX_PTR;

2.2 【扩展性】新增组件设计—动态库新增组件

(1)动态库加载设计:omx设计上 每一个解码器组件 都做成一个单独的.so来 通过动态加载实现。通过OMX_GetHandle传入解码器名称,创建解码器实例,在这个过程加载so并获取对应的OMX_ComponentInit符号名称进行初始化,在so中注册好对应的指针函数等,提供后续的访问,关键部分如下:

// OMX_GetHandle创建解码器实例关键部分
OMX_API OMX_ERRORTYPE OMX_APIENTRY OMX_GetHandle(OMX_OUT OMX_HANDLETYPE* pHandle, OMX_IN  OMX_STRING cComponentName,OMX_IN  OMX_PTR pAppData,OMX_IN  OMX_CALLBACKTYPE* pCallBacks) {// 略dlhandle = dlopen (libname, RTLD_LAZY | RTLD_GLOBAL); // 通过dlopen库称获取到库的handle// libname通常用cComponentName按固定规则生成entry = dlsym (dlhandle, "OMX_ComponentInit")); // 通过库的访问handle获取到初始化函数符号地址ret = entry(*pHandle); // 通过OMX_ComponentInit的符号,调用到so库实现进行真正的解码器初始化
}
// OMX_ComponentInit(在对应so库)中关键实现
OMX_ERRORTYPE OMX_ComponentInit (OMX_HANDLETYPE handle) {OMX_COMPONENTTYPE *phdl = (OMX_COMPONENTTYPE *) handle; // 把handle强转为实例标准上下文p_hdl->SendCommand = SendCommand; // 注册对用组件回调p_hdl->SetParameter = SetParameter;p_hdl->SetConfig = SetConfig;p_hdl->EmptyThisBuffer = EmptyThisBuffer;// 略
}

通过这种库加载的方式,把解码器的实现封装到一个一个so中隔离开来,需要用到的时候再载入使用;

2.3 【扩展性】新增组件新增私有功能 设计—扩展参数设置类型枚举

(1)扩展参数设置类型枚举:omx的通用常见调用流程都有定义对应的指针函数如SendCommand、SetConfig配置参数,EmptyThisBuffer等buffer流程调用。我们前面已经了解了SetParameter基本用法,通过cmd类型枚举表示设置什么参数,void*传递参数数据。需要扩展新的设置时,就是扩展新的参数类型,通过组件的GetExtensionIndex函数指针传入函数名称获取扩展的函数类型。

// 标准的cmd类型在定义的时候已经预留了很多中间位,都是按段定义的
typedef enum OMX_INDEXTYPE {OMX_IndexComponentStartUnused = 0x01000000,OMX_IndexParamPriorityMgmt,             /**< reference: OMX_PRIORITYMGMTTYPE */OMX_IndexParamAudioInit,                /**< reference: OMX_PORT_PARAM_TYPE */// 略OMX_IndexPortStartUnused = 0x02000000,OMX_IndexParamPortDefinition,           /**< reference: OMX_PARAM_PORTDEFINITIONTYPE */OMX_IndexParamCompBufferSupplier,       /**< reference: OMX_PARAM_BUFFERSUPPLIERTYPE */ OMX_IndexReservedStartUnused = 0x03000000,// 略/* Audio parameters and configurations */OMX_IndexAudioStartUnused = 0x04000000,OMX_IndexParamAudioPortFormat,          /**< reference: OMX_AUDIO_PARAM_PORTFORMATTYPE */// 略OMX_IndexMax = 0x7FFFFFFF
} OMX_INDEXTYPE;
// 通过core的api接口,调用到组件的函数指针GetExtensionIndex查询扩展命令
#define OMX_GetExtensionIndex(                              \hComponent,                                         \cParameterName,                                     \pIndexType)                                         \((OMX_COMPONENTTYPE*)hComponent)->GetExtensionIndex(    \hComponent,                                         \cParameterName,                                     \pIndexType)
// 组件的查询扩展命令函数指针,传入函数方法名称或者 方法枚举,来设置使用
OMX_ERRORTYPE (*GetExtensionIndex)(OMX_IN  OMX_HANDLETYPE hComponent,OMX_IN  OMX_STRING cParameterName,OMX_OUT OMX_INDEXTYPE* pIndexType);

2.4 【性能】异步处理性能设计—配置异步通知 / buffer轮转回调通知

性能方面的设计是多媒体框架重要的一环,这里面主要包含两方面
基于事件和回调的异步处理机制
(1)配置异步通知:是接口是同步的阻塞函数还是异步的非阻塞函数,omx框架中 大量类型的接口 都建议是设计层 异步接口,比如参数设置 port参数调整生效等,通过异步事件回调来通知上层动作何时完成。
(2)buffer轮转回调通知:最原始的就是把输入输出buffer送解码器 然后 轮询 的aquire输出dequeue输入获取,这种方式基于循环遍历,占用调度性能而且不够及时,omx通过EmptyBufferDone和EmptyBufferDone事件来让上层去取帧更及时而且不需要循环调度。

// 在OMX_GetHandle创建解码器实例的时候 设置回调中就包含事件通知 回调。
OMX_API OMX_ERRORTYPE OMX_APIENTRY OMX_GetHandle(OMX_OUT OMX_HANDLETYPE* pHandle, OMX_IN  OMX_STRING cComponentName,OMX_IN  OMX_PTR pAppData,OMX_IN  OMX_CALLBACKTYPE* pCallBacks); // 注册回调类型
// 注册的回调类型如下,包含事件、输入帧使用完成、输出帧填写完成
typedef struct OMX_CALLBACKTYPE {OMX_ERRORTYPE (*EventHandler)( // 事件通知回调OMX_IN OMX_HANDLETYPE hComponent,OMX_IN OMX_PTR pAppData,OMX_IN OMX_EVENTTYPE eEvent,OMX_IN OMX_U32 nData1,OMX_IN OMX_U32 nData2,OMX_IN OMX_PTR pEventData);OMX_ERRORTYPE (*EmptyBufferDone)( // 输入帧使用完成事件回调,可以在填写传入组件OMX_IN OMX_HANDLETYPE hComponent,OMX_IN OMX_PTR pAppData,OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);OMX_ERRORTYPE (*FillBufferDone)( //输入帧填写完成事件回调,可以拿输出去使用OMX_IN OMX_HANDLETYPE hComponent,OMX_IN OMX_PTR pAppData,OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
} OMX_CALLBACKTYPE;

备注:omx设计文档中有说明,事件回调的处理函数必须 是 满足线程安全设计,回调可能同时发生(同一个回调同时并发,或者不同回调同时并发),要注意并发执行时的临界区安保护设计,如用信号量等机制;底层不能在非线程中执行回调(比如硬件中断)避免上层回调中发生阻塞 引发系统异常;避免在 回调中执行耗时操作,实际项目实现中,往往 底层的事件通知 是用独立的线程,而 上层当回调可能出现较多耗时也需要 用 单独的线程 收到事件执行 而非在回调直接实现。

2.5 【编解码领域】组件间tunnel模式建立buffer自动轮转通路 设计点

前面buffer轮转回调通知的介绍 说明了buffer轮转的异步通知设计机制,通过这种设计,可以实现底层组件之间的串联调度不需要framework或者用户参与buffer轮转,直接让组件之间通过回调事件管理buffer轮转,也叫组件之间的tunnel通信模式。如下图中,source Component与Host Component是非tunnel的普通调用方式,需要framework根据EmptyBufferDone和EmptyBufferDone事件来触发轮转,而后面几个组件就是tunnel模式。

tunnel模式举例 :camera采集组件—mjpeg解码组件—render渲染显示组件 三者绑定了tunnel模式,当camera采集完一帧,底层驱动触发了filldone事件把帧EmptyThisBuffer送到 mjpeg解码组件,mjpeg解码组件解码完成把帧EmptyThisBuffer送到render渲染显示模块渲染显示,无论送帧还帧,都不要用户或者framework参与buffer轮转调度。

2.6 【编解码领域】组件基础抽象设计—handle封装 / port抽象 / 内外buffer /state状态

(1)handle封装:用户或者framework对于组件的访问都是通过handle并调用core api传入handle进行访问,只需要包含omx_core.h的头文件即可,这个handle类型OMX_HANDLETYPE实际就是一个void*类型指针,指向组件的上下文。上下文真正的定义是在omx_component.h的头文件,定义真正数据结构OMX_COMPONENTTYPE,用户不可见,omx的core核心实现才可见,以此来实现对组件内部细节的封装。同时OMX_COMPONENTTYPE中还包含pComponentPrivate指针 指向真正的解码器内部私有上下文,类似驱动访问file文件句柄的private,这个上下文只在组件内部访问可见,omx core也不可见,一般在OMX_ComponentInit时候malloc私有上下文挂载到pComponentPrivate,后续组件访问中内部使用;

typedef void* OMX_HANDLETYPE; // 用户和framework看到的handle
typedef struct OMX_COMPONENTTYPE { // omx core实现中把handle转换为真的上下文实现OMX_U32 nSize;OMX_VERSIONTYPE nVersion;OMX_PTR pComponentPrivate; // omx component组件私有数据结构上下文访问OMX_PTR pApplicationPrivate;OMX_ERRORTYPE (*GetComponentVersion)(OMX_IN  OMX_HANDLETYPE hComponent,OMX_OUT OMX_STRING pComponentName,OMX_OUT OMX_VERSIONTYPE* pComponentVersion,OMX_OUT OMX_VERSIONTYPE* pSpecVersion,OMX_OUT OMX_UUIDTYPE* pComponentUUID);OMX_ERRORTYPE (*SendCommand)(OMX_IN  OMX_HANDLETYPE hComponent,OMX_IN  OMX_COMMANDTYPE Cmd,OMX_IN  OMX_U32 nParam1,OMX_IN  OMX_PTR pCmdData);// 略
} OMX_COMPONENTTYPE;

(2)port抽象:port是对于数据端口的抽象,分输入in port和输出out port,一般常见组件都是一个输入一个输出,如果是source组件可能只有输出,最后一级如render渲染显示组件可能只有输入。有些组件可能存在多个port,比如支持一进多出的处理组件输出存在多个port;

(3)内外buffer模式:buffer模式表示某个port的内存是组件内部申请还是外部送入的,这是常见的解码器用法,使用外部buffer能更方便上层使用管理。
(4)state状态机 :每个组件都有一个状态机管理内部的状态切换,每个组件首先被认为是卸载的,组件被CoreAPI进行加载,然后在通信过程中与其他状态转换,当遇到无效数据时组件可能进入Invalid无效状态。不同的状态下组件的行为操作存在差异和限制。状态的切换通过SendCommand调用OMX_CommandStateSet类型命令进行设置。

2.7 【性能】0拷贝的buffer share与buffer透传(待更新)

三、关于多媒体框架思考

为什么编解码这么需要框架支持—编解码需求/硬解私有/用户隔离

(1)随着音视频领域技术发展,对编解码的性能要求日益加剧,fhd到4k到8k,帧率从24hz到60hz到120hz,还有hdr等提升显示还原效果等技术出现,特别是手机等可移动设备发展,以往的软解码的方式难以保证性能稳定,并且软解对于cpu性能要求非常高
(2)于是各种设备厂商都在通过硬解码进行加速,硬解的方案往往非常私有化,解码协议实现方案多种多样,很多协议模糊处依靠大量问题处理迭代累计,属于关键技术资产,形成各家技术壁垒,通常都通过闭源发布 ,比如跑在mcu上 而非 放到kernel设备中 来规避内核Licence传染,玩法自然差异非常大。并且可能同一个设备上 不同解码器 来着不同团队,甚至经常是不同公司
(3)而用户自然不能看到这些差异,因此用户通常面对的是多媒体framework甚至是更高层次的封装,framework往下来对接 各种软解码、硬解码器方案,并且这些解码器之间并不是独立的,他们经常需要组合使用,并且还对性能有很高要求(软件调度、异步处理),这里面如果没有一套标准来对接,将会是极其复杂的。
(4)再进一步,不同平台有自己的framework,如果每个芯片厂商都去适配一次,将会极其困难,严格意义上讲,openmax应该是算 对接framework和硬解码方案 之间的封装层,并不是一个framework,比如ffmpeg、Gstreamer、Android的mediaCodec都可以和openmax对接,而硬件厂商只需要对接openmax即可。

如何才能建立一个好的通用多媒体框架(待更新)

私有多媒体通路能从OpenMax吸取哪些好设计(待更新)

参考

官网IL层详细文档:https://www.khronos.org/files/openmax_il_spec_1_0.pdf
编解码抽象层OpenMax简介:https://blog.csdn.net/runafterhit/article/details/119961868
omx头文件:http://androidxref.com/9.0.0_r3/xref/frameworks/native/headers/media_plugin/media/openmax/

OpenMax IL层设计分析总结相关推荐

  1. 【多媒体编解码】Openmax IL (一)官方文档概述

    OpenMax IL 概述 PS:被博客主要是对官方文档前三章的学习于记录. 官网文档请移步: https://www.khronos.org/openmax/ Openmax 框架图: openma ...

  2. Android4.4 多媒体开发(五)----OpenMax简介

    原址 android中的 AwesomePlayer就是用OpenMax来做(codec)编解码的,上一篇最后一步初始化解码器我们只是初窥了一下,以后会仔细分析.本节就主要科普一下OpenMax和它在 ...

  3. OpenMAX编程初识

    原文链接 导读: 本篇文章对OpenMAX做了一个整体的介绍与概述,说明OpenMAX是什么?可以解决什么问题?用在什么地方?以及为什么要用OpenMAX?这里并不对OpenMAX进行深入介绍(放到接 ...

  4. OpenMAX编程-组件

    阅读原文 OpenMAX的重点组成部分就是组件,OpenMAX通过将meida流过程中的各个模块抽象化为组件来进行耦合,在OpenMAX标准下,数据流通过组件来进行传递.处理.显示.在该篇文章里,不需 ...

  5. OpenMax在Android上的实现

    摘要:本文简要介绍了OpenMax 的集成层,并阐述了其在Android上的实现和运行过程. 关键字:OMX, 多媒体框架, IL, Android, Stagefright 1.OpenMax 集成 ...

  6. linux内核epub,Android底层开发技术实战详解——内核、移植和驱动(第2版)[EPUB][MOBI][AZW3][42.33MB]...

    内容简介 本书从底层原理开始讲起,结合真实的案例向读者详细介绍了Android内核.移植和驱动开发的整个流程.全书分为21章,依次讲解驱动移植的必要性, Goldfish.OMAP内核和驱动解析,显示 ...

  7. Android OMX介绍(总括)

    一.OpenMax简介(缩写为:OMX)     OpenMAX是一个多媒体应用程序的标准.由NVIDIA公司和Khronos™在2006年推出.     它是无授权费的.跨平台的C语言程序接口序列, ...

  8. Android Media Framework(1): 总纲

    转自:http://www.jianshu.com/users/5ba48666a89d/latest_articles Android系统整体架构: 我们先看一下多媒体框架在整个Android系统所 ...

  9. Android MediaExtractor + MediaCodec构建简单播放器

    对于一个播放器,基本上可以分为以下模块:数据接收(网络/本地)->解复用->音视频解码->音视频同步->音视频输出. 今天我们介绍Android系统中提供的两个播放器模块Med ...

最新文章

  1. Win7+Ubuntu11
  2. python简介怎么写-Python开发工程师岗位项目经历怎么写
  3. JNI调用(github有代码可下载)
  4. 【完结】12篇GAN的优化文章大盘点,浓浓的数学味儿
  5. pinpoint全链路监控系统安装配置
  6. 一步一步了解Promise原理
  7. 浮点高精求和(洛谷P2393题题解,弃坑Java拥抱C++)
  8. 【操作系统】死锁-思维导图
  9. vue弹窗调用另一个弹窗_电脑桌面“脏乱差”,插件弹窗广告一个不少,学会say no很重要...
  10. mysql的常用引擎
  11. 安卓商城 php后台,洛克商城APP 开源商城安卓源码 含前端和后台
  12. SSM框架整合(xml配置)
  13. PIL ellipse函数画椭圆
  14. 批量转化文件夹下图片格式方法,简单易操作!
  15. 遏制流氓软件的方法:禁止360的sesvc.exe在后台运行
  16. 一文学会会计记账-会计科目、借贷关系和会计分录的小白理解
  17. Cmake的重新编译
  18. WPF在mxs里引用图片资源并释放的方法
  19. 线性代数系列(四)--解方程组
  20. Strerror函数和Perror函数的介绍及使用

热门文章

  1. Excel Vba无法连接 远程 Mysql数据库问题的解决方法
  2. 吴式太极拳老架(原乐志先生授)(2008.04.09修改)
  3. 关于自由移站法及坐标转换模型的综述
  4. 监督学习、无监督学习、半监督学习、自监督学习的区别与联系
  5. CMD批量转换GIF图片为PNG图片
  6. 近七成美国汽车消费者下一辆仍想买燃油车,中韩最热衷纯电车,日本最偏好混动车 | 美通社头条...
  7. 【c语言】厄密多项式--用递归实现
  8. C_TFIN52_67 - SAP PA认证考试真题 Financial Accounting with SAP ERP
  9. Android 保活从入门到放弃:乖乖引导用户加白名单吧(附7大机型加白示例)
  10. 通过支付宝服务中断事件看系统可靠性和YunOS的可靠性