title: Getting volume change notifications on Vista/7 (C++)
body: <p>I'm trying to get notifications whenever the master volume changes on Windows Vista/7. This is the code I'm using:</p>
  
  <pre><code>#include &lt;audiopolicy.h&gt;
  #include &lt;audioclient.h&gt;
  #include &lt;mmdeviceapi.h&gt;
  #include &lt;endpointvolume.h&gt;
  #include &lt;windows.h&gt;
  #include &lt;shlwapi.h&gt;
  #include &lt;iostream&gt;
  #include &lt;Tchar.h&gt;
  
  static const GUID AudioSessionVolumeCtx = { 0x2715279f, 0x4139, 0x4ba0, { 0x9c, 0xb1, 0xb3, 0x51, 0xf1, 0xb5, 0x8a, 0x4a } };
  
  template &lt;class T&gt; void SafeRelease(T **ppT)
  {
      if (*ppT)
      {
          (*ppT)-&gt;Release();
          *ppT = NULL;
      }
  }
  
  class CAudioSessionVolume : public IAudioSessionEvents
  {
  public:
      static HRESULT CreateInstance( UINT uNotificationMessage, HWND hwndNotification, CAudioSessionVolume **ppAudioSessionVolume )
      {
          CAudioSessionVolume *pAudioSessionVolume = new (std::nothrow) 
              CAudioSessionVolume(uNotificationMessage, hwndNotification);
  
          if (pAudioSessionVolume == NULL)
          {
              return E_OUTOFMEMORY;
          }
  
          HRESULT hr = pAudioSessionVolume-&gt;Initialize();
          if (SUCCEEDED(hr))
          {
              *ppAudioSessionVolume = pAudioSessionVolume;
          }
          else
          {
              pAudioSessionVolume-&gt;Release();
          }
  
          return hr;
      }
  
      // IUnknown methods.
      STDMETHODIMP QueryInterface(REFIID riid, void **ppv)
      {
          static const QITAB qit[] = 
          {
              QITABENT(CAudioSessionVolume, IAudioSessionEvents),
              { 0 },
          };
          return QISearch(this, qit, riid, ppv);
      }
  
      STDMETHODIMP_(ULONG) AddRef()
      {
          return InterlockedIncrement(&amp;m_cRef);
      }
  
      STDMETHODIMP_(ULONG) Release()
      {
          LONG cRef = InterlockedDecrement( &amp;m_cRef );
          if (cRef == 0)
          {
              delete this;
          }
          return cRef;
      }
  
      STDMETHODIMP OnSimpleVolumeChanged( float NewVolume, BOOL NewMute, LPCGUID EventContext )
      {
          MessageBox(NULL, _T("vol changed"), _T("test"), MB_OK);
          return S_OK;
      }
  
      // The remaining audio session events do not require any action.
      STDMETHODIMP OnDisplayNameChanged(LPCWSTR,LPCGUID)
      {
          return S_OK;
      }
  
      STDMETHODIMP OnIconPathChanged(LPCWSTR,LPCGUID)
      {
          return S_OK;
      }
  
      STDMETHODIMP OnChannelVolumeChanged(DWORD,float[],DWORD,LPCGUID)
      {
          return S_OK;
      }
  
      STDMETHODIMP OnGroupingParamChanged(LPCGUID,LPCGUID)
      {
          return S_OK;
      }
  
      STDMETHODIMP OnStateChanged(AudioSessionState)
      {
          return S_OK;
      }
  
      STDMETHODIMP OnSessionDisconnected(AudioSessionDisconnectReason)
      {
          return S_OK;
      }
  
      // Other methods
      HRESULT EnableNotifications(BOOL bEnable)
      {
          HRESULT hr = S_OK;
  
          if (bEnable)
          {
              hr = m_pAudioSession-&gt;RegisterAudioSessionNotification(this);
          }
          else
          {
              hr = m_pAudioSession-&gt;UnregisterAudioSessionNotification(this);
          }
  
          return hr;
      }
  
      HRESULT SetDisplayName(const WCHAR *wszName)
      {
          if (m_pAudioSession == NULL)
          {
              return E_FAIL;
          }
          else
          {
              return m_pAudioSession-&gt;SetDisplayName(wszName, NULL);
          }
      }
  
  protected:
      CAudioSessionVolume( UINT uNotificationMessage, HWND hwndNotification ) :
          m_cRef(1), m_pAudioSession(NULL), m_pSimpleAudioVolume(NULL)
      {
      }
  
      ~CAudioSessionVolume()
      {
          EnableNotifications(FALSE);
  
          SafeRelease(&amp;m_pAudioSession);
          SafeRelease(&amp;m_pSimpleAudioVolume);
      }
  
      HRESULT Initialize()
      {
          HRESULT hr = S_OK;
  
          IMMDeviceEnumerator *pDeviceEnumerator = NULL;
          IMMDevice *pDevice = NULL;
          IAudioSessionManager *pAudioSessionManager = NULL;
  
          // Get the enumerator for the audio endpoint devices.
          hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL,
              CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&amp;pDeviceEnumerator));
  
          if (FAILED(hr)) 
          { 
              goto done; 
          }
  
          // Get the default audio endpoint that the SAR will use.
          hr = pDeviceEnumerator-&gt;GetDefaultAudioEndpoint(eRender, eConsole,
              &amp;pDevice);
  
          if (FAILED(hr)) 
          { 
              goto done; 
          }
  
          // Get the session manager for this device.
          hr = pDevice-&gt;Activate(__uuidof(IAudioSessionManager), 
              CLSCTX_INPROC_SERVER, NULL, (void**) &amp;pAudioSessionManager);
  
          if (FAILED(hr)) 
          { 
              goto done; 
          }
  
          // Get the audio session. 
          hr = pAudioSessionManager-&gt;GetAudioSessionControl( 
              &amp;GUID_NULL,     // Get the default audio session. 
              FALSE,          // The session is not cross-process.
              &amp;m_pAudioSession);
  
  
          if (FAILED(hr)) 
          { 
              goto done; 
          }
  
          hr = pAudioSessionManager-&gt;GetSimpleAudioVolume(&amp;GUID_NULL, 0,
              &amp;m_pSimpleAudioVolume);
  
      done:
          SafeRelease(&amp;pDeviceEnumerator);
          SafeRelease(&amp;pDevice);
          SafeRelease(&amp;pAudioSessionManager);
          return hr;
      }
  
  private:
      LONG m_cRef;
  
      IAudioSessionControl    *m_pAudioSession;
      ISimpleAudioVolume      *m_pSimpleAudioVolume;
  };
  
  int main()
  {
      CoInitialize(NULL);
      CAudioSessionVolume *asv;
      CAudioSessionVolume::CreateInstance(0, NULL, &amp;asv);
      asv-&gt;EnableNotifications(true);
  
      char s;
      gets(&amp;s);
  }
  </code></pre>
  
  <p>I was expecting to get a message box saying "vol changed" when the system volume changes, but I never get a message box.</p>
  
  <p>I got most of the code from <a href="http://msdn.microsoft.com/en-us/library/dd374921%28VS.85%29.aspx" rel="nofollow noreferrer">http://msdn.microsoft.com/en-us/library/dd374921(VS.85).aspx</a></p>
  
  <p>What am I doing wrong?</p>
  
  <p><strong>EDIT:</strong> I do get notifications when changing the volume for my application (thanks @nobugz). But I want notification when changing the master volume. (Listed as "Device" in Win7's Volume Mixers dialog)</p>
  
  <p><strong>EDIT 2:</strong> Larry Osterman has a series of blog posts about the volume in Vista/7. This one in particular is interesting: <a href="http://blogs.msdn.com/larryosterman/archive/2007/03/22/fun-with-the-endpoint-volume-interfaces-closing-the-loop.aspx" rel="nofollow noreferrer">http://blogs.msdn.com/larryosterman/archive/2007/03/22/fun-with-the-endpoint-volume-interfaces-closing-the-loop.aspx</a> I'll try the code out tomorrow to see if I can get what I want. Now it's time for bed.</p>
  
  <p><strong>EDIT 3:</strong> This is the complete code from Larry's blog posts up to and including the link posted above. It does what I need and then some. I'll try to trim the features I don't need.</p>
  
  <pre><code>#include &lt;windows.h&gt;
  #include &lt;mmdeviceapi.h&gt;
  #include &lt;endpointvolume.h&gt;
  #include &lt;Tchar.h&gt;
  #include &lt;strsafe.h&gt;
  
  class CVolumeNotification : public IAudioEndpointVolumeCallback 
  { 
      LONG m_RefCount; 
      ~CVolumeNotification(void) {}; 
  public: 
      CVolumeNotification(void) : m_RefCount(1) 
      { 
      } 
      STDMETHODIMP_(ULONG)AddRef() { return InterlockedIncrement(&amp;m_RefCount); } 
      STDMETHODIMP_(ULONG)Release()  
      { 
          LONG ref = InterlockedDecrement(&amp;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&lt;IUnknown*&gt;(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-&gt;fMasterVolume); 
  
          writeCoord.X = 0; 
          writeCoord.Y = 3; 
          WriteConsoleOutputCharacter(GetStdHandle(STD_OUTPUT_HANDLE), outputString, (DWORD)wcslen(outputString), writeCoord, &amp;written); 
          return S_OK; 
      } 
  }; 
  
  struct TimerContext 
  { 
      IAudioMeterInformation *_Meter; 
  }; 
  
  const int TimerPeriodicityMS = 100; 
  void CALLBACK TimerMeterCallback(PTP_CALLBACK_INSTANCE CallbackInstance, PVOID Context, PTP_TIMER Timer) 
  {
      TimerContext *timerContext = (TimerContext *)Context; 
      wchar_t outputString[256]; 
      float peakValue; 
      DWORD written; 
      COORD writeCoord; 
      StringCbCopy(outputString, sizeof(outputString), L"Meter: "); 
  
      timerContext-&gt;_Meter-&gt;GetPeakValue(&amp;peakValue); 
      for (size_t i = 0 ; i &lt; peakValue*100; i += 1) 
      { 
          StringCbCat(outputString, sizeof(outputString), L"*"); 
      } 
      for (size_t i = (size_t)(peakValue*100) ; i &lt; 100; i += 1) 
      { 
          StringCbCat(outputString, sizeof(outputString), L"."); 
      } 
      writeCoord.X = 0; 
      writeCoord.Y = 1; 
      WriteConsoleOutputCharacter(GetStdHandle(STD_OUTPUT_HANDLE), outputString, (DWORD)wcslen(outputString), writeCoord, &amp;written); 
  } 
  
  int _tmain(int argc, _TCHAR* argv[])
  {
      CoInitialize(NULL);
      HRESULT hr;
      IMMDeviceEnumerator *deviceEnumerator = NULL;;
      HANDLE handle; 
      TP_CALLBACK_ENVIRON callbackEnvironment; 
      PTP_CLEANUP_GROUP cleanupGroup; 
      PTP_TIMER timer; 
      TimerContext context; 
  
      InitializeThreadpoolEnvironment(&amp;callbackEnvironment); 
      cleanupGroup = CreateThreadpoolCleanupGroup(); 
      SetThreadpoolCallbackCleanupGroup(&amp;callbackEnvironment, cleanupGroup, NULL); 
  
      timer = CreateThreadpoolTimer(TimerMeterCallback, &amp;context, &amp;callbackEnvironment); 
  
      //
      //    Clear the screen.  Code stolen from: http://support.microsoft.com/kb/319257.
      //
      handle = GetStdHandle(STD_OUTPUT_HANDLE);
      DWORD writtenChars = 0;
      CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
      COORD home;
      home.X = home.Y = 0;
      GetConsoleScreenBufferInfo(handle, &amp;consoleInfo);
      FillConsoleOutputCharacter(handle, L' ', consoleInfo.dwSize.X * consoleInfo.dwSize.Y, home, &amp;writtenChars);
      SetConsoleCursorPosition(handle, home);
  
      //
      //    Set the console to raw mode. 
      //
      DWORD oldMode;
      GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &amp;oldMode);
      SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldMode &amp; ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT));
  
      // 
      //    Instantiate an endpoint volume object.
      //
      hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID *)&amp;deviceEnumerator);
      IMMDevice *defaultDevice = NULL;
  
      hr = deviceEnumerator-&gt;GetDefaultAudioEndpoint(eRender, eConsole, &amp;defaultDevice);
      deviceEnumerator-&gt;Release();
      deviceEnumerator = NULL;
  
      IAudioEndpointVolume *endpointVolume = NULL;
      hr = defaultDevice-&gt;Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&amp;endpointVolume);
      CVolumeNotification *volumeNotification = new CVolumeNotification(); 
      hr = endpointVolume-&gt;RegisterControlChangeNotify(volumeNotification); 
      hr = defaultDevice-&gt;Activate(__uuidof(IAudioMeterInformation), CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&amp;context._Meter); 
      defaultDevice-&gt;Release(); 
      defaultDevice = NULL; 
      // Set a 100 millisecond timer. 
      LARGE_INTEGER timerPeriodicityLI; 
      FILETIME timerPeriodicity; 
      timerPeriodicityLI.QuadPart = -1*(TimerPeriodicityMS* 10000 ); 
  
      timerPeriodicity.dwLowDateTime = timerPeriodicityLI.LowPart; 
      timerPeriodicity.dwHighDateTime = timerPeriodicityLI.HighPart; 
  
      SetThreadpoolTimer(timer, &amp;timerPeriodicity, TimerPeriodicityMS, 10); 
  
      wchar_t inputChar = '\0';
      while (inputChar != '\r')
      {
          UINT currentStep, stepCount;
          DWORD read, written;
          COORD writeCoord;
          wchar_t outputString[256];
          StringCbCopy(outputString, sizeof(outputString), L"Volume: ");
  
          // 
          //    Calculate the cheesy OSD.
          //
          endpointVolume-&gt;GetVolumeStepInfo(&amp;currentStep, &amp;stepCount);
          for (size_t i = 0 ; i &lt; stepCount ; i += 1)
          {
              if (i &lt;= currentStep)
              {
                  StringCbCat(outputString, sizeof(outputString), L"=");
              }
              else
              {
                  StringCbCat(outputString, sizeof(outputString), L"-");
              }
          }
          writeCoord.X = 0;
          writeCoord.Y = 0;
          WriteConsoleOutputCharacter(handle, outputString, (DWORD)wcslen(outputString), writeCoord, &amp;written);
  
          ReadConsole(GetStdHandle(STD_INPUT_HANDLE), &amp;inputChar, 1, &amp;read, NULL);
          if (inputChar == '+')
          {
              endpointVolume-&gt;VolumeStepUp(NULL);
          }
          else if (inputChar == '-')
          {
              endpointVolume-&gt;VolumeStepDown(NULL);
          }
      }
  
      // 
      //    Remove our notification. 
      // 
      endpointVolume-&gt;UnregisterControlChangeNotify(volumeNotification); 
  
      endpointVolume-&gt;Release(); 
      volumeNotification-&gt;Release(); 
      SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldMode);
  
      CloseThreadpoolCleanupGroupMembers(cleanupGroup, FALSE, NULL); 
      CloseThreadpoolCleanupGroup(cleanupGroup); 
      cleanupGroup = NULL; 
      DestroyThreadpoolEnvironment(&amp;callbackEnvironment); 
  
      context._Meter-&gt;Release(); 
  
      CoUninitialize();
  
      return 0;
  }
  </code></pre>
  
  <p><strong>EDIT 4:</strong> This is a stripped down version of Larry's code.</p>
  
  <pre><code>#include &lt;windows.h&gt;
  #include &lt;mmdeviceapi.h&gt;
  #include &lt;endpointvolume.h&gt;
  #include &lt;iostream&gt;
  #include &lt;Tchar.h&gt;
  
  class CVolumeNotification : public IAudioEndpointVolumeCallback 
  { 
      LONG m_RefCount; 
      ~CVolumeNotification(void) {}; 
  public: 
      CVolumeNotification(void) : m_RefCount(1) 
      { 
      } 
      STDMETHODIMP_(ULONG)AddRef() { return InterlockedIncrement(&amp;m_RefCount); } 
      STDMETHODIMP_(ULONG)Release()  
      { 
          LONG ref = InterlockedDecrement(&amp;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&lt;IUnknown*&gt;(this); 
              AddRef(); 
              return S_OK; 
          } 
          *ReturnValue = NULL; 
          return E_NOINTERFACE; 
      } 
  
      STDMETHODIMP OnNotify(PAUDIO_VOLUME_NOTIFICATION_DATA NotificationData) 
      { 
          std::cout &lt;&lt; NotificationData-&gt;fMasterVolume &lt;&lt; _T(" ");
  
          return S_OK; 
      } 
  }; 
  
  int _tmain(int argc, _TCHAR* argv[])
  {
      CoInitialize(NULL);
      HRESULT hr;
      IMMDeviceEnumerator *deviceEnumerator = NULL;;
  
      // 
      //    Instantiate an endpoint volume object.
      //
      hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_INPROC_SERVER, __uuidof(IMMDeviceEnumerator), (LPVOID *)&amp;deviceEnumerator);
      IMMDevice *defaultDevice = NULL;
  
      hr = deviceEnumerator-&gt;GetDefaultAudioEndpoint(eRender, eConsole, &amp;defaultDevice);
      deviceEnumerator-&gt;Release();
      deviceEnumerator = NULL;
  
      IAudioEndpointVolume *endpointVolume = NULL;
      hr = defaultDevice-&gt;Activate(__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, (LPVOID *)&amp;endpointVolume);
      CVolumeNotification *volumeNotification = new CVolumeNotification(); 
      hr = endpointVolume-&gt;RegisterControlChangeNotify(volumeNotification); 
      defaultDevice-&gt;Release(); 
      defaultDevice = NULL; 
  
      wchar_t inputChar = '\0';
      while (inputChar != '\r')
      {
          DWORD read;
          ReadConsole(GetStdHandle(STD_INPUT_HANDLE), &amp;inputChar, 1, &amp;read, NULL);
      }
  
      // 
      //    Remove our notification. 
      // 
      endpointVolume-&gt;UnregisterControlChangeNotify(volumeNotification); 
      endpointVolume-&gt;Release(); 
      volumeNotification-&gt;Release(); 
  
      CoUninitialize();
  
      return 0;
  }
  </code></pre>
  
  <p>I still haven't looked at the SDK example. I now know how this stuff works well enough to add what I need to my application. Thanks for everyones help!</p>