终结点音量控件

ISimpleAudioVolume/IChannelAudioVolumeIAudioStreamVolume 接口让客户端能够控制音频会话的音量水平,这些会话是共享模式音频流的集合。 这些接口不支持独占模式音频流。

管理独占模式流的应用程序可通过 IAudioEndpointVolume 接口来控制这些流的音量水平。 此接口可控制音频终结点设备的音量水平。 如果音频硬件实现了终结点设备的硬件音量控件,它就会使用这种控件。 否则,IAudioEndpointVolume 接口将在软件中实现音量控件。

如果设备具有硬件音量控件,则通过 IAudioEndpointVolume 接口对该控件所做的更改会影响共享模式和独占模式下的音量水平。 如果设备没有硬件音量和静音控件,则通过该接口对软件音量和静音控件所做的更改会影响共享模式下的音量水平,但不会影响独享模式下的音量水平。 在独占模式下,应用程序和音频硬件会绕过软件控件直接交换音频数据。

一般来说,应用程序应避免使用 IAudioEndpointVolume 接口来控制共享模式流的音量水平。 为此,应用程序应使用 ISimpleAudioVolumeIChannelAudioVolumeIAudioStreamVolume接口。

如果应用程序显示的音量控件使用 IAudioEndpointVolume 接口来控制音频终结点设备的音量水平,则该音量控件应与系统音量控件程序 Sndvol 显示的终结点音量控件相匹配。 如前所述,终结点音量控件出现在 Sndvol 窗口左侧标有 Device 的组框中。 如果用户通过 Sndvol 中的音量控件改变设备的终结点音量,应用程序中相应的终结点音量控件应与 Sndvol 中的控件保持一致。 同样,如果用户通过应用程序窗口中的终结点音量控件来更改音量水平,则 Sndvol 中相应的音量控件也应与应用程序的音量控件同步移动。

为确保应用程序窗口中的终结点音量控件反映了 Sndvol 中的终结点音量控件,应用程序应实现 IAudioEndpointVolumeCallback 接口,并注册该接口以接收通知。 此后,每次用户更改 Sndvol 中的终结点音量水平时,应用程序都会通过其 IAudioEndpointVolumeCallback::OnNotify 方法收到一个通知调用。 在此调用过程中,OnNotify 方法可以更新应用程序窗口中的终结点音量控件,使其与 Sndvol 中显示的控件设置相匹配。 同样,每次用户通过应用程序窗口中的音量控件更改终结点音量时,Sndvol 都会收到通知,并立即更新其终结点音量控件以显示新的音量水平。

以下代码示例是一个头文件,其中显示了 IAudioEndpointVolumeCallback 接口的可能实现:

// Epvolume.h -- Implementation of IAudioEndpointVolumeCallback interface

#include <windows.h>
#include <commctrl.h>
#include <mmdeviceapi.h>
#include <endpointvolume.h>
#include "resource.h"

// Dialog handle from dialog box procedure
extern HWND g_hDlg;

// Client's proprietary event-context GUID
extern GUID g_guidMyContext;

// Maximum volume level on trackbar
#define MAX_VOL  100

#define SAFE_RELEASE(punk)  \
              if ((punk) != NULL)  \
                { (punk)->Release(); (punk) = NULL; }

//-----------------------------------------------------------
// Client implementation of IAudioEndpointVolumeCallback
// interface. When a method in the IAudioEndpointVolume
// interface changes the volume level or muting state of the
// endpoint device, the change initiates a call to the
// client's IAudioEndpointVolumeCallback::OnNotify method.
//-----------------------------------------------------------
class CAudioEndpointVolumeCallback : public IAudioEndpointVolumeCallback
{
    LONG _cRef;

public:
    CAudioEndpointVolumeCallback() :
        _cRef(1)
    {
    }

    ~CAudioEndpointVolumeCallback()
    {
    }

    // IUnknown methods -- AddRef, Release, and QueryInterface

    ULONG STDMETHODCALLTYPE AddRef()
    {
        return InterlockedIncrement(&_cRef);
    }

