OpenMax(OMX) 简析

  • OpenMax
    • 1. 概述
      • 1.1 作用
      • 1.2 分层
    • 2. 结构
      • 2.1 框架
      • 2.2 核心API
      • 2.2 组件API
      • 2.3 端口
      • 2.4 隧道
    • 3. 工作流
      • 3.1 状态机
      • 3.2 数据流
        • 3.2.1 交互
        • 3.2.2 Buffer Allocation and share
        • 3.2.3 端口重新连接
        • 3.2.4 队列
        • 3.2.5 Events and Callbacks
        • 3.2.6 Buffer Payload
      • 3.3 数据结构
        • 3.3.1 header files
        • 3.3.2 Types
        • 3.3.3 Methods
      • 3.4 调用队列
    • 4. 应用
      • 4.1 Video编码(AVC)
    • 5. 参考

1. 概述

开放多媒体加速层(Open Media Acceleration,缩写为OpenMAX).一个免费的跨平台抽象软件层,用于加速在嵌入式和移动设备上的多媒体应用程序中捕获和呈现音频、视频和图像。基于其强大的可移植性,在手持式设备和嵌入式设备的开发上,OpenMax 被广泛地采用。
它是由Khronos Group提出的标准,也由他们来维持,目标在于创造一个统一的接口,加速大量多媒体资料的处理。

1.1 作用

  • 加速跨OS和silicon平台的多媒体组件的开发、整合和编程
  • 使library和codec实现者能够快速有效的利用新silicon的潜在的加速功能,而不关心下层的硬件结构

1.2 分层

  • OpenMAX AL (Application Layer)
    应用层,当前最新版本1.1.
    • The OpenMAX AL 1.1 Reference Guide.
    • OpenMAX-AL提供了应用程序和多媒体中间件之间的标准化接口,其中多媒体中间件提供了执行预期API功能所需的服务。
    • OpenMAX-AL为多媒体接口提供了应用程序的可移植性。
  • OpenMAX IL (Integration Layer)
    集成层,当前最新版本1.1
    • OpenMax IL component sample
    • OpenMAX IL 1.0 Specification
    • OpenMAX IL(集成层)API定义了一个标准化的媒体组件接口,使开发人员和平台提供商能够与硬件或软件中实现的多媒体编解码器进行集成和通信。
    • 它使应用程序和媒体框架能够以统一的方式与多媒体编解码器和支持组件(即源和汇)接口。编解码器本身可以是硬件或软件的任何组合,并且对用户完全透明。
    • IL的主要目标是使用一系列专门的特性,为编解码器提供一定程度的系统抽象,这些特性经过磨练,可以解决许多迥然不同的媒体系统之间的可移植性问题。
  • OpenMAX DL (Development Layer)
    开发层,当前最新版本是1.0.2
    • OpenMAX-DL定义的API,包含一套全面的音频、视频和图像功能,这些功能可以由芯片供应商在新处理器上实现和优化,然后由编解码器供应商用来编写各种编解码器功能。
    • 它包括音频信号处理功能(如fft和滤波器)、图像处理(如色域转换)和视频处理,以实现诸如MPEG-4、H.264、MP3、AAC和JPEG等编解码器的优化实现。

目前普遍使用的是 IL 层。IL 层作为在嵌入式和移动设备中使用的 audio、video、image codecs 的底层接口,使得应用和多媒体框架可以用统一的方式访问多媒体 codec 等相关组间。每一个 component 可以是软件和硬件加速的任意组合,对用户透明。用户只需要按照 OpenMax 规定的 API 来调用即可。

2. 结构

OpenMAX IL API致力于为媒体编解码器提供跨平台阵列的可移植性。该接口抽象了系统的硬件和软件体系结构。每个编解码器和相关转换都封装在组件接口中。OpenMAX IL API允许用户加载、控制、连接和卸载各个组件。这种灵活的核心架构允许集成层轻松实现几乎任何媒体用例,并与现有的基于图形的媒体框架相结合。
IL包含了一个专门的功能库,这些功能经过磨练,可以解决许多迥然不同的媒体系统之间的可移植性问题。这些特征包括:

  • 灵活的基于组件的API核心
  • 能够轻松外挂新的编解码器
  • 在保持由Khronos集团和单个供应商可以简单进行扩展的同时,覆盖目标领域(音频、视频和图像)
  • 不管是静态库还是动态库都可以被实现
  • 保留父软件(如媒体框架)所需的关键功能和配置选项
  • 客户端和编解码器之间以及编解码器之间的通信更加方便

