Using the IKsControl Interface to Access Audio Properties

In rare cases, a specialized audio application might need to use the IKsControl interface to access certain hardware capabilities of an audio adapter that are not exposed by the DeviceTopology API or the MMDevice API. The IKsControl interface makes the properties, events, and methods of kernel-streaming (KS) devices available to user-mode applications. Of primary interest to audio applications are KS properties. The IKsControl interface can be used in conjunction with the DeviceTopology API and the MMDevice API to access the KS properties of audio adapters.

The IKsControl interface is intended primarily for use by hardware vendors who write control-panel applications to manage their audio hardware. IKsControl is less useful for general-purpose audio applications that are not tied to particular hardware devices. The reason is that hardware vendors frequently implement proprietary mechanisms to access the audio properties of their devices. In contrast to the DeviceTopology API, which hides hardware-specific driver quirks and provides a relatively uniform interface for accessing audio properties, applications use IKsControl to communicate directly with drivers. For more information about IKsControl, see the Windows DDK documentation.

As discussed in Device Topologies, a subunit in the topology of an adapter device might support one or more of the function-specific control interfaces shown in the left column of the following table. Each entry in the right column of the table is the KS property that corresponds to the control interface on the left. The control interface provides convenient access to the property. Most audio drivers use KS properties to represent the function-specific processing capabilities of the subunits (also referred to as KS nodes) in the topologies of their audio adapters. For more information about KS properties and KS nodes, see the Windows DDK documentation.

Control interface KS property
IAudioAutoGainControl KSPROPERTY_AUDIO_AGC
IAudioBass KSPROPERTY_AUDIO_BASS
IAudioChannelConfig KSPROPERTY_AUDIO_CHANNEL_CONFIG
IAudioInputSelector KSPROPERTY_AUDIO_MUX_SOURCE
IAudioLoudness KSPROPERTY_AUDIO_LOUDNESS
IAudioMidrange KSPROPERTY_AUDIO_MID
IAudioMute KSPROPERTY_AUDIO_MUTE
IAudioOutputSelector KSPROPERTY_AUDIO_DEMUX_DEST
IAudioPeakMeter KSPROPERTY_AUDIO_PEAKMETER
IAudioTreble KSPROPERTY_AUDIO_TREBLE
IAudioVolumeLevel KSPROPERTY_AUDIO_VOLUMELEVEL
IDeviceSpecificProperty KSPROPERTY_AUDIO_DEV_SPECIFIC

 

The topologies of some audio adapters might contain subunits that have KS properties that are not listed in the preceding table. For example, assume that a call to the IPart::GetSubType method for a particular subunit retrieves the GUID value KSNODETYPE_TONE. This subtype GUID indicates that the subunit supports one or more of the following KS properties:

  • KSPROPERTY_AUDIO_BASS
  • KSPROPERTY_AUDIO_MID
  • KSPROPERTY_AUDIO_TREBLE
  • KSPROPERTY_AUDIO_BASS_BOOST

The first three properties in this list can be accessed through the control interfaces that appear in the preceding table, but the KSPROPERTY_AUDIO_BASS_BOOST property has no corresponding control interface in the DeviceTopology API. However, an application can use the IKsControl interface to access this property, if the subunit supports the property.

The following steps are required to access the KSPROPERTY_AUDIO_BASS_BOOST property of a subunit of subtype KSNODETYPE_TONE:

  1. Call the IConnector::GetDeviceIdConnectedTo or IDeviceTopology::GetDeviceId method to obtain the device ID string that identifies the adapter device. This string is similar to an endpoint ID string, except that it identifies an adapter device instead of an endpoint device. For more information about the difference between an adapter device and an endpoint device, see Audio Endpoint Devices.
  2. Obtain the IMMDevice interface of the adapter device by calling the IMMDeviceEnumerator::GetDevice method with the device ID string. This IMMDevice interface is the same as the interface described in IMMDevice Interface, but it represents an adapter device instead of an endpoint device.
  3. Obtain the IKsControl interface on the subunit by calling the IMMDevice::Activate method with parameter iid set to REFIID IID_IKsControl. Note that the interfaces supported by this Activate method, which is for an adapter device, are different from the interfaces supported by the Activate method for an endpoint device. In particular, the Activate method for an adapter device supports IKsControl.
  4. Call the IPart::GetLocalId method to obtain the local ID of the subunit.
  5. Construct a KS property request. The KS node ID required for the request is contained in the 16 least-significant bits of the local ID obtained in the previous step.
  6. Send the KS property request to the audio driver by calling the IKsControl::KsProperty method. For more information about this method, see the Windows DDK documentation.

The following code example retrieves the value of the KSPROPERTY_AUDIO_BASS_BOOST property from a subunit of subtype KSNODETYPE_TONE:

//-----------------------------------------------------------
// This function calls the IKsControl::Property method to get
// the value of the KSPROPERTY_AUDIO_BASS_BOOST property of
// a subunit. Parameter pPart should point to a part that is
// a subunit with a subtype GUID value of KSNODETYPE_TONE.
//-----------------------------------------------------------
#define PARTID_MASK 0x0000ffff
#define EXIT_ON_ERROR(hres)  \
              if (FAILED(hres)) { goto Exit; }
#define SAFE_RELEASE(punk)  \
              if ((punk) != NULL)  \
                { (punk)->Release(); (punk) = NULL; }

const IID IID_IKsControl = __uuidof(IKsControl);

