Windows COM使用注册表来注册COM组件, director fitler遵从COM开发规范,所以也需要注册到注册表里。
Directorshow filter在注册表里有两种注册布局:
- 大多数的Fitler不需要进行filter类型的注册,按如下布局注册:
KEY_CLASSES_ROOT
CLSID
Filter CLSID
REG_SZ: (Default) = Friendly name
InprocServer32
REG_SZ: (Default) = File name of the DLL
REG_SZ: ThreadingModel = Both
- 为了让 System Device Enumerator可以发现自己,virture camera filter需要将自己注册到Category下, 布局如下:
HKEY_CLASSES_ROOT
CLSID
Category
Instance
Filter CLSID
REG_SZ: CLSID = Filter CLSID
REG_BINARY: FilterData = Filter information
REG_SZ: FriendlyName = Friendly name
带有Category的值注册在下面的路径下:
HKEY_CLASSES_ROOT\CLSID{DA4E3DA0-D07D-11d0-BD50-00A0C911CE86}\Instance
OBS-vcam 的注册:
image.png
Vivek‘s VCam的注册:
image.png
注册信息的声明
directorshow使用三个结构类来帮助组织filter, pin, meida type 需要注册的信息。
-
AMOVIESETUP_MEDIATYPE 被用来注册媒体类型。
以下是分别是OBS-VCAM和Vivek项目的媒体类型声明:
const AMOVIESETUP_MEDIATYPE AMSMediaTypesVCam =
{
&MEDIATYPE_Video, //视频媒体
&MEDIASUBTYPE_NULL //null 表示支持任意类型
};
const AMOVIESETUP_MEDIATYPE AMSMediaTypesV =
{
&MEDIATYPE_Video,
&MEDIASUBTYPE_YUY2 //obs-vcam只是YUY2格式
};
- AMOVIESETUP_PIN用来注册Pin的相关信息:
const AMOVIESETUP_PIN AMSPinVCam=
{
L"Output", // Pin string name
FALSE, // Is it rendered
TRUE, // Is it an output
FALSE, // Can we have none
FALSE, // Can we have many
&CLSID_NULL, // Connects to filter Obsolete
NULL, // Connects to pin
1, // Number of types
&AMSMediaTypesVCam // Pin Media types
};
要注意的是, AMOVIESETUP_PIN有对mediatype的引用。
-
AMOVIESETUP_FILTER 用来注册filter相关信息:
const AMOVIESETUP_FILTER AMSFilterVCam =
{
&CLSID_VirtualCam, // Filter CLSID
L"Virtual Cam", // String name
MERIT_DO_NOT_USE, // Filter merit
1, // Number pins
&AMSPinVCam // Pin details
};
AMOVIESETUP_FILTER包含了对outPin的引用,参考obs-vcam可以看到,当需要多个虚拟摄像头时,需要为每一个注册一个独立的fitler, 如果有音频,音频要独立声明filter.
将注册声明加入factory Template
为了方便com开发,Directoryshow引入的fatory Temple 帮助实现COM要求:
image.png
如上图 , directshow实现的COM接口调用逻辑如下:
a. client 程序调用coGetclassObject 获取ClassFacotory.
b. coGetclassObject 调用DllgetClassObject函数,DllgetClassObject在factory template数组里搜寻满足CLSID条件的模板。
C. factory template 创建classfatory并让其拥有一个执行匹配模板的指针,将他返回给client.
D. Client 调用IClassFatcory的createInstance方法
D. createInstance通过执行模板的指针调用模板中的初始化函数,创建真实的实例。
factory Template被声明为全局数组,它的结构如下:
CFactoryTemplate g_Templates[1] =
{
{
L"My Component", // Name
&CLSID_MyComponent, // CLSID
CMyComponent::CreateInstance, // Method to create an instance of MyComponent
NULL, // Initialization function
NULL // Set-up information (for filters)
}
};
每个模板包含5个字段,对应注册的com组件名称,CLSID, 创建方法, 额外的初始化方法,以及用于注册表的注册信息。
将注册表需要的注册变量填入最后一个字段,就可以用directshow提供的方法进行注册。
除了要生命一个全局模板数组外,同时还需要声明一个全局变量指明模板数量。
下面时obs-vcam的类工厂模板和全局变量:
CFactoryTemplate g_Templates[NUM_VIDEO_FILTERS + 1] =
{
{
L"OBS-Camera",
&CLSID_OBS_VirtualV,
CreateInstance,
NULL,
&AMSFilterV
},
{
L"OBS-Camera2",
&CLSID_OBS_VirtualV2,
CreateInstance2,
NULL,
&AMSFilterV2
},
{
L"OBS-Camera3",
&CLSID_OBS_VirtualV3,
CreateInstance3,
NULL,
&AMSFilterV3
},
{
L"OBS-Camera4",
&CLSID_OBS_VirtualV4,
CreateInstance4,
NULL,
&AMSFilterV4
},
{
L"OBS-Audio",
&CLSID_OBS_VirtualA,
CVAudio::CreateInstance,
NULL,
&AMSFilterA
}
};
int g_cTemplates = sizeof(g_Templates) / sizeof(g_Templates[0]);
OBS-VCAM注册了4个虚拟摄像头组件,每个组件都有不同的CLSID,但是它们引用的是相同的组件,也就是每个cliid下的InprocServer32的内容相同,这样虽然有不同的CLIID但是会指向同一个组件。
实现注册方法
DLL 规定了5个需要实现的函数:
DllMain Dll的入口函数,在Directshow中使用DllEntryPoint作为入口声明。
DllGetClassObject: 创建COM对象工厂。
DllUnloadNow(): 查询是否可以被卸载了。
DllRegisterServer: 为dll注册注册表信息。
DllUnregisterServer: 移除注册的信息。
Directshow lib已经实现了前三个,创建对象只需要根据模板定义好实例化方法就可以。 但是后两个需要开发者来实现。
Directshow提供了 AMovieDllRegisterServer2 来帮助注册信息。
Vivek 项目注册方法的实现:
STDAPI RegisterFilters( BOOL bRegister )
{
HRESULT hr = NOERROR;
WCHAR achFileName[MAX_PATH];
char achTemp[MAX_PATH];
ASSERT(g_hInst != 0);
if( 0 == GetModuleFileNameA(g_hInst, achTemp, sizeof(achTemp)))
return AmHresultFromWin32(GetLastError());
MultiByteToWideChar(CP_ACP, 0L, achTemp, lstrlenA(achTemp) + 1,
achFileName, NUMELMS(achFileName));
hr = CoInitialize(0);
if(bRegister)
{
hr = AMovieSetupRegisterServer(CLSID_VirtualCam, L"Virtual Cam", achFileName, L"Both", L"InprocServer32");
}
if( SUCCEEDED(hr) )
{
IFilterMapper2 *fm = 0;
hr = CreateComObject( CLSID_FilterMapper2, IID_IFilterMapper2, fm );
if( SUCCEEDED(hr) )
{
if(bRegister)
{
IMoniker *pMoniker = 0;
REGFILTER2 rf2;
rf2.dwVersion = 1;
rf2.dwMerit = MERIT_DO_NOT_USE;
rf2.cPins = 1;
rf2.rgPins = &AMSPinVCam;
hr = fm->RegisterFilter(CLSID_VirtualCam, L"Virtual Cam", &pMoniker, &CLSID_VideoInputDeviceCategory, NULL, &rf2);
}
else
{
hr = fm->UnregisterFilter(&CLSID_VideoInputDeviceCategory, 0, CLSID_VirtualCam);
}
}
// release interface
//
if(fm)
fm->Release();
}
if( SUCCEEDED(hr) && !bRegister )
hr = AMovieSetupUnregisterServer( CLSID_VirtualCam );
CoFreeUnusedLibraries(); //卸载所有不在需要的dll,例如注册完后的IID_IFilterMapper2
CoUninitialize(); //在当前线程关闭com服务,下载所有dll和释放资源等。
return hr;
}
参数bRegister为ture时表示注册,为false表示移除;
根据directshow文档,使用 AMovieDllRegisterServer2注册有很多的限制,对于vcam场景,由于需要支持硬件设备,需要注册额外的两个信息,medium 和pin category. medium定义了和已经建通信的方法, pin category定义了pin的功能。
在上文,AMovieSetupRegisterServer被用来注册通用的filter信息,它被注册在CLSID下:
然后使用IFilterMapper2::RegisterFilter方法将fitler注册到CLSID_VideoInputDeviceCategory下。这里需要用的REGFILTERPINS2结构:
REGFILTER2 rf2FilterReg = {
1, // Version 1 (no pin mediums or pin category).
MERIT_NORMAL, // Merit.
1, // Number of pins.
&sudPins // Pointer to pin information.
};
这里dwVersion(第一个字段)为一,表示使用AMOVIESETUP_PIN格式的的定义。Merit(第二个字段,表示加入Filter Graph Manager的顺序)这里使用MERIT_DO_NOT_USE.
注册结果如下:
在第二个注册信息中,它包含了pin的定义,媒体类型等信息,但是没有包含路径信息(包含在第一个中),所以第二条信息看起来只是给system_device_enumerates使用的。 这也是obs-vcam可以用一个lib模拟多个摄像头的原因。
网友评论