旧版音频应用程序的音频事件

旧版音频 API(如 DirectSound、DirectShow 和 waveOutXxx 函数)使应用程序能够获取和设置音频流的音量级别。 应用程序可以使用这些 API 中的音量控制功能在其应用程序窗口中显示音量滑块。

在 Windows Vista 中,系统音量控制程序 Sndvol 允许用户控制各个应用程序的音频音量级别。 应用程序显示的音量滑块应链接到 Sndvol 中的相应卷滑块。 如果用户通过应用程序窗口中的卷滑块调整应用程序卷,则 Sndvol 中的相应卷滑块将立即移动以指示新的卷级别。 相反,如果用户通过 Sndvol 调整应用程序卷,则应用程序窗口中的卷滑块应移动以指示新的卷级别。

在 Windows Vista 中,Sndvol 立即反映应用程序通过调用 IDirectSoundBuffer::SetVolume 方法或 waveOutSetVolume 函数进行的卷更改。 但是,旧版音频 API(如 DirectSound 或 waveOutXxx 函数)在用户通过 Sndvol 更改应用程序音量时,无法通知应用程序。 如果应用程序显示卷滑块但未收到卷更改通知,则滑块将无法反映用户在 Sndvol 中所做的更改。 若要实现适当的行为,应用程序设计器必须以某种方式补偿旧音频 API 缺少通知。

一种解决方案可能是让应用程序设置计时器来定期提醒它检查音量级别,以查看它是否已更改。

更优雅的解决方案是让应用程序使用核心音频 API 的事件通知功能。 具体而言,应用程序可以注册 IAudioSessionEvents 接口,以在发生音量更改或其他类型的音频事件时接收回调。 当卷更改时,卷更改回调例程可以立即更新应用程序的卷滑块以反映更改。

以下代码示例演示应用程序如何注册以接收音量更改和其他音频事件的通知:

//-----------------------------------------------------------
// Register the application to receive notifications when the
// volume level changes on the default process-specific audio
// session (with session GUID value GUID_NULL) on the audio
// endpoint device with the specified data-flow direction
// (eRender or eCapture) and device role.
//-----------------------------------------------------------
#define EXIT_ON_ERROR(hr)  \
              if (FAILED(hr)) { goto Exit; }
#define SAFE_RELEASE(punk)  \
              if ((punk) != NULL)  \
                { (punk)->Release(); (punk) = NULL; }

class AudioVolumeEvents
{
    HRESULT _hrStatus;
    IAudioSessionManager *_pManager;
    IAudioSessionControl *_pControl;
    IAudioSessionEvents *_pAudioEvents;
public:
    AudioVolumeEvents(EDataFlow, ERole, IAudioSessionEvents*);
    ~AudioVolumeEvents();
    HRESULT GetStatus() { return _hrStatus; };
};

// Constructor
AudioVolumeEvents::AudioVolumeEvents(EDataFlow flow, ERole role,
                                     IAudioSessionEvents *pAudioEvents)
{
    IMMDeviceEnumerator *pEnumerator = NULL;
    IMMDevice *pDevice = NULL;

    _hrStatus = S_OK;
    _pManager = NULL;
    _pControl = NULL;
    _pAudioEvents = pAudioEvents;

    if (_pAudioEvents == NULL)
    {
        _hrStatus = E_POINTER;
        return;
    }

    _pAudioEvents->AddRef();

    // Get the enumerator for the audio endpoint devices
    // on this system.
    _hrStatus = CoCreateInstance(__uuidof(MMDeviceEnumerator),
                                 NULL, CLSCTX_INPROC_SERVER,
                                 __uuidof(IMMDeviceEnumerator),
                                 (void**)&pEnumerator);
    EXIT_ON_ERROR(_hrStatus)

    // Get the audio endpoint device with the specified data-flow
    // direction (eRender or eCapture) and device role.
    _hrStatus = pEnumerator->GetDefaultAudioEndpoint(flow, role,
                                                     &pDevice);
    EXIT_ON_ERROR(_hrStatus)

    // Get the session manager for the endpoint device.
    _hrStatus = pDevice->Activate(__uuidof(IAudioSessionManager),
                                  CLSCTX_INPROC_SERVER, NULL,
                                  (void**)&_pManager);
    EXIT_ON_ERROR(_hrStatus)

    // Get the control interface for the process-specific audio
    // session with session GUID = GUID_NULL. This is the session
    // that an audio stream for a DirectSound, DirectShow, waveOut,
    // or PlaySound application stream belongs to by default.
    _hrStatus = _pManager->GetAudioSessionControl(NULL, 0, &_pControl);
    EXIT_ON_ERROR(_hrStatus)

    _hrStatus = _pControl->RegisterAudioSessionNotification(_pAudioEvents);
    EXIT_ON_ERROR(_hrStatus)

Exit:
    SAFE_RELEASE(pEnumerator)
    SAFE_RELEASE(pDevice)
}

