views:

176

answers:

1

I'm trying to monitor new audio sessions via Windows 7's IAudioSessionManager2 COM interface (coupled with IAudioSessionNotification). Currently, IAudioSessionNotification::OnSessionCreated() is never called and I've run out of ideas as to why.

Code registering custom IAudioSessionNotification:

#define SAFE_RELEASE(comObj) \
if(comObj != NULL) \
 { (comObj)->Release(); comObj = NULL; }

BOOL success = false;

HRESULT res;
IClassFactory* pFactory;
IMMDevice* pDevice;
IMMDeviceEnumerator* pEnumerator;

SESSION_LISTENER = NULL;
SESSION = NULL;

res = CoInitialize(NULL);

if(res != S_OK && res != S_FALSE)
 return false;

res = CoGetClassObject(CLSID_CustomAudioFactory, CLSCTX_ALL, NULL, __uuidof(IClassFactory), (void**)&pFactory);
if(res != S_OK)  goto Exit;

res = pFactory->CreateInstance(NULL, CLSID_CustomAudioNotifications, (void**)&SESSION_LISTENER);
if(res != S_OK)  goto Exit;

res = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pEnumerator);
if(res != S_OK)  goto Exit;

res = pEnumerator->GetDefaultAudioEndpoint(eRender, eMultimedia, &pDevice);
if(res != S_OK)  goto Exit;

res = pDevice->Activate(__uuidof(IAudioSessionManager2), CLSCTX_ALL, NULL, (void**)&SESSION);
if(res != S_OK)  goto Exit;

res = SESSION->RegisterSessionNotification(SESSION_LISTENER);
if(res != S_OK)  goto Exit;

success = true;

Exit:
SAFE_RELEASE(pFactory);
SAFE_RELEASE(pEnumerator);
SAFE_RELEASE(pDevice);
if(!success)
{
 SAFE_RELEASE(SESSION_LISTENER);
 SAFE_RELEASE(SESSION);
}

CustomAudioNotifications declaration:

class CustomAudioNotifications : public IAudioSessionNotification
{
public:
//Constructors
CustomAudioNotifications()  { InterlockedIncrement(&g_notifyCount); m_listener = NULL; }
~CustomAudioNotifications() { InterlockedDecrement(&g_notifyCount); SAFE_RELEASE(m_listener); }

//IUnknown interface
HRESULT __stdcall QueryInterface(
                            REFIID riid ,
                            void **ppObj);
ULONG   __stdcall AddRef();
ULONG   __stdcall Release();

//Notification
HRESULT __stdcall OnSessionCreated(IAudioSessionControl *NewSession);

private:
LONG m_nRefCount;
};

OnSessionCreated just posts a message to a window whenever a session is created for the time being; which never happens. Just in case my assumptions are totally off base, I'm expecting a notification whenever an application that has yet to play audio starts to do so; so launching VLC with a video file should immediately result in a notice, while visiting Pandora via a web browser would also trigger such a notice.

Debugging shows all returned values are S_OK.

My COM experience is pretty limitted, so pointing out general "WTFs?" would also be appreciated.

+5  A: 

That's a TON more work than you need to do.

You just need to write a class that derives from IAudioSessionNotifications - you don't need to actually write a whole COM object and register it.

You should also use the eConsole role instead of the eMultimedia role. It doesn't effectively matter (if you have only one audio device) but it's more correct.

The destructor for the CustomAudioNotification class should be private - that way you prevent accidental destruction. So I'd write:

CustomAudioNotification *customNotification = new CustomAudioNotification(); SESSION->RegisterSessionNotification(customNotification);

I'm also assuming that you've initialized COM before your code snippet.

UPDATED: Kevin sent me his application and there are a couple of other issues with his application that are more fundamental (I'm working to get the documentation for the APIs improve to prevent any confusion in the future)

The first is that his application hasn't retrieved the current list of sessions. This is one of the really subtle things about the session enumeration APIs. In order to prevent a race condition that can occur when a session notification arrives while the application using the session APIs is starting up, the session enumeration API discards new session notifications until the application has first retrieved the list of existing sessions.

The expected usage pattern is:

Application activates a session manager2. Application registers for session notifications. Application retrieves the current list of sessions for the endpoint and stores the session control objects into a list (don't forget to addref the session).

When a new session is created, the application takes a reference to the newly created session control object and inserts it into the list if it's not already present. Note that the session control object passed into the notification will be destroyed when the session notification returns - if you call GetSessionEnumerator at this point it will probably NOT hold the newly created session (it might, it all depends on timing).

The application manages the lifetime of the session based on its own criteria - as long as the application has a reference to the session control the session control object will be valid. There is no expiration mechanism for audio session control objects.

In addition, the session APIs require that the MTA be initialized - this is unfortunate but because we create COM objects (which implement IAudioSessionControl) on a worker thread the API requires that the MTA be created before the notification is received.

Larry Osterman
@Kevin, you probably can't get any better answer then that. Larry is one of the guys who wrote Windows Audio code. :)
Shay Erlichmen