OpenMAX编程-音视频等组件介绍

我的小站

往期文章索引:
03 - OpenMAX编程-实现一个组件
02 - OpenMAX编程-数据结构
01 - OpenMAX编程-组件
00 - OpenMAX编程初识

导读:
本文着重介绍不同类型组件的具体构成(参数类型、特性设置等),包括audio、video、image等组件。另外对OpenMAX的一些扩展用法以及以前文章当中的缺漏进行补充。

音频组件-audio domain

从spec里面截取一张图,如下所示:

OpenMAX编程-音视频等组件介绍_第1张图片
audio应用举例
  1. 第一个流程图展示了来自不同输入源的音频经过混合送入耳机播放的流程,其中一路是MP3,与另一路经过均衡器调节的MIDI格式的音频同时送入音频混合组件进行混合处理,最后经过立体声效果增强,输送给耳机播放。流程图中的每一个节点都可以作为一个组件存在,当然也可以组合其中几个作为一个组件(比如MP3与MIDI作为同一个组件,内部区分输入文件格式)、mix加立体声效果增强作为一个组件,音频播放作为一个组件。更甚者,将它们全部合并在同一个组件里面进行实现都是可以的。

应用场景:用作不同格式的音频混合,比如在不同的物理环境下录制一段不同格式的音频文件,然后使用软件将其混合起来。

  1. 第二个流程图中分有两路输入与两路输出,其中一路是下行音频数据流(经过编码压缩的),输入给音频解码组件进行解码,然后一路给喇叭外放,另一路与麦克风输入音频组合进行回音消除,然后经过编码生成上行音频数据流进行传输。

应用场景:对讲设备,比如微信或者QQ语音聊天,双方可以同时讲话,从第一视角来看,下行数据流指的就是对方讲的话经过通讯设备(手机等)采集编码之后网络传输过来的音频信息,需要经过解码在自己的通讯设备上面播放(这样才能听到对方讲话)。同时自己说的话需要与下行音频数据流组合进行回音消除(试想没有回音消除,自己说的话传过去,对面外放的同时又被对面的通讯设备采集再次发送过来,不断循环就会导致回声问题,需要与下行音频数据流组合的原因就是需要其做分析进行回声消除),然后经过编码形成上行音频数据流传递给对方。

特殊情况

  1. 未压缩数据的最小加载大小
    对于所有的未经过压缩的音频数据流类型,OpenMAX规定了其最小的数据加载包大小。对于PCM audio来说最小数据加载大小是5ms,此时音频组件的输出端口一次产生的数据包最小得是5ms的,另外此限制仅针对PCM格式(例如OMX_AUDIO_CodingADPCM格式)。
  2. MIDI格式的全文件缓冲
    所有的MIDI格式媒体文件都会包含多个并行音轨,但是在文件里面是以串行的方式进行存放的,存放顺序并不是实时播放时候的交错顺序。(也就是说文件存放是按照音轨1、音轨2来顺序存放的,而不是音轨1与2内容交叉存放)。并且MIDI格式下,音频播放的状态只有在从文件头部处理的时候才是确定的,如果需要进行跳播的话仍然需要处理一部分文件头部的内容,猜测需要这样做的原因是在文件头部会存放音频的各个信息,比如全音符,四分之一音符的持续长度等等,由于音轨串行排列并且字节是变长存储的,所以需要在OMX_AUDIO_PARAM_MIDITYPE结构体的nFileSize成员中存放整个文件的大小。

