Android多媒体开发(六)----Android中OpenMax的实现(preview)

       上一篇对OpenMax有了简单介绍,本篇就讲讲Android上对OpenMax IL层的实现。(可以忽略,下一篇会分析流程)

OpenMax的接口与实现

       在Android中实现OpenMax IL层和标准的OpenMax IL层的方式基本,一般需要实现以下两个环节。

  • 编解码驱动程序:位于Linux内核空间,需要通过Linux内核调用驱动程序,通常使用非标准的驱动程序。
  • OpenMax IL层:根据OpenMax IL层的标准头文件实现不同功能的组件。

       Android中还提供了OpenMax的适配层接口(对OpenMax IL的标准组件进行封装适配),它作为Android本地层的接口,可以被Android的多媒体引擎调用。

OpenMax IL层接口

       OpenMax IL层的接口定义由若干个头文件组成,这也是实现它需要实现的内容,位于frameworks/native/include/media/openmax下,它们的基本描述如下所示:

OMX_Types.h:OpenMax Il的数据类型定义
OMX_Core.h:OpenMax IL核心的API
OMX_Component.h:OpenMax IL 组件相关的 API
OMX_Audio.h:音频相关的常量和数据结构
OMX_IVCommon.h:图像和视频公共的常量和数据结构
OMX_Image.h:图像相关的常量和数据结构
OMX_Video.h:视频相关的常量和数据结构
OMX_Other.h:其他数据结构(包括A/V 同步)
OMX_Index.h:OpenMax IL定义的数据结构索引
OMX_ContentPipe.h:内容的管道定义

       提示:OpenMax标准只有头文件,没有标准的库,设置没有定义函数接口。对于实现者,需要实现的主要是包含函数指针的结构体。

       其中,OMX_Component.h中定义的OMX_COMPONENTTYPE结构体是OpenMax IL层的核心内容,表示一个组件,其内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
typedef struct OMX_COMPONENTTYPE    
{
OMX_U32 nSize; /* 这个结构体的大小 */
OMX_VERSIONTYPE nVersion; /* 版本号 */
OMX_PTR pComponentPrivate; /* 这个组件的私有数据指针. */

/* 调用者(IL client)设置的指针,用于保存它的私有数据,传回给所有的回调函数 */
OMX_PTR pApplicationPrivate;
/* 以下的函数指针返回OMX_core.h中的对应内容 */
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_ERRORTYPE (*GetParameter)(/* 获得参数 */
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_INDEXTYPE nParamIndex,
OMX_INOUT OMX_PTR pComponentParameterStructure);
OMX_ERRORTYPE (*SetParameter)(/* 设置参数 */
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_INDEXTYPE nIndex,
OMX_IN OMX_PTR pComponentParameterStructure);
OMX_ERRORTYPE (*GetConfig)(/* 获得配置 */
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_INDEXTYPE nIndex,
OMX_INOUT OMX_PTR pComponentConfigStructure);
OMX_ERRORTYPE (*SetConfig)(/* 设置配置 */
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_INDEXTYPE nIndex,
OMX_IN OMX_PTR pComponentConfigStructure);
OMX_ERRORTYPE (*GetExtensionIndex)(/* 转换成OMX结构的索引 */
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_STRING cParameterName,
OMX_OUT OMX_INDEXTYPE* pIndexType);
OMX_ERRORTYPE (*GetState)(/* 获得组件当前的状态 */
OMX_IN OMX_HANDLETYPE hComponent,
OMX_OUT OMX_STATETYPE* pState);
OMX_ERRORTYPE (*ComponentTunnelRequest)(/* 用于连接到另一个组件*/
OMX_IN OMX_HANDLETYPE hComp,
OMX_IN OMX_U32 nPort,
OMX_IN OMX_HANDLETYPE hTunneledComp,
OMX_IN OMX_U32 nTunneledPort,
OMX_INOUT OMX_TUNNELSETUPTYPE* pTunnelSetup);
OMX_ERRORTYPE (*UseBuffer)(/* 为某个端口使用Buffer */
OMX_IN OMX_HANDLETYPE hComponent,
OMX_INOUT OMX_BUFFERHEADERTYPE** ppBufferHdr,
OMX_IN OMX_U32 nPortIndex,
OMX_IN OMX_PTR pAppPrivate,
OMX_IN OMX_U32 nSizeBytes,
OMX_IN OMX_U8* pBuffer);
OMX_ERRORTYPE (*AllocateBuffer)(/* 在某个端口分配Buffer */
OMX_IN OMX_HANDLETYPE hComponent,
OMX_INOUT OMX_BUFFERHEADERTYPE** ppBuffer,
OMX_IN OMX_U32 nPortIndex,
OMX_IN OMX_PTR pAppPrivate,
OMX_IN OMX_U32 nSizeBytes);
OMX_ERRORTYPE (*FreeBuffer)(/*将某个端口Buffer释放*/
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_U32 nPortIndex,
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
OMX_ERRORTYPE (*EmptyThisBuffer)(/* 让组件消耗这个Buffer */
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
OMX_ERRORTYPE (*FillThisBuffer)(/* 让组件填充这个Buffer */
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
OMX_ERRORTYPE (*SetCallbacks)(/* 设置回调函数 */
OMX_IN OMX_HANDLETYPE hComponent,
OMX_IN OMX_CALLBACKTYPE* pCallbacks,
OMX_IN OMX_PTR pAppData);
OMX_ERRORTYPE (*ComponentDeInit)(/* 反初始化组件 */
OMX_IN OMX_HANDLETYPE hComponent);
OMX_ERRORTYPE (*UseEGLImage)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_INOUT OMX_BUFFERHEADERTYPE** ppBufferHdr,
OMX_IN OMX_U32 nPortIndex,
OMX_IN OMX_PTR pAppPrivate,
OMX_IN void* eglImage);
OMX_ERRORTYPE (*ComponentRoleEnum)(
OMX_IN OMX_HANDLETYPE hComponent,
OMX_OUT OMX_U8 *cRole,
OMX_IN OMX_U32 nIndex);
} OMX_COMPONENTTYPE;

       1)EmptyThisBuffer和FillThisBuffer是驱动组件运行的基本的机制,前者表示让组件消耗缓冲区,表示对应组件输入的内容;后者表示让组件填充缓冲区,表示对应组件输出的内容。
       2)UseBuffer,AllocateBuffer,FreeBuffer为和端口相关的缓冲区管理函数,对于组件的端口有些可以自己分配缓冲区,有些可以使用外部的缓冲区,因此有不同的接口对其进行操作。
       3)SendCommand表示向组件发送控制类的命令。GetParameter,SetParameter,GetConfig,SetConfig几个接口用于辅助的参数和配置的设置和获取。
       4)ComponentTunnelRequest用于组件之间的隧道化连接,其中需要制定两个组件及其相连的端口。
       5)ComponentDeInit用于组件的反初始化。

       OMX_COMPONENTTYPE结构体实现后,其中的各个函数指针就是调用者可以使用的内容。各个函数指针和OMX_core.h中定义的内容相对应
       提示:OpenMax函数的参数中,经常包含OMX_IN和OMX_OUT等宏,它们的实际内容为空,只是为了标记参数的方向是输入还是输出。

       OMX_Component.h中端口类型的定义为OMX_PORTDOMAINTYPE枚举类型,内容如下所示:

