1
votes

I'm working on an application which polls for the states and peak levels of audio sessions on the default audio rendering endpoint every X milliseconds, and implements some logic based on that. It seems to run fine on Windows 10, but on Windows 7 I get occasional crashes when I change the default playback device (e.g. when I switch between my USB headset and the PC speaker). The exact line where the crash occurs changes between runs, but it's usually when I access the IAudioSessionControl or IAudioSessionControl2 pointers to make various WASAPI calls. What I could glean from the stack traces created by ProcDump and analyzed by WinDbg is that the COM objects representing audio sessions are destroyed when the default playback device has changed (even though I had obtained and am still holding pointers to those objects' interfaces), and then my polling thread crashes randomly in places where I access them through the interface pointers. I figured that maybe I'm doing something wrong, so this led me to Matthew van Eerde's blog and sample, where he does the same querying (and more), but for all available audio endpoints in the system. So I modified his sample program to do it every 5 milliseconds and only for the default rendering endpoint, and I get the same occasional crashes on Windows 7.

Here is a stripped-down version of the sample, which sometimes results in crashes when switching between playback devices. I usually get the crash within ~2 minutes of switching back-and-forth between devices. YMMV.

#include <windows.h>
#include <atlbase.h>
#include <stdio.h>
#include <mmdeviceapi.h>
#include <audiopolicy.h>
#include <endpointvolume.h>
#include <functiondiscoverykeys_devpkey.h>

#define LOG(format, ...) wprintf(format L"\n", __VA_ARGS__)

class CoUninitializeOnExit {
public:
    CoUninitializeOnExit() {}
    ~CoUninitializeOnExit() {
        CoUninitialize();
    }
};

class PropVariantClearOnExit {
public:
    PropVariantClearOnExit(PROPVARIANT *p) : m_p(p) {}
    ~PropVariantClearOnExit() {
        PropVariantClear(m_p);
    }
private:
    PROPVARIANT *m_p;
};

int _cdecl wmain() {
    HRESULT hr = S_OK;

    hr = CoInitialize(NULL);
    if (FAILED(hr)) {
        LOG(L"CoInitialize failed: hr = 0x%08x", hr);
        return -__LINE__;
    }
    CoUninitializeOnExit cuoe;

    while (1)
    {
      Sleep(5);
      // get default device
      CComPtr<IMMDeviceEnumerator> pMMDeviceEnumerator;
      hr = pMMDeviceEnumerator.CoCreateInstance(__uuidof(MMDeviceEnumerator));
      if (FAILED(hr)) {
        LOG(L"CoCreateInstance(IMMDeviceEnumerator) failed: hr = 0x%08x", hr);
        return -__LINE__;
      }

      CComPtr<IMMDevice> pMMDevice;
      hr = pMMDeviceEnumerator->GetDefaultAudioEndpoint(eRender, eConsole, &pMMDevice);
      if (FAILED(hr)) {
        LOG(L"IMMDeviceEnumerator::GetDefaultAudioEndpoint failed: hr = 0x%08x", hr);
        return -__LINE__;
      }

      // get the name of the endpoint
      CComPtr<IPropertyStore> pPropertyStore;
      hr = pMMDevice->OpenPropertyStore(STGM_READ, &pPropertyStore);
      if (FAILED(hr)) {
        LOG(L"IMMDevice::OpenPropertyStore failed: hr = 0x%08x", hr);
        return -__LINE__;
      }

      PROPVARIANT v; PropVariantInit(&v);
      PropVariantClearOnExit pvcoe(&v);
      hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &v);
      if (FAILED(hr)) {
        LOG(L"IPropertyStore::GetValue(PKEY_Device_FriendlyName) failed: hr = 0x%08x", hr);
        return -__LINE__;
      }
      if (VT_LPWSTR != v.vt) {
        LOG(L"PKEY_Device_FriendlyName has unexpected vartype %u", v.vt);
        return -__LINE__;
      }

      LOG(L"Selected playback device: %s\n", v.pwszVal);

      // get a session enumerator
      CComPtr<IAudioSessionManager2> pAudioSessionManager2;
      hr = pMMDevice->Activate(
        __uuidof(IAudioSessionManager2),
        CLSCTX_ALL,
        nullptr,
        reinterpret_cast<void **>(&pAudioSessionManager2)
      );
      if (FAILED(hr)) {
        LOG(L"IMMDevice::Activate(IAudioSessionManager2) failed: hr = 0x%08x", hr);
        return -__LINE__;
      }

      CComPtr<IAudioSessionEnumerator> pAudioSessionEnumerator;
      hr = pAudioSessionManager2->GetSessionEnumerator(&pAudioSessionEnumerator);
      if (FAILED(hr)) {
        LOG(L"IAudioSessionManager2::GetSessionEnumerator() failed: hr = 0x%08x", hr);
        return -__LINE__;
      }

      // iterate over all the sessions
      int count = 0;
      hr = pAudioSessionEnumerator->GetCount(&count);
      if (FAILED(hr)) {
        LOG(L"IAudioSessionEnumerator::GetCount() failed: hr = 0x%08x", hr);
        return -__LINE__;
      }

      for (int session = 0; session < count; session++) {
        // get the session control
        CComPtr<IAudioSessionControl> pAudioSessionControl;
        hr = pAudioSessionEnumerator->GetSession(session, &pAudioSessionControl);
        if (FAILED(hr)) {
          LOG(L"IAudioSessionEnumerator::GetSession() failed: hr = 0x%08x", hr);
          return -__LINE__;
        }

        AudioSessionState state;
        hr = pAudioSessionControl->GetState(&state);
        if (FAILED(hr)) {
          LOG(L"IAudioSessionControl::GetState() failed: hr = 0x%08x", hr);
          return -__LINE__;
        }

        CComPtr<IAudioSessionControl2> pAudioSessionControl2;
        hr = pAudioSessionControl->QueryInterface(IID_PPV_ARGS(&pAudioSessionControl2));
        if (FAILED(hr)) {
          LOG(L"IAudioSessionControl::QueryInterface(IAudioSessionControl2) failed: hr = 0x%08x", hr);
          return -__LINE__;
        }

        DWORD pid = 0;
        hr = pAudioSessionControl2->GetProcessId(&pid);
        if (FAILED(hr)) {
          LOG(L"IAudioSessionControl2::GetProcessId() failed: hr = 0x%08x", hr);
          return -__LINE__;
        }

        hr = pAudioSessionControl2->IsSystemSoundsSession();
        if (FAILED(hr)) {
          LOG(L"IAudioSessionControl2::IsSystemSoundsSession() failed: hr = 0x%08x", hr);
          return -__LINE__;
        }

        bool bIsSystemSoundsSession = (S_OK == hr);

        // get the current audio peak meter level for this session
        CComPtr<IAudioMeterInformation> pAudioMeterInformation;
        hr = pAudioSessionControl->QueryInterface(IID_PPV_ARGS(&pAudioMeterInformation));
        if (FAILED(hr)) {
          LOG(L"IAudioSessionControl::QueryInterface(IAudioMeterInformation) failed: hr = 0x%08x", hr);
          return -__LINE__;
        }

        float peak = 0.0f;
        hr = pAudioMeterInformation->GetPeakValue(&peak);
        if (FAILED(hr)) {
          LOG(L"IAudioMeterInformation::GetPeakValue() failed: hr = 0x%08x", hr);
          return -__LINE__;
        }

        LOG(
          L"    Session #%d\n"
          L"        State: %d\n"
          L"        Peak value: %g\n"
          L"        Process ID: %u\n"
          L"        System sounds session: %s",
          session,
          state,
          peak,
          pid,
          (bIsSystemSoundsSession ? L"yes" : L"no")
        );

      } // session
    } // while

    return 0;
}

Here is one instance of a crash dump analysis: https://pastebin.com/tvRV8ukY.
Is this an issue with the Windows 7 implementation of the Core Audio APIs, or am I doing something wrong? Thanks.

1
Can you provide exception information from the dump, e.g. by running !analyze -v?IInspectable
@IInspectable: Edited the question to include an instance of dump info.Lurch

1 Answers

0
votes

I eventually contacted Matthew van Eerde by e-mail, and he said it does look like a race condition in Core Audio API on Windows 7, and my best bet is to file a support request to Microsoft.
Thanks for your help, Matthew!