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) {}; 
    CVolumeNotification(void) : m_RefCount(1) 
    STDMETHODIMP_(ULONG)AddRef() { return InterlockedIncrement(&m_RefCount); } 
        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); 
            return S_OK; 
        *ReturnValue = NULL; 
        return E_NOINTERFACE; 

        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):

    //    Remove our notification. 

    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. Anonymous says:

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

  2. Anonymous says:

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

  3. Anonymous says:

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

  4. Anonymous 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. Anonymous says:

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

Skip to main content