让Windows Media Player播放自定义媒体文件类型

来源:互联网 发布:python 获取svn版本号 编辑:程序博客网 时间:2024/05/15 07:54
原作者姓名 陆其明  

有时候,我们会生成自定义的媒体文件(不同于标准的AVI、MPEG等文件格式)。为了回放这些特殊格式的文件,于是,我们不得不再写一个播放器。那么,有没有一种办法,让Windows Media Player能够认识我们的文件,进而让它像播放其他标准文件一样播放我们的文件呢?答案是肯定的。

Windows Media Player是基于DirectShow架构来播放媒体文件的。我们只要从DirectShow方面入手,就一定能够解决问题。首先,我们来看看一般的播放文件的处理过程。大家知道,使用DirectShow播放媒体文件,肯定要创建一个Filter Graph。给定了一个文件路径,首先是要找到一个合适的Source Filter,即得到它的CLSID。一般只需调用IGraphBuilder::RenderFile或者IGraphBuilder:: AddSourceFilter就能做到。那么,Filter Graph Manager(以下简称为FGM)的实现细节是怎么样的呢?了解这些,对我们解决问题有着决定性的作用。

首先要分析一下文件路径,因为它可能包含有协议(比如http、htp等)。FGM认为冒号前面的即为协议名。例如文件路径为“ myprotocol://myfile.ext”,则myprotocol即为协议名。于是,FGM到注册表的如下位置去找这个协议:
HKEY_CLASSES_ROOT
<protocol>
Source Filter = <Source filter CLSID>
Extensions
<.ext1> = <Source filter CLSID>
<.ext2> = <Source filter CLSID>
如果找到了这个协议,并且存在Extensions子键,FGM再到Extensions子键中进行文件扩展名的匹配。如果匹配成功,FGM就用这个找到的 GUID字符串作为Source Filter的CLSID;如果不成功,FGM就使用该协议子键下的Source Filter的值。如果myprotocol这个协议并没有在注册表中注册,FGM就默认使用一个叫File Source (URL)的Filter。另外,有两点注意:(1)为了区别协议和驱动器盘符(如C:/myfile.ext),FGM不承认单字符的协议;(2)字符串“file:”或“ file://”不认为是协议。/

如果文件路径中不包含协议呢?FGM就分析文件的扩展名,即到注册表的HKEY_CLASSES_ROOT/Media Type/Extensions下去匹配。相应的扩展名子键下指定了Source Filter的CLSID字符串,有的还有Media Type和Subtype指定Source Filter输出Pin上使用的Media Type。但是,如果这种文件的扩展名在这里也没有注册呢?FGM还有最后一招,就是通过文件的校验字节(Check Bytes)去判断。这些校验字节在如下位置定义:
HKEY_CLASSES_ROOT/MediaType/{major type}/{subtype}
典型的如下:
{e436eb83-524f-11ce-9f53-0020af0ba770}
{7364696D-0000-0010-8000-00AA00389B71}
0 "0,4,,52494646,8,4,,524D4944"
1 "0,4,,4D546864"
Source Filter "{E436EBB5-524F-11CE-9F53-0020AF0BA770}"
打开注册表,我们可以看到MediaType有一个{e436eb83-524f-11ce-9f53-0020af0ba770}子键,即为 MEDIATYPE_Stream,它下面又定义了一系列的Subtype,有{e436eb88-524f-11ce-9f53- 0020af0ba770}(MEDIASUBTYPE_Avi)、{e436eb84-524f-11ce-9f53-0020af0ba770} (MEDIASUBTYPE_MPEG1System)等。在Subtype子键下,我们还可以看到一些子键,其中编号为0,1,2…的即是定义的校验字节,Source Filter即为定义的相应的CLSID。校验字节定义块一般的格式为:offset,cb,mask,val,意思为在文件头偏移offset字节处读取cb个字节的数据,将其与mask做位与操作,结果等于val即表示匹配。如果定义的mask为空,可以不做位与操作;如果offset为负数,表示从文件尾开始计算偏移;如果一个子键有多个定义块,则所有的定义块都匹配才能算这个子键匹配;如果有多个子键用于校验字节的定义,那么任何一个匹配就表示 Source Filter的匹配。如果注册表中定义的所有校验字节都不能匹配,FGM最后就只能默认创建一个叫File Source (Async.)的Filter,Media Type使用{MEDIATYPE_Stream, MEDIASUBTYPE_None}。