音频通用设置

  1. OMX_AUDIO_CODINGTYPE
    用来标识不同的编码格式,具体有PCM、AMR等等,该枚举变量用在组件的port结构体定义与参数定义当中,可以通过set parameter回调接口进行设置,也可以在组件内部指定(不同的端口可以处理不同格式的音频数据,使用该枚举类型进行标识)。
  2. audio端口
    如[02 - OpenMAX编程-数据结构]一文中有关端口部分描述所讲,一个组件的端口会由几个结构体来共同描述,大致有OMX_PORT_PARAM_TYPE(描述端口数量、起始端口号等),OMX_PARAM_PORTDEFINITIONTYPE(端口定义),还有特定类型端口的参数结构体描述,例如OMX_VIDEO_PARAM_PORTFORMATTYPE,OMX_AUDIO_PARAM_PORTFORMATTYPE等。后面两种类型的结构体通常是数组形式的,两者一一对应,用来描述一个端口。
  • OMX_AUDIO_PORTDEFINITIONTYPE:音频类型的端口定义,作为一个共用体成员存在于OMX_PARAM_PORTDEFINITIONTYPE结构体内部,使用的时候通常直接初始化一个OMX_PARAM_PORTDEFINITIONTYPE类型的结构体变量。
  • OMX_AUDIO_PARAM_PORTFORMATTYPE:顾名思义,该结构体描述了端口支持的音频流数据格式,它是一个枚举类型。支持使用OMX_Get/SetParameter来获取、设置端口的格式。nIndex的值范围是0~N-1,N就是该端口所支持的格式数量,IL Client可以通过枚举所有支持的格式来获知N的大小(枚举过程以返回OMX_ErrorNoMore为结束)。
  1. Param与config
  • OMX_AUDIO_PARAM_PCMMODETYPE:该结构体是格式特定的参数描述,有PCM、MP3等等,每一种格式的参数描述都不同,都有与之对应的结构体类型。其内容就不再一一描述,都是与特定格式有关的。该种类型的参数都可以用OMX_Get/SetParameter来进行获取、设置。
  • OMX_AUDIO_CONFIG_VOLUMETYPE:这些是与配置相关的结构体类型,有音量、通道音量、均衡等等,该种类型的参数都可以用OMX_Get/SetConfig来进行获取、设置。

视频图像组件-audio&image domain

视频图像通用设置

该部分介绍视频、图像通用的一些Config与Param设置,这部分类型定义在OMX_ivcommon.h头文件中可以找到。

  1. 未压缩的数据格式
    这部分使用一个OMX_COLOR_FORMATTYPE枚举类型来描述,包括RGB的8、16、32位未压缩格式与YUV格式极其子格式等的枚举。未压缩数据格式有最小加载大小的要求,由视频或者图像端口定义结构体OMX_IMAGE/VIDEO_PORTDEFINITIONTYPE中的nSliceHeightnStride成员共同决定,两个成员前者表示“跨度”、后者表示“切片”,这个在不同的场合有不同的解释。例如:对于一个YUV图像来说,“跨度”就可以理解为图像实际的大小加上扩展内容,如下图所示:
OpenMAX编程-音视频等组件介绍_第2张图片
image stride

stride可能会有正值有负值,正值表示从左至右、从上至下扫描,负值表示从左至右、从下至上扫描。“切片”就是切片编码(如H264,该编码有条带的概念)时使用到的高度,可以简单的使用(nSliceHeight * nStride)(条带大小)的绝对值来作为最小传输大小。
2. 未压缩数据格式的要求
每一个image或者video组件对于未压缩的数据缓冲区都有一些要求,这些要求是为了使得不同供应商之间的数据能够正常地被处理。比如这些数据有以下几种要求:

  • 每一个非空的数据缓冲区都应该包含至少一个完整的slice,除非是遇到数据的结尾(比如未对齐的情况下:图像高度100,要求slice 16,那么最后一个slice就只有4,称之为遇到数据的结尾)
  • 每一个非空的数据缓冲区都应该包含有整数倍数个slice高度
  • 当未压缩的数据格式是平面(planar)模式的话,来自两个平面的数据不能保存在同一个缓冲区内。比如YUV420有平面模式的格式(如NV21),它有两个平面,那么在两个平面的交界处就不能够使用同一个缓冲区来存放跨越交界处的平面数据
  • 对于YUV420、YUV422、YUV411(YUV420)格式pack类型的数据来说,每一个缓冲区内部的slice都包含有Y、U、V的plane,其中YUV分量都是按照顺序来排列的。前面说过的nSliceHeight指明了Y分量的slice高度值,剩下的UV分量所占据的空间大小就根据YUV的格式以及Y分量的高度值来决定。比如YUV420格式下nSliceHeight为16,就表明有16个跨度的Y分量,加上合起来8个跨度的UV分量,合计16+8个跨度(注:有关YUV格式可以去看相关的spec)。这可以使得端口同时处理slice内的三个分量数据,而不用缓冲一整张image图片之后才去进行处理。

