Fun with the endpoint volume interfaces – closing the loop


Yesterday, I added support for metering to the cheesy OSD application, today I want to add in the one thing that’s been missing: notification of external volume changes.

The good news is that once again, it’s pretty easy to add support.  All you need to do is to define a class to handle the notification:

class CVolumeNotification : public IAudioEndpointVolumeCallback 
{ 
    LONG m_RefCount; 
    ~CVolumeNotification(void) {}; 
public: 
    CVolumeNotification(void) : m_RefCount(1) 
    { 
    } 
    STDMETHODIMP_(ULONG)AddRef() { return InterlockedIncrement(&m_RefCount); } 
    STDMETHODIMP_(ULONG)Release()  
    { 
        LONG ref = InterlockedDecrement(&m_RefCount);  
        if (ref == 0) 
            delete this; 
        return ref; 
    } 
    STDMETHODIMP QueryInterface(REFIID IID, void **ReturnValue) 
    { 
        if (IID == IID_IUnknown || IID== __uuidof(IAudioEndpointVolumeCallback))  
        { 
            *ReturnValue = static_cast<IUnknown*>(this); 
            AddRef(); 
            return S_OK; 
        } 
        *ReturnValue = NULL; 
        return E_NOINTERFACE; 
    } 

    STDMETHODIMP OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA NotificationData) 
    { 
        wchar_t outputString[256]; 
        DWORD written; 
        COORD writeCoord; 
        StringCbPrintf(outputString, sizeof(outputString), L"Volume Changed: %f", NotificationData->fMasterVolume); 

        writeCoord.X = 0; 
        writeCoord.Y = 3; 
        WriteConsoleOutputCharacter(GetStdHandle(STD_OUTPUT_HANDLE), outputString, (DWORD)wcslen(outputString), writeCoord, &written); 
        return S_OK; 
    } 
}; 

This function is mostly COM goo to handle references, the guts of the function simply print out the new master volume.  If I was writing a real OSD, I’d have the volume callback object keep a reference to the endpoint volume interface and update the OSD with the current step information, but for a cheesy application like this one, it’ll do.  Please note that it writes the notification to line 3 – that’s to handle the case where the application runs in a window that is narrower than 100 characters – in that case, the meter wraps to line 2 :).

Of course that’s not all you need to do – you need to instantiate a volume notification object and register it for notifications (I’ve included some lines from the previous versions for context).

    hr = defaultDevice->Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&endpointVolume); 

    CVolumeNotification *volumeNotification = new CVolumeNotification(); 

    hr = endpointVolume->RegisterControlChangeNotify(volumeNotification); 

    hr = defaultDevice->Activate(__uuidof(IAudioMeterInformation), CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&context._Meter); 

and finally, to clean things up (again, I’ve included some lines from yesterday for context):

    context._Meter->Release(); 
    // 
    //    Remove our notification. 
    // 
    endpointVolume->UnregisterControlChangeNotify(volumeNotification); 

    endpointVolume->Release(); 
    volumeNotification->Release(); 
    SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldMode); 

There’s still one minor problem with this sample, but it’s a big one that will absolutely hit people who try to use this functionality in a real application.  I’ll talk about that and show how to fix it next.

And again, this is a poor sample – there is no attempt at useful things like error recovery and the like.  It’s just to show how this stuff works.

Comments (6)

  1. Yesterday , at the close of my article about adding notifications support to the cheesy OSD application,

  2. RSS It All says:

    Yesterday , at the close of my article about adding notifications support to the cheesy OSD application

  3. GUID says:

    Don’t you need a call to volumeNotification->Release() after RegisterControlChangeNotify(volumeNotification)?

  4. GUID says:

    Rather, couldn’t you Release() it immediately after calling RegisterControlChangeNotify?

  5. GUID – yes, to your 2nd comment – the RegisterControlChangeNotify method takes a reference to the callback.

  6. Greek Jeen says:

    It doesn’t work .Can you show all the codes public? thx!