找好Source Filter之后,FGM在这个Filter上得到IFileSourceFilter接口,通过其接口方法IFileSourceFilter:: Load加载将要播放的这个媒体文件(或者URL)。然后Render这个Source Filter的所有Output pin,完成Filter Graph的构造。

知道了以上原理后,我们就可以采取策略了。针对我们自定义的媒体文件,我们可以开发一个Pull模式的“Parser Filter”。这个Filter必须实现一个Pull模式的Input pin,Filter完成的主要功能还有:从File Source中读取数据;分析数据,如果是音视频交互格式的,则要把它们分离,然后从不同的Output pin输出;实现IMediaSeeking接口;响应Quality Control消息等。另外,我们还要保证“Parser Filter”输出的数据,系统中有相应的Decoder进行解码(如果没有,我们要写自己的Decoder)。注意,注册这个“Parser Filter”(以及相应的Decoder)时,要让它(们)的Merit值大于MERIT_DO_NOT_USE。这种处理方法,我们不在注册表中写额外的信息。在文件回放时,FGM使用默认的File Source (Async.) Filter作为Source Filter,而后面接上我们的“Parser Filter”(以及相应的Decoder)。

另外一种解决方法,我们可以写一个 Push模式的Source Filter。这个Filter类可以从CSource类中继承;Filter完成的功能包括:读取文件内容,如果是音视频交互格式,则将它们分离后从不同的Output pin输出,实现IFileSourceFilter接口,实现IMediaSeeking接口,响应Quality Control消息等。(如果我们的Source Filter输出的数据不是裸数据,而系统中没有对应的Decoder,那我们也必须自己写相应的Decoder,类似于第一种处理方法。)这种情况下,要让FGM自动找到我们的Source Filter,我们就必须在Filter注册的时候向注册表中写入一些额外的信息:可以是注册文件扩展名,也可以注册这种文件的校验字节。下面,笔者给出一种注册文件扩展名的方法,代码参考如下(假设我们的自定义文件扩展名为“.avx”,Source Filter的CLSID为CLSID_MySourceFiler):
STDAPI DllRegisterServer()
{
// Register the ".avx" file extension
DWORD dwDisposition;
DWORD dwReserved = 0;
HKEY hTempKey = (HKEY)0;
TCHAR szKey[] = "Media Type//Extensions//.avx";
TCHAR szValue[] = "Source Filter";
TCHAR szData[100];

WCHAR * pClsid = NULL;
StringFromCLSID(CLSID_MySourceFiler, &pClsid);
WideCharToMultiByte(CP_ACP, 0, pClsid, -1, szData, 100, NULL, NULL);
CoTaskMemFree(pClsid);

DWORD dwBufferLength = lstrlen(szData) * sizeof(TCHAR);
if (ERROR_SUCCESS == ::RegCreateKeyEx(HKEY_CLASSES_ROOT, szKey, dwReserved,
(LPTSTR)0, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, 0,
&hTempKey, &dwDisposition))
{

// dwBufferLength must include size of terminating nul
// character when using REG_SZ with RegSetValueEx function
dwBufferLength += sizeof(TCHAR);
::RegSetValueEx(hTempKey, (LPTSTR)szValue, dwReserved, REG_SZ,
(LPBYTE)szData, dwBufferLength);
::RegCloseKey(hTempKey);
}

return AMovieDllRegisterServer2( TRUE );
}

STDAPI DllUnregisterServer()
{
// Remove ".avx" file extension registry
HKEY hKey = NULL;
DWORD dw = 0;
TCHAR szKey[] = "Media Type//Extensions";
if (ERROR_SUCCESS == ::RegCreateKeyEx(HKEY_CLASSES_ROOT, szKey, 0L,
NULL, REG_OPTION_VOLATILE, KEY_ALL_ACCESS, NULL, &hKey, &dw))
{
EliminateSubKey(hKey, ".avx");
::RegCloseKey(hKey);
}

return AMovieDllRegisterServer2( FALSE );
}
原创粉丝点击