    ULONG STDMETHODCALLTYPE Release()
    {
        ULONG ulRef = InterlockedDecrement(&_cRef);
        if (0 == ulRef)
        {
            delete this;
        }
        return ulRef;
    }

    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, VOID **ppvInterface)
    {
        if (IID_IUnknown == riid)
        {
            AddRef();
            *ppvInterface = (IUnknown*)this;
        }
        else if (__uuidof(IAudioEndpointVolumeCallback) == riid)
        {
            AddRef();
            *ppvInterface = (IAudioEndpointVolumeCallback*)this;
        }
        else
        {
            *ppvInterface = NULL;
            return E_NOINTERFACE;
        }
        return S_OK;
    }

    // Callback method for endpoint-volume-change notifications.

    HRESULT STDMETHODCALLTYPE OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA pNotify)
    {
        if (pNotify == NULL)
        {
            return E_INVALIDARG;
        }
        if (g_hDlg != NULL && pNotify->guidEventContext != g_guidMyContext)
        {
            PostMessage(GetDlgItem(g_hDlg, IDC_CHECK_MUTE), BM_SETCHECK,
                        (pNotify->bMuted) ? BST_CHECKED : BST_UNCHECKED, 0);

            PostMessage(GetDlgItem(g_hDlg, IDC_SLIDER_VOLUME),
                        TBM_SETPOS, TRUE,
                        LPARAM((UINT32)(MAX_VOL*pNotify->fMasterVolume + 0.5)));
        }
        return S_OK;
    }
};

上述代码示例中的 CAudioEndpointVolumeCallback 类是 IAudioEndpointVolumeCallback 接口的实现。 由于 IAudioEndpointVolumeCallback 继承自 IUnknown,因此该类的定义包含对 IUnknown 方法 AddRefReleaseQueryInterface 的实现。 每次以下列方法之一更改终结点音量水平时,都会调用类定义中的 OnNotify 方法:

上述代码示例中的 OnNotify 方法会向应用程序窗口中的音量控件发送消息,以更新显示的音量水平。

应用程序调用 IAudioEndpointVolume::RegisterControlChangeNotify 方法来注册其 IAudioEndpointVolumeCallback 接口以接收通知。 当应用程序不再需要通知时,它会调用 IAudioEndpointVolume::UnregisterControlChangeNotify 方法来删除注册。

下面的代码示例是一个 Windows 应用程序,它会调用 RegisterControlChangeNotifyUnregisterControlChangeNotify 方法来注册和取消注册前面代码示例中的 CAudioEndpointVolumeCallback 类:

// Epvolume.cpp -- WinMain and dialog box functions

#include "stdafx.h"
#include "Epvolume.h"

HWND g_hDlg = NULL;
GUID g_guidMyContext = GUID_NULL;

static IAudioEndpointVolume *g_pEndptVol = NULL;
static BOOL CALLBACK DlgProc(HWND, UINT, WPARAM, LPARAM);

#define EXIT_ON_ERROR(hr)  \
              if (FAILED(hr)) { goto Exit; }
#define ERROR_CANCEL(hr)  \
              if (FAILED(hr)) {  \
                  MessageBox(hDlg, TEXT("The program will exit."),  \
                             TEXT("Fatal error"), MB_OK);  \
                  EndDialog(hDlg, TRUE); return TRUE; }

//-----------------------------------------------------------
// WinMain -- Registers an IAudioEndpointVolumeCallback
//   interface to monitor endpoint volume level, and opens
//   a dialog box that displays a volume control that will
//   mirror the endpoint volume control in SndVol.
//-----------------------------------------------------------
int APIENTRY WinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR lpCmdLine,
                     int nCmdShow)
{
    HRESULT hr = S_OK;
    IMMDeviceEnumerator *pEnumerator = NULL;
    IMMDevice *pDevice = NULL;
    CAudioEndpointVolumeCallback EPVolEvents;

    if (hPrevInstance)
    {
        return 0;
    }

    CoInitialize(NULL);

    hr = CoCreateGuid(&g_guidMyContext);
    EXIT_ON_ERROR(hr)

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

    // Get default audio-rendering device.
    hr = pEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pDevice);
    EXIT_ON_ERROR(hr)

    hr = pDevice->Activate(__uuidof(IAudioEndpointVolume),
                           CLSCTX_ALL, NULL, (void**)&g_pEndptVol);
    EXIT_ON_ERROR(hr)

    hr = g_pEndptVol->RegisterControlChangeNotify(
                     (IAudioEndpointVolumeCallback*)&EPVolEvents);
    EXIT_ON_ERROR(hr)

    InitCommonControls();
    DialogBox(hInstance, L"VOLUMECONTROL", NULL, (DLGPROC)DlgProc);