2.1 框架

OpenMax IL API 的软件结构:


SoftwareLandscape.png

OpenMAX IL-API是一种基于组件的媒体API,由两个主要部分组成:核心API和组件API。

2.2 核心API

OpenMax IL Core 用来动态加载和卸载组件,并促进组件通信。

  • 一旦加载,用户可以直接与组件通信,从而消除了很多复杂命令的高消耗。
  • 一旦加载,用户可以在两个组件中间建立沟通隧道,两个组件直接通信

2.2 组件API

OpenMax IL Component 做为一个单元,一个组件实现一个功能,可以作为以下角色:

  • sources
  • sinks
  • codecs
  • filters
  • splitters
  • mixers或者其他的数据处理模块

具体取决于其实现。但是在我们的多媒体处理当中,一个组件,很可能是某个硬件,软件编解码器,处理器或者以上组合。 系统组件概述。 参数描述包括 Buffer状态,错误,一系列的回调函数等。组件之间的通信接口,称之为Port,代表组件和数据流之间的链接,以及保持链接所需要维护的buffers。

组件的结构如下:


Component.png
Payload1.png

例如一个需要实现四个多媒体处理功能的系统,这些功能表示为F1、F2、F3和F4。这些功能可能来自不同的供应商,也可能由内部开发,但由组织内的不同团队开发。

对于安装和拆卸,每种可能有不同的要求。每种方法都有不同的方法来促进配置和数据传输。OpenMAX IL-API提供了一种将这些函数单独或以逻辑组封装到组件中的方法。

该API包括一个标准协议,该协议允许可能来自不同供应商/组的兼容组件彼此交换数据并可交换使用。

在包含四个多媒体处理函数F1、F2、F3和F4的系统中,系统实现者可以为每个函数提供标准的OpenMAX接口。实现者可以很容易地选择函数的任何组合。此功能的划分基于端口。

这些功能的可能组合如下:

Partitions.png

2.3 端口

与组件之间的数据通信接口。

  • 表示组件到数据流的连接
  • 表示维护连接所需的缓冲区
  • 可以通过输入端口向组件发送数据
  • 可以通过输出端口接收数据

2.4 隧道

通信隧道通过将一个组件的输出端口连接到另一个组件的类似格式的输入端口,可以建立两个组件之间的关系。

如下图,可以看到OpenMax所支持的各种类型的通信:

Communication.png

OpenMAL IL的客户端,通过调用四个OpenMAL IL组件,实现了一个功能。四个组件分别是Source组件、Host组件、Accelerator组件和Sink组件。Source组件只有一个输出端口;而Host组件有一个输入端口和一个输出端口;Accelerator组件具有一个输入端口,调用了硬件的编解码器,加速主要体现在这个环节上。Accelerator组件和Sink组件通过私有通讯方式在内部进行连接,没有经过明确的组件端口。

3. 工作流

3.1 状态机

State.png
  • 初始状态unloaded
  • 通过调用core 进入loaded
  • 其他状态的转换可以通过与组件直接通信实现
  • 使用无效数据进行状态转换时,进入invalid 状态
  • 进入invalide 状态后,只有卸载组件,进入unloaded状态,才能退出invalid状态
  • 从idle到loaded的状态,将导致诸如通信缓冲区之类的操作资源丢失
  • 从executing 或者 paused 到 idle 将造成处理缓冲区的上下文丢失

3.2 数据流

3.2.1 交互
Operation.png
  • 当我们从OpenMax Core获得句柄之后,对组件进行相关配置,并完成
  • 可以与组件进行数据通信,该通信是非阻塞的异步回调
  • 输入端口总是从带有OMX_EmptyThisBuffer的IL客户端调用
  • 输出端口总是通过OMX_FillThisBuffer从IL客户端调用
  • 在上下文内实现中,将在返回结果之前回调OMX_EmptyBufferDone或OMX_FillBufferDone。
3.2.2 Buffer Allocation and share
  • 严格的来说,组件只需要根据外部调用的要求,进行如下操作:

    • Provide buffers on all of its supplier ports.
    • Accurately communicate buffer requirements on its ports.
    • Pass a buffer from an output port to an input port with an OMX_EmptyThisBuffer call.
    • Return a buffer from an input port to an output port with an OMX_FillThisBuffer call.
  • 如果组件想要共享缓冲区,可以执行以下操作:

    • Provide re-used buffers on some supplier ports.
    • Account for the needs of shared ports when communicating buffer requirements on ports.
    • Internally pass a buffer from an input port to an output port between an OMX_EmptyThisBuffer call and its corresponding OMX_EmptyBufferDone call.
