播放器主要由核心框架模塊(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 (根節點)
├─FLOW (流控制模塊)
│ ├─CODEC (解碼模塊)
│ │ ├─EQUALIZER_ID (聲音均衡器模塊)
│ │ ├─VBUFFER_ID (視頻緩衝模塊)
│ │ ├─DMO (DirectX Media Object)
│ │ │ ├─WMV_ID
│ │ │ ├─WMS_ID
│ │ │ ├─WMVA_ID
│ │ │ ├─WMA_ID
│ │ │ └─WMAV_ID
│ │ ├─FFMPEG VIDEO (FFMpeg 解碼模塊)
│ │ └─LIBMAD_ID (Libmad Mp3解碼模塊)
│ ├─OUT (信號渲染模塊)
│ │ ├─AOUT (音頻信號渲染)
│ │ │ ├─NULLAUDIO_ID
│ │ │ └─WAVEOUT_ID
│ │ └─VOUT (視頻信號渲染)
│ │ ├─NULLVIDEO_ID
│ │ └─OVERLAY
│ ├─IDCT (離散餘弦解碼模塊)
│ │ └─SOFTIDCT_ID
│ └─CODECIDCT(離散餘弦解碼模塊,函數比IDCT要少)
│ └─MPEG1_ID
├─MEDIA (媒體文件格式編碼解析模塊)
│ ├─FORMAT (格式解析模塊)
│ │ └─FORMATBASE
│ │ ├─RAWAUDIO
│ │ │ └─MP3_ID
│ │ ├─RAWIMAGE
│ │ ├─ASF_ID
│ │ ├─AVI_ID
│ │ ├─MP4_ID
│ │ ├─MPG_ID
│ │ ├─NSV_ID
│ │ └─WAV_ID
│ ├─PLAYLIST (播放列表模塊)
│ │ ├─ASX_ID
│ │ ├─M3U_ID
│ │ └─PLS_ID
│ └─STREAMPROCESS (數據流處理模塊)
├─STREAM (數據輸入模塊)
│ ├─MEMSTREAM_ID (內存數據流模塊)
│ ├─FILE_ID (文件IO模塊)
│ └─HTTP_ID (網絡數據獲取模塊)
├─TIMER (定時器模塊)
│ └─SYSTIMER_ID
├─ASSOCIATION_ID (文件擴展名自動關聯模塊)
├─ADVANCED_ID (高級設置模塊)
├─COLOR_ID (顏色控制模塊)
├─PLATFORM_ID (平臺信息模塊)
├─XSCALEDRIVER_ID
├─PLAYER_ID (播放控制模塊)
└─PLAYER_BUFFER_ID (播放緩衝模塊)
以下是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)實例實現,
typedef struct 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,
第三個參數是媒體文件的目錄和名稱,
第四個參數爲媒體文件標題,該參數可以忽略。