Exit:
    if (FAILED(hr))
    {
        MessageBox(NULL, TEXT("This program requires Windows Vista."),
                   TEXT("Error termination"), MB_OK);
    }
    if (g_pEndptVol != NULL)
    {
        g_pEndptVol->UnregisterControlChangeNotify(
                    (IAudioEndpointVolumeCallback*)&EPVolEvents);
    }
    SAFE_RELEASE(pEnumerator)
    SAFE_RELEASE(pDevice)
    SAFE_RELEASE(g_pEndptVol)
    CoUninitialize();
    return 0;
}

//-----------------------------------------------------------
// DlgProc -- Dialog box procedure
//-----------------------------------------------------------

BOOL CALLBACK DlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    HRESULT hr;
    BOOL bMute;
    float fVolume;
    int nVolume;
    int nChecked;

    switch (message)
    {
    case WM_INITDIALOG:
        g_hDlg = hDlg;
        SendDlgItemMessage(hDlg, IDC_SLIDER_VOLUME, TBM_SETRANGEMIN, FALSE, 0);
        SendDlgItemMessage(hDlg, IDC_SLIDER_VOLUME, TBM_SETRANGEMAX, FALSE, MAX_VOL);
        hr = g_pEndptVol->GetMute(&bMute);
        ERROR_CANCEL(hr)
        SendDlgItemMessage(hDlg, IDC_CHECK_MUTE, BM_SETCHECK,
                           bMute ? BST_CHECKED : BST_UNCHECKED, 0);
        hr = g_pEndptVol->GetMasterVolumeLevelScalar(&fVolume);
        ERROR_CANCEL(hr)
        nVolume = (int)(MAX_VOL*fVolume + 0.5);
        SendDlgItemMessage(hDlg, IDC_SLIDER_VOLUME, TBM_SETPOS, TRUE, nVolume);
        return TRUE;

    case WM_HSCROLL:
        switch (LOWORD(wParam))
        {
        case SB_THUMBPOSITION:
        case SB_THUMBTRACK:
        case SB_LINERIGHT:
        case SB_LINELEFT:
        case SB_PAGERIGHT:
        case SB_PAGELEFT:
        case SB_RIGHT:
        case SB_LEFT:
            // The user moved the volume slider in the dialog box.
            SendDlgItemMessage(hDlg, IDC_CHECK_MUTE, BM_SETCHECK, BST_UNCHECKED, 0);
            hr = g_pEndptVol->SetMute(FALSE, &g_guidMyContext);
            ERROR_CANCEL(hr)
            nVolume = SendDlgItemMessage(hDlg, IDC_SLIDER_VOLUME, TBM_GETPOS, 0, 0);
            fVolume = (float)nVolume/MAX_VOL;
            hr = g_pEndptVol->SetMasterVolumeLevelScalar(fVolume, &g_guidMyContext);
            ERROR_CANCEL(hr)
            return TRUE;
        }
        break;

    case WM_COMMAND:
        switch ((int)LOWORD(wParam))
        {
        case IDC_CHECK_MUTE:
            // The user selected the Mute check box in the dialog box.
            nChecked = SendDlgItemMessage(hDlg, IDC_CHECK_MUTE, BM_GETCHECK, 0, 0);
            bMute = (BST_CHECKED == nChecked);
            hr = g_pEndptVol->SetMute(bMute, &g_guidMyContext);
            ERROR_CANCEL(hr)
            return TRUE;
        case IDCANCEL:
            EndDialog(hDlg, TRUE);
            return TRUE;
        }
        break;
    }
    return FALSE;
}