HRESULT GetBassBoost(IMMDeviceEnumerator *pEnumerator,
                     IPart *pPart, BOOL *pbValue)
{
    HRESULT hr;
    IDeviceTopology *pTopology = NULL;
    IMMDevice *pPnpDevice = NULL;
    IKsControl *pKsControl = NULL;
    LPWSTR pwszDeviceId = NULL;

    if (pEnumerator == NULL || pPart == NULL || pbValue == NULL)
    {
        return E_INVALIDARG;
    }

    // Get the topology object for the adapter device that contains
    // the subunit represented by the IPart interface.
    hr = pPart->GetTopologyObject(&pTopology);
    EXIT_ON_ERROR(hr)

    // Get the device ID string that identifies the adapter device.
    hr = pTopology->GetDeviceId(&pwszDeviceId);
    EXIT_ON_ERROR(hr)

    // Get the IMMDevice interface of the adapter device object.
    hr = pEnumerator->GetDevice(pwszDeviceId, &pPnpDevice);
    EXIT_ON_ERROR(hr)

    // Activate an IKsControl interface on the adapter device object.
    hr = pPnpDevice->Activate(IID_IKsControl, CLSCTX_ALL, NULL, (void**)&pKsControl);
    EXIT_ON_ERROR(hr)

    // Get the local ID of the subunit (contains the KS node ID).
    UINT localId = 0;
    hr = pPart->GetLocalId(&localId);
    EXIT_ON_ERROR(hr)

    KSNODEPROPERTY_AUDIO_CHANNEL ksprop;
    ZeroMemory(&ksprop, sizeof(ksprop));
    ksprop.NodeProperty.Property.Set = KSPROPSETID_Audio;
    ksprop.NodeProperty.Property.Id = KSPROPERTY_AUDIO_BASS_BOOST;
    ksprop.NodeProperty.Property.Flags = KSPROPERTY_TYPE_GET | KSPROPERTY_TYPE_TOPOLOGY;
    ksprop.NodeProperty.NodeId = localId & PARTID_MASK;
    ksprop.Channel = 0;

    // Send the property request.to the device driver.
    BOOL bValue = FALSE;
    ULONG valueSize;
    hr = pKsControl->KsProperty(
                         &ksprop.NodeProperty.Property, sizeof(ksprop),
                         &bValue, sizeof(bValue), &valueSize);
    EXIT_ON_ERROR(hr)

    *pbValue = bValue;

Exit:
    SAFE_RELEASE(pTopology)
    SAFE_RELEASE(pPnpDevice)
    SAFE_RELEASE(pKsControl)
    CoTaskMemFree(pwszDeviceId);
    return hr;
}

In the preceding code example, the GetBassBoost function takes the following three parameters:

  • pEnumerator points to the IMMDeviceEnumerator interface of an audio endpoint enumerator.
  • pPart points to the IPart interface of a subunit that has a subtype of KSNODETYPE_TONE.
  • pbValue points to a BOOL variable into which the function writes the property value.

A program calls GetBassBoost only after it calls IPart::GetSubType and determines that the subtype of the subunit is KSNODETYPE_TONE. The property value is TRUE if bass boost is enabled. It is FALSE if bass boost is disabled.

At the beginning of the GetBassBoost function, the call to the IPart::GetTopologyObject method obtains the IDeviceTopology interface of the adapter device that contains the KSNODETYPE_TONE subunit. The IDeviceTopology::GetDeviceId method call retrieves the device ID string that identifies the adapter device. The IMMDeviceEnumerator::GetDevice method call takes the device ID string as an input parameter, and retrieves the IMMDevice interface of the adapter device. Next, the IMMDevice::Activate method call retrieves the IKsControl interface of the subunit. For more information about the IKsControl interface, see the Windows DDK documentation.

Next, the preceding code example creates a KSNODEPROPERTY_AUDIO_CHANNEL structure that describes the bass-boost property. The code example passes a pointer to the structure to the IKsControl::KsProperty method, which uses the information in the structure to retrieve the property value. For more information about the KSNODEPROPERTY_AUDIO_CHANNEL structure and the IKsControl::KsProperty method, see the Windows DDK documentation.

Audio hardware typically assigns a separate bass-boost state to each channel in an audio stream. In principle, bass boost can be enabled for some channels and disabled for others. The KSNODEPROPERTY_AUDIO_CHANNEL structure contains a Channel member that specifies the channel number. If a stream contains N channels, the channels are numbered from 0 to N— 1. The preceding code example gets the value of the bass-boost property for channel 0 only. This implementation implicitly assumes that the bass-boost properties for all of the channels are set uniformly to the same state. Thus, reading the bass-boost property for channel 0 is sufficient to determine the bass-boost property value for the stream. To be consistent with this assumption, a corresponding function to set the bass-boost property would set all channels to the same bass-boost property value. These are reasonable conventions, but not all hardware vendors necessarily follow them. For example, a vendor might supply a control-panel application that enables bass boost only for channels that drive full-range speakers. (A full-range speaker is capable of playing sounds over the full range from bass to treble.) In that case, the property value retrieved by the preceding code example might not accurately represent the bass-boost state of the stream.

Clients that use the control interfaces listed in the preceding table can call the IPart::RegisterControlChangeCallback method to register for notifications when the value of a control parameter changes. Note that the IKsControl interface does not provide a similar means for clients to register for notifications when a property value changes. If IKsControl did support property-change notifications, it could inform an application when another application changed a property value. However, in contrast to the more commonly used controls that are monitored through IPart notifications, the properties managed by IKsControl are intended primarily for use by hardware vendors, and those properties are likely to be changed only by control-panel applications written by the vendors. If an application must detect property changes made by another application, it can detect the changes by periodically polling the property value through IKsControl.

Programming Guide