1
2
3
4
5
6
7
8
9
typedef enum OMX_PORTDOMAINTYPE {   
OMX_PortDomainAudio, /* 音频类型端口 */
OMX_PortDomainVideo, /* 视频类型端口 */
OMX_PortDomainImage, /* 图像类型端口 */
OMX_PortDomainOther, /* 其他类型端口 */
OMX_PortDomainKhronosExtensions = 0x6F000000, //为Khronos标准预留宽展
OMX_PortDomainVendorStartUnused = 0x7F000000 //为厂商预留扩展
OMX_PortDomainMax = 0x7ffffff
} OMX_PORTDOMAINTYPE;

       音频类型,视频类型,图像类型,其他类型是OpenMax IL层此所定义的四种端口的类型。

端口具体内容的定义使用OMX_PARAM_PORTDEFINITIONTYPE类(也在OMX_Component.h中定义)来表示,其内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
typedef struct OMX_PARAM_PORTDEFINITIONTYPE {   
OMX_U32 nSize; /* 结构体大小 */
OMX_VERSIONTYPE nVersion; /* 版本*/
OMX_U32 nPortIndex; /* 端口号 */
OMX_DIRTYPE eDir; /* 端口的方向 */
OMX_U32 nBufferCountActual; /* 为这个端口实际分配的Buffer的数目 */
OMX_U32 nBufferCountMin; /* 这个端口最小Buffer的数目*/
OMX_U32 nBufferSize; /* 缓冲区的字节数 */
OMX_BOOL bEnabled; /* 是否使能 */
OMX_BOOL bPopulated; /* 是否在填充 */
OMX_PORTDOMAINTYPE eDomain; /* 端口的类型 */
union { /* 端口实际的内容,由类型确定具体结构 */
OMX_AUDIO_PORTDEFINITIONTYPE audio;
OMX_VIDEO_PORTDEFINITIONTYPE video;
OMX_IMAGE_PORTDEFINITIONTYPE image;
OMX_OTHER_PORTDEFINITIONTYPE other;
} format;
OMX_BOOL bBuffersContiguous;
OMX_U32 nBufferAlignment;
} OMX_PARAM_PORTDEFINITIONTYPE;

       对于一个端口,其重点的内容如下:

  • 端口的方向(OMX_DIRTYPE):包含OMX_DirInput(输入)和OMX_DirOutput(输出)两种
  • 端口分配的缓冲区数目和最小缓冲区数目
  • 端口的类型(OMX_PORTDOMAINTYPE):可以是四种类型
  • 端口格式的数据结构:使用format联合体来表示,具体由四种不同类型来表示,与端口的类型相对应
    OMX_AUDIO_PORTDEFINITIONTYPE,OMX_VIDEO_PORTDEFINITIONTYPE,OMX_IMAGE_PORTDEFINITIONTYPE和OMX_OTHER_PORTDEFINITIONTYPE等几个具体的格式类型,分别在OMX_Audio.h,OMX_Video.h,OMX_Image.h和OMX_Other.h这四个头文件中定义。

       OMX_Core.h中定义的枚举类型OMX_STATETYPE命令表示OpenMax的状态机,内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
typedef enum OMX_STATETYPE   
{
OMX_StateInvalid, /* 组件监测到内部的数据结构被破坏 */
OMX_StateLoaded, /* 组件被加载但是没有完成初始化 */
OMX_StateIdle, /* 组件初始化完成,准备开始 */
OMX_StateExecuting, /* 组件接受了开始命令,正在树立数据 */
OMX_StatePause, /* 组件接受暂停命令*/
OMX_StateWaitForResources, /* 组件正在等待资源 */
OMX_StateKhronosExtensions = 0x6F000000, /* 保留for Khronos */
OMX_StateVendorStartUnused = 0x7F000000, /* 保留for厂商 */
OMX_StateMax = 0X7FFFFFFF
} OMX_STATETYPE;

       OpenMax组件的状态机可以由外部的命令改变,也可以由内部发生的情况改变。OpenMax IL组件的状态机的迁移关系如图所示:
状态机

       OMX_Core.h中定义的枚举类型OMX_COMMANDTYPE表示对组件的命令类型,内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
typedef enum OMX_COMMANDTYPE   
{
OMX_CommandStateSet, /* 改变状态机器 */
OMX_CommandFlush, /* 刷新数据队列 */
OMX_CommandPortDisable, /* 禁止端口 */
OMX_CommandPortEnable, /* 使能端口 */
OMX_CommandMarkBuffer, /* 标记组件或Buffer用于观察 */
OMX_CommandKhronosExtensions = 0x6F000000, /* 保留for Khronos */
OMX_CommandVendorStartUnused = 0x7F000000, /* 保留for厂商 */
OMX_CommandMax = 0X7FFFFFFF
} OMX_COMMANDTYPE;

       OMX_COMMANDTYPE类型在SendCommand调用中作为参数被使用,其中OMX_CommandStateSet就是改变状态机的命令。

OpenMax IL实现的内容

       对于OpenMax IL层的实现,一般的方式并不调用OpenMax DL层。具体实现的内容就是各个不同的组件。
       OpenMax IL组件的实现包含以下两个步骤:

  • 组件的初始化函数:硬件和OpenMax数据结构的初始化,一般分成函数指针初始化、私有数据结构的初始化、端口的初始化等几个步骤,使用OMX_Component.h其中的pComponentPrivate成员保留本组件的私有数据为上下文,最后获得填充完成OMX_COMPONENTTYPE类型的结构体。
  • OMX_COMPONENTTYPE类型结构体的各个指针:实现其中的各个函数指针,需要使用私有数据的时候,从其中的pComponentPrivate得到指针,转化成实际的数据结构使用。

       端口的定义是OpenMax IL组件对外部的接口。OpenMax IL常用的组件大都是输入和输出端口各一个。对于最常用的编解码(Codec)组件,通常需要在每个组件的实现过程中,调用硬件的编解码接口来实现。在组件的内部处理中,可以建立线程来处理。OpenMax的组件的端口有默认参数,但也可以在运行时设置,因此一个端口也可以支持不同的编码格式。音频编码组件的输出和音频编码组件的输入通常是原始数据格式(PCM格式),视频编码组件的输出和视频编码组件的输入通常是原始数据格式(YUV格式)。
       提示:在一种特定的硬件实现中,编解码部分具有相似性,因此通常可以构建一个OpenMax组件的”基类”或者公共函数,来完成公共性的操作。

