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