UVC摄像头扩展单元功能的开发步骤是什么?
写在扩展单元的题外话
本人在开发支持UVC扩展单元的directShow应用时,必现并不需要注册接口,只需要在其源过滤器参照后续的代码枚举相关的接口如IKsControl,IKsTopologyInfo定位到指定的扩展单元接口后,直接使用其对应的IKsControl::KsProperty即可实现扩展单元的特定类请求。
使用本站的UVC扩展单元调试工具进行调试:http://www.usbzh.com/article/detail-516.html
UVC扩展单元的固件支持
要进行UVC摄像头扩展单元的开发,首先第一是需要摄像头的描述符中含有UVC扩展单元描述符.
一个扩展单元描述符的示例:
BYTE Length: 0x1a
BYTE DescriptorType: 0x24
BYTE DescriptorSubtype: 0x06
BYTE bUnitID: 0x05
GUID guidExtensionCode: xxxxxxxx-xxxx-xxxx-xxxxxxxxxxxxxxxx
BYTE bNumControls: 0x03
BYTE bNrInPins: 0x01
BYTE baSourceID[0]: 0x01
说明:
- bUnitID:不能和别的ID冲突。
- guidExtensionCode:需要通过guidgen.exe生成一个惟一的ID标识。
扩展单元的注册
扩展单元的注册可以使用INF文件进行注册,也可以吏用注册表进行注册,也可以使用DLL注册。
使用INF文件进行扩展单元注册
INF 文件可用于提供设备特定的名称或注册扩展插件单元。
详情可叁考:
https://docs.microsoft.com/zh-cn/windows-hardware/drivers/stream/providing-a-uvc-inf-file
使用注册表进行扩展单元进行注册:
HKLM
{
NoRemove SYSTEM
{
NoRemove CurrentControlSet
{
NoRemove Control
{
NoRemove NodeInterfaces
{
ForceRemove {xxxxxxxx-xxxx-xxxx-xxxx-
xxxxxxxxxxxx} = s 'Extension Unit
Property Set'
{
val IID = b 'yyyyyyyyyyyyyyyyyyy
yyyyyyyyyyyyy'
val CLSID = b 'zzzzzzzzzzzzzzzzz
zzzzzzzzzzzzzzz'
}
}
}
}
}
}
https://docs.microsoft.com/zh-cn/windows-hardware/drivers/stream/sample-registry-entry-for-uvc-extension-units
https://docs.microsoft.com/zh-cn/windows-hardware/drivers/stream/sample-extension-unit-plug-in-dll
使用插件DLL注册
使用DLL注册,需要先添加注册表项:
HKCR
{
NoRemove CLSID
{
ForceRemove {zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz} = s 'CompanyName Extension Unit Interface'
{
InprocServer32 = s '%MODULE%'
{
val ThreadingModel = s 'Both'
}
}
}
}
示例详见:https://docs.microsoft.com/zh-cn/windows-hardware/drivers/stream/sample-extension-unit-plug-in-dll
UVC 扩展单元的示例应用程序
pUnkOuter这个指针是UVC-CAMERA源,可通过下列函数指针ppSrcFilter返回。DeviceName是UVC设备名称。
HRESULT CCamera::FindCaptureDevice(const std::wstring& DeviceName,IBaseFilter** ppSrcFilter)
{
HRESULT hr;
IBaseFilter* pSrc = NULL;
IMoniker* pMoniker = NULL;
ULONG cFetched;
if (!ppSrcFilter)
return E_POINTER;
// Create the system device enumerator
CComPtr <ICreateDevEnum> pDevEnum = NULL;
hr = CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC,
IID_ICreateDevEnum, (void**)&pDevEnum);
if (FAILED(hr))
{
printf("Couldn't create system enumerator! hr=0x%x", hr);
return hr;
}
// Create an enumerator for the video capture devices
CComPtr <IEnumMoniker> pClassEnum = NULL;
hr = pDevEnum->CreateClassEnumerator(CLSID_VideoInputDeviceCategory, &pClassEnum, 0);
if (FAILED(hr))
{
printf("Couldn't create class enumerator! hr=0x%x", hr);
return hr;
}
// If there are no enumerators for the requested type, then
// CreateClassEnumerator will succeed, but pClassEnum will be NULL.
if (pClassEnum == NULL)
{
MessageBox(NULL, TEXT("No video capture device was detected.\r\n\r\n")
TEXT("This sample requires a video capture device, such as a USB WebCam,\r\n")
TEXT("to be installed and working properly. The sample will now close."),
TEXT("No Video Capture Hardware"), MB_OK | MB_ICONINFORMATION);
return E_FAIL;
}
// Use the first video capture device on the device list.
// Note that if the Next() call succeeds but there are no monikers,
// it will return S_FALSE (which is not a failure). Therefore, we
// check that the return code is S_OK instead of using SUCCEEDED() macro.
while (S_OK == (pClassEnum->Next(1, &pMoniker, &cFetched)))
{
IPropertyBag* pPropBag;
hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, (void**)&pPropBag);
if (SUCCEEDED(hr))
{
//获得Filter的Friendly Name
VARIANT varName;
VariantInit(&varName);
hr = pPropBag->Read(L"FriendlyName", &varName, 0);
if (SUCCEEDED(hr))
{
// TCHAR* pName = (TCHAR*)varName.bstrVal;
std::wstring str(varName.bstrVal);
if (str == DeviceName)
{
hr = pMoniker->BindToObject(0, 0, IID_IBaseFilter, (void**)&pSrc);
if (FAILED(hr))
{
printf("Couldn't bind moniker to filter object! hr=0x%x", hr);
return hr;
}
break;
}
}
VariantClear(&varName);
////创建Filter实例
//IBaseFilter* pFilater;
//hr = pMoniker->BindToObject(NULL, NULL, IID_IBaseFilter, (void**)&pFilter);
//pPropBag->Release();
}
pPropBag->Release();
pMoniker->Release();
}
* ppSrcFilter = pSrc;
return hr;
}
应用程序通过使用 IKsTopologyInfo::CreateNodeInstance ,然后调用节点对象上的 QueryInterface 以获取所需的 COM API 来访问接口。
// pUnkOuter is the unknown associated with the base filter
hr = pUnkOuter->QueryInterface(__uuidof(IKsTopologyInfo),
(void **) &pKsTopologyInfo);
if (!SUCCEEDED(hr))
{
printf("Unable to obtain IKsTopologyInfo %x\n", hr);
goto errExit;
}
hr = FindExtensionNode(pKsTopologyInfo,
GUID_EXTENSION_UNIT_DESCRIPTOR,
&dwExtensionNode);
if (FAILED(hr))
{
printf("Unable to find extension node : %x\n", hr);
goto errExit;
}
hr = pKsTopologyInfo->CreateNodeInstance(
dwExtensionNode,
__uuidof(IExtensionUnit),
(void **) &pExtensionUnit);
if (FAILED(hr))
{
printf("Unable to create extension node instance : %x\n", hr);
goto errExit;
}
hr = pExtensionUnit->get_PropertySize(1, &ulSize);
if (FAILED(hr))
{
printf("Unable to find property size : %x\n", hr);
goto errExit;
}
pbPropertyValue = new BYTE[ulSize];
if (!pbPropertyValue)
{
printf("Unable to allocate memory for property value\n");
goto errExit;
}
hr = pExtensionUnit->get_Property(1,ulSize, pbPropertyValue);
if (FAILED(hr))
{
printf("Unable to get property value\n");
goto errExit;
}
// assume the property value is an integer
ASSERT(ulSize == 4);
printf("The value of property 1 = %d\n", *((int *)
pbPropertyValue));
编写 FindExtensionNode 函数的代码以查找必需的扩展单元节点并在 dwExtensionNode 中返回其 ID。 此 ID 在此示例代码中对 IKsTopologyInfo:: CreateNodeInstance 方法的后续调用中使用。
支持扩展单元的自动更新事件
hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (!hEvent)
{
printf("CreateEvent failed\n");
goto errExit;
}
Event.Set = KSEVENTSETID_VIDCAPNotify;
Event.Id = KSEVENT_VIDCAP_AUTO_UPDATE;
Event.Flags = KSEVENT_TYPE_ENABLE;
EventData.NotificationType = KSEVENTF_EVENT_HANDLE;
EventData.EventHandle.Event = hEvent;
EventData.EventHandle.Reserved[0] = 0;
EventData.EventHandle.Reserved[1] = 0;
// register for autoupdate events
hr = m_pKsControl->KsEvent(
&Event,
sizeof(KSEVENT),
&EventData,
sizeof(KSEVENTDATA),
&ulBytesReturned);
if (FAILED(hr))
{
printf("Failed to register for auto-update event : %x\n", hr);
goto errExit;
}
// Wait for event for 5 seconds
dwError = WaitForSingleObject(hEvent, 5000);
// cancel further notifications
hr = m_pKsControl->KsEvent(
NULL,
0,
&EventData,
sizeof(KSEVENTDATA),
&ulBytesReturned);
if (FAILED(hr)) printf("Cancel event returns : %x\n", hr);
if ((dwError == WAIT_FAILED) ||
(dwError == WAIT_ABANDONED) ||
(dwError == WAIT_TIMEOUT))
{
printf("Wait failed : %d\n", dwError);
goto errExit;
}
printf("Wait returned : %d\n", dwError);
// handle the autoupdate event..
FindExtensionNode
HRESULT FindExtensionNode(IKsTopologyInfo *pKsTopologyInfo, GUID guid, DWORD *node)
{
HRESULT hr = E_FAIL;
DWORD dwNumNodes = 0;
GUID guidNodeType;
IKsControl *pKsControl = NULL;
ULONG ulBytesReturned = 0;
KSP_NODE ExtensionProp;
if(!pKsTopologyInfo || !node)
return E_POINTER;
// Retrieve the number of nodes in the filter
hr = pKsTopologyInfo->get_NumNodes(&dwNumNodes);
if(!SUCCEEDED(hr))
return hr;
if(dwNumNodes == 0)
return E_FAIL;
// Find the extension unit node that corresponds to the given GUID
for(unsigned int i = 0; i < dwNumNodes; i++)
{
hr = E_FAIL;
pKsTopologyInfo->get_NodeType(i, &guidNodeType);
if(IsEqualGUID(guidNodeType, KSNODETYPE_DEV_SPECIFIC))
{
printf("found one xu node\n");
IExtensionUnit* pExtensionUnit = NULL;
hr = pKsTopologyInfo->CreateNodeInstance(i, __uuidof(IExtensionUnit), (void **)&pExtensionUnit);
if(SUCCEEDED(hr))
{
ExtensionProp.Property.Set = guid;
ExtensionProp.Property.Id = 0;
ExtensionProp.Property.Flags = KSPROPERTY_TYPE_SETSUPPORT | KSPROPERTY_TYPE_TOPOLOGY;
ExtensionProp.NodeId = i;
ExtensionProp.Reserved = 0;
*node = i;
return hr;
/*
hr = pKsControl->KsProperty((PKSPROPERTY)&ExtensionProp, sizeof(ExtensionProp), NULL, 0, &ulBytesReturned);
if(SUCCEEDED(hr))
{
*node = i;
break;
}
*/
}
else
{
printf("CreateNodeInstance failed - 0x%x\n", hr);
}
}
}
return hr;
}
参考资料:
https://docs.microsoft.com/en-us/windows-hardware/drivers/stream/extension-unit-plug-in-architecture