Controles de volume de ponto de extremidade

As interfaces ISimpleAudioVolume, IChannelAudioVolume e IAudioStreamVolume permitem que os clientes controlem os níveis de volume das sessões de áudio, que são coleções de fluxos de áudio de modo compartilhado. Essas interfaces não funcionam com fluxos de áudio de modo exclusivo.

Os aplicativos que gerenciam fluxos de modo exclusivo podem controlar os níveis de volume desses fluxos por meio da interface IAudioEndpointVolume. Essa interface controla o nível de volume do dispositivo de ponto de extremidade de áudio. Ele usa o controle de volume de hardware para o dispositivo de ponto de extremidade se o hardware de áudio implementa esse controle. Caso contrário, a interface IAudioEndpointVolume implementa o controle de volume no software.

Se um dispositivo tiver um controle de volume de hardware, as alterações feitas no controle por meio da interface IAudioEndpointVolume afetarão o nível de volume no modo compartilhado e no modo exclusivo. Se um dispositivo não tiver controles de volume de hardware e mudo, as alterações feitas nos controles de volume e mudo de software por meio dessa interface afetarão o nível de volume no modo compartilhado, mas não no modo exclusivo. No modo exclusivo, o aplicativo e o hardware de áudio trocam dados de áudio diretamente, ignorando os controles do software.

Como regra geral, os aplicativos devem evitar usar a interface IAudioEndpointVolume para controlar os níveis de volume de fluxos de modo compartilhado. Em vez disso, os aplicativos devem usar a interface ISimpleAudioVolume, IChannelAudioVolume ou IAudioStreamVolume para essa finalidade.

Se um aplicativo exibir um controle de volume que usa a interface IAudioEndpointVolume para controlar o nível de volume de um dispositivo de ponto de extremidade de áudio, esse controle de volume deverá espelhar o controle de volume de ponto de extremidade exibido pelo programa de controle de volume do sistema, Sndvol. Conforme explicado anteriormente, o controle de volume do ponto de extremidade aparece no lado esquerdo da janela do Sndvol, na caixa de grupo denominada Dispositivo. Se o usuário alterar o volume de ponto de extremidade de um dispositivo por meio do controle de volume no Sndvol, o controle de volume de ponto de extremidade correspondente no aplicativo deverá se mover em siníssono com o controle no Sndvol. Da mesma forma, se o usuário alterar o nível de volume por meio do controle de volume de ponto de extremidade na janela do aplicativo, o controle de volume correspondente no Sndvol deverá se mover em sintonia com o controle de volume do aplicativo.

Para garantir que o controle de volume de ponto de extremidade em uma janela de aplicativo espelhe o controle de volume de ponto de extremidade no Sndvol, o aplicativo deve implementar uma interface IAudioEndpointVolumeCallback e registrar essa interface para receber notificações. Depois disso, cada vez que o usuário altera o nível de volume do ponto de extremidade no Sndvol, o aplicativo recebe uma chamada de notificação por meio de seu método IAudioEndpointVolumeCallback::OnNotify . Durante essa chamada, o método OnNotify pode atualizar o controle de volume de ponto de extremidade na janela do aplicativo para corresponder à configuração de controle mostrada no Sndvol. Da mesma forma, cada vez que o usuário altera o nível de volume do ponto de extremidade por meio do controle de volume na janela do aplicativo, o Sndvol recebe uma notificação e atualiza imediatamente seu controle de volume do ponto de extremidade para exibir o novo nível de volume.

O exemplo de código a seguir é um arquivo de cabeçalho que mostra uma possível implementação da interface 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;
    }
};

A classe CAudioEndpointVolumeCallback no exemplo de código anterior é uma implementação da interface IAudioEndpointVolumeCallback. Como IAudioEndpointVolumeCallback herda de IUnknown, a definição de classe contém implementações dos métodos IUnknown AddRef, Release e QueryInterface. O método OnNotify na definição de classe é chamado sempre que um dos seguintes métodos altera o nível de volume do ponto de extremidade:

A implementação do método OnNotify no exemplo de código anterior envia mensagens para o controle de volume na janela do aplicativo para atualizar o nível de volume exibido.