3.parameter与config
该部分的内容众多,这里就不再把所有的条目都列出来了。

  • OMX_IndexParamCommonDeblocking:操作对象是OMX_CONFIG_DEBLOCKINGTYPE结构体,用于决定是否使能编解码模块的去除块状伪影功能(编码时编码块之间的边缘伪影,编码时块之间没有平滑过渡导致的编码出现边缘不一致问题)。
  • OMX_IndexParamCommonSensorMode:操作对象是OMX_CONFIG_SENSORMODETYPE结构体,用于设置sensor的帧率、分辨率。
  • OMX_IndexConfigCommonColorFormatConversion:操作对象是OMX_CONFIG_COLORCONVERSIONTYPE结构体。用于做格式转换,在需要从RGB转为YUV格式时用到。
    剩下的还有很多,需要结合spec文档来进行梳理解析。

video组件

  1. 通用枚举类型
  • OMX_VIDEO_CODINGTYPE
    • OMX_VIDEO_CodingUnused:未实现编码功能
    • OMX_VIDEO_CodingAutoDetect:自动检测编码类型(组件内部完成)
    • OMX_VIDEO_CodingMPEG2:MPEG2格式编码
    • OMX_VIDEO_CodingH263:H263格式编码
    • OMX_VIDEO_CodingMPEG4:MPEG4格式编码
    • OMX_VIDEO_CodingWMV:WMV格式编码
    • OMX_VIDEO_CodingRV:RV格式
    • OMX_VIDEO_CodingAVC:H264格式
    • OMX_VIDEO_CodingMJPEG:MJPEG格式
  • OMX_VIDEO_PICTURETYPE:标识视频的一帧
    • OMX_VIDEO_PictureTypeI:通用I帧
    • OMX_VIDEO_PictureTypeP:通用P帧
    • OMX_VIDEO_PictureTypeB:通用B帧
    • OMX_VIDEO_PictureTypeSI:H.263的SI帧
    • OMX_VIDEO_PictureTypeSP:H.263的SP帧
    • OMX_VIDEO_PictureTypeEI:H.264的EI帧
    • OMX_VIDEO_PictureTypeEP:H.264的EP帧
    • OMX_VIDEO_PictureTypeS:MPEG-4的S帧
      视频帧的标识可以加在编码后的帧信息里面以供后续解码或者mux进行使用。编码格式需要在port端口的属性里面进行设置。
OpenMAX编程-音视频等组件介绍_第3张图片
index以及对应的参数结构体
OpenMAX编程-音视频等组件介绍_第4张图片
video组件实例

上图中第一个框图是一个图像采集编码、预览一体化的video组件实例,从camera硬件采集图像数据,经过图像过滤器,然后经由一个分发器输出一路视频给video显示组件进行预览,一路送由H263编码组件编码最终写入到文件里面。
下面的一幅图可以看作一个简单的视频通话通路。首先是上行数据通路:从camera硬件采集数据经过分发器分出一路给H263编码,然后送给网络上行链路传输给对方,另一路与下行数据解码之后的未压缩视频数据混合(就是聊天时的大图与小图,一个是对方的相机图像,一个是本机的图像,大小图可以切换,混合就是将两路视频流叠放在一块的过程)之后送由显示组件显示到本机上面。