3.2.3 端口重新连接
  • 端口重新连接使隧道组件可以替换为另一个隧道组件,而不必拆下周围的组件
  • 通过disable和enable可以实现组件的拆卸和安装
  • 通过各个组件的拆卸和安装可以实现多个组件的协同工作
3.2.4 队列
  • 独立的命令行队列,使组件可以刷新没有处理的buffers,并返回给客户端或是通信端口
  • 如下图:组件使用客户端分配的buffers,在收到flush命令前收到5个buffers,并已经处理了两个的数据,收到flush命令之后,组件按照原来的顺序返回了所有包括未处理的buffers,并触发一个结束的event,而客户端会在收到这个event之后再做卸载组件的处理
Flushing.png
3.2.5 Events and Callbacks
  • 组件会发送6种evenets给客户端
    • Error events are enumerated and can occur at any time
    • Command complete notification events are triggered upon successful execution of a command.
    • Marked buffer events are triggered upon detection of a marked buffer by a component.
    • A port settings changed notification event is generated when the component changes its port settings.
    • A buffer flag event is triggered when an end of stream is encountered.
    • A resources acquired event is generated when a component gets resources that it has been waiting for.
3.2.6 Buffer Payload
  • buffers填充数据的方式通常有三种
    • 每个缓冲区全部或部分填充
    • 每个缓冲区只填充完整的压缩数据帧
    • 每个缓冲区只填充一帧压缩数据

3.3 数据结构

3.3.1 header files
  • API被定义在如下头文件:
    • OMX_Types.h: Data types used in the OpenMAX IL
    • OMX_Core.h: OpenMAX IL core API
    • OMX_Component.h: OpenMAX component API
    • OMX_Audio.h: OpenMAX audio domain data structures
    • OMX_IVCommon.h: OpenMAX structures common to image and video domains
    • OMX_Video.h: OpenMAX video domain data structures
    • OMX_Image.h: OpenMAX image domain data structures
    • OMX_Other.h: OpenMAX other domain data structure(includes A/V synchronization)
    • OMX_Index.h: Index of all OpenMAX-defined data structures
3.3.2 Types
3.3.3 Methods

更多详细内容可以在头文件中进一步确认

3.4 调用队列

  • 初始化

    • OMX_GetHandle
    • SetCallbacks
    • in OMX_StateLoaded
    • OMX_SetParameter
    • OMX_AllocateBuffer/ OMX_UseBuffer
    • reveive command
  • 数据流

    • OMX_EmptyThisBuffer
    • OMX_FillThisBuffer
    • OMX_FillBufferDone
    • OMX_EmptyBufferDone
  • 卸载

    • switch to OMX_StateIdle state
    • OMX_FreeBuffer
    • OMX_FreeHandle
  • 端口的禁用(disable)

    • 状态切换到OMX_StateLoaded状态
    • 缓冲区返还客户端
    • 释放所有被禁用端口分配的缓冲区
    • 若端口在OMX_StateLoaded时被禁用,则后续操作会受到影响无法继续
  • 端口的启用(enable)

    • 退出OMX_StateLoaded状态
    • 开始交换缓冲区
    • 开始获取缓冲区
  • 动态配置

    • 视频解码器解析序列头发现输出文件的帧大小与设置的大小不匹配,输出端口buffer需要重新配置时
    • 音频流的参数动态变化时
    • 过程
      • 视频解码器收到一个输入流,当前在OMX_StateExecuting状态,输入输出端口还没有配置
      • 解码器收到序列头,解析出文件的帧大小等信息,并配置给端口
      • 通知客户端通过OMX_PortSettingsChanged
      • 客户端收到通知后,disable input和output 端口
      • 客户端通过OMX_GetConfig获取当前新的配置
      • 通过OMX_UseBuffer 申请新的buffer
      • 通过OMX_SetConfig 设置新的配置
      • 重新enable input 和 output 端口
  • 资源管理器

    • 资源管理器作为内部接口,IL客户端可以不做关注

4. 应用