Um aplicativo chama o método IAudioEndpointVolume::RegisterControlChangeNotify para registrar sua interface IAudioEndpointVolumeCallback para receber notificações. Quando o aplicativo não requer mais notificações, ele chama o método IAudioEndpointVolume::UnregisterControlChangeNotify para excluir o registro.

O exemplo de código a seguir é um aplicativo do Windows que chama os métodos RegisterControlChangeNotify e UnregisterControlChangeNotify para registrar e cancelar o registro da classe CAudioEndpointVolumeCallback no exemplo de código anterior:

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

No exemplo de código anterior, a função WinMain chama a função CoCreateInstance para criar uma instância da interface IMMDeviceEnumerator e chama o método IMMDeviceEnumerator::GetDefaultAudioEndpoint para obter a interface IMMDevice do dispositivo de renderização padrão. WinMain chama o método IMMDevice::Activate para obter a interface IAudioEndpointVolume do dispositivo e chama RegisterControlChangeNotify para registrar o aplicativo para receber notificações de alterações de volume de ponto de extremidade. Em seguida, WinMain abre uma caixa de diálogo para exibir um controle de volume de ponto de extremidade para o dispositivo. A caixa de diálogo também exibe uma caixa de seleção que indica se o dispositivo está mudo. A caixa de seleção controle de volume de ponto de extremidade e mudo na caixa de diálogo espelham as configurações da caixa de seleção de controle de volume de ponto de extremidade e mudo exibida pelo Sndvol. Para obter mais informações sobre WinMain e CoCreateInstance, consulte a documentação do SDK do Windows. Para obter mais informações sobre IMMDeviceEnumerator e IMMDevice, consulte Enumerando dispositivos de áudio.

O procedimento de caixa de diálogo, DlgProc, no exemplo de código anterior, manipula as alterações que o usuário faz nas configurações de volume e mudo por meio dos controles na caixa de diálogo. Quando o DlgProc chama SetMasterVolumeLevelScalar ou SetMute, o Sndvol recebe notificação da alteração e atualiza o controle correspondente em sua janela para refletir a nova configuração de volume ou mudo. Se, em vez de usar a caixa de diálogo, o usuário atualizar as configurações de volume e mudo por meio dos controles na janela Sndvol, o método OnNotify na classe CAudioEndpointVolumeCallback atualizará os controles na caixa de diálogo para exibir as novas configurações.

Se o usuário alterar o volume por meio dos controles na caixa de diálogo, o método OnNotify na classe CAudioEndpointVolumeCallback não enviará mensagens para atualizar os controles na caixa de diálogo. Fazê-lo seria redundante. OnNotify atualiza os controles na caixa de diálogo somente se a alteração de volume se originou no Sndvol ou em algum outro aplicativo. O segundo parâmetro nas chamadas de método SetMasterVolumeLevelScalar e SetMute na função DlgProc é um ponteiro para um GUID de contexto de evento que qualquer um dos métodos passa para OnNotify. OnNotify verifica o valor do GUID de contexto de evento para determinar se a caixa de diálogo é a origem da alteração de volume. Para obter mais informações sobre GUIDs de contexto de evento, consulte IAudioEndpointVolumeCallback::OnNotify.

Quando o usuário sai da caixa de diálogo, a chamada UnregisterControlChangeNotify no exemplo de código anterior exclui o registro da classe CAudioEndpointVolumeCallback antes que o programa seja encerrado.

Você pode modificar facilmente o exemplo de código anterior para exibir controles de volume e mudo para o dispositivo de captura padrão. Na função WinMain, altere o valor do primeiro parâmetro na chamada para o método IMMDeviceEnumerator::GetDefaultAudioEndpoint de eRender para eCapture.

O exemplo de código a seguir é o script de recurso que define os controles de volume e mudo que aparecem no exemplo de código anterior:

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

O exemplo de código a seguir é o arquivo de cabeçalho de recurso que define os identificadores de controle que aparecem nos exemplos de código anteriores:

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

Os exemplos de código anteriores se combinam para formar um aplicativo simples para controlar e monitorar o volume de ponto de extremidade do dispositivo de renderização padrão. Um aplicativo mais útil também pode notificar o usuário quando o status do dispositivo for alterado. Por exemplo, o dispositivo pode ser desativado, desconectado ou removido. Para obter mais informações sobre como monitorar esses tipos de eventos, consulte Eventos de dispositivo.

Controles de volume