Android中OpenMax的适配层

       Android中的OpenMax适配层的接口在frameworks/av/include/media/IOMX.h文件定义,其内容如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class IOMX : public IInterface {    
public:
DECLARE_META_INTERFACE(OMX);
typedef void *buffer_id;
typedef void *node_id;
virtual bool livesLocally(pid_t pid) = 0;
struct ComponentInfo {// 组件的信息
String8 mName;
List<String8> mRoles;
};
virtual status_t listNodes(List<ComponentInfo> *list) = 0; // 节点列表
virtual status_t allocateNode(
const char *name, const sp<IOMXObserver> &observer, // 分配节点
node_id *node)
= 0;

virtual status_t freeNode(node_id node) = 0; // 找到节点
virtual status_t sendCommand(// 发送命令
node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param)
= 0;

virtual status_t getParameter(// 获得参数
node_id node, OMX_INDEXTYPE index,
void *params, size_t size)
= 0;

virtual status_t setParameter(// 设置参数
node_id node, OMX_INDEXTYPE index,
const void *params, size_t size)
= 0;

virtual status_t getConfig(// 获得配置
node_id node, OMX_INDEXTYPE index,
void *params, size_t size)
= 0;

virtual status_t setConfig(// 设置配置
node_id node, OMX_INDEXTYPE index,
const void *params, size_t size)
= 0;

virtual status_t useBuffer(// 使用缓冲区
node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms,
buffer_id *buffer)
= 0;

virtual status_t allocateBuffer(// 分配缓冲区
node_id node, OMX_U32 port_index, size_t size,
buffer_id *buffer, void **buffer_data)
= 0;

virtual status_t allocateBufferWithBackup(// 分配带后备缓冲区
node_id node, OMX_U32 port_index, const sp<IMemory> ¶ms,
buffer_id *buffer)
= 0;

virtual status_t freeBuffer(// 释放缓冲区
node_id node, OMX_U32 port_index, buffer_id buffer)
= 0;

virtual status_t fillBuffer(node_id node, buffer_id buffer) = 0; // 填充缓冲区
virtual status_t emptyBuffer(// 消耗缓冲区
node_id node,
buffer_id buffer,
OMX_U32 range_offset, OMX_U32 range_length,
OMX_U32 flags, OMX_TICKS timestamp)
= 0;

virtual status_t getExtensionIndex(
node_id node,
const char *parameter_name,
OMX_INDEXTYPE *index)
= 0;

virtual sp<IOMXRenderer> createRenderer(// 创建渲染器(从ISurface)
const sp<ISurface> &surface,
const char *componentName,
OMX_COLOR_FORMATTYPE colorFormat,
size_t encodedWidth, size_t encodedHeight,
size_t displayWidth, size_t displayHeight) = 0;

......
};

       IOMX表示的是OpenMax的一个组件,根据Android的Binder IPC机制,BnOMX继承IOMX,实现者需要继承实现BnOMX。IOMX类中,有标准的OpenMax的GetParameter,SetParameter,GetConfig,SetConfig,SendCommand,UseBuffer,AllocateBuffer,FreeBuffer,FillThisBuffer和EmptyThisBuffer等接口。
       在IOMX.h文件中,另有表示观察器类的IOMXObserver,这个类表示OpenMax的观察者,其中只包含一个onMessage()函数,其参数为omx_message接口体,其中包含Event事件类型、FillThisBuffer完成和EmptyThisBuffer完成几种类型。
       提示:Android中OpenMax的适配层是OpenMAX IL层至上的封装层,在Android系统中被StageFright调用,也可以被其他部分调用。

TI(Texas Instruments 德州仪器) OpenMax IL的硬件实现

TI OpenMax IL实现的结构和机制

       Android的开源代码中,已经包含了TI的OpenMax IL层的实现代码,其路径如hardware/ti/omap3/omx下。其中包含的主要目录如下所示:

  • system:OpenMax核心和公共部分
  • audio:音频处理部分的OpenMax IL组件
  • video:视频处理部分OpenMax IL组件
  • image:图像处理部分OpenMax IL组件

       TI OpenMax IL实现的结构如图所示:
TI openmax IL实现

       在TI OpenMax IL实现中,最上面的内容是OpenMax的管理者用于管理和初始化,中间层是各个编解码单元的OpenMax IL标准组件,下层是LCML层,供各个OpenMax IL标准组件所调用。
       (1)TI OpenMax IL实现的公共部分在system/src/openmax_il/目录中,主要的内容如下所示。

  • omx_core/src:OpenMax IL的核心,生成动态库libOMX_Core.so
  • lcml/:LCML的工具库,生成动态库libLCML.so

       (2)I OpenMax IL的视频(Video)相关的组件在video/src/openmax_il/目录中,主要的内容如下所示。

  • prepost_processor:Video数据的前处理和后处理,生成动态库libOMX.TI.VPP.so
  • video_decode:Video解码器,生成动态库libOMX.TI.Video.Decoder.so
  • video_encode:Video编码器,生成动态库libOMX.TI.Video.encoder.so

       (3)TI OpenMax IL的音频(Audio)相关的组件在audio/src/openmax_il/目录中,主要的内容如下所示。

  • g711_dec:G711解码器,生成动态库libOMX.TI.G711.decode.so
  • g711_enc:G711编码器,生成动态库libOMX.TI.G711.encode.so
  • g722_dec:G722解码器,生成动态库libOMX.TI.G722.decode.so
  • g722_enc:G722编码器,生成动态库libOMX.TI.G722.encode.so
  • g726_dec:G726解码器,生成动态库libOMX.TI.G726.decode.so
  • g726_enc:G726编码器,生成动态库libOMX.TI.G726.encode.so
  • g729_dec:G729解码器,生成动态库libOMX.TI.G729.decode.so
  • g729_enc:G720编码器,生成动态库libOMX.TI.G729.encode.so
  • nbamr_dec:AMR窄带解码器,生成动态库libOMX.TI.AMR.decode.so
  • nbamr_enc:AMR窄带编码器,生成动态库libOMX.TI.AMR.encode.so
  • wbamr_dec:AMR宽带解码器,生成动态库libOMX.TI.WBAMR.decode.so
  • wbamr_enc:AMR宽带编码器,生成动态库libOMX.TI.WBAMR.encode.so
  • mp3_dec:MP3解码器,生成动态库libOMX.TI.MP3.decode.so
  • aac_dec:AAC解码器,生成动态库libOMX.TI.AAC.decode.so
  • aac_enc:AAC编码器,生成动态库libOMX.TI.AAC.encode.so
  • wma_dec:WMA解码器,生成动态库libOMX.TI.WMA.decode.so

       (4)TI OpenMax IL的图像(Image)相关的组件在image/src/openmax_il/目录中,主要的内容如下所示。

  • jpeg_enc:JPEG编码器,生成动态库libOMX.TI.JPEG.Encoder.so
  • jpeg_dec:JPEG解码器,生成动态库libOMX.TI.JPEG.decoder.so

TI OpenMax IL的核心和公共内容

       LCML的全称是”Linux Common Multimedia Layer“,是TI的Linux公共多媒体层。在OpenMax IL的实现中,这个内容在system/src/openmax_il/lcml/目录中,主要文件是子目录src中的LCML_DspCodec.c文件。通过调用DSPBridge的内容, 让ARM和DSP进行通信,然DSP进行编解码方面的处理。DSP的运行还需要固件的支持。
       TI OpenMax IL的核心实现在system/src/openmax_il/omx_core/目录中,生成TI OpenMax IL的核心库libOMX_Core.so。
       其中子目录src中的OMX_Core.c为主要文件,其中定义了编解码器的名称等,其片断如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
char *tComponentName[MAXCOMP][2] = {    
{"OMX.TI.JPEG.decoder", "image_decoder.jpeg"},/* 图像和视频编解码器 */
{"OMX.TI.JPEG.Encoder", "image_encoder.jpeg"},
{"OMX.TI.Video.Decoder", "video_decoder.avc"},
{"OMX.TI.Video.Decoder", "video_decoder.mpeg4"},
{"OMX.TI.Video.Decoder", "video_decoder.wmv"},
{"OMX.TI.Video.encoder", "video_encoder.mpeg4"},
{"OMX.TI.Video.encoder", "video_encoder.h263"},
{"OMX.TI.Video.encoder", "video_encoder.avc"},
/* ......省略 ,语音相关组件*/
#ifdef BUILD_WITH_TI_AUDIO /* 音频编解码器 */
{"OMX.TI.MP3.decode", "audio_decoder.mp3"},
{"OMX.TI.AAC.encode", "audio_encoder.aac"},
{"OMX.TI.AAC.decode", "audio_decoder.aac"},
{"OMX.TI.WMA.decode", "audio_decoder.wma"},
{"OMX.TI.WBAMR.decode", "audio_decoder.amrwb"},
{"OMX.TI.AMR.decode", "audio_decoder.amrnb"},
{"OMX.TI.AMR.encode", "audio_encoder.amrnb"},
{"OMX.TI.WBAMR.encode", "audio_encoder.amrwb"},
#endif
{NULL, NULL},
};

       tComponentName数组的各个项中,第一个表示编解码库内容,第二个表示库所实现的功能
       其中,TIOMX_GetHandle()函数用于获得各个组件的句柄,其实现的主要片断如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
