原作者姓名 陆其明 有
时候,我们会生成自定义的媒体文件(不同于标准的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
Source Filter
= Extensions <.ext1> =
<.ext2> = 如果找到了这个协议,并且存在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 ); }
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/634491/viewspace-661562/
,如需转载,请注明出处,否则将追究法律责任。