Share via


엔드포인트 볼륨 컨트롤

ISimpleAudioVolume, IChannelAudioVolumeIAudioStreamVolume 인터페이스를 사용하면 클라이언트가 공유 모드 오디오 스트림의 컬렉션인 오디오 세션의 볼륨 수준을 제어할 수 있습니다. 이러한 인터페이스는 전용 모드 오디오 스트림에서 작동하지 않습니다.

단독 모드 스트림을 관리하는 애플리케이션은 IAudioEndpointVolume 인터페이스를 통해 해당 스트림의 볼륨 수준을 제어할 수 있습니다. 이 인터페이스는 오디오 엔드포인트 디바이스의 볼륨 수준을 제어합니다. 오디오 하드웨어가 이러한 컨트롤을 구현하는 경우 엔드포인트 디바이스에 대한 하드웨어 볼륨 컨트롤을 사용합니다. 그렇지 않으면 IAudioEndpointVolume 인터페이스는 소프트웨어에서 볼륨 제어를 구현합니다.

디바이스에 하드웨어 볼륨 컨트롤이 있는 경우 IAudioEndpointVolume 인터페이스를 통해 컨트롤을 변경하면 공유 모드와 배타적 모드 모두에서 볼륨 수준에 영향을 줍니다. 디바이스에 하드웨어 볼륨 및 음소거 컨트롤이 없는 경우 이 인터페이스를 통해 소프트웨어 볼륨 및 음소거 컨트롤에 대한 변경 내용은 공유 모드의 볼륨 수준에 영향을 주지만 단독 모드에서는 적용되지 않습니다. 전용 모드에서 애플리케이션 및 오디오 하드웨어는 소프트웨어 컨트롤을 우회하여 오디오 데이터를 직접 교환합니다.

일반적으로 애플리케이션은 IAudioEndpointVolume 인터페이스를 사용하여 공유 모드 스트림의 볼륨 수준을 제어하지 않아야 합니다. 대신 애플리케이션은 해당 용도로 ISimpleAudioVolume, IChannelAudioVolume 또는 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 인터페이스의 구현입니다. IAudioEndpointVolumeCallbackIUnknown에서 상속되므로 클래스 정의에는 IUnknown 메서드 AddRef, ReleaseQueryInterface의 구현이 포함됩니다. 클래스 정의의 OnNotify 메서드는 다음 메서드 중 하나가 엔드포인트 볼륨 수준을 변경할 때마다 호출됩니다.

앞의 코드 예제에서 OnNotify 메서드를 구현하면 표시된 볼륨 수준을 업데이트하기 위해 애플리케이션 창의 볼륨 컨트롤에 메시지를 보냅니다.

애플리케이션은 IAudioEndpointVolume::RegisterControlChangeNotify 메서드를 호출하여 알림을 받을 IAudioEndpointVolumeCallback 인터페이스를 등록합니다. 애플리케이션에 더 이상 알림이 필요하지 않으면 IAudioEndpointVolume::UnregisterControlChangeNotify 메서드를 호출하여 등록을 삭제합니다.

다음 코드 예제는 이전 코드 예제에서 RegisterControlChangeNotifyUnregisterControlChangeNotify 메서드를 호출하여 CAudioEndpointVolumeCallback 클래스를 등록 및 등록 취소하는 Windows 애플리케이션입니다.

// 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 인터페이스의 instance 만들고 IMMDeviceEnumerator::GetDefaultAudioEndpoint 메서드를 호출하여 기본 렌더링 디바이스의 IMMDevice 인터페이스를 가져옵니다. WinMain은IMMDevice::Activate 메서드를 호출하여 디바이스의 IAudioEndpointVolume 인터페이스를 가져오고 RegisterControlChangeNotify 를 호출하여 엔드포인트 볼륨 변경 알림을 수신하도록 애플리케이션을 등록합니다. 그런 다음 WinMain 은 디바이스에 대한 엔드포인트 볼륨 컨트롤을 표시하는 대화 상자를 엽니다. 대화 상자에는 디바이스가 음소거되었는지 여부를 나타내는 검사 상자도 표시됩니다. 대화 상자의 엔드포인트 볼륨 제어 및 음소거 검사 상자는 Sndvol이 표시하는 엔드포인트 볼륨 컨트롤 및 음소거 검사 상자의 설정을 미러. WinMainCoCreateInstance에 대한 자세한 내용은 Windows SDK 설명서를 참조하세요. IMMDeviceEnumeratorIMMDevice에 대한 자세한 내용은 오디오 디바이스 열거를 참조하세요.

앞의 코드 예제에서 DlgProc 대화 상자 프로시저는 사용자가 볼륨에 적용한 변경 내용을 처리하고 대화 상자의 컨트롤을 통해 설정을 음소거합니다. DlgProc이 SetMasterVolumeLevelScalar 또는 SetMute를 호출하면 Sndvol은 변경에 대한 알림을 받고 해당 창의 해당 컨트롤을 업데이트하여 새 볼륨 또는 음소거 설정을 반영합니다. 대화 상자를 사용하는 대신 사용자가 Sndvol 창의 컨트롤을 통해 볼륨을 업데이트하고 설정을 음소거하면 CAudioEndpointVolumeCallback 클래스의 OnNotify 메서드가 대화 상자의 컨트롤을 업데이트하여 새 설정을 표시합니다.

사용자가 대화 상자의 컨트롤을 통해 볼륨을 변경하는 경우 CAudioEndpointVolumeCallback 클래스의 OnNotify 메서드는 대화 상자에서 컨트롤을 업데이트하는 메시지를 보내지 않습니다. 이렇게 하려면 중복됩니다. OnNotify 는 볼륨 변경이 Sndvol 또는 다른 애플리케이션에서 시작된 경우에만 대화 상자의 컨트롤을 업데이트합니다. DlgProc 함수에서 SetMasterVolumeLevelScalarSetMute 메서드 호출의 두 번째 매개 변수는 두 메서드가 OnNotify에 전달하는 이벤트 컨텍스트 GUID에 대한 포인터입니다. 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

앞의 코드 예제는 결합되어 기본 렌더링 디바이스의 엔드포인트 볼륨을 제어하고 모니터링하기 위한 간단한 애플리케이션을 형성합니다. 더 유용한 애플리케이션은 디바이스의 상태 변경될 때 사용자에게 추가로 알릴 수 있습니다. 예를 들어 디바이스를 사용하지 않도록 설정하거나, 분리하거나, 제거할 수 있습니다. 이러한 유형의 이벤트를 모니터링하는 방법에 대한 자세한 내용은 디바이스 이벤트를 참조하세요.

볼륨 컨트롤