OMX_ERRORTYPE TIOMX_GetHandle( OMX_HANDLETYPE* pHandle, OMX_STRING cComponentName,    
OMX_PTR pAppData, OMX_CALLBACKTYPE* pCallBacks)

{

static const char prefix[] = "lib";
static const char postfix[] = ".so";
OMX_ERRORTYPE (*pComponentInit)(OMX_HANDLETYPE*);
OMX_ERRORTYPE err = OMX_ErrorNone;
OMX_COMPONENTTYPE *componentType;
const char* pErr = dlerror();
// ...... 省略错误处理内容
int i = 0;
for(i=0; i< COUNTOF(pModules); i++) { // 循环查找
if(pModules[i] == NULL) break;
}
// ...... 省略错误处理内容
int refIndex = 0;
for (refIndex=0; refIndex < MAX_TABLE_SIZE; refIndex++) {
// 循环查找组件列表
if (strcmp(componentTable[refIndex].name, cComponentName) == 0) {
if (componentTable[refIndex].refCount>= MAX_CONCURRENT_INSTANCES) {
// ...... 省略错误处理内容
} else {
char buf[sizeof(prefix) + MAXNAMESIZE+ sizeof(postfix)];
strcpy(buf, prefix);
strcat(buf, cComponentName);
strcat(buf, postfix);
pModules[i] = dlopen(buf, RTLD_LAZY | RTLD_GLOBAL);
// ...... 省略错误处理内容
// 动态取出初始化的符号
pComponentInit = dlsym(pModules[i], "OMX_ComponentInit");
pErr = dlerror();
// ...... 省略错误处理内容
*pHandle = malloc(sizeof(OMX_COMPONENTTYPE));
// ...... 省略错误处理内容
pComponents[i] = *pHandle;
componentType = (OMX_COMPONENTTYPE*) *pHandle;
componentType->nSize = sizeof(OMX_COMPONENTTYPE);
err = (*pComponentInit)(*pHandle); // 执行初始化工作
// ...... 省略部分内容
}
}
}
err = OMX_ErrorComponentNotFound;
goto UNLOCK_MUTEX;
// ...... 省略部分内容
return (err);
}

       在TIOMX_GetHandle()函数中,根据tComponentName数组中动态库的名称,动态打开各个编解码实现的动态库,取出其中的OMX_ComponentInit符号来执行各个组件的初始化。

一个TI OpenMax IL组件的实现

       TI OpenMax IL中各个组件都是通过调用LCML来实现的,实现的方式基本类似。主要都是实现了名称为OMX_ComponentInit的初始化函数,实现OMX_COMPONENTTYPE类型的结构体中的各个成员。各个组件其目录结构和文件结构也类似。

       以MP3解码器的实现为例,在audio/src/openmax_il/mp3_dec/src目录中,主要包含以下文件:

  • OMX_Mp3Decoder.c:MP3解码器组件实现
  • OMX_Mp3Dec_CompThread.c:MP3解码器组件的线程循环
  • OMX_Mp3Dec_Utils.c:MP3解码器的相关工具,调用LCML实现真正的MP3解码的功能

       OMX_Mp3Decoder.c中的OMX_ComponentInit()函数负责组件的初始化,返回的内容再从参数中得到,这个函数的主要片断如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
OMX_ERRORTYPE OMX_ComponentInit (OMX_HANDLETYPE hComp)    
{

OMX_ERRORTYPE eError = OMX_ErrorNone;
OMX_COMPONENTTYPE *pHandle = (OMX_COMPONENTTYPE*) hComp;
OMX_PARAM_PORTDEFINITIONTYPE *pPortDef_ip = NULL, *pPortDef_op = NULL;
OMX_AUDIO_PARAM_PORTFORMATTYPE *pPortFormat = NULL;
OMX_AUDIO_PARAM_MP3TYPE *mp3_ip = NULL;
OMX_AUDIO_PARAM_PCMMODETYPE *mp3_op = NULL;
MP3DEC_COMPONENT_PRIVATE *pComponentPrivate = NULL;
MP3D_AUDIODEC_PORT_TYPE *pCompPort = NULL;
MP3D_BUFFERLIST *pTemp = NULL;
int i=0;

MP3D_OMX_CONF_CHECK_CMD(pHandle,1,1);
/* ......省略,初始化OMX_COMPONENTTYPE类型的指针pHandle */
OMX_MALLOC_GENERIC(pHandle->pComponentPrivate, MP3DEC_COMPONENT_PRIVATE);
pComponentPrivate = pHandle->pComponentPrivate; /* 私有指针互相指向 */
pComponentPrivate->pHandlepHandle = pHandle;
/* ......略,初始化似有数据指针pComponentPrivate */
/* 设置输入端口(OMX_PARAM_PORTDEFINITIONTYPE类型)的默认值 */
pPortDef_ip->nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE);
pPortDef_ip->nPortIndex = MP3D_INPUT_PORT;
pPortDef_ip->eDir = OMX_DirInput;
pPortDef_ip->nBufferCountActual = MP3D_NUM_INPUT_BUFFERS;
pPortDef_ip->nBufferCountMin = MP3D_NUM_INPUT_BUFFERS;
pPortDef_ip->nBufferSize = MP3D_INPUT_BUFFER_SIZE;
pPortDef_ip->nBufferAlignment = DSP_CACHE_ALIGNMENT;
pPortDef_ip->bEnabled = OMX_TRUE;
pPortDef_ip->bPopulated = OMX_FALSE;
pPortDef_ip->eDomain = OMX_PortDomainAudio;
pPortDef_ip->format.audio.eEncoding = OMX_AUDIO_CodingMP3;
pPortDef_ip->format.audio.cMIMEType = NULL;
pPortDef_ip->format.audio.pNativeRender = NULL;
pPortDef_ip->format.audio.bFlagErrorConcealment = OMX_FALSE;
/* 设置输出端口(OMX_PARAM_PORTDEFINITIONTYPE类型)的默认值 */
pPortDef_op->nSize = sizeof(OMX_PARAM_PORTDEFINITIONTYPE);
pPortDef_op->nPortIndex = MP3D_OUTPUT_PORT;
pPortDef_op->eDir = OMX_DirOutput;
pPortDef_op->nBufferCountMin = MP3D_NUM_OUTPUT_BUFFERS;
pPortDef_op->nBufferCountActual = MP3D_NUM_OUTPUT_BUFFERS;
pPortDef_op->nBufferSize = MP3D_OUTPUT_BUFFER_SIZE;
pPortDef_op->nBufferAlignment = DSP_CACHE_ALIGNMENT;
pPortDef_op->bEnabled = OMX_TRUE;
pPortDef_op->bPopulated = OMX_FALSE;
pPortDef_op->eDomain = OMX_PortDomainAudio;
pPortDef_op->format.audio.eEncoding = OMX_AUDIO_CodingPCM;
pPortDef_op->format.audio.cMIMEType = NULL;
pPortDef_op->format.audio.pNativeRender = NULL;
pPortDef_op->format.audio.bFlagErrorConcealment = OMX_FALSE;
/* ......省略,分配端口 */
/* 设置输入端口的默认格式 */
pPortFormat = pComponentPrivate->pCompPort[MP3D_INPUT_PORT]->pPortFormat;
OMX_CONF_INIT_STRUCT(pPortFormat, OMX_AUDIO_PARAM_PORTFORMATTYPE);
pPortFormat->nPortIndex = MP3D_INPUT_PORT;
pPortFormat->nIndex = OMX_IndexParamAudioMp3;
pPortFormat->eEncoding = OMX_AUDIO_CodingMP3;
/* 设置输出端口的默认格式 */
pPortFormat = pComponentPrivate->pCompPort[MP3D_OUTPUT_PORT]->pPortFormat;
OMX_CONF_INIT_STRUCT(pPortFormat, OMX_AUDIO_PARAM_PORTFORMATTYPE);
pPortFormat->nPortIndex = MP3D_OUTPUT_PORT;
pPortFormat->nIndex = OMX_IndexParamAudioPcm;
pPortFormat->eEncoding = OMX_AUDIO_CodingPCM;
/* ......省略部分内容 */
eError = Mp3Dec_StartCompThread(pHandle); // 启动MP3解码线程
/* ......省略部分内容 */
return eError;
}

       这个组件是OpenMax的标准实现方式,对外的接口的内容只有一个初始化函数。完成OMX_COMPONENTTYPE类型的初始化。输入端口的编号为MP3D_INPUT_PORT(==0),类型为OMX_PortDomainAudio,格式为OMX_AUDIO_CodingMP3。输出端口的编号是MP3D_OUTPUT_PORT(==1),类型为OMX_PortDomainAudio,格式为OMXAUDIO CodingPCM。

       OMX_Mp3Dec_CompThread.c中定义了MP3DEC_ComponentThread()函数,用于创建MP3解码的线程的执行函数。
       OMX_Mp3Dec_Utils.c中的Mp3Dec_StartCompThread()函数,调用了POSIX的线程库建立MP3解码的线程,如下所示:

