Controlli del volume degli endpoint

Le interfacce ISimpleAudioVolume, IChannelAudioVolume e IAudioStreamVolume consentono ai client di controllare i livelli di volume delle sessioni audio, che sono raccolte di flussi audio in modalità condivisa. Queste interfacce non funzionano con flussi audio in modalità esclusiva.

Le applicazioni che gestiscono flussi in modalità esclusiva possono controllare i livelli di volume di tali flussi tramite l'interfaccia IAudioEndpointVolume . Questa interfaccia controlla il livello di volume del dispositivo endpoint audio. Usa il controllo del volume hardware per il dispositivo endpoint se l'hardware audio implementa tale controllo. In caso contrario, l'interfaccia IAudioEndpointVolume implementa il controllo del volume nel software.

Se un dispositivo dispone di un controllo del volume hardware, le modifiche apportate al controllo tramite l'interfaccia IAudioEndpointVolume influiscono sul livello del volume sia in modalità condivisa che in modalità esclusiva. Se un dispositivo non dispone di controlli di volume hardware e disattivazione, le modifiche apportate al volume software e disattivare i controlli tramite questa interfaccia influiscono sul livello del volume in modalità condivisa, ma non in modalità esclusiva. In modalità esclusiva, l'applicazione e l'hardware audio scambiano direttamente i dati audio, ignorando i controlli software.

Come regola generale, le applicazioni devono evitare di usare l'interfaccia IAudioEndpointVolume per controllare i livelli di volume dei flussi in modalità condivisa. Le applicazioni devono invece usare l'interfaccia ISimpleAudioVolume, IChannelAudioVolume o IAudioStreamVolume a tale scopo.

Se un'applicazione visualizza un controllo volume che usa l'interfaccia IAudioEndpointVolume per controllare il livello di volume di un dispositivo endpoint audio, tale controllo volume deve eseguire il mirroring del controllo del volume dell'endpoint visualizzato dal programma di controllo del volume di sistema, Sndvol. Come spiegato in precedenza, il controllo del volume dell'endpoint viene visualizzato sul lato sinistro della finestra Sndvol, nella casella di gruppo con etichetta Dispositivo. Se l'utente modifica il volume endpoint di un dispositivo tramite il controllo del volume in Sndvol, il controllo del volume dell'endpoint corrispondente nell'applicazione deve spostarsi all'unisono con il controllo in Sndvol. Analogamente, se l'utente modifica il livello del volume tramite il controllo del volume dell'endpoint nella finestra dell'applicazione, il controllo del volume corrispondente in Sndvol deve spostarsi all'unisono con il controllo del volume dell'applicazione.

Per garantire che il controllo del volume dell'endpoint in una finestra dell'applicazione rispecchi il controllo del volume dell'endpoint in Sndvol, l'applicazione deve implementare un'interfaccia IAudioEndpointVolumeCallback e registrare tale interfaccia per ricevere le notifiche. Successivamente, ogni volta che l'utente modifica il livello di volume dell'endpoint in Sndvol, l'applicazione riceve una chiamata di notifica tramite il relativo metodo IAudioEndpointVolumeCallback::OnNotify. Durante questa chiamata, il metodo OnNotify può aggiornare il controllo del volume dell'endpoint nella finestra dell'applicazione in modo che corrisponda all'impostazione del controllo visualizzata in Sndvol. Analogamente, ogni volta che l'utente modifica il livello del volume dell'endpoint tramite il controllo del volume nella finestra dell'applicazione, Sndvol riceve una notifica e aggiorna immediatamente il controllo del volume dell'endpoint per visualizzare il nuovo livello di volume.

L'esempio di codice seguente è un file di intestazione che mostra una possibile implementazione dell'interfaccia 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;
    }
};

La classe CAudioEndpointVolumeCallback nell'esempio di codice precedente è un'implementazione dell'interfaccia IAudioEndpointVolumeCallback . Poiché IAudioEndpointVolumeCallback eredita da IUnknown, la definizione della classe contiene implementazioni dei metodi IUnknown AddRef, Release e QueryInterface. Il metodo OnNotify nella definizione della classe viene chiamato ogni volta che uno dei metodi seguenti modifica il livello del volume dell'endpoint:

L'implementazione del metodo OnNotify nell'esempio di codice precedente invia messaggi al controllo volume nella finestra dell'applicazione per aggiornare il livello del volume visualizzato.

Un'applicazione chiama il metodo IAudioEndpointVolume::RegisterControlChangeNotify per registrare l'interfaccia IAudioEndpointVolumeCallback per ricevere le notifiche. Quando l'applicazione non richiede più notifiche, chiama il metodo IAudioEndpointVolume::UnregisterControlChangeNotify per eliminare la registrazione.

