终结点卷控件

ISimpleAudioVolumeIChannelAudioVolumeIAudioStreamVolume 接口使客户端能够控制 音频会话的音量级别,这些会话是共享模式音频流的集合。 这些接口不适用于独占模式音频流。

管理独占模式流的应用程序可以通过 IAudioEndpointVolume 接口控制这些流的卷级别。 此接口控制 音频终结点设备的音量级别。 如果音频硬件实现此类控件,它将使用终结点设备的硬件音量控制。 否则,IAudioEndpointVolume 接口在软件中实现卷控制。

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

一般情况下,应用程序应避免使用 IAudioEndpointVolume 接口来控制共享模式流的卷级别。 相反,应用程序应使用 ISimpleAudioVolumeIChannelAudioVolume,或 IAudioStreamVolume 接口实现此目的。

如果应用程序显示使用 IAudioEndpointVolume 接口控制音频终结点设备的音量级别的音量控件,该音量控制应镜像系统音量控制程序 Sndvol 显示的终结点音量控制。 如前所述,终结点卷控件显示在 Sndvol 窗口的左侧,在标记为 设备的组框中。 如果用户通过 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,因此类定义包含 addRefReleaseQueryInterfaceIUnknown方法的实现。 每次以下方法之一更改终结点卷级别时,都会调用类定义中的 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 的指针,该方法传递给 onNotify OnNotify 检查事件上下文 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

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

卷控件