1
2
nRet = pthread_create (&(pComponentPrivate->ComponentThread), NULL,    
MP3DEC_ComponentThread, pComponentPrivate);

       Mp3Dec_StartCompThread()函数就是在组件初始化函数OMX_ComponentInit()最后调用的内容。MP3线程的开始并不表示解码过程开始,线程需要等待通过pipe机制获得命令和数据(cmdPipe和dataPipe),在适当的时候开始工作。这个pipe在MP3解码组件的SendCommand等实现写操作,在线程中读取其内容。

Qualcomm(高通) OpenMax IL的硬件实现

qcom OpenMax IL实现的结构和机制

       (1)在AOSP中依然有对高通平台的OpenMax IL层实现代码,位于hardware/qcom/media/mm-core下。这一部分是OpenMax核心和公共部分,主要编译为libOmxCore.so
       (2)e.g. 继续在hardware/qcom/media下,选取mm-video-v4l2目录。即Video4linux2(简称V4L2),是linux中关于视频设备的内核驱动。再次进入vidc,(DivxDrmDecrypt为DRM数字版权相关)主要目录如下:

  • vdec:视频解码处理,编译成libOmxVdec.so/libOmxVdecHevc.so
  • venc:视频编码处理,编译成libOmxVenc.so

qcom OpenMax IL的核心和公共内容

       类似于前面介绍的TI,高通平台在OpenMax IL实现也是大同小异,位于hardware/qcom/media/mm-core,生成libOmxCore.so库。
       其中qc_omx_core为主要文件,位于hardware/qcom/media/mm-core/omxcore/src/common/下面。和TI的差不多,OMX_GetHandle()函数用户获取各个组件的句柄,其实现的主要片断如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
//编解码器组件集合数组
extern omx_core_cb_type core[];

OMX_API OMX_ERRORTYPE OMX_APIENTRY
OMX_GetHandle(OMX_OUT OMX_HANDLETYPE* handle,
OMX_IN OMX_STRING componentName,
OMX_IN OMX_PTR appData,
OMX_IN OMX_CALLBACKTYPE* callBacks)

{

OMX_ERRORTYPE eRet = OMX_ErrorNone;
int cmp_index = -1;
int hnd_index = -1;

DEBUG_PRINT("OMXCORE API : Get Handle %p %s %p\n", handle,
componentName,
appData);
pthread_mutex_lock(&lock_core);
if(handle)
{
struct stat sd;
//组件句柄
*handle = NULL;
//获取根据组件名获取相应index
cmp_index = get_cmp_index(componentName);

if(cmp_index >= 0)
{
DEBUG_PRINT("getting fn pointer\n");

// dynamically load the so 动态加载组件的so库
core[cmp_index].fn_ptr =
omx_core_load_cmp_library(core[cmp_index].so_lib_name,
&core[cmp_index].so_lib_handle);

if(core[cmp_index].fn_ptr)
{
// Construct the component requested
// Function returns the opaque handle
//根据获取的组件句柄初始化它
void* pThis = (*(core[cmp_index].fn_ptr))();
if(pThis)
{
//包装一层,忽略
void *hComp = NULL;
hComp = qc_omx_create_component_wrapper((OMX_PTR)pThis);
if((eRet = qc_omx_component_init(hComp, core[cmp_index].name)) !=
OMX_ErrorNone)
{
DEBUG_PRINT("Component not created succesfully\n");
pthread_mutex_unlock(&lock_core);
return eRet;

}
//设置回调
qc_omx_component_set_callbacks(hComp,callBacks,appData);
hnd_index = get_comp_handle_index(componentName);

if(hnd_index >= 0)
{
//保存这个组件句柄
core[cmp_index].inst[hnd_index]= *handle = (OMX_HANDLETYPE) hComp;
}
else
{
/*-------下面全是错误处理,忽略------*/
DEBUG_PRINT("OMX_GetHandle:NO free slot available to store Component Handle\n");
pthread_mutex_unlock(&lock_core);
return OMX_ErrorInsufficientResources;
}
DEBUG_PRINT("Component %p Successfully created\n",*handle);
}
else
{
eRet = OMX_ErrorInsufficientResources;
DEBUG_PRINT("Component Creation failed\n");
}
}
else
{
eRet = OMX_ErrorNotImplemented;
DEBUG_PRINT("library couldnt return create instance fn\n");
}

}
else
{
eRet = OMX_ErrorNotImplemented;
DEBUG_PRINT("ERROR: Already another instance active ;rejecting \n");
}
}
else
{
eRet = OMX_ErrorBadParameter;
DEBUG_PRINT("\n OMX_GetHandle: NULL handle \n");
}
pthread_mutex_unlock(&lock_core);
return eRet;
}

       这里的有个数组:extern omx_core_cb_type core[],是从别的文件中声明过来的全局变量,其中包含了各种编解码器的名称和一些属性的结构体。结构体定义位于hardware/qcom/media/mm-core/omxcore/src/common/qc_omx_core.h:

1
2
3
4
5
6
7
8
9
typedef struct _omx_core_cb_type
{
char* name;// Component name 组件名
create_qc_omx_component fn_ptr;// create instance fn ptr 创建实例函数指针
void* inst[OMX_COMP_MAX_INST];// Instance handle 实例句柄
void* so_lib_handle;// So Library handle so库句柄
char* so_lib_name;// so directory so名
char* roles[OMX_CORE_MAX_CMP_ROLES];// roles played 组件扮演的角色
}omx_core_cb_type;

       但是给omx_core_cb_type core[]这个结构体数组复制的地方要根据不同型号进行选取,我们进入hardware/qcom/media/mm-core/src下面,会看到有许多型号,7627A、7630、8084、8226、8610、8660等等。比如这个8974的,位于hardware/qcom/media/mm-core/src/8974/qc_registry_table_android.c中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
