我的小站
往期文章索引:
03 - OpenMAX编程-实现一个组件
02 - OpenMAX编程-数据结构
01 - OpenMAX编程-组件
00 - OpenMAX编程初识
导读:
本文着重介绍不同类型组件的具体构成(参数类型、特性设置等),包括audio、video、image等组件。另外对OpenMAX的一些扩展用法以及以前文章当中的缺漏进行补充。
从spec里面截取一张图,如下所示:
应用场景:用作不同格式的音频混合,比如在不同的物理环境下录制一段不同格式的音频文件,然后使用软件将其混合起来。
应用场景:对讲设备,比如微信或者QQ语音聊天,双方可以同时讲话,从第一视角来看,下行数据流指的就是对方讲的话经过通讯设备(手机等)采集编码之后网络传输过来的音频信息,需要经过解码在自己的通讯设备上面播放(这样才能听到对方讲话)。同时自己说的话需要与下行音频数据流组合进行回音消除(试想没有回音消除,自己说的话传过去,对面外放的同时又被对面的通讯设备采集再次发送过来,不断循环就会导致回声问题,需要与下行音频数据流组合的原因就是需要其做分析进行回声消除),然后经过编码形成上行音频数据流传递给对方。
OMX_PORT_PARAM_TYPE
(描述端口数量、起始端口号等),OMX_PARAM_PORTDEFINITIONTYPE
(端口定义),还有特定类型端口的参数结构体描述,例如OMX_VIDEO_PARAM_PORTFORMATTYPE
,OMX_AUDIO_PARAM_PORTFORMATTYPE
等。后面两种类型的结构体通常是数组形式的,两者一一对应,用来描述一个端口。OMX_PARAM_PORTDEFINITIONTYPE
结构体内部,使用的时候通常直接初始化一个OMX_PARAM_PORTDEFINITIONTYPE
类型的结构体变量。OMX_AUDIO_PARAM_PORTFORMATTYPE
:顾名思义,该结构体描述了端口支持的音频流数据格式,它是一个枚举类型。支持使用OMX_Get/SetParameter
来获取、设置端口的格式。nIndex
的值范围是0~N-1,N就是该端口所支持的格式数量,IL Client可以通过枚举所有支持的格式来获知N的大小(枚举过程以返回OMX_ErrorNoMore
为结束)。OMX_AUDIO_PARAM_PCMMODETYPE
:该结构体是格式特定的参数描述,有PCM、MP3等等,每一种格式的参数描述都不同,都有与之对应的结构体类型。其内容就不再一一描述,都是与特定格式有关的。该种类型的参数都可以用OMX_Get/SetParameter
来进行获取、设置。OMX_AUDIO_CONFIG_VOLUMETYPE
:这些是与配置相关的结构体类型,有音量、通道音量、均衡等等,该种类型的参数都可以用OMX_Get/SetConfig
来进行获取、设置。该部分介绍视频、图像通用的一些Config与Param设置,这部分类型定义在OMX_ivcommon.h
头文件中可以找到。
OMX_COLOR_FORMATTYPE
枚举类型来描述,包括RGB的8、16、32位未压缩格式与YUV格式极其子格式等的枚举。未压缩数据格式有最小加载大小的要求,由视频或者图像端口定义结构体OMX_IMAGE/VIDEO_PORTDEFINITIONTYPE
中的nSliceHeight
与nStride
成员共同决定,两个成员前者表示“跨度”、后者表示“切片”,这个在不同的场合有不同的解释。例如:对于一个YUV图像来说,“跨度”就可以理解为图像实际的大小加上扩展内容,如下图所示:stride可能会有正值有负值,正值表示从左至右、从上至下扫描,负值表示从左至右、从下至上扫描。“切片”就是切片编码(如H264,该编码有条带的概念)时使用到的高度,可以简单的使用(nSliceHeight * nStride)(条带大小)的绝对值来作为最小传输大小。
2. 未压缩数据格式的要求
每一个image或者video组件对于未压缩的数据缓冲区都有一些要求,这些要求是为了使得不同供应商之间的数据能够正常地被处理。比如这些数据有以下几种要求:
nSliceHeight
指明了Y分量的slice高度值,剩下的UV分量所占据的空间大小就根据YUV的格式以及Y分量的高度值来决定。比如YUV420格式下nSliceHeight
为16,就表明有16个跨度的Y分量,加上合起来8个跨度的UV分量,合计16+8个跨度(注:有关YUV格式可以去看相关的spec)。这可以使得端口同时处理slice内的三个分量数据,而不用缓冲一整张image图片之后才去进行处理。3.parameter与config
该部分的内容众多,这里就不再把所有的条目都列出来了。
OMX_CONFIG_DEBLOCKINGTYPE
结构体,用于决定是否使能编解码模块的去除块状伪影功能(编码时编码块之间的边缘伪影,编码时块之间没有平滑过渡导致的编码出现边缘不一致问题)。OMX_CONFIG_SENSORMODETYPE
结构体,用于设置sensor的帧率、分辨率。OMX_CONFIG_COLORCONVERSIONTYPE
结构体。用于做格式转换,在需要从RGB转为YUV格式时用到。OMX_VIDEO_CODINGTYPE
:
OMX_VIDEO_PICTURETYPE
:标识视频的一帧
上图中第一个框图是一个图像采集编码、预览一体化的video组件实例,从camera硬件采集图像数据,经过图像过滤器,然后经由一个分发器输出一路视频给video显示组件进行预览,一路送由H263编码组件编码最终写入到文件里面。
下面的一幅图可以看作一个简单的视频通话通路。首先是上行数据通路:从camera硬件采集数据经过分发器分出一路给H263编码,然后送给网络上行链路传输给对方,另一路与下行数据解码之后的未压缩视频数据混合(就是聊天时的大图与小图,一个是对方的相机图像,一个是本机的图像,大小图可以切换,混合就是将两路视频流叠放在一块的过程)之后送由显示组件显示到本机上面。
2.端口相关结构体
OMX_VIDEO_PORTDEFINITIONTYPE
:端口的定义结构体,该结构体用来描述一个端口,如果需要额外参数的话可以添加额外的结构体用于描述端口属性,比如OMX_VIDEO_PARAM_BITRATETYPE
结构体用以描述端口的bit率。使用OMX_Index_ParamVideoPort
这个index可以获取/设置端口的属性。成员中需要注意的是nStride
与nSliceHeight
两个,这两个成员在前面也提到过。OMX_VIDEO_PARAM_PORTFORMATTYPE
:用以描述端口所支持的格式,这部分与音频对应的结构体意义几乎一样,结构体成员的意义可以参考音频部分的描述。OMX_VIDEO_PARAM_QUANTIZATIONTYPE
等,这些结构体都可以通过Set/GetParameter来进行设置获取,这里就不一一详述,spec上面描述的很清楚。值得注意的是,不是所有的video组件都能用得上所有的TYPE的,比如video sink就用不着与编码相关的TYPE,所以细分的video组件可能仅仅会使用到其中部分TYPE,这个要视组件细分类型而定。image相关的定义在OMX_Image.h
头文件中描述。下图描述了image相关的param参数类型:
如上图中框图所描述的,这里给出了类似video组件第一种用法的场景。camera获取图像经由image过滤器,然后由分发器分出一路显示图像,一路编成H263然后写入到文件。纠正:spec这里画错了,H.263 encode应该是jpeg encode,所以是编成jpeg格式图像写入文件。况且单幅图像也发挥不出H.263编码的特性。
端口相关的结构体如下:
OMX_IMAGE_PORTDEFINITIONTYPE
:端口定义(与video的极为相似,不再赘述),其中不同细分类型组件的输入输出端口数量也是不一样的。
OMX_IMAGE_PARAM_PORTFORMATTYPE
:与video的一样。1.参数控制
image有些比较特殊的参数类型(TYPE),比如:
OMX_IMAGE_PARAM_FLASHCONTROLTYPE
:该结构体参数就是用来控制闪光灯的(拍照的闪光灯功能),其中eFlashControl
成员可以设置闪光灯常亮、关闭、自动、红眼消除(拍照时的干扰,平时黑暗的地方手机拍照就可能出现人眼球部分是红色的,参考google相关解释)。OMX_IMAGE_PARAM_FOCUSCONTROLTYPE
:聚焦,用于焦距调节。eFocusControl
成员可以指定手动调焦、关闭调焦、自动调焦、自动锁定目标焦距等等。很多时候,组件内部标准的API并不能满足需求,此时就需要添加自定义的API,OpenMax也允许这种扩展,新加的API就称之为extensions。如果需要添加自定义的index,就在OMX_INDEXTYPE中预留的空间范围内增加(OMX_IndexIndexVendorStartUnused
~OMX_IndexMax
之间)。
新增的index也会有与之对应的结构体成员来作为index的参数,它可以是属于image、video、audio、other四个domain或者自定义的domain,(自定义的domain需要自己去实现)。这些自定义的index需要有与之对应的字符串类型的描述,IL Client可以使用OMX_GetExtensionIndex
来根据index的属性获取到index的索引值,通过该索引值可以使用OMX_Get/SetParameter或者OMX_Get/SetConfig来进行相关的数据参数存取。
GetExtensionIndex
回调函数OMX_ERRORTYPE (*GetExtensionIndex)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_STRING cParameterName,
OMX_OUT OMX_INDEXTYPE* pIndexType);
如前所述,每一个自定义的index都会与一个结构体或者指向结构体参数描述的地址相关联,使用者需要定义一个头文件把这些结构体描述包含进去,然后组件可以使用这些头文件来实现特定的结构体参数读写。如果有些index对应的参数描述仅仅用一个指针来指定,那么使用者与组件需要约定好如何去管理这部分数据,最好在一份扩展API文档里面能够有这些描述信息。下面是spec文档里面给出的两个使用的例子:
/* Set the vendor-specific filename parameter
on a reader */
OMX_U32 eIndexParamFilename;
OMX_PTR oFileName;
OMX_GetExtensionIndex(
hFileReaderComp,
"OMX.CompanyXYZ.index.param.filename",
&eIndexParamFilename);
OMX_SetParameter(hComp, eIndexParamFilename, &oFileName);
/* Get the vendor-specific mp3 faster
decoding feature settings */
OMX_U32 eIndexParamFasterDecomp;
OMX_CUSTOM_AUDIO_STRUCTURE oFasterDecompParams;
OMX_GetExtensionIndex(
hMp3DecoderComp,
"OMX.CompanyXYZ.index.param.fasterdecomp",
&eIndexParamFasterDecomp);
OMX_GetParameter(hMp3DecoderComp, eIndexParamFasterDecomp,
&oFasterDecompParams);
可以理解为是跳播组件,也就是手机播放器上面快进、点击拖动到指定的位置播放,该种组件的播放源可以是本地的文件,也可以是远程的文件(在线视频文件),除了提供跳播功能之外,IL Client也会有获取文件当前播放位置的需求,所以组件也要提供该种功能。
OMX_IndexConfigTimePosition
OMX_TIME_CONFIG_TIMESTAMPTYPE
,IL Client可以通过该索引来获取/设置组件的时间戳控制文件的播放。OMX_IndexConfigTimeSeekMode
OMX_TIME_SeekModeFast
模式的话表明是快速跳播,意思就是尽可能的快速切换到所要跳播的位置,即使最终跳播到的位置并不是精确的跳播位置(速度优先)。OMX_TIME_SeekModeAccurate
模式的话表明是以精确度为优先,及时因此导致跳播耗时较长也在所不惜。数据流中的任意跳播请求会指向任意的目标位置,该位置的数据可能会依赖于前面的数据。比如跳播时指定的时间点对应整个数据流的某一帧,而该帧要解码必须依赖于前面若干数量帧(需要重建解码数据结构),那么Fast模式就指的是从能够解码的第一帧开始显示(该帧极大概率会比实际请求跳播的那一帧要靠前),此为牺牲跳播的精度。如果是Accurate模式则是等待整个解码结构体重建完毕,直到解码到真正请求的那一帧开始显示,此为精度优先,可能会牺牲跳播时间。
2.seeking缓冲区标志
缓冲区标志用于sekking组件告诉下游组件传递给它的buffer的具体作用,通常情况下,需要加flag的buffer都是一个缓冲区单元里面的第一个逻辑数据帧,就比如上面说的重建解码结构体需要的数据帧,该部分的帧就需要加上一个flag。
OMX_BUFFERFLAG_DECODEONLY
OMX_BUFFERFLAG_STARTTIME
OMX_IndexConfigTimeClientStartTime
索引来设置时钟组件,该索引表明客户端的数据已经准备好要播放。3.seek事件的产生步骤
该步骤是由IL Client来完成的:
OMX_IndexConfigTimeClockState
索引来设置OMX_TIME_CONFIG_CLOCKSTATETYPE
的eState
成员为OMX_TIME_ClockStateStopped
,停止组件的media clock。OMX_IndexConfigTimePosition
索引来设置需要跳转的时间戳。OMX_IndexConfigTimeClockState
索引来设置OMX_TIME_CONFIG_CLOCKSTATETYPE
的eState
成员为OMX_TIME_ClockStateRunning
或者OMX_TIME_ClockStateWaitingForStartTime
状态。对于Running状态,在seeking操作结束之后,clock组件就立即开始media clock的流动,这个方法虽然比转换到Waiting状态要简单,但是它忽略了stream的开始时间(seek之后的开始时间),并且它也没有等待所有的组件都准备完毕(或者说没法保证所有被刷新的组件都处于准备完毕的状态),这就可能会造成同步的问题。
对于Waiting状态,在seekin操作结束之后,clock组件会等待其麾下所有的组件都回复完OMX_IndexConfigTimeClientStartTime
配置之后,选择最早回复的组件设置的时间戳来运行media clock。这样的话就能够确保以下两点:
最后,seeking组件并不一定或者大部分时间并不是一个独立的组件,而是指的是具有seeking功能的组件,比如音频解码、视频解码组件,seeking作为一个功能模块嵌入到具体的组件内部实现。
这一章就讲这些组件类型相关的内容,下一章会对clock组件做一个详细的介绍,会对音视频的同步问题做一个详细的剖析,同步问题(不限于音视频的同步)一直是一个非常大的问题。