Getting a WPD Sample Driver working with Windows Media Player 11

We mentioned in a previous post that the WPD sample drivers are not designed to enumerate in Windows Media Player 11 (WMP11) due to compatibility issues.   This post covers the changes needed to enable the WPD Comprehensive Sample Driver (WpdWudfSampleDriver) to appear as a portable device in WMP11 and simulate a sync.    

The WpdWudfSampleDriver is featured in this post because it contains a lot more functionality than the WpdHelloWorldDriver, and is closer to meeting the requirements of WMP11 than the WpdHelloWorldDriver.    More specifically, the WpdWudfSampleDriver simulates the following additional functionality:

1. Creating and transferring data objects on the device.

2. Rendering information profiles for creating content to the device.

3. Setting properties on objects.

4. Posting device events.

5. Bulk property operations.

In the list above, items 1-3 provide basic compatibility with WMP11.  Device events are a "good to have" feature, for application responsiveness to changes in the device content and device state.  Bulk property operations are optional, but also provide improved application responsiveness - multiple properties for objects can be sent in batches, instead of multiple per-object "GetProperty" requests.

During sync, the WpdWudfSampleDriver simulates file and folder creation by allowing content to be created and transferred to the device.   The data is not persisted by the driver, and will exist as long as the sample driver is still loaded.   Once the driver is disconnected (e.g. disable in Device Manager), the data goes away.   Typically, your WPD driver would be sending this data to your physical device and persisting it there, so this should not be an issue for real devices.

 

Modifications to the WpdWudfSampleDriver to Enumerate and Sync in WMP11

Some of the changes below involve updating the fake contents on the device for illustration.   The same property requirements will apply for your own driver or device objects.  

1.  Add WPD_DEVICE_SYNC_PARTNER as a writable property for the "Device" object.   This is needed by WMP11 for setting up a sync partnership with the device. 

 In deviceobjectfakecontent.h:

class DeviceObjectFakeContent : public FakeContent
{
  ...

    virtual HRESULT GetAllValues(
        IPortableDeviceValues*  pValues)
    {
    ...
        // Add WPD_DEVICE_SYNC_PARTNER
        hrSetValue = pValues->SetStringValue(WPD_DEVICE_SYNC_PARTNER, SyncPartner);
        if (hrSetValue != S_OK)
        {
            CHECK_HR(hrSetValue, "Failed to set WPD_DEVICE_SYNC_PARTNER");
            return hrSetValue;
        }
        ...
    }    

    virtual HRESULT WriteValue(
        const PROPERTYKEY& key,
        const PROPVARIANT& Value)
    {
        ...
        else if(IsEqualPropertyKey(key, WPD_DEVICE_SYNC_PARTNER))
        {
            if (Value.vt == VT_LPWSTR)
            {
               SyncPartner = Value.pwszVal;
            }
            else
            {
                hr = E_INVALIDARG;
                CHECK_HR(hr, "Failed to set WPD_DEVICE_SYNC_PARTNER because type was not VT_LPWSTR");
            }
        }
  ...
    }
   ...

private:
    ...
    CAtlStringW SyncPartner;
};

 

2. Add WPD_OBJECT_NON_CONSUMABLE as a writable property for all objects.   This allows simulating the saving of sync settings into a file on the device called WMPInfo.xml.   This file is created by WMP11 on the device and marked as "Non Consumable," with Format = WPD_OBJECT_FORMAT_UNSPECIFIED and Content Type = WPD_CONTENT_TYPE_UNSPECIFIED.  

 In fakecontent.h:

class FakeContent
{

    virtual HRESULT WriteValue(
        const PROPERTYKEY& key,
        const PROPVARIANT& Value)
    {
        ...
        else if (IsEqualPropertyKey(key, WPD_OBJECT_NON_CONSUMABLE))
        {
            if(Value.vt == VT_BOOL)
            {
                NonConsumable = Value.boolVal;
            }
            else
            {
                hr = E_INVALIDARG;
                CHECK_HR(hr, "Failed to set WPD_OBJECT_NON_CONSUMABLE because type was not VT_BOOL");
            }
        }
        ...
    }
   ...
};

 

3. Add support for WPD_OBJECT_ORIGINAL_FILE_NAME in for all objects.  This involves updating the support properties and fixed attributes tables, and overriding FakeGenericFileContent::GetAllValues() to return this property in addition to the standard properties.

 In helpers.cpp:

const PROPERTYKEY* g_SupportedPropertiesForFakeContentFormat[] =
{
    ...
    &WPD_OBJECT_ORIGINAL_FILE_NAME,
    ...
};

KeyAndAttributesEntry g_FixedAttributesTable [] =
{     
    ...
    {&FakeContent_Format, &WPD_OBJECT_ORIGINAL_FILE_NAME, 
         UnspecifiedForm_CanRead_CannotWrite_CannotDelete_Fast},
    // Properties for the device object
    ...
}

In fakecontent.h:

class FakeGenericFileContent : public FakeContent
{
public:

    virtual HRESULT GetAllValues(
        IPortableDeviceValues*  pStore)
    {
        HRESULT             hr          = S_OK;
        PropVariantWrapper  pvValue;

        // Call the base class to fill in the standard properties
        hr = FakeContent::GetAllValues(pStore);
        if (FAILED(hr))
        {
            CHECK_HR(hr, "Failed to get basic property set");
            return hr;
        }

        // Add WPD_OBJECT_ORIGINAL_FILE_NAME
        pvValue = FileName;
        hr = pStore->SetValue(WPD_OBJECT_ORIGINAL_FILE_NAME, &pvValue);
        if (hr != S_OK)
        {
            CHECK_HR(hr, ("Failed to set WPD_OBJECT_ORIGINAL_FILE_NAME"));
            return hr;
        }

        return hr;
    }
    ...
};

 

4. Add support for creation of folders.   These folders need to support WPD_OBJECT_ORIGINAL_FILE_NAME.    This allows WMP11 to create the "Album", "Artist" folders on the device during the sync operation.   

One way to do this is to define a new FakeFolderContent class that inherits from FakeContent, and return WPD_OBJECT_ORIGINAL_FILE_NAME in FakeFolderContent::GetAllValues() .

 Added new header file: fakefoldercontent.h

class FakeFolderContent : public FakeContent
{
public:
    ...

    virtual HRESULT GetAllValues(
        IPortableDeviceValues*  pStore)
    {
        HRESULT             hr          = S_OK;
        PropVariantWrapper  pvValue;

        // Call the base class to fill in the standard properties
        hr = FakeContent::GetAllValues(pStore);
        if (FAILED(hr))
        {
            CHECK_HR(hr, "Failed to get basic property set");
            return hr;
        }

        // Add WPD_OBJECT_ORIGINAL_FILE_NAME
        pvValue = FileName;
        hr = pStore->SetValue(WPD_OBJECT_ORIGINAL_FILE_NAME, &pvValue);
        if (hr != S_OK)
        {
            CHECK_HR(hr, ("Failed to set WPD_OBJECT_ORIGINAL_FILE_NAME"));
            return hr;
        }

        return hr;
    }
};

All top level folders (e.g. Video Folder, Music Folder) in the sample driver can then initialize a FakeFolderContent instead of a FakeContent.    The value of WPD_OBJECT_ORIGINAL_FILE_NAME is usually the same as WPD_OBJECT_NAME.  Assign the value of FileName in FakeDevice::CreateContentObject() whenever an object is created, so that it will be returned during subsequent "Get Property" queries.

 In fakedevice.h:

class FakeDevice
{
    ...   
    HRESULT InitializeContent(
        IPortableDeviceClassExtension *pPortableDeviceClassExtension)
    {
        ...
        
        // Add Media folder: Music folder
        pContent = new FakeFolderContent();
        if (pContent)
        {
            ...
            pContent->FileName  = pContent->Name;  // Set Original File Name property
            ...
        }

        // Repeat for other types of folders declared here
        ...

        // Add generic file content objects to storage 1
        for(DWORD dwIndex = 0; dwIndex < NUM_VERTICAL_OBJECTS; dwIndex++)
        {
            m_dwLastObjectID++;

            pContent = new FakeGenericFileContent();
            if (pContent)
            {
                ...
                pContent->FileName = pContent->Name;
                ...
            }
        }
        ...
    }

    HRESULT CreateContentObject(
        LPCWSTR                 pszObjectName,
        LPCWSTR                 pszParentID,
        REFGUID                 guidContentType,
        IPortableDeviceValues*  pObjectProperties,
        FakeContent**           ppContent)
    {
        ...
        if(guidContentType == WPD_CONTENT_TYPE_FOLDER)
        {
            FakeContent* pContent = NULL;

            pContent = new FakeContent();
            if (pContent)
            {
                ...
                pContent->FileName = pszObjectName;
                ...
            }
        }
   }
   ...
}; 

 

5.  Modify WpdWudfSampleDriver.INF to set EnableLegacySupport to 3 so that the WMDM compatibility component is registered and enabled for this device when it installs.   

 ; Enable support for legacy WIA and WMDM applications
HKR,,"EnableLegacySupport",0x10001,3

 

6. When returning the list of rendering information profiles in SetRenderingProfiles() , return an additional profile for WPD_OBJECT_FORMAT_WMV.  This profile contains a single WPD_PROPERTY_ATTRIBUTE_FORM_ENUMERATION entry with an IPortableDevicePropVariantCollection containing the list of valid FourCC Codec VT_UI4 values.   

This step is needed because the sample driver reports support for WMV.    In your real driver implementation, you don't need this step unless your device needs to support WPD_OBJECT_FORMAT_WMV.   WMP11 has additional requirements on the attributes of FourCC Codec property, and would not enumerate any content on the device otherwise [Symptom: you will see the device in the Sync Pane, but no content under the device].

 In helpers.cpp:

HRESULT GetVideoProfile(
    IPortableDeviceValues** ppProfile)
{
    HRESULT hr = S_OK;
    CComPtr<IPortableDeviceValues> pProfile;
    CComPtr<IPortableDeviceValues> pFourCCCode;

    hr = CoCreateInstance(CLSID_PortableDeviceValues,
                          NULL,
                          CLSCTX_INPROC_SERVER,
                          IID_IPortableDeviceValues,
                          (VOID**) &pProfile);
    CHECK_HR(hr, "Failed to CoCreateInstance CLSID_PortableDeviceValues");

    if (hr == S_OK)
    {
        hr = CoCreateInstance(CLSID_PortableDeviceValues,
                              NULL,
                              CLSCTX_INPROC_SERVER,
                              IID_IPortableDeviceValues,
                              (VOID**) &pFourCCCode);
        CHECK_HR(hr, "Failed to CoCreateInstance CLSID_PortableDeviceValues");
    }

    // Set the value for WPD_OBJECT_FORMAT to indicate this profile applies to WMV objects
    if (hr == S_OK)
    {
        hr = pProfile->SetGuidValue(WPD_OBJECT_FORMAT, WPD_OBJECT_FORMAT_WMV);
        CHECK_HR(hr, "Failed to set WPD_OBJECT_FORMAT");
    }

    // Set the value for WPD_VIDEO_FOURCC_CODE
    if (hr == S_OK)
    {
        CComPtr<IPortableDevicePropVariantCollection> pFourCCCodeEnumElements;

        if (hr == S_OK)
        {
            hr = CoCreateInstance(CLSID_PortableDevicePropVariantCollection,
                                  NULL,
                                  CLSCTX_INPROC_SERVER,
                                  IID_IPortableDevicePropVariantCollection,
                                  (VOID**) &pFourCCCodeEnumElements);
            CHECK_HR(hr, "Failed to CoCreateInstance 
                     CLSID_PortableDevicePropVariantCollection");
        }

        if (hr == S_OK)
        {
            hr = pFourCCCode->SetUnsignedIntegerValue(
                                 WPD_PROPERTY_ATTRIBUTE_FORM, 
                                 WPD_PROPERTY_ATTRIBUTE_FORM_ENUMERATION);
            CHECK_HR(hr, "Failed to set WPD_PROPERTY_ATTRIBUTE_FORM 
                          for WPD_VIDEO_FOURCC_CODE");
        }

        if (hr == S_OK)
        {
            // Only 1 sample value is set here, add more as appropriate for your device
            PROPVARIANT pvValue;
            PropVariantInit(&pvValue);
            pvValue.vt = VT_UI4;
            pvValue.ulVal = MAKEFOURCC('W', 'M', 'V', '3');
            hr = pFourCCCodeEnumElements->Add(&pvValue);
            CHECK_HR(hr, "Failed to populate the FourCC Code Enumeration Elements");
        }

        if (hr == S_OK)
        {
            hr = pFourCCCode->SetIPortableDevicePropVariantCollectionValue(
                                 WPD_PROPERTY_ATTRIBUTE_ENUMERATION_ELEMENTS, 
                                 pFourCCCodeEnumElements);
            CHECK_HR(hr, "Failed to set WPD_PROPERTY_ATTRIBUTE_ENUMERATION_ELEMENTS 
                          for WPD_VIDEO_FOURCC_CODE");
        }

        // Now set the Video FourCC Code property to be pFourCCCode
        if (hr == S_OK)
        {
            hr = pProfile->SetIPortableDeviceValuesValue(
                              WPD_VIDEO_FOURCC_CODE, 
                              pFourCCCode);
            CHECK_HR(hr, "Failed to add the WPD_VIDEO_FOURCC_CODE attributes 
                          to the WPD_OBJECT_FORMAT_WMV rendering profile");
        }
    }

    // Set the output result
    if (hr == S_OK)
    {
        hr = pProfile->QueryInterface(IID_PPV_ARGS(ppProfile));
        CHECK_HR(hr, "Failed to QI for IPortableDeviceValues");
    }

    return hr;
}

HRESULT SetRenderingProfiles(
    IPortableDeviceValues*          pValues)
{
    ...
    CComPtr<IPortableDeviceValues>  pVideoProfile;
    ...

    // Get the video profile
    if (hr == S_OK)
    {
        hr = GetVideoProfile(&pVideoProfile);
        CHECK_HR(hr, "Failed to get video profile properties");
    }

    // Add the profile to the collection
    if (hr == S_OK)
    {
        hr = pProfiles->Add(pVideoProfile);
        CHECK_HR(hr, "Failed to add video profile to profile collection");
    }
    ...
}

Known Issue with Multiple Storages

When a device that contains multiple storages is disconnected, one of the storages will still remain in the treeview.   This is a known bug in WMP11.   Since the WpdWudfSampleDriver emulates multiple storages, you'll see this bug when you disable the driver from Device Manager while WMP11 is running.

 

Update [August 7, 2009]:   The WDK 7.0.0 now contains
an updated WpdWudfSampleDriver sample that supports enumeration in
Windows Media Player. You can get the latest WDK here. See this
WDK 7.0.0 RTM release note
for the complete list of build environments and
target operating systems applicable to the WPD driver samples.

 

 

This posting is provided "AS IS" with no warranties, and confers no rights.