omx_core_cb_type core[] =
{
//avc/h264解码器
{
"OMX.qcom.video.decoder.avc",
NULL, // Create instance function
// Unique instance handle
{
NULL
},
NULL, // Shared object library handle
"libOmxVdec.so",
{
"video_decoder.avc"
}
},
//mpeg4解码器
{
"OMX.qcom.video.decoder.mpeg4",
NULL, // Create instance function
// Unique instance handle
{
NULL
},
NULL, // Shared object library handle
"libOmxVdec.so",
{
"video_decoder.mpeg4"
}
},
//wmv解码器
{
"OMX.qcom.video.decoder.wmv",
NULL, // Create instance function
// Unique instance handle
{
NULL
},
NULL, // Shared object library handle
"libOmxVdec.so",
{
"video_decoder.vc1"
}
},
//hevc/h265编码器
{
"OMX.qcom.video.encoder.hevc",
NULL, // Create instance function
// Unique instance handle
{
NULL
},
NULL, // Shared object library handle
"libOmxVencHevc.so",
{
"video_encoder.hevc"
}
},

...太多了,省略...

}

       上面就是对编解码器相关信息的注册。

       在OMX_GetHandle()函数中,根据omx_core_cb_type core[]数组中动态库的名称,动态打开各个编解码实现的动态库,然后进行初始化。

一个qcom OpenMax IL组件的实现

       高通平台对于编解码组件的处理都比较集中,不像TI那么分散和细致。一个组件实现都要包含Qc_omx_component.h头文件,位于很多地方,如hardware/qcom/media/mm-core/inc,要实现里面相关纯虚函数。当一个组件被创建后要初始化,就要实现component_init(OMX_IN OMX_STRING componentName)方法。

       举个例子,依然以Video4linux2平台,进入hardware/qcom/media/mm-video-v4l2/vidc/vdec/src查看视频解码相关组件。比如我们看看解码组件omx_vdec_hevc.cpp,查看component_init方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414

#ifdef VENUS_HEVC
#define DEVICE_NAME "/dev/video/venus_dec"
#else
#define DEVICE_NAME "/dev/video/q6_dec"
#endif

/* ======================================================================
FUNCTION
omx_vdec::ComponentInit

DESCRIPTION
Initialize the component.

PARAMETERS
ctxt -- Context information related to the self.
id -- Event identifier. This could be any of the following:
1. Command completion event
2. Buffer done callback event
3. Frame done callback event

RETURN VALUE
None.

========================================================================== */