在上述代码示例中,WinMain 函数会调用 CoCreateInstance 函数来创建 IMMDeviceEnumerator 接口的实例,并会调用 IMMDeviceEnumerator::GetDefaultAudioEndpoint 方法来获取默认呈现设备的 IMMDevice 接口。 WinMain 会调用 IMMDevice::Activate 方法来获取设备的 IAudioEndpointVolume 接口,并调用 RegisterControlChangeNotify 注册应用程序以接收终结点音量更改通知。 接下来,WinMain 会打开一个对话框,显示设备的终结点音量控件。 对话框还会显示一个复选框,指示设备是否已静音。 对话框中的终结点音量控件和静音复选框反映了 Sndvol 显示的终结点音量控件和静音复选框的设置。 有关 WinMainCoCreateInstance 的详细信息,请参阅 Windows SDK 文档。 有关 IMMDeviceEnumeratorIMMDevice 的详细信息,请参阅枚举音频设备

在上述代码示例中,对话框过程 DlgProc 会处理用户通过对话框中的控件对音量和静音设置所做的更改。 当 DlgProc 调用 SetMasterVolumeLevelScalarSetMute 时,Sndvol 会收到更改通知,并更新其窗口中的相应控件,以反映新的音量或静音设置。 如果用户不使用对话框,而是通过 Sndvol 窗口中的控件来更新音量和静音设置,那么 CAudioEndpointVolumeCallback 类中的 OnNotify 方法将更新对话框中的控件,以显示新的设置。

如果用户通过对话框中的控件来更改音量,则 CAudioEndpointVolumeCallback 类中的 OnNotify 方法不会发送更新对话框控件的消息。 这样做是多余的。 OnNotify 只有在音量变化源自 Sndvol 或其他应用程序时,才会更新对话框中的控件。 DlgProc 函数中的 SetMasterVolumeLevelScalarSetMute 方法调用的第二个参数是指向事件上下文 GUID 的指针,这两个方法都会将该指针传递给 OnNotifyOnNotify 会检查事件上下文 GUID 的值,以确定对话框是否为音量更改的来源。 有关事件上下文 GUID 的详细信息,请参阅 IAudioEndpointVolumeCallback::OnNotify

当用户退出对话框时,上述代码示例中的 UnregisterControlChangeNotify 调用将在程序终止前删除 CAudioEndpointVolumeCallback 类的注册。

可以轻松修改前面的代码示例,以显示默认捕获设备的音量和静音控件。 在 WinMain 函数中,会将调用 IMMDeviceEnumerator::GetDefaultAudioEndpoint 方法时的第一个参数值从 eRender 更改为 eCapture。

下面的代码示例是一个资源脚本,它定义了前面代码示例中出现的音量和静音控件:

// Epvolume.rc -- Resource script

#include "resource.h"
#include "windows.h"
#include "commctrl.h"

//
// Dialog box
//
VOLUMECONTROL DIALOGEX 0, 0, 160, 60
STYLE DS_MODALFRAME | WS_CAPTION | WS_SYSMENU | DS_SETFONT
CAPTION "Audio Endpoint Volume"
FONT 8, "Arial Rounded MT Bold", 400, 0, 0x0
BEGIN
    LTEXT      "Min",IDC_STATIC_MINVOL,10,10,20,12
    RTEXT      "Max",IDC_STATIC_MAXVOL,130,10,20,12
    CONTROL    "",IDC_SLIDER_VOLUME,"msctls_trackbar32",
               TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,10,20,140,12
    CONTROL    "Mute",IDC_CHECK_MUTE,"Button",
               BS_AUTOCHECKBOX | WS_TABSTOP,20,40,70,12
END

下面的代码示例是资源头文件,它定义了前面代码示例中出现的控件标识符:

// Resource.h -- Control identifiers (epvolume)

#define IDC_SLIDER_VOLUME      1001
#define IDC_CHECK_MUTE         1002
#define IDC_STATIC_MINVOL      1003
#define IDC_STATIC_MAXVOL      1004

前面的代码示例组合成一个简单的应用程序,用于控制和监控默认呈现设备的终结点音量。 更有用的应用程序可能还会在设备状态发生变化时通知用户。 例如,设备可能被禁用、拔出或删除。 有关监控这些类型的事件的详细信息,请参阅设备事件

音量控件