L'esempio di codice seguente è un'applicazione Windows che chiama i metodi RegisterControlChangeNotify e UnregisterControlChangeNotify per registrare e annullare la registrazione della classe CAudioEndpointVolumeCallback nell'esempio di codice precedente:

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

Nell'esempio di codice precedente, la funzione WinMain chiama la funzione CoCreateInstance per creare un'istanza dell'interfaccia IMMDeviceEnumerator e chiama il metodo IMMDeviceEnumerator::GetDefaultAudioEndpoint per ottenere l'interfaccia IMMDevice del dispositivo di rendering predefinito. WinMain chiama il metodo IMMDevice::Activate per ottenere l'interfaccia IAudioEndpointVolume del dispositivo e chiama RegisterControlChangeNotify per registrare l'applicazione per ricevere notifiche di modifiche al volume dell'endpoint. WinMain apre quindi una finestra di dialogo per visualizzare un controllo del volume dell'endpoint per il dispositivo. La finestra di dialogo visualizza anche una casella di controllo che indica se il dispositivo è disattivato. La casella di controllo del volume dell'endpoint e disattivazione disattivata nella finestra di dialogo rispecchia le impostazioni del controllo del volume dell'endpoint e disattiva la casella di controllo visualizzata da Sndvol. Per altre informazioni su WinMain e CoCreateInstance, vedere la documentazione di Windows SDK. Per altre informazioni su IMMDeviceEnumerator e IMMDevice, vedere Enumerazione di dispositivi audio.

La procedura della finestra di dialogo DlgProc, nell'esempio di codice precedente, gestisce le modifiche apportate dall'utente alle impostazioni del volume e disattiva le impostazioni tramite i controlli nella finestra di dialogo. Quando DlgProc chiama SetMasterVolumeLevelScalar o SetMute, Sndvol riceve la notifica della modifica e aggiorna il controllo corrispondente nella finestra per riflettere il nuovo volume o l'impostazione di disattivazione. Se, invece di usare la finestra di dialogo, l'utente aggiorna le impostazioni del volume e disattiva le impostazioni tramite i controlli nella finestra Sndvol, il metodo OnNotify nella classe CAudioEndpointVolumeCallback aggiorna i controlli nella finestra di dialogo per visualizzare le nuove impostazioni.

Se l'utente modifica il volume tramite i controlli nella finestra di dialogo, il metodo OnNotify nella classe CAudioEndpointVolumeCallback non invia messaggi per aggiornare i controlli nella finestra di dialogo. A tale scopo, sarebbe ridondante. OnNotify aggiorna i controlli nella finestra di dialogo solo se la modifica del volume è stata originata in Sndvol o in un'altra applicazione. Il secondo parametro nelle chiamate al metodo SetMasterVolumeLevelScalar e SetMute nella funzione DlgProc è un puntatore a un GUID del contesto di evento che entrambi i metodi passano a OnNotify. OnNotify controlla il valore del GUID del contesto eventi per determinare se la finestra di dialogo è l'origine della modifica del volume. Per altre informazioni sui GUID del contesto di evento, vedere IAudioEndpointVolumeCallback::OnNotify.

Quando l'utente esce dalla finestra di dialogo, la chiamata UnregisterControlChangeNotify nell'esempio di codice precedente elimina la registrazione della classe CAudioEndpointVolumeCallback prima che il programma termini.

È possibile modificare facilmente l'esempio di codice precedente per visualizzare i controlli del volume e disattivare l'audio per il dispositivo di acquisizione predefinito. Nella funzione WinMain modificare il valore del primo parametro nella chiamata al metodo IMMDeviceEnumerator::GetDefaultAudioEndpoint da eRender a eCapture.

L'esempio di codice seguente è lo script di risorsa che definisce il volume e disattiva i controlli visualizzati nell'esempio di codice precedente:

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

L'esempio di codice seguente è il file di intestazione di risorsa che definisce gli identificatori di controllo visualizzati negli esempi di codice precedenti:

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

Gli esempi di codice precedenti si combinano per formare una semplice applicazione per controllare e monitorare il volume dell'endpoint del dispositivo di rendering predefinito. Un'applicazione più utile potrebbe inoltre notificare all'utente quando lo stato del dispositivo cambia. Ad esempio, il dispositivo potrebbe essere disabilitato, scollegato o rimosso. Per altre informazioni sul monitoraggio di questi tipi di eventi, vedere Eventi del dispositivo.

Controlli volume