4.1 Video编码(AVC)

  • 初始化
    result = OMX_Init();
    result = OMX_GetHandle(&m_Handle,
                      (OMX_STRING)"OMX.qcom.video.encoder.avc",
                      NULL, &callbacks);
    
  • 配置
    • SetFrameScale
      Scale和crop设置需要在InputPort和OutputPort配置之前,若cropFrame的配置宽高不为0,则scale的宽高要与crop的宽高相同
      if (crop_width != 0 && crop_height != 0) {
        width = crop_width;
        height = crop_height;
      }
      QOMX_INDEXDOWNSCALAR downscalar;
      OMX_INIT_STRUCT(&downscalar, QOMX_INDEXDOWNSCALAR);
      downscalar.nPortIndex = (OMX_U32)PORT_INDEX_OUT;
      
      downscalar.bEnable = OMX_TRUE;
      downscalar.nOutputWidth = (OMX_U32)width;
      downscalar.nOutputHeight = (OMX_U32)height;
      
      result = OMX_SetParameter(m_Handle,
                      (OMX_INDEXTYPE)OMX_QcomIndexParamVideoDownScalar, (OMX_PTR)&downscalar);
      
    • CropFrame
      打开cropframe的配置开关,具体参数随入帧一起输送给omx
      OMX_ERRORTYPE result = OMX_ErrorNone;
      QOMX_INDEXEXTRADATATYPE e;  // OMX_QcomIndexParamIndexExtraDataType
      OMX_INIT_STRUCT(&e, QOMX_INDEXEXTRADATATYPE);
      e.nPortIndex = (OMX_U32)PORT_INDEX_IN;
      e.nIndex = (OMX_INDEXTYPE)OMX_ExtraDataFrameDimension;
      e.bEnabled = OMX_TRUE;
      result = OMX_SetParameter(m_Handle, (OMX_INDEXTYPE)OMX_QcomIndexParamIndexExtraDataType, (OMX_PTR)&e);
      
    • SetInPortParameters
      OMX_INIT_STRUCT(&portdef, OMX_PARAM_PORTDEFINITIONTYPE);
      portdef.nPortIndex = (OMX_U32) PORT_INDEX_IN;  // input
      result = OMX_GetParameter(m_Handle,
                             OMX_IndexParamPortDefinition,
                             &portdef);
      portdef.format.video.nFrameWidth = nWidth;
      portdef.format.video.nFrameHeight = nHeight;
      portdef.format.video.eColorFormat = (OMX_COLOR_FORMATTYPE)nFormat;
      FractionToQ16(portdef.format.video.xFramerate, static_cast(nFrameRate * 2), 2);
      result = OMX_SetParameter(m_Handle,
                                OMX_IndexParamPortDefinition,
                                &portdef);
      
    • SetOutPortParameters
      OMX_INIT_STRUCT(&portdef, OMX_PARAM_PORTDEFINITIONTYPE);
      result = OMX_GetParameter(m_Handle,
                          OMX_IndexParamPortDefinition,
                          &portdef);
      portdef.nPortIndex = (OMX_U32) PORT_INDEX_OUT;
      portdef.format.video.nFrameWidth = nWidth;
      portdef.format.video.nFrameHeight = nHeight;
      portdef.format.video.nBitrate = nBitRate;
      FractionToQ16(portdef.format.video.xFramerate, static_cast(nFrameRate * 2), 2);
      result = OMX_SetParameter(m_Handle,
                      OMX_IndexParamPortDefinition,
                      &portdef);
      
    • ConfigAVC
      OMX_INIT_STRUCT(&level, OMX_VIDEO_PARAM_PROFILELEVELTYPE);
      level.nPortIndex = (OMX_U32) PORT_INDEX_OUT;
      level.eProfile = userProfile;
      level.eLevel =  eLevel;
      result = OMX_SetParameter(m_Handle,
                              OMX_IndexParamVideoProfileLevelCurrent,
                              &profileLevel);
      
      OMX_INIT_STRUCT(&avcdata, OMX_VIDEO_PARAM_AVCTYPE);
      avcdata.nPortIndex = (OMX_U32)PORT_INDEX_OUT;
      result = OMX_GetParameter(m_Handle,
                             OMX_IndexParamVideoAvc,
                             &avcdata);
      avcdata.nPFrames = AVC_DEFAULT_P_FRAME_NUM;  // update by user
      avcdata.nBFrames = AVC_DEFAULT_B_FRAME_NUM;  // update by user
      avcdata.bUseHadamard = OMX_FALSE;
      avcdata.nRefFrames = AVC_DEFAULT_REF_FRAME_NUM;
      ......
      
      avcdata.eLoopFilterMode = OMX_VIDEO_AVCLoopFilterDisable;
      
      result = OMX_SetParameter(m_Handle,
                              OMX_IndexParamVideoAvc,
                              &avcdata);
      
    • SetBitRate
      OMX_INIT_STRUCT(&bitrate, OMX_VIDEO_PARAM_BITRATETYPE);
      bitrate.nPortIndex = (OMX_U32)PORT_INDEX_OUT;
      result = OMX_GetParameter(m_Handle,
        OMX_IndexParamVideoBitrate, (OMX_PTR)&bitrate);
      if (bSetEControlRate) {
        bitrate.eControlRate = (OMX_VIDEO_CONTROLRATETYPE)eControlRate;  // ControlRate
      }
      
      bitrate.nTargetBitrate = nBitRate;
      result = OMX_SetParameter(m_Handle,
        OMX_IndexParamVideoBitrate, (OMX_PTR)&bitrate);
      
    • ......
  • 状态迁移
    • 不需要等待
    result = OMX_SendCommand(m_Handle, OMX_CommandStateSet,OMX_StateIdle, NULL);
    
  • 配置buffer
    • 这里OMX提供了两个接口,根据自己的需求选择。
    • OMX_UseBuffer
      • buffer由客户端申请管理,并通过该接口交换给OMX
      for (i = 0; i < m_InputBufferCount; i++) {
        result = OMX_UseBuffer(m_Handle,
                    &m_InputBufferHeaders[i],
                    (OMX_U32) PORT_INDEX_IN,
                    NULL,
                    m_InputBufferSize,
                    m_InputBuffer + m_InputBufferSize * i);
      }
      for (i = 0; i < m_OutputBufferCount; i++) {
        result = OMX_UseBuffer(m_Handle,
                            &m_OutputBufferHeaders[i],
                            (OMX_U32) PORT_INDEX_OUT,
                            NULL,
                            m_OutputBufferSize,
                            m_OutputBuffer + m_OutputBufferSize * i);
        CHECK_RESULT("use output buffer failed", result);
      }
      
      • 如果涉及到extradata,MetaMode一定不要为它们申请整块的缓存,不利于后续extradata的灵活配置
      • allocate buffer manager 需要与hardware 适配,ION/GBM等选择要慎重灵活
    • OMX_AllocateBuffer
      • 由OMX进行buffer申请和管理
      for (i = 0; i < m_InputBufferCount; i++) {
        result = OMX_AllocateBuffer(m_Handle,
                            &m_InputBufferHeaders[i],
                            (OMX_U32) PORT_INDEX_IN,
                            NULL,
                            m_InputBufferSize);
      }
      for (i = 0; i < m_OutputBufferCount; i++) {
        result = OMX_AllocateBuffer(m_Handle,
                            &m_OutputBufferHeaders[i],
                            (OMX_U32) PORT_INDEX_OUT,
                            NULL,
                            m_OutputBufferSize);
        CHECK_RESULT("use output buffer failed", result);
      }
      
  • 状态迁移
    • 需要等待
    result = OMX_SendCommand(m_Handle, OMX_CommandStateSet,OMX_StateExecuting, NULL);
    
  • 编码
    • 启动两个线程完成Input和Output的工作


      encode.png
  • 编码结束,状态迁移
      result = OMX_SendCommand(m_Handle, OMX_CommandStateSet,OMX_StateIdle, NULL);
    
  • 资源释放
    for (i = 0; i < m_InputBufferCount; i++) {
        if (m_InputBufferHeaders[i] != NULL) {
            result = OMX_FreeBuffer(handle,
                                 PORT_INDEX_IN,
                                 m_InputBufferHeaders[i]);
            CHECK_RESULT("free input buffer header failed", result);
        }
    }
    
    for (i = 0; i < m_OutputBufferCount; i++) {
        if (m_OutputBufferHeaders[i] != NULL) {
            result = OMX_FreeBuffer(handle,
                                 PORT_INDEX_OUT,
                                 m_OutputBufferHeaders[i]);
            CHECK_RESULT("free output buffer header failed", result);
        }
    }
    
    若是客户端申请的buffer,由客户端主动释放
  • 卸载
    if (m_Handle != NULL) {
        OMX_FreeHandle(m_Handle);
        m_Handle = NULL;
    }
    

5. 参考

  • openmax
  • 百度百科

你可能感兴趣的:(OpenMax(OMX) 简析)