OMX_ERRORTYPE omx_vdec::component_init(OMX_STRING role)
{

OMX_ERRORTYPE eRet = OMX_ErrorNone;
struct v4l2_fmtdesc fdesc;
struct v4l2_format fmt;
struct v4l2_requestbuffers bufreq;
struct v4l2_control control;
unsigned int alignment = 0,buffer_size = 0;
int fds[2];
int r,ret=0;
bool codec_ambiguous = false;
//打开设备文件"/dev/video/venus_dec"或"/dev/video/q6_dec"
OMX_STRING device_name = (OMX_STRING)DEVICE_NAME;
......
drv_ctx.video_driver_fd = open(device_name, O_RDWR);

......
//如果是一个打开成功后,为什么要再次打开??excuse me ?
if (drv_ctx.video_driver_fd == 0) {
drv_ctx.video_driver_fd = open(device_name, O_RDWR);
}
//打开设备文件失败
if (drv_ctx.video_driver_fd < 0) {
DEBUG_PRINT_ERROR("Omx_vdec::Comp Init Returning failure, errno %d", errno);
return OMX_ErrorInsufficientResources;
}
drv_ctx.frame_rate.fps_numerator = DEFAULT_FPS;//帧率分子
drv_ctx.frame_rate.fps_denominator = 1;//帧率分母
//创建一个异步线程,执行async_message_thread函数,对输入端进行设置
ret = pthread_create(&async_thread_id,0,async_message_thread,this);
//创建线程失败,则关闭设备文件
if (ret < 0) {
close(drv_ctx.video_driver_fd);
DEBUG_PRINT_ERROR("Failed to create async_message_thread");
return OMX_ErrorInsufficientResources;
}

......

// Copy the role information which provides the decoder kind
//将组建角色名字copy进设备驱动上下文结构体的kind属性
strlcpy(drv_ctx.kind,role,128);
//如果是mpeg4解码组件
if (!strncmp(drv_ctx.kind,"OMX.qcom.video.decoder.mpeg4",\
OMX_MAX_STRINGNAME_SIZE)) {
strlcpy((char *)m_cRole, "video_decoder.mpeg4",\
OMX_MAX_STRINGNAME_SIZE);
drv_ctx.timestamp_adjust = true;
drv_ctx.decoder_format = VDEC_CODECTYPE_MPEG4;
eCompressionFormat = OMX_VIDEO_CodingMPEG4;
output_capability=V4L2_PIX_FMT_MPEG4;
/*Initialize Start Code for MPEG4*/
codec_type_parse = CODEC_TYPE_MPEG4;
m_frame_parser.init_start_codes (codec_type_parse);
......
//如果是mpeg2解码组件
} else if (!strncmp(drv_ctx.kind,"OMX.qcom.video.decoder.mpeg2",\
OMX_MAX_STRINGNAME_SIZE)) {
strlcpy((char *)m_cRole, "video_decoder.mpeg2",\
OMX_MAX_STRINGNAME_SIZE);
drv_ctx.decoder_format = VDEC_CODECTYPE_MPEG2;
output_capability = V4L2_PIX_FMT_MPEG2;
eCompressionFormat = OMX_VIDEO_CodingMPEG2;
/*Initialize Start Code for MPEG2*/
codec_type_parse = CODEC_TYPE_MPEG2;
m_frame_parser.init_start_codes (codec_type_parse);
......
//如果是h263解码组件
} else if (!strncmp(drv_ctx.kind, "OMX.qcom.video.decoder.h263",\
OMX_MAX_STRINGNAME_SIZE)) {
strlcpy((char *)m_cRole, "video_decoder.h263",OMX_MAX_STRINGNAME_SIZE);
DEBUG_PRINT_LOW("H263 Decoder selected");
drv_ctx.decoder_format = VDEC_CODECTYPE_H263;
eCompressionFormat = OMX_VIDEO_CodingH263;
output_capability = V4L2_PIX_FMT_H263;
codec_type_parse = CODEC_TYPE_H263;
m_frame_parser.init_start_codes (codec_type_parse);
......
//如果是divx311...
} else if (!strncmp(drv_ctx.kind, "OMX.qcom.video.decoder.divx311",\
OMX_MAX_STRINGNAME_SIZE)) {
strlcpy((char *)m_cRole, "video_decoder.divx",OMX_MAX_STRINGNAME_SIZE);
DEBUG_PRINT_LOW ("DIVX 311 Decoder selected");
drv_ctx.decoder_format = VDEC_CODECTYPE_DIVX_3;
output_capability = V4L2_PIX_FMT_DIVX_311;
eCompressionFormat = (OMX_VIDEO_CODINGTYPE)QOMX_VIDEO_CodingDivx;
codec_type_parse = CODEC_TYPE_DIVX;
m_frame_parser.init_start_codes (codec_type_parse);

eRet = createDivxDrmContext();
if (eRet != OMX_ErrorNone) {
DEBUG_PRINT_ERROR("createDivxDrmContext Failed");
return eRet;
}
//如果是divx4...
} else if (!strncmp(drv_ctx.kind, "OMX.qcom.video.decoder.divx4",\
OMX_MAX_STRINGNAME_SIZE)) {
strlcpy((char *)m_cRole, "video_decoder.divx",OMX_MAX_STRINGNAME_SIZE);
DEBUG_PRINT_ERROR ("DIVX 4 Decoder selected");
drv_ctx.decoder_format = VDEC_CODECTYPE_DIVX_4;
output_capability = V4L2_PIX_FMT_DIVX;
eCompressionFormat = (OMX_VIDEO_CODINGTYPE)QOMX_VIDEO_CodingDivx;
codec_type_parse = CODEC_TYPE_DIVX;
codec_ambiguous = true;
m_frame_parser.init_start_codes (codec_type_parse);

eRet = createDivxDrmContext();
if (eRet != OMX_ErrorNone) {
DEBUG_PRINT_ERROR("createDivxDrmContext Failed");
return eRet;
}
//如果是divx...
} else if (!strncmp(drv_ctx.kind, "OMX.qcom.video.decoder.divx",\
OMX_MAX_STRINGNAME_SIZE)) {
strlcpy((char *)m_cRole, "video_decoder.divx",OMX_MAX_STRINGNAME_SIZE);
DEBUG_PRINT_ERROR ("DIVX 5/6 Decoder selected");
drv_ctx.decoder_format = VDEC_CODECTYPE_DIVX_6;
output_capability = V4L2_PIX_FMT_DIVX;
eCompressionFormat = (OMX_VIDEO_CODINGTYPE)QOMX_VIDEO_CodingDivx;
codec_type_parse = CODEC_TYPE_DIVX;
codec_ambiguous = true;
m_frame_parser.init_start_codes (codec_type_parse);

eRet = createDivxDrmContext();
if (eRet != OMX_ErrorNone) {
DEBUG_PRINT_ERROR("createDivxDrmContext Failed");
return eRet;
}
//如果是avc/h264...
} else if (!strncmp(drv_ctx.kind, "OMX.qcom.video.decoder.avc",\
OMX_MAX_STRINGNAME_SIZE)) {
strlcpy((char *)m_cRole, "video_decoder.avc",OMX_MAX_STRINGNAME_SIZE);
drv_ctx.decoder_format = VDEC_CODECTYPE_H264;
output_capability=V4L2_PIX_FMT_H264;
eCompressionFormat = OMX_VIDEO_CodingAVC;
codec_type_parse = CODEC_TYPE_H264;
m_frame_parser.init_start_codes (codec_type_parse);
m_frame_parser.init_nal_length(nal_length);
......
//如果是hevc/h265...
} else if (!strncmp(drv_ctx.kind, "OMX.qcom.video.decoder.hevc",\
OMX_MAX_STRINGNAME_SIZE)) {
strlcpy((char *)m_cRole, "video_decoder.hevc",OMX_MAX_STRINGNAME_SIZE);
drv_ctx.decoder_format = VDEC_CODECTYPE_HEVC;
output_capability=V4L2_PIX_FMT_HEVC;
eCompressionFormat = (OMX_VIDEO_CODINGTYPE)QOMX_VIDEO_CodingHevc;
codec_type_parse = CODEC_TYPE_HEVC;
m_frame_parser.init_start_codes (codec_type_parse);
m_frame_parser.init_nal_length(nal_length);
...
//如果是vc1...
} else if (!strncmp(drv_ctx.kind, "OMX.qcom.video.decoder.vc1",\
OMX_MAX_STRINGNAME_SIZE)) {
strlcpy((char *)m_cRole, "video_decoder.vc1",OMX_MAX_STRINGNAME_SIZE);
drv_ctx.decoder_format = VDEC_CODECTYPE_VC1;
eCompressionFormat = OMX_VIDEO_CodingWMV;
codec_type_parse = CODEC_TYPE_VC1;
output_capability = V4L2_PIX_FMT_VC1_ANNEX_G;
m_frame_parser.init_start_codes (codec_type_parse);
//如果是wmv...
} else if (!strncmp(drv_ctx.kind, "OMX.qcom.video.decoder.wmv",\
OMX_MAX_STRINGNAME_SIZE)) {
strlcpy((char *)m_cRole, "video_decoder.vc1",OMX_MAX_STRINGNAME_SIZE);
drv_ctx.decoder_format = VDEC_CODECTYPE_VC1_RCV;
eCompressionFormat = OMX_VIDEO_CodingWMV;
codec_type_parse = CODEC_TYPE_VC1;
output_capability = V4L2_PIX_FMT_VC1_ANNEX_L;
m_frame_parser.init_start_codes (codec_type_parse);
//如果是vp8...
} else if (!strncmp(drv_ctx.kind, "OMX.qcom.video.decoder.vp8",\
OMX_MAX_STRINGNAME_SIZE)) {
strlcpy((char *)m_cRole, "video_decoder.vp8",OMX_MAX_STRINGNAME_SIZE);
output_capability=V4L2_PIX_FMT_VP8;
eCompressionFormat = OMX_VIDEO_CodingVPX;
codec_type_parse = CODEC_TYPE_VP8;
arbitrary_bytes = false;

// 如果是不认识的解码组件,则报错
} else {
DEBUG_PRINT_ERROR("ERROR:Unknown Component");
eRet = OMX_ErrorInvalidComponentName;
}
//如果错误
if (eRet == OMX_ErrorNone) {
//设置视频输出编码格式为YUV的一种
drv_ctx.output_format = VDEC_YUV_FORMAT_NV12;
//设置颜色编码
OMX_COLOR_FORMATTYPE dest_color_format = (OMX_COLOR_FORMATTYPE)
QOMX_COLOR_FORMATYUV420PackedSemiPlanar32m;
if (!client_buffers.set_color_format(dest_color_format)) {
DEBUG_PRINT_ERROR("Setting color format failed");
eRet = OMX_ErrorInsufficientResources;
}
//订阅事件
capture_capability= V4L2_PIX_FMT_NV12;
ret = subscribe_to_events(drv_ctx.video_driver_fd);
if (ret) {
DEBUG_PRINT_ERROR("Subscribe Event Failed");
return OMX_ErrorInsufficientResources;
}

struct v4l2_capability cap;
//设置查询能力标志位
ret = ioctl(drv_ctx.video_driver_fd, VIDIOC_QUERYCAP, &cap);
if (ret) {
DEBUG_PRINT_ERROR("Failed to query capabilities");
/*TODO: How to handle this case */
} else {
DEBUG_PRINT_HIGH("Capabilities: driver_name = %s, card = %s, bus_info = %s,"
" version = %d, capabilities = %x", cap.driver, cap.card,
cap.bus_info, cap.version, cap.capabilities);
}
ret=0;
fdesc.type=V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
fdesc.index=0;
while (ioctl(drv_ctx.video_driver_fd, VIDIOC_ENUM_FMT, &fdesc) == 0) {
DEBUG_PRINT_HIGH("fmt: description: %s, fmt: %x, flags = %x", fdesc.description,
fdesc.pixelformat, fdesc.flags);
fdesc.index++;
}
fdesc.type=V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
fdesc.index=0;
while (ioctl(drv_ctx.video_driver_fd, VIDIOC_ENUM_FMT, &fdesc) == 0) {

DEBUG_PRINT_HIGH("fmt: description: %s, fmt: %x, flags = %x", fdesc.description,
fdesc.pixelformat, fdesc.flags);
fdesc.index++;
}
update_resolution(320, 240);
fmt.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
fmt.fmt.pix_mp.height = drv_ctx.video_resolution.frame_height;
fmt.fmt.pix_mp.width = drv_ctx.video_resolution.frame_width;
fmt.fmt.pix_mp.pixelformat = output_capability;
ret = ioctl(drv_ctx.video_driver_fd, VIDIOC_S_FMT, &fmt);
if (ret) {
/*TODO: How to handle this case */
DEBUG_PRINT_ERROR("Failed to set format on output port");
}
DEBUG_PRINT_HIGH("Set Format was successful");
//如果有歧义的解码组件
if (codec_ambiguous) {
if (output_capability == V4L2_PIX_FMT_DIVX) {
struct v4l2_control divx_ctrl;

if (drv_ctx.decoder_format == VDEC_CODECTYPE_DIVX_4) {
divx_ctrl.value = V4L2_MPEG_VIDC_VIDEO_DIVX_FORMAT_4;
} else if (drv_ctx.decoder_format == VDEC_CODECTYPE_DIVX_5) {
divx_ctrl.value = V4L2_MPEG_VIDC_VIDEO_DIVX_FORMAT_5;
} else {
divx_ctrl.value = V4L2_MPEG_VIDC_VIDEO_DIVX_FORMAT_6;
}

divx_ctrl.id = V4L2_CID_MPEG_VIDC_VIDEO_DIVX_FORMAT;
ret = ioctl(drv_ctx.video_driver_fd, VIDIOC_S_CTRL, &divx_ctrl);
if (ret) {
DEBUG_PRINT_ERROR("Failed to set divx version");
}
} else {
DEBUG_PRINT_ERROR("Codec should not be ambiguous");
}
}
//解码相关参数设置
fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
fmt.fmt.pix_mp.height = drv_ctx.video_resolution.frame_height;
fmt.fmt.pix_mp.width = drv_ctx.video_resolution.frame_width;
fmt.fmt.pix_mp.pixelformat = capture_capability;
ret = ioctl(drv_ctx.video_driver_fd, VIDIOC_S_FMT, &fmt);
if (ret) {
/*TODO: How to handle this case */
DEBUG_PRINT_ERROR("Failed to set format on capture port");
}
DEBUG_PRINT_HIGH("Set Format was successful");
if (secure_mode) {
control.id = V4L2_CID_MPEG_VIDC_VIDEO_SECURE;
control.value = 1;
DEBUG_PRINT_LOW("Omx_vdec:: calling to open secure device %d", ret);
ret=ioctl(drv_ctx.video_driver_fd, VIDIOC_S_CTRL,&control);
if (ret) {
DEBUG_PRINT_ERROR("Omx_vdec:: Unable to open secure device %d", ret);
close(drv_ctx.video_driver_fd);
return OMX_ErrorInsufficientResources;
}
}

/*Get the Buffer requirements for input and output ports*/
//获得输入和输出的缓冲条件
drv_ctx.ip_buf.buffer_type = VDEC_BUFFER_TYPE_INPUT;
drv_ctx.op_buf.buffer_type = VDEC_BUFFER_TYPE_OUTPUT;
if (secure_mode) {
drv_ctx.op_buf.alignment=SZ_1M;
drv_ctx.ip_buf.alignment=SZ_1M;
} else {
drv_ctx.op_buf.alignment=SZ_4K;
drv_ctx.ip_buf.alignment=SZ_4K;
}
drv_ctx.interlace = VDEC_InterlaceFrameProgressive;
drv_ctx.extradata = 0;
drv_ctx.picture_order = VDEC_ORDER_DISPLAY;
control.id = V4L2_CID_MPEG_VIDC_VIDEO_OUTPUT_ORDER;
control.value = V4L2_MPEG_VIDC_VIDEO_OUTPUT_ORDER_DISPLAY;
ret = ioctl(drv_ctx.video_driver_fd, VIDIOC_S_CTRL, &control);
drv_ctx.idr_only_decoding = 0;

m_state = OMX_StateLoaded;
#ifdef DEFAULT_EXTRADATA
if (eRet == OMX_ErrorNone && !secure_mode)
enable_extradata(DEFAULT_EXTRADATA, true, true);
#endif
eRet=get_buffer_req(&drv_ctx.ip_buf);
DEBUG_PRINT_HIGH("Input Buffer Size =%d",drv_ctx.ip_buf.buffer_size);
get_buffer_req(&drv_ctx.op_buf);
//如果解码器格式是h264或者hevc/h265
if (drv_ctx.decoder_format == VDEC_CODECTYPE_H264 ||
drv_ctx.decoder_format == VDEC_CODECTYPE_HEVC) {
h264_scratch.nAllocLen = drv_ctx.ip_buf.buffer_size;
h264_scratch.pBuffer = (OMX_U8 *)malloc (drv_ctx.ip_buf.buffer_size);
h264_scratch.nFilledLen = 0;
h264_scratch.nOffset = 0;

if (h264_scratch.pBuffer == NULL) {
DEBUG_PRINT_ERROR("h264_scratch.pBuffer Allocation failed ");
return OMX_ErrorInsufficientResources;
}
}
//如果解码器格式是h264
if (drv_ctx.decoder_format == VDEC_CODECTYPE_H264) {
if (m_frame_parser.mutils == NULL) {
m_frame_parser.mutils = new H264_Utils();

if (m_frame_parser.mutils == NULL) {
DEBUG_PRINT_ERROR("parser utils Allocation failed ");
eRet = OMX_ErrorInsufficientResources;
} else {
m_frame_parser.mutils->initialize_frame_checking_environment();
m_frame_parser.mutils->allocate_rbsp_buffer (drv_ctx.ip_buf.buffer_size);
}
}
//创建一个h264流的解析器
h264_parser = new h264_stream_parser();
if (!h264_parser) {
DEBUG_PRINT_ERROR("ERROR: H264 parser allocation failed!");
eRet = OMX_ErrorInsufficientResources;
}
}
//打开一个管道,读写端保存进fds数组
if (pipe(fds)) {
DEBUG_PRINT_ERROR("pipe creation failed");
eRet = OMX_ErrorInsufficientResources;
} else {
int temp1[2];
if (fds[0] == 0 || fds[1] == 0) {
if (pipe (temp1)) {
DEBUG_PRINT_ERROR("pipe creation failed");
return OMX_ErrorInsufficientResources;
}
//close (fds[0]);
//close (fds[1]);
fds[0] = temp1 [0];
fds[1] = temp1 [1];
}
//输入/读
m_pipe_in = fds[0];
//输出/写
m_pipe_out = fds[1];
//创建一个工作线程,调用omx开始处理解码,并进行i/o操作
r = pthread_create(&msg_thread_id,0,message_thread,this);

if (r < 0) {
DEBUG_PRINT_ERROR("component_init(): message_thread creation failed");
eRet = OMX_ErrorInsufficientResources;
}
}
}
//没有错误,然后收尾
if (eRet != OMX_ErrorNone) {
DEBUG_PRINT_ERROR("Component Init Failed");
DEBUG_PRINT_HIGH("Calling VDEC_IOCTL_STOP_NEXT_MSG");
(void)ioctl(drv_ctx.video_driver_fd, VDEC_IOCTL_STOP_NEXT_MSG,
NULL);
DEBUG_PRINT_HIGH("Calling close() on Video Driver");
close (drv_ctx.video_driver_fd);
drv_ctx.video_driver_fd = -1;
} else {
DEBUG_PRINT_HIGH("omx_vdec::component_init() success");
}
//memset(&h264_mv_buff,0,sizeof(struct h264_mv_buffer));
return eRet;
}

       这个是解码组件的初始化实现。我们能够看出和TI的差距挺大的。步骤大概如下(maybe wrong):

  • 打开media相关设备文件
  • 创建一个异步线程,执行async_message_thread函数,对输入端进行设置
  • 根据解码器role名配置相关属性
  • 对视频解码相关基本配置进行设置
  • 创建一个管道,然后再开一个个工作线程,调用omx开始处理解码,并进行i/o操作

结语

       本篇和上一篇都是科普一下OpenMax和它在android上的实现,可以忽略不用看。下一篇我们将承接上上一篇和上一篇结尾的部分,分析android平台Stagefright和codec的交互。
妹子

坚持技术分享,您的支持将鼓励我继续创作!