2.端口相关结构体

  • OMX_VIDEO_PORTDEFINITIONTYPE:端口的定义结构体,该结构体用来描述一个端口,如果需要额外参数的话可以添加额外的结构体用于描述端口属性,比如OMX_VIDEO_PARAM_BITRATETYPE结构体用以描述端口的bit率。使用OMX_Index_ParamVideoPort这个index可以获取/设置端口的属性。成员中需要注意的是nStridenSliceHeight两个,这两个成员在前面也提到过。
  • OMX_VIDEO_PARAM_PORTFORMATTYPE:用以描述端口所支持的格式,这部分与音频对应的结构体意义几乎一样,结构体成员的意义可以参考音频部分的描述。
    与音频相同,视频组件也有很多TYPE类型结构体用以描述一些video属性,例如OMX_VIDEO_PARAM_QUANTIZATIONTYPE等,这些结构体都可以通过Set/GetParameter来进行设置获取,这里就不一一详述,spec上面描述的很清楚。值得注意的是,不是所有的video组件都能用得上所有的TYPE的,比如video sink就用不着与编码相关的TYPE,所以细分的video组件可能仅仅会使用到其中部分TYPE,这个要视组件细分类型而定。

image

image相关的定义在OMX_Image.h头文件中描述。下图描述了image相关的param参数类型:

OpenMAX编程-音视频等组件介绍_第5张图片
index以及对应的参数结构体
OpenMAX编程-音视频等组件介绍_第6张图片
image组件举例

如上图中框图所描述的,这里给出了类似video组件第一种用法的场景。camera获取图像经由image过滤器,然后由分发器分出一路显示图像,一路编成H263然后写入到文件。纠正:spec这里画错了,H.263 encode应该是jpeg encode,所以是编成jpeg格式图像写入文件。况且单幅图像也发挥不出H.263编码的特性。

端口相关的结构体如下:

  • OMX_IMAGE_PORTDEFINITIONTYPE:端口定义(与video的极为相似,不再赘述),其中不同细分类型组件的输入输出端口数量也是不一样的。
    • 输入组件(source组件)没有input,有一个output
    • 分发组件有一个input与多个output
    • 处理单元只有一个input一个output
    • mix组件有多个input,一个output
    • 输出组件(sink组件/显示)有一个input,没有output
      注意这里说的没有input指的是没有更上游的组件来提供输入,该组件(输入组件)的input就来自于camera的硬件驱动,没有output也同理,是指的是没有下游组件再来接收数据,该组件(输出组件)的output就是屏硬件驱动。再者,input与output的数量并不是严格界定的,而是根据具体的使用场景有具体的数量设置。
  • OMX_IMAGE_PARAM_PORTFORMATTYPE:与video的一样。

1.参数控制
image有些比较特殊的参数类型(TYPE),比如:

  • OMX_IMAGE_PARAM_FLASHCONTROLTYPE:该结构体参数就是用来控制闪光灯的(拍照的闪光灯功能),其中eFlashControl成员可以设置闪光灯常亮、关闭、自动、红眼消除(拍照时的干扰,平时黑暗的地方手机拍照就可能出现人眼球部分是红色的,参考google相关解释)。
  • OMX_IMAGE_PARAM_FOCUSCONTROLTYPE:聚焦,用于焦距调节。eFocusControl成员可以指定手动调焦、关闭调焦、自动调焦、自动锁定目标焦距等等。
    其余的与video的很类似,在spec中可以看到比较详细的描述。

组件扩展API与通用组件

API扩展

很多时候,组件内部标准的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回调函数
    该宏专门用于获取扩展的index索引值,该回调函数最终会传入一个字符串给特定的组件,组件会根据字符串去找到对应的索引值返回给调用者,对于组件来说,OpenMax已有的标准的index可以不必实现该回调函数。该回调函数指向的的定义如下:
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);

通用组件类型

seeking组件

可以理解为是跳播组件,也就是手机播放器上面快进、点击拖动到指定的位置播放,该种组件的播放源可以是本地的文件,也可以是远程的文件(在线视频文件),除了提供跳播功能之外,IL Client也会有获取文件当前播放位置的需求,所以组件也要提供该种功能。

  1. seeking配置
  • OMX_IndexConfigTimePosition
    该参数对应的结构体描述是OMX_TIME_CONFIG_TIMESTAMPTYPE,IL Client可以通过该索引来获取/设置组件的时间戳控制文件的播放。
  • OMX_IndexConfigTimeSeekMode
    seek模式设置,如果是OMX_TIME_SeekModeFast模式的话表明是快速跳播,意思就是尽可能的快速切换到所要跳播的位置,即使最终跳播到的位置并不是精确的跳播位置(速度优先)。OMX_TIME_SeekModeAccurate模式的话表明是以精确度为优先,及时因此导致跳播耗时较长也在所不惜。

