TCPMP源代码分析 播放器主要由核心框架模块(common工程)和解码器、分离器插件组成。TCPMP的插件非常多,其中主要的插件有:interface插件实现了TCPMP的界面,ffmpeg是系统主要的音视频解码模块,splitter是媒体文件分离器。 由于ffmpeg的解码效率不高,系统仅使用了ffmpeg的部分功能。并且未使用其中的libavformat模块,而使用splitter模块进行。其他插件暂时没有研究。本周主要分析的是common工程。 common工程是核心模块,是一个开放的集数据输入、转换、音/视频解码、信号输出等功能为一体的完整的多媒体播放框架。这个框架自身不包含任何的Decode和Split功能,这些功能由插件实现,核心模块以一个树状结构管理所有的功能模块和插件模块,实现数据Render功能,对输入、转换、输出流程的控制,接受播放过程中的操作和对事件进行处理,同时也实现系统运行中经常使用的一些共用函数,比如解码过程中经常使用的逆离散余弦变换,内存操作,界面中需要使用的多语言字符处理等。 common工程的主目录下主要有:blit、dyncode、overlay、pcm、softidct、win32、zlib等子目录。其中blit和overlay存放是视频信号渲染模块,pcm存放PCM音频信号转换模块,softidct存放逆离散余弦变换函数,win32存放内存操作等常用模块,dyncode这个目录的代码比较晦涩,存放的是程序运行时动态生成代码模块,针对不同的CPU指令集,PCM数据声道和采样率不同,视频渲染数据格式和色深等不同情况动态生成不同的优化代码,zlib则提供了内存中压缩和解压缩的函数,包括未压缩数据的完整性检查。 以下是common工程核心模块中几个重要的概念:(1)上下文对象context该对象在初始化函数bool_t Context_Init中创建了一个该对象实例(context.h)。该对象实例记录管理各个功能模块,用户界面可以通过该对象和核心模块交互,管理控制播放过程。 (2)功能模块功能模块包括定义对象nodedef和数据对象node,定义对象描述功能模块相互间的逻辑结构,数据对象记录模块属性和方法。所有的功能模块结构按一个树状结构来组织,结构关系如下,NODE是整个结构的根结点,其下为子节点,节点按类型可分为实节点,全局节点,设置节点,抽象节点。 抽象节点没有对应的对象实例,类似C++的抽象基类,为了按照逻辑关系组织系统结构而存在,例如NODE就是抽象节点。全局节点只有一个对象的实例,如播放控制模块PLAYER_ID。设置节点表示和系统播放设置相关,比如声音均衡器模块EQUALIZER_ID,颜色控制模块COLOR_ID。实节点与抽象节点不同,指可以生成对象实例的节点,实节点没有特殊标识,一般以数据对象占用内存大小表示是否是一个实节点,创建节点时要根据该信息分配内存单元,实节点也可以有子节点,例如:MMS_ID的父节点是HTTP_ID。全局节点,设置节点和实节点可以相互组合,比如播放控制节点同时是全局节点,设置节点和实节点。 下面是主要的节点树状分布图: NODE (根节点) 以下是common工程核心模块的几个重要数据结构:(1)context 上下文对象typedef struct context { int Version;//版本信息 uint32_t ProgramId;//应用程序句柄 const tchar_t* ProgramName;//应用程序名称 const tchar_t* ProgramVersion;//程序版本号,字符串 const tchar_t* CmdLine;//程序命令行信息 void* Wnd;//视频渲染窗口句柄 void* NodeLock;//功能模块访问临界区互斥变量 array Node; //功能模块数据对象数组 array NodeClass; // ordered by id功能模块定义对象数组,按照系统逻辑关系组织 array NodeClassPri; // ordered by priority|id功能模块定义对象数组,按照系统逻辑关系和优先级排列 array NodeModule;//外部插件模块数组 int LoadModuleNo;//当前正在加载的外部插件序号 void* LoadModule;//当前正在加载的外部插件 array StrTable[2];//字符串资源数组,字符串分为:给底层使用的标准字符串资源、给界面使用的显示字符串资源,两个资源用两个数组表示 array StrBuffer; array StrModule;//未使用 void* StrLock;//字符串数组访问临界区互斥变量 uint32_t Lang;//当前使用语言标志 int CodePage;//当前使用代码页标志 struct pcm_soft* PCM;//PCM音频信号转换模块 struct blitpack* Blit;//视频信号渲染模块 struct node* Platform;//得到平台相关信息 struct node* Advanced;//得到播放模块高级信息 struct node* Player;//播放控制模块 notify Error;//信息错误回调函数 int (*HwOrientation)(void*); void *HwOrientationContext; bool_t TryDynamic;//未使用 int SettingsPage;//未使用 size_t StartUpMemory;//可以使用的有效内存数 bool_t InHibernate;//是否进入休眠状态 bool_t WaitDisable;//未使用 int FtrId;//未使用 bool_t LowMemory;//可以使用的有效内存数是否小于系统要求的最低要求 //动态代码生成中间状态及数据 bool_t CodeFailed; bool_t CodeMoveBack; bool_t CodeDelaySlot; void* CodeLock; void* CodeInstBegin; void* CodeInstEnd; int NextCond; bool_t NextSet; bool_t NextByte; bool_t NextHalf; bool_t NextSign; uint32_t* FlushCache;//未使用 void* CharConvertUTF8;//未使用 void* CharConvertCustom;//未使用 int CustomCodePage;//未使用 void* CharConvertAscii;//未使用 void* Application; void* Logger;//未使用 bool_t KeepDisplay;//是否保持背光长亮 int DisableOutOfMemory;//未使用
} context; (2)nodedef 功能模块定义对象功能模块树状结构通常由若干个静态定义对象(nodedef)实例实现, { int Flags;//功能模块节点的类型:抽象、实节点、全局、设置。 int Class;//功能模块节点的标识,如MEDIA_CLASS或ASF_ID等等。 int ParentClass;//功能模块父节点的标识,如SYSTIMER_ID对象的父节点是TIMER_CLASS。 int Priority;//表示功能模块节点优先级。 nodecreate Create;//创建功能模块定义对象的函数指针 nodedelete Delete;//销毁功能模块定义对象的函数指针
} nodedef;//功能模块定义对象
如解码器功能模块静态定义对象: static const nodedef Codec = { sizeof(codec)|CF_ABSTRACT, CODEC_CLASS, FLOW_CLASS, PRI_DEFAULT, (nodecreate)Create, (nodedelete)Delete, }; (3)nodeclass 功能模块定义对象链表结构用链表的方式实现了功能模块树状结构,每个链表代表树状结构的一个分支。 typedef struct nodeclass { nodedef Def;//功能模块定义对象 bool_t Registered;//是否注册 int ModuleNo;//模块标识 struct nodeclass* Parent;//功能模块定义对象的父对象
} nodeclass;//功能模块定义节点对象链表结构 (4)node 功能模块数据对象typedef struct node { int Class;//功能模块节点的类型,如MEDIA_CLASS等等,与nodedef相同。 nodeenum Enum;//枚举节点属性函数指针 nodeget Get;//获取节点属性的函数指针 nodeset Set;//设置节点属性的函数指针
} node;//功能模块数据对象
上述几个数据对象的相互关系: 在系统上下文对象context中有两个元素记录功能模块信息array Node和array NodeClass,array是数组数据类型(在buffer.h/c中定义和实现),Node是功能模块数据对象的数组,NodeClass功能模块定义对象的数组,按照系统逻辑关系组织。 创建功能模块时传入nodedef对象到功能模块创建函数,函数会根据nodedef信息生成对应nodeclass对象添加到NodeClass数组,同时根据nodedef信息分配数据对象的内存空间。在该节点的Create函数里面再初始化该功能模块的数据对象node。
(5)datadef 功能模块属性typedef struct datadef { int No;//属性的标识,如播放控制模块的#define PLAYER_PLAY 0x32 就表示控制播放器播放或暂停。 int Type;//属性的数据类型,在node.h中定义,如TYPE_BOOL int Flags;//属性数据的标志,是属性数据的标志,表示该数据是不是只读数据,是否有最大最小值等等,node.h中定义,如DF_RDONLY int Format1; int Format2; const tchar_t* Name; int Class; int Size;
} datadef;//属性对象定义 其中Format1和Format2是可选标志与Flags配合使用,比如如果Flags表示该属性存在最大最小值,Format1就是最大值,Format2则是最小值; 另外,如果(!(Flags & DF_NOSAVE) && !(Flags & DF_RDONLY))即属性标识为保存且可读写,则会被记录到注册表中,下次启动时用注册表的数据初始化该属性表。
(6)datatable 功能模块属性列表typedef struct datatable { int No; int Type; int Flags; int Format1; int Format2; } datatable;//功能模块属性列表 各功能模块的属性通常以数组的形式定义和存储,如格式解析模块属性列表 static const datatable Params[] = { { FORMAT_INPUT, TYPE_NODE, DF_INPUT|DF_HIDDEN, STREAM_CLASS }, { FORMAT_OUTPUT, TYPE_NODE, DF_HIDDEN, STREAM_CLASS }, { FORMAT_DURATION, TYPE_TICK }, { FORMAT_FILEPOS, TYPE_INT, DF_HIDDEN }, { FORMAT_FILESIZE, TYPE_INT, DF_KBYTE }, { FORMAT_AUTO_READSIZE, TYPE_BOOL, DF_HIDDEN }, { FORMAT_GLOBAL_COMMENT,TYPE_COMMENT, DF_OUTPUT }, { FORMAT_FIND_SUBTITLES,TYPE_BOOL, DF_HIDDEN }, { FORMAT_STREAM_COUNT, TYPE_INT, DF_HIDDEN },
DATATABLE_END(FORMAT_CLASS) }; (7)nodemodule 外部插件功能模块typedef struct nodemodule { int Id;//插件标识 int ObjectCount;//该插件的实例个数(引用计数) bool_t Tmp;//是否是临时节点 int64_t Date;//设置时间 int KeepAlive;//保持时间 void* Module;//外部插件模块 void* Db; void* Func; uint8_t* Min; uint8_t* Max;
} nodemodule;//外部插件模块节点 核心模块的初始化流程及相应代码对应关系(参考context.c中的Context_Init函数)Mem_Init(); //内存等资源初始化(Win32/mem_win32.c) DynCode_Init(); //程序运行动态生成代码模块,优化PCM,视频渲染模块等(DynCode/DynCode.c) String_Init(); //系统使用字符串初始化(str.c,Win32/str_win32.c) PCM_Init(); //音频信号转换模块初始化(PCM/pcm_soft.c) Blit_Init(); //视频信号渲染模块初始化(Blit/blit_soft.c) Node_Init(); //根节点模块初始化(node.c,Win32/node_win32.c) Platform_Init(); //平台信息模块初始化(platform.c,Win32/platform_win32.c) Stream_Init(); //输入数据流模块初始化(streams.c) Advanced_Init(); //高级设置模块初始化(advance.c) Flow_Init(); //流控制模块初始化(flow.c) Codec_Init(); //解码模块初始化(codec.c) Audio_Init(); //音频信号处理模块初始化(audio.c) Video_Init(); //视频信号处理模块初始化(video.c) Format_Init(); //格式解析模块初始化(format.c) Playlist_Init(); //播放列表模块初始化(playlist.c) FormatBase_Init(); //基本格式解析模块初始化(format_base.c,format_subtitle.c) NullOutput_Init(); //无输出设备模块初始化(nulloutput.c) RawAudio_Init(); //RawAudio模块初始化(rawaudio.c) RawImage_Init(); //RawImage模块初始化(rawimage.c) Timer_Init(); //定时器模块初始化(timer.c) IDCT_Init(); //离散余弦解码模块初始化(idct.c) Overlay_Init(); //视频叠加模块初始化(overlay.c) M3U_Init(); //M3U格式播放列表模块初始化(PlayList/m3u.c) PLS_Init(); //PLS格式播放列表模块初始化(PlayList/pls.c) ASX_Init(); //ASX格式播放列表模块初始化(PlayList/asx.c) WaveOut_Init(); //波形输出模块初始化(waveout.c,Win32/waveout_win32.c) SoftIDCT_Init(); //soft离散余弦解码模块初始化(SoftIDCT/softidct.c) Plugins_Init(); //外部插件模块初始化(Win32/node_win32.c) 另外还有文件扩展名自动关联模块Association_Init (参考文件Win32/ association_win32.c);颜色控制模块Color_Init(参考color.c);声音均衡器模块Equalizer_Init(参考equalizer.c);播放控制模块初始化(参考player.c )。 向系统中载入外部插件模块(参考node.c以及node_win32.c)node.c中的LoadModule函数,可以在系统中载入外部插件模块, static NOINLINE nodemodule* LoadModule(context* p,int No), 第一个参数是上下文对象, 第二个参数是外部插件模块标识
node_win32.c定义了dll的载入与卸载函数以及相应的注册表操作,如 在功能模块节点载入外部插件模块 void* NodeLoadModule(const tchar_t* Path,int* Id,void** AnyFunc,void** Db)
与界面相交互的播放控制模块(player.c)在所有功能模块中和界面加交互的主要就是播放控制模块struct node* Player;使用方法如下: context* p = Context(); player* myplayer = NULL; if(p) myplayer = (player*)(p->Player);
控制播放使用 Set(void* This,int No,const void* Data,int Size) 第一个参数是播放模块指针, 第二个参数是控制代码,即要进行什么操作, 第三个参数是需要赋值给控制代码的数值, 最后一个参数是所赋数值的占用内存的大小。 myplayer->Set(myplayer,PLAYER_PLAY,1,sizeof(int)); PLAYER_PLAY为控制代码,表示当前控制的是播放暂停功能,数值为1表 示播放为0表示暂停。 得到某一控制属性使用Get(void* This,int No,void* Data,int Size);函数,参数含义和Set函数相同。 控制代码是一组宏,定义在player.h文件中。比较重要的控制参数有播放控制模块所有可用参数见static const datatable PlayerParams[]结构。 添加一个媒体文件到播放模块使用 int PlayerAdd(player* Player,int Index, const tchar_t* Path, const tchar_t* Title); 第二个参数是添加到播放模块文件队列的序号,如果是使文件成为第一个文 件该参数设为0, 第三个参数是媒体文件的目录和名称, 第四个参数为媒体文件标题,该参数可以忽略。 |
|