// Destructor
AudioVolumeEvents::~AudioVolumeEvents()
{
    if (_pControl != NULL)
    {
        _pControl->UnregisterAudioSessionNotification(_pAudioEvents);
    }
    SAFE_RELEASE(_pManager)
    SAFE_RELEASE(_pControl)
    SAFE_RELEASE(_pAudioEvents)
};

前面的代码示例实现名为 AudioVolumeEvents 的类。 在程序初始化期间,音频应用程序通过创建 AudioVolumeEvents 对象来启用音频事件通知。 此类的构造函数采用三个输入参数:

构造函数将流和角色值作为输入参数提供给 IMMDeviceEnumerator::GetDefaultAudioEndpoint 方法。 该方法创建 IMMDevice 对象,该对象使用指定的数据流方向和设备角色封装音频终结点设备。

应用程序实现由 pAudioEvents指向的对象。 (上述代码示例中未显示实现。有关实现 IAudioSessionEvents 接口的代码示例,请参阅 音频会话事件。)此接口中的每个方法都接收特定类型的音频事件的通知。 如果应用程序对特定事件类型不感兴趣,则该事件类型的方法应不执行任何作,但返回S_OK。

IAudioSessionEvents::OnSimpleVolumeChanged 方法接收卷更改通知。 通常,此方法更新应用程序的卷滑块。

在前面的代码示例中,AudioVolumeEvents 类的构造函数注册特定于进程的 音频会话的通知, 由会话 GUID 值GUID_NULL标识。 默认情况下,旧版音频 API(如 DirectSound、DirectShow 和 waveOutXxx 函数)会将其流分配给此会话。 但是,DirectSound 或 DirectShow 应用程序可以选择替代默认行为,并将其流分配给跨进程会话或由 GUID 值标识的会话,而不是GUID_NULL。 (目前没有为 waveOutXxx 应用程序提供机制,以类似的方式替代默认行为。有关具有此行为的 DirectShow 应用程序的代码示例,请参阅 directShow 应用程序的 设备角色。 若要容纳此类应用程序,可以修改前面的代码示例中的构造函数以接受两个附加的输入参数,即会话 GUID 和一个标志,以指示要监视的会话是跨进程会话还是特定于进程的会话。 将这些参数传递给对构造函数中的 IAudioSessionManager::GetAudioSessionControl 方法的调用。

构造函数调用 IAudioSessionControl::RegisterAudioSessionNotification 方法注册通知后,只要存在 IAudioSessionControlIAudioSessionManager 接口,应用程序才会继续接收通知。 前面的代码示例中的 AudioVolumeEvents 对象保留对这些接口的引用,直到调用其析构函数。 此行为可确保应用程序在 AudioVolumeEvents 对象的生存期内继续接收通知。

DirectSound 或旧版 Windows 多媒体应用程序可能允许用户从由其友好名称标识的可用设备列表中显式选择设备,而不是隐式选择音频设备。 若要支持此行为,必须修改前面的代码示例,以便为所选设备生成音频事件通知。 需要两次修改。 首先,更改构造函数定义以接受 终结点 ID 字符串 作为输入参数(代替代码示例中的流和角色参数)。 此字符串标识与所选 DirectSound 或旧波形设备相对应的音频终结点设备。 其次,将对 IMMDeviceEnumerator::GetDefaultAudioEndpoint 方法的调用替换为对 IMMDeviceEnumerator::GetDevice 方法的调用。 GetDevice 调用采用终结点 ID 字符串作为输入参数,并创建由字符串标识的终结点设备的实例。

获取 DirectSound 设备或旧波形设备的终结点 ID 字符串的技术如下所示。

首先,在设备枚举期间,DirectSound 为每个枚举设备提供终结点 ID 字符串。 若要开始设备枚举,应用程序会将回调函数指针作为输入参数传递给 DirectSoundCreateDirectSoundCaptureCretureCreate 函数。 回调函数的定义是:

BOOL DSEnumCallback(
  LPGUID  lpGuid,
  LPCSTR  lpcstrDescription,
  LPCSTR  lpcstrModule,
  LPVOID  lpContext
);

在 Windows Vista 中,lpcstrModule 参数指向终结点 ID 字符串。 (在早期版本的 Windows(包括 Windows Server 2003、Windows XP 和 Windows 2000)中,lpcstrModule 参数指向设备的驱动程序模块的名称。lpcstrDescription 参数指向包含设备的友好名称的字符串。 有关 DirectSound 设备枚举的详细信息,请参阅 Windows SDK 文档。

其次,若要获取旧波形设备的终结点 ID 字符串,请使用 waveOutMessagewaveInMessage 函数将DRV_QUERYFUNCTIONINSTANCEID消息发送到波形设备驱动程序。 有关显示使用此消息的代码示例,请参阅 旧版 Windows 多媒体应用程序的设备角色

与旧版音频 API 的互作性