使用 IKsControl 接口访问音频属性

在极少数情况下,专用音频应用程序可能需要使用 IKsControl 接口访问 DeviceTopology APIMMDevice API 未公开的音频适配器的某些硬件功能。 用户模式应用程序可以通过 IKsControl 接口使用内核流式处理 (KS) 设备的属性、事件和方法。 音频应用程序主要关注 KS 属性。 IKsControl 接口可与 DeviceTopology API 和 MMDevice API 结合使用,以访问音频适配器的 KS 属性。

IKsControl 接口主要由编写控制面板应用程序的硬件供应商用于管理其音频硬件。 对于不绑定到特定硬件设备的常规用途音频应用程序,IKsControl 不太有用。 原因是硬件供应商经常实施专有机制来访问其设备的音频属性。 与隐藏特定于硬件的驱动程序习惯并提供相对统一的接口来访问音频属性的 DeviceTopology API 相比,应用程序使用 IKsControl 直接与驱动程序通信。 有关 IKsControl 的详细信息,请参阅 Windows DDK 文档。

设备拓扑中所述,适配器设备拓扑中的子单元可能支持下表左列中所示的一个或多个特定于函数的控制接口。 表右列中的每个项都是对应于左侧控件接口的 KS 属性。 控件接口提供对属性的便捷访问。 大多数音频驱动程序使用 KS 属性来表示其音频适配器拓扑中子单元(也称为 KS 节点)的特定于函数的处理功能。 有关 KS 属性和 KS 节点的详细信息,请参阅 Windows DDK 文档。

控件接口 KS 属性
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

 

某些音频适配器的拓扑可能包含具有上表中未列出的 KS 属性的子单元。 例如,假设对特定子单元的 IPart::GetSubType 方法的调用将检索 GUID 值 KSNODETYPE_TONE。 此子类型 GUID 指示子单元支持以下一个或多个 KS 属性:

  • KSPROPERTY_AUDIO_BASS
  • KSPROPERTY_AUDIO_MID
  • KSPROPERTY_AUDIO_TREBLE
  • KSPROPERTY_AUDIO_BASS_BOOST

可以通过上表中显示的控件接口访问此列表中的前三个属性,但 KSPROPERTY_AUDIO_BASS_BOOST 属性在 DeviceTopology API 中没有相应的控件接口。 但是,如果子单元支持此属性,则应用程序可以使用 IKsControl 接口访问此属性。

访问子类型 KSNODETYPE_TONE 子单位的 KSPROPERTY_AUDIO_BASS_BOOST 属性需要执行以下步骤:

  1. 调用 IConnector::GetDeviceIdConnectedToIDeviceTopology::GetDeviceId 方法以获取标识适配器设备的设备 ID 字符串。 此字符串类似于终结点 ID 字符串,只不过它标识适配器设备而不是终结点设备。 有关适配器设备和终结点设备之间差异的详细信息,请参阅音频终结点设备
  2. 使用设备 ID 字符串调用 IMMDeviceEnumerator::GetDevice 方法,获取适配器设备的 IMMDevice 接口。 此 IMMDevice 接口与所述的 IMMDevice 接口相同,但它表示适配器设备而不是终结点设备。
  3. 通过调用参数 iid 设置为 REFIID IID_IKsControl 的 IMMDevice::Activate 方法,获取子单元上的 IKsControl 接口。 请注意,此 Activate 方法支持的接口(适用于适配器设备)不同于终结点设备的 Activate 方法支持的接口。 具体而言,适配器设备的 Activate 方法支持 IKsControl
  4. 调用 IPart::GetLocalId 方法以获取子单元的本地 ID。
  5. 构造 KS 属性请求。 请求所需的 KS 节点 ID 包含在上一步中获取的本地 ID 的 16 个最低有效位中。
  6. 通过调用 IKsControl::KsProperty 方法将 KS 属性请求发送到音频驱动程序。 有关此方法的详细信息,请参阅 Windows DDK 文档。

下面的代码示例从子类型 KSNODETYPE_TONE 的子单元中检索 KSPROPERTY_AUDIO_BASS_BOOST 属性的值:

//-----------------------------------------------------------
// 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;
}

在前面的代码示例中,GetBassBoost 函数采用以下三个参数:

  • pEnumerator 指向音频终结点枚举器的 IMMDeviceEnumerator 接口。
  • pPart 指向子类型为 KSNODETYPE_TONE 的子单元的 IPart 接口。
  • pbValue 指向函数写入属性值的 BOOL 变量。

程序仅在调用 IPart::GetSubType 后调用 GetBassBoost,并确定子单元的子类型为 KSNODETYPE_TONE。 如果启用低音提升,则属性值为 TRUE。 如果低音提升已禁用,则为 FALSE

在 GetBassBoost 函数的开头,对 IPart::GetTopologyObject 方法的调用获取包含 KSNODETYPE_TONE 子单元的适配器设备的 IDeviceTopology 接口。 IDeviceTopology::GetDeviceId 方法调用检索标识适配器设备的设备 ID 字符串。 IMMDeviceEnumerator::GetDevice 方法调用将设备 ID 字符串作为输入参数,并检索适配器设备的 IMMDevice 接口。 接下来,IMMDevice::Activate 方法调用检索子单元的 IKsControl 接口。 有关 IKsControl 接口的详细信息,请参阅 Windows DDK 文档。

接下来,前面的代码示例创建描述低音提升属性的 KSNODEPROPERTY_AUDIO_CHANNEL 结构。 该代码示例将指向结构的指针传递给 IKsControl::KsProperty 方法,该方法使用结构中的信息检索属性值。 有关 KSNODEPROPERTY_AUDIO_CHANNEL 结构和 IKsControl::KsProperty 方法的详细信息,请参阅 Windows DDK 文档。

音频硬件通常为音频流中的每个通道分配单独的低音提升状态。 原则上,可以针对某些通道启用低音提升,并针对其他通道禁用低音提升。 KSNODEPROPERTY_AUDIO_CHANNEL 结构包含一个指定通道编号的 Channel 成员。 如果流包含 N 个通道,则通道编号为 0 到 N — 1。 前面的代码示例仅获取通道 0 的低音提升属性的值。 此实现隐式假定所有通道的低音提升属性都统一设置为相同的状态。 因此,读取通道 0 的低音提升属性足以确定流的低音提升属性值。 为了与此假设保持一致,设置低音提升属性的相应函数会将所有通道设置为相同的低音提升属性值。 这些是合理的约定,但并非所有硬件供应商都会遵循这些约定。 例如,供应商可能提供一个控制面板应用程序,该应用程序仅为驱动全频扬声器的通道启用低音提升。 (全频扬声器能够播放从低音到高音整个范围的声音。在这种情况下,前面的代码示例检索的属性值可能无法准确表示流的低音提升状态。

使用上表中列出的控件接口的客户端可以调用 IPart::RegisterControlChangeCallback 方法,以便在控件参数的值发生更改时注册通知。 请注意,当属性值发生更改时,IKsControl 接口不会为客户端提供类似的方法来注册通知。 如果 IKsControl 确实支持属性更改通知,则当另一个应用程序更改属性值时,可以通知应用程序。 但是,与通过 IPart 通知监视的较常用控件相比,IKsControl 管理的属性主要由硬件供应商使用,并且这些属性可能仅通过供应商编写的控制面板应用程序更改。 如果应用程序必须检测另一个应用程序所做的属性更改,则可以通过定期通过 IKsControl 轮询属性值来检测更改。

编程指南