Android 13 - Media框架(14)- OpenMax(三)
上一节学习了 media.codec 服务中的部分内容,这一节我们将一起了解 OMX IL 层的 API 以及相关的结构体等内容。
1、相关路径
以下是 Media 相关的头文件路径:
frameworks/native/headers/media_plugin/media/
cas 和 drm 是用于加密流解密使用,editor 里面有个 OMX_COLOR_FormatYUV420Planar 颜色空间的转换工具,这三个目录这里不做了解(不会),我们要重点关注的是 openmax
和 hardware
目录 。
1.1 hardware
frameworks/native/headers/media_plugin/media/hardware/
hardware 目录下放的是和硬件相关的一些头文件,CryptoAPI.h 大概是用于解密的,HDCPAPI.h 是用于HDMI加密的,这两个暂时不做了解;
OMXPluginBase.h 在前一节中已经做过了解了,libstagefrighthw.so 中的方法会创建一个 OMXPluginBase 对象;
VideoAPI.h 中声明有 media image (frame),以及 Aspects of color 等相关的结构体,这里不做过多描述。
1.1.1 HardwareAPI.h
以下内容部分翻译自 HardwareAPI.h 中的注释:
struct EnableAndroidNativeBuffersParams
:这个结构体用于把android native buffer 用作 graphic buffer 和 secure buffergraphic buffer usage
:当 OMX node 通过 OMX_SetParameter 设定 OMX.google.android.index.enableAndroidNativeBuffers 配置时,这个功能将被打开(默认关闭);
当某个端口的 native buffer 使用被禁止时,该端口将正常被使用,端口使用的 buffer 会用 UseBuffer 来设定,这是当 CPU 需要访问 buffer 时将使用的模式;
当 native buffer 被启用时,视频的 color format 信息将会使用 Android pixel格式,而不是使用 OMX 格式。启用 native buffer 可能会改变 component 接收 buffer 的方式,如果 store-metadata-mode 被使能,那么将会以 metadata的形式传输。此外,除非支持 OMX.google.android.index.useAndroidNativeBuffer2 扩展,否则应该使用 OMX_SetParameter(UseAndroidNativeBuffer )来设定使用的 buffer。secure buffer usage
:当 OMX node 通过 OMX_SetParameter 设定 OMX.google.android.index.allocateNativeHandle 配置时,这个功能将被打开(默认关闭);
当 native handle 在某个端口被禁用了,这个端口将会正常工作,并且预期调用 AllocateBuffer 来分配 buffer 并且返回 buffer 指针,这个模式会用作于 non-secure buffer 并且要组件分配 buffer 的情况;
当 native handle 被使用时,组件需要分配一个 native_buffer_t 对象(可以通过binder传递),这个模式被用于 secure buffer,与之配合使用的有 nFilledLength、nAllocLength、nOffset 等参数。注释
:之所以这个结构体放在 Hardware API中,我的理解是无论 graphic buffer 还是 secure buffer 都是由硬件分配管理的,上面的翻译中描述了两种模式成功应用和失败应用的情况,至于上层如何使用,后续我们将在 ACodec 章节中来学习。
struct StoreMetaDataInBuffersParams
:这个结构体用于在 video source(camera…)和 video encoder之间,video decoder 和 output buffer 之间传递数据,而避免数据的拷贝。 meta data 在 OMX client 和 OMX component之间传递时,buffer中存储的并不是真正的 video 数据,而是一些用于定位/解析真正数据的信息。启动 meta data 时,bStoreMetaData 需要设置为 OMX_TRUE,默认情况下将会启用 meta data mode。
当 bStoreMetaData 为 false 时,真正的 YUV 帧将会存储在 input/output buffer当中。
Metadata buffer 需要使用 UseBuffer 来注册到 OMX component 中。- 当组件支援 OMX.google.android.index.storeANWBufferInMetadata 时,数据将会以
VideoNativeMetadata
的形式传递,并且会用 fence 来协同管理; - 当组件支援 OMX.google.android.index.storeMetaDataInBuffers 时,数据将会以
VideoGrallocMetadata
的形式传递。 注释
:之所以叫 meta data,指的是buffer中传递的不是真的数据,而是handle或者是物理地址,以及解析出buffer所需要的size、offset等信息,这些信息称为元信息,这个结构体一般会与上面的grapgic buffer usage
搭配使用。
- 当组件支援 OMX.google.android.index.storeANWBufferInMetadata 时,数据将会以
struct ConfigureVideoTunnelModeParams
:用于 Android Tunnel Mode,网上讲 Tunnel Mode 的资料还比较少,具体如何实现不同的厂商可能有不同的方案,这里只翻译部分注释。describe
:如果 Tunnel Mode 被打开,那么 video decoder 解出的帧将会直接送到 sink(池子) 当中,nAudioHwSync 作为 HW SYNC ID 用来同步 audio hal 与 video 的输出,pSidebandWindow 需要绑定 codec 分配的 sideband window handle。
以上就是 HardwareAPI.h 中的部分内容,ACodec 并不会直接使用这些结构体,ACodec 回调用 IOmxNode 的接口,再由 OMXNodeInstance 解析参数,封装参数,最后传给 OMX component。
1.1.2 MetadataBufferType.h
这个文件中只定义了一个 enum MetadataBufferType
,这里只对注释做一下翻译:
MetadataBufferType 定义了可以传递给 encoder 的 metadata buffer 的类型,这些类型主要是用于 libstagefright record框架使用的,这里的 record 可能指的是录屏或者是摄像头。Stagefright 框架是不需要知道关于 meta buffer的细节的,只要创建 meta buffer 和 video encoder 能够保持一致就行。
1.2 openmax
1.2.1 OMX_Core.h
这个文件应该算是 OMX 的入口了,文件一开始的注释:OMX_Core 头文件定了application 和 component 公用的内容,我的理解就是 OpenMax AL 和 IL 层之间的接口。
我们直接拉到文件末尾,可以看到一组函数声明,有心的小伙伴会发现我们在上一节的 QComOMXPlugin
中见过它们,这些方法会被封装在 libOmxCore.so
中,具体的内容需要 vendor 来实现,这里对这几个方法描述进行翻译:
函数参数里的OMX_OUT
和 OMX_IN
用于记录当前的参数是返回参数还是输入参数。
OMX_Init
:用来初始化 OMX core,这个方法执行时间应该小于 20 ms;OMX_Deinit
:对应于 OMX_Init,用于销毁 OMX core 中的内容,执行时间应该小于 20 ms;OMX_ComponentNameEnum
:枚举遍历系统中所有可用的编解码组件,第二个参数为一个字符指针,返回组件名称,名称类似于OMX.qcom.video.decoder.h263
;既然有遍历,那就会有一个容器来存储系统支援的所有组件信息,这个容器有各家vendor独立实现,实现不同会影响当前方法的实现;OMX_GetHandle
:根据给定的组件名称,将组件加载到内容当中(dlopen),调用组件的方法创建一个组件实例;(这里的意思是每个组件都是一个lib,每次要创建编解码实例都会dlopen,从而实现实例间互不影响,打开lib后会调用其中的方法返回示例,之后会直接使用这个实例,并且实例会被记录在 OMX core 当中);OMX_FreeHandle
:释放 component Handle;OMX_GetRolesOfComponent
:获取组件的 role;这里的 role 指的是什么呢?个人理解是这样:多个不同的编解码组件可能会走同一套流程,并不是我们所想象的每个组件都会单独编译出一个 lib,虽然说流程相同,但是内部的一些配置可能会不一样,为了区分当前使用的是哪一个组件,就用 role 来区分。
再往上看,又有一组函数声明,这组函数的命名和 struct OMX_COMPONENTTYPE
中定义的函数指针是一一对应的,我们之前有提到创建 OMX 组件后,我们并不是直接使用组件,而是将其封装到 OMXNode 当中,最终由 OMXNode 调用组件的方法,以下这组函数声明就是对调用组件方法的封装,第一个参数 hComponent
指的就是 handle component(组件句柄/指针):
OMX_GetComponentVersion
:获取组件的信息,这是一个阻塞调用,耗时需要小于 5ms;OMX_SendCommand
:向组件发送一条命令,该调用是非阻塞
的,组件需要先检查参数是否正确,然后才能将命令加入到组件线程中
去执行,组件可能会调用 EventHandler callback 来返回结果,该函数的调用耗时需要小于 5ms;- 从这里我们可以知道,omx 组件中应该会有一个 component 线程来专门执行上层下发的命令;
- 当命令是
OMX_CommandStateSet
,组件将会从 nParam 中获取新的状态,并且加入到命令队列中,从 executing 状态到 loaded 状态需要小于 500ms; - 当命令是
OMX_CommandFlush
,组件将会强制把指定 port 上所有不在处理状态
的 buffer 全部按照接收的顺序 return,每个 port 执行 flush 应该小于 5ms; - 当命令是
OMX_CommandPortDisable
或者是OMX_CommandPortEnable
,port 将会被关闭或者重启,耗时应该也小于 5ms;
OMX_GetParameter
:获取当前组件的参数设置,它是阻塞调用,在OMX_StateInvalid
状态下不能被调用,其他状态都能调用;它的第二个参数 nParamIndex 表示需要获取的参数类型,该方法执行时间应该小于 20ms;OMX_SetParameter
:类似 GetParameter;OMX_GetConfig
:从组件获取一个配置的结构体,这是一个阻塞调用,需要在 5ms 内完成;OMX_SetConfig
:给组件传递配置,需要与 GetConfig 搭配使用;OMX_GetExtensionIndex
:用于将 特定的vendor 配置或者参数翻译成为 OMX 结构体索引;OMX_GetState
OMX_UseBuffer
:请求组件使用一个已经分配的buffer(来自于另一个组件 或者 来自于上层),并且分配出组件自己的 buffer header,该调用应该在 5ms 内完成;OMX_AllocateBuffer
:请求组件分配一个新的buffer 和一个 buffer header,并且将 buffer header 的指针返回,该调用应该在 5ms 内完成;OMX_FreeBuffer
:组件将会释放调用创建的 buffer header,如果 buffer 也是组件创建的,那么也会释放掉该 buffer;OMX_EmptyThisBuffer
:将一块填有数据的 buffer 送给组件的 input 端口,这块 buffer 被清空时将会调用EmptyBufferDone
回调,将 buffer 返回给应用层,这是一个非阻塞调用
。OMX_FillThisBuffer
:将一块空的 buffer 送到组件的 output 端口,组件将会填充该 buffer 并且调用 FillBufferDone 回传给应用层,同样的该函数也是非阻塞的
;OMX_UseEGLImage
解下来再看看其他结构体:
typedef enum OMX_COMMANDTYPE
{
OMX_CommandStateSet, /**< Change the component state */
OMX_CommandFlush, /**< Flush the data queue(s) of a component */
OMX_CommandPortDisable, /**< Disable a port on a component. */
OMX_CommandPortEnable, /**< Enable a port on a component. */
OMX_CommandMarkBuffer, /**< Mark a component/buffer for observation */
OMX_CommandKhronosExtensions = 0x6F000000, /**< Reserved region for introducing Khronos Standard Extensions */
OMX_CommandVendorStartUnused = 0x7F000000, /**< Reserved region for introducing Vendor Extensions */
OMX_CommandMax = 0X7FFFFFFF
} OMX_COMMANDTYPE;
command type 就是可以设置的命令类型,一般有状态设置,flush,端口的开启和关闭,vendor也可以自定义命令类型。
typedef enum OMX_STATETYPE
OMX_STATETYPE 指的是组件的状态,比较常见的有如下几种:
OMX_StateInvalid
:组件发生异常;OMX_StateLoaded
:组件被创建但是还没有初始化完成;OMX_StateIdle
:组件初始化完成并且准备好开始;OMX_StateExecuting
:组件收到开始命令并且开始处理数据;OMX_StatePause
:组件收到暂停命令,这个用的很少
一些常见的flag:
OMX_BUFFERFLAG_EOS
:EOS 表示输出端口将不会有数据再出来,所以在最后一块buffer上应该附上 EOS,上层给 decoder 写数据带上 eos 表示当前流结束;OMX_BUFFERFLAG_STARTTIME
:这个 flag 标记了起播时或者seek后第一帧应该从哪一帧开始显示渲染;比如说 seek 之后要从某个 pts 开始播放,但是刚好这一帧不是关键帧,那么需要从关键帧开始解码,从目标帧开始渲染。如果组件接收到 starttime,它应该调用SetConfig
去设置OMX_ConfigTimeClientStartTime
。OMX_BUFFERFLAG_DECODEONLY
:组件只解码不渲染;OMX_BUFFERFLAG_DATACORRUPT
:表示当前buffer中的数据存在错误;OMX_BUFFERFLAG_ENDOFFRAME
:表示当前buffer标志着一帧数据的结束,默认情况下Android每次写入都是一帧数据,所以每次数据写给 component 都会带这个flag;OMX_BUFFERFLAG_EXTRADATA
:额外的数据需要加在数据流之前,某些格式需要加一些数据头;OMX_BUFFERFLAG_CODECCONFIG
:codec specific data (csd),例如播放 h264需要的 SPS/PPS,播放AAC需要的AudioSpecificConfig,没有这些配置信息将无法解码播放。