Windows 设备:为 UVC 网络摄像头的给定 IMFActivate* 获取 "location" 字符串

Windows device: get "location" string for a given IMFActivate* of a UVC webcam

当转到 Windows' "Device manager" 并单击(几乎)列表中的任何设备时,"General" 选项卡中的一条信息称为 "Location"。这是一个字符串,它是:

该信息可通过 Windows' Unified device property model API 获得。

我正在寻找的是从给定的 IMFActivate 对象中获取该信息。

有办法吗?我找不到如何从该激活对象获取 "device" 信息。我拥有的唯一数据是 "symbolic link"(在我的例子中,这个字符串:\?\usb#vid_04b4&pid_8888&mi_00#9&4fe28be&0&0000#{e5323777-f976-4f5b-9b55-b94699c46e44}\global)但是 link 的格式与我在 [=65= 中看到的字符串完全不同].

因此我的问题是:如何获取设备的 "Location" 字符串,给定其 IMFActivate 对象?

更新

这是我用来 "convert" 符号 link 的代码,由 IMFActivate 提供给设备 ID 字符串,可由设置 -api 函数识别,并且然后提取 "location string":

CString symLink2Location(const CString & _symLink)
{
    DEVINST di;
    CString devId = _symLink;
    devId = devId.Left( devId.Find(L"#{") );
    devId.Replace(L"\\?\", L"");
    devId.Replace(L"#", L"\");
    const auto rc = CM_Locate_DevNodeW(&di, devId.GetBuffer(), CM_LOCATE_DEVNODE_NORMAL);
    if(rc == CR_SUCCESS){
        DEVPROPTYPE dpt;
        ULONG sz = MAX_PATH;
        WCHAR prop[MAX_PATH];
        if(CM_Get_DevNode_PropertyW(di, &DEVPKEY_Device_LocationInfo, &dpt, (PBYTE)&prop, &sz, 0) == CR_SUCCESS){
            if(dpt == DEVPROP_TYPE_STRING){
                return prop;
            }
        }
    }
    return L"";
}

更新 2

这是 "Sound, video and game controllers" 下 devmgmt.msc 中看到的 3 个音频输入设备:

这 3 个都是 USB 设备,调用 MFEnumDeviceSources 时列出了所有 3 个,但它们的 "symbolic link" 没有解析为硬件设备。

IMFAttributes::Get[Allocated]String with MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK or MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_AUDCAP_SYMBOLIC_LINK this is device interface string which we can use as input to CM_Get_Device_Interface_PropertyW 返回的字符串。要获取位置信息(如果存在)需要执行 3 个步骤:

  1. 呼叫 CM_Get_Device_Interface_PropertyW DEVPKEY_Device_InstanceId - 结果我们得到了设备实例 设备标识符
  2. 在调用中使用返回的字符串 CM_Locate_DevNodeW
  3. 最后调用 CM_Get_DevNode_PropertyW DEVPKEY_Device_LocationInfo

代码示例:

CONFIGRET PrintLocation(PCWSTR pszDeviceInterface)
{
    ULONG cb = 0, rcb = 64;

    static volatile UCHAR guz;

    PVOID stack = alloca(guz);
    DEVPROPTYPE PropertyType;

    CONFIGRET err;

    union {
        PVOID pv;
        PWSTR sz;
        PBYTE pb;
    };

    do 
    {
        if (cb < rcb)
        {
            rcb = cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
        }

        if (!(err = CM_Get_Device_Interface_PropertyW(pszDeviceInterface, &DEVPKEY_Device_InstanceId, &PropertyType, pb, &rcb, 0)))
        {
            if (PropertyType == DEVPROP_TYPE_STRING)
            {
                DbgPrint("InstanceId=%S\n", sz);

                DEVINST dnDevInst;

                if (!(err = CM_Locate_DevNodeW(&dnDevInst, sz, CM_LOCATE_DEVNODE_NORMAL)))
                {
                    do 
                    {
                        if (cb < rcb)
                        {
                            rcb = cb = RtlPointerToOffset(pv = alloca(rcb - cb), stack);
                        }

                        if (!(err = CM_Get_DevNode_PropertyW(dnDevInst, &DEVPKEY_Device_LocationInfo, &PropertyType, pb, &rcb, 0)))
                        {
                            if (PropertyType == DEVPROP_TYPE_STRING)
                            {
                                DbgPrint("Location=%S\n", sz);
                            }
                            else
                            {
                                err = CR_WRONG_TYPE;
                            }
                        }

                    } while (err == CR_BUFFER_SMALL);
                }
            }
            else
            {
                err = CR_WRONG_TYPE;
            }

            break;
        }

    } while (err == CR_BUFFER_SMALL);

    return err;
}

当然如果硬编码缓冲区大小,函数可以更简单

CONFIGRET PrintLocationSimp(PCWSTR pszDeviceInterface)
{
    WCHAR buf[1024];

    DEVPROPTYPE PropertyType;

    ULONG BufferSize = sizeof(buf);

    CONFIGRET err;

    if (!(err = CM_Get_Device_Interface_PropertyW(pszDeviceInterface, &DEVPKEY_Device_InstanceId, &PropertyType, (PBYTE)buf, &BufferSize, 0)))
    {
        if (PropertyType == DEVPROP_TYPE_STRING)
        {
            DbgPrint("InstanceId=%S\n", buf);

            DEVINST dnDevInst;

            if (!(err = CM_Locate_DevNodeW(&dnDevInst, buf, CM_LOCATE_DEVNODE_NORMAL)))
            {
                BufferSize = sizeof(buf);

                if (!(err = CM_Get_DevNode_PropertyW(dnDevInst, &DEVPKEY_Device_LocationInfo, &PropertyType, (PBYTE)buf, &BufferSize, 0)))
                {
                    if (PropertyType == DEVPROP_TYPE_STRING)
                    {
                        DbgPrint("Location=%S\n", buf);
                    }
                    else
                    {
                        err = CR_WRONG_TYPE;
                    }
                }
            }
        }
        else
        {
            err = CR_WRONG_TYPE;
        }
    }

    return err;
}

对于IMFActivate我们可以使用下一个代码:

void mftest()
{
    IMFAttributes *pAttributes;

    if (SUCCEEDED(MFCreateAttributes(&pAttributes, 1)))
    {
        UINT32 count, cchLength;
        IMFActivate **ppDevices, *pDevice;

        if (SUCCEEDED(pAttributes->SetGUID(
            MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID)) && 
            SUCCEEDED(MFEnumDeviceSources(pAttributes, &ppDevices, &count)) &&
            count)
        {
            PVOID pv = ppDevices;

            do 
            {
                pDevice = *ppDevices++;

                PWSTR pszDeviceInterface;

                if (SUCCEEDED(pDevice->GetAllocatedString(
                    MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, &pszDeviceInterface, &cchLength)))
                {
                    DbgPrint("%S\n", pszDeviceInterface);

                    PrintLocation(pszDeviceInterface);

                    CoTaskMemFree(pszDeviceInterface);
                }

            } while (--count);

            CoTaskMemFree(pv);
        }

        pAttributes->Release();
    }
}