数据流中的任意跳播请求会指向任意的目标位置,该位置的数据可能会依赖于前面的数据。比如跳播时指定的时间点对应整个数据流的某一帧,而该帧要解码必须依赖于前面若干数量帧(需要重建解码数据结构),那么Fast模式就指的是从能够解码的第一帧开始显示(该帧极大概率会比实际请求跳播的那一帧要靠前),此为牺牲跳播的精度。如果是Accurate模式则是等待整个解码结构体重建完毕,直到解码到真正请求的那一帧开始显示,此为精度优先,可能会牺牲跳播时间。

2.seeking缓冲区标志
缓冲区标志用于sekking组件告诉下游组件传递给它的buffer的具体作用,通常情况下,需要加flag的buffer都是一个缓冲区单元里面的第一个逻辑数据帧,就比如上面说的重建解码结构体需要的数据帧,该部分的帧就需要加上一个flag。

  • OMX_BUFFERFLAG_DECODEONLY
    只解码标志,还记得上面说的Accurate模式吗?在该模式下,用于重建解码结构体的帧(非跳播指定的帧)就需要加上该flag,当负责显示的组件接收到带有该flag的帧的时候就不能再去显示这些帧。
  • OMX_BUFFERFLAG_STARTTIME
    该flags表明该帧数据携带整个播放数据流的开始时间戳,对于Fast模式来说就是重建解码结构体需要的第一帧数据,对于Accurate模式来说就是跳播位置所在的第一帧。当一个时钟组件的客户端(使用时钟组件的组件,例如video render组件)收到带有该flag的buffer,就会调用SetConfig使用OMX_IndexConfigTimeClientStartTime索引来设置时钟组件,该索引表明客户端的数据已经准备好要播放。

3.seek事件的产生步骤
该步骤是由IL Client来完成的:

  • 使用OMX_IndexConfigTimeClockState索引来设置OMX_TIME_CONFIG_CLOCKSTATETYPEeState成员为OMX_TIME_ClockStateStopped,停止组件的media clock。
  • 使用OMX_IndexConfigTimePosition索引来设置需要跳转的时间戳。
  • 刷新所有的组件。
  • 使用OMX_IndexConfigTimeClockState索引来设置OMX_TIME_CONFIG_CLOCKSTATETYPEeState成员为OMX_TIME_ClockStateRunning或者OMX_TIME_ClockStateWaitingForStartTime状态。

对于Running状态,在seeking操作结束之后,clock组件就立即开始media clock的流动,这个方法虽然比转换到Waiting状态要简单,但是它忽略了stream的开始时间(seek之后的开始时间),并且它也没有等待所有的组件都准备完毕(或者说没法保证所有被刷新的组件都处于准备完毕的状态),这就可能会造成同步的问题。
对于Waiting状态,在seekin操作结束之后,clock组件会等待其麾下所有的组件都回复完OMX_IndexConfigTimeClientStartTime配置之后,选择最早回复的组件设置的时间戳来运行media clock。这样的话就能够确保以下两点:

  • 所有的组件都准备好处理数据了,消除组件之间的初始化漂移。
  • media clock开始时间会根据所有的Client组件的反馈值来进行确认,达到准确性、适应性的要求。

最后,seeking组件并不一定或者大部分时间并不是一个独立的组件,而是指的是具有seeking功能的组件,比如音频解码、视频解码组件,seeking作为一个功能模块嵌入到具体的组件内部实现。

这一章就讲这些组件类型相关的内容,下一章会对clock组件做一个详细的介绍,会对音视频的同步问题做一个详细的剖析,同步问题(不限于音视频的同步)一直是一个非常大的问题。


如果觉得本文章不错,请关注微信公众号-YellowMax多多支持,查看更多文章

你可能感兴趣的:(音视频文字处理,OpenMAX框架拆解与实现)