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.
Yesterday , at the close of my article about adding notifications support to the cheesy OSD application,
Yesterday , at the close of my article about adding notifications support to the cheesy OSD application
Don't you need a call to volumeNotification->Release() after RegisterControlChangeNotify(volumeNotification)?
Rather, couldn't you Release() it immediately after calling RegisterControlChangeNotify?
GUID - yes, to your 2nd comment - the RegisterControlChangeNotify method takes a reference to the callback.
It doesn't work .Can you show all the codes public? thx!