WASAPI: no sound after sample rate changed


#1

Hi there.

After WASAPI running device’s sample rate was changed (with OS control panel),
there are no sound from application but audioIOCallback occurs.
I think users expect sound to continue after a sample rate change, like ASIO audio devices and its control panels.

I tried Windows SDK’s WinAudio sample and found it catches “OnSessionDisconnected” event via IAudioSessionEvents under such circumstances.
WinAudio (deprecated but works on Windows 7): http://msdn.microsoft.com/en-us/library/dd316764(v=VS.85).aspx
IAudioSessionEvents: http://msdn.microsoft.com/en-us/library/dd368289(v=vs.85).aspx

I don’t know what is the best solution, but it might be good to implement this interface on Juce WASAPI classes.
Do you have any plans?

Thanks,
qvox


#2

Hmm. Interesting point. I guess it should handle that. Looks like a huge amount of hassle to set up a IAudioSessionEvents com object though, I’m a bit too busy to get stuck into doing that at the moment…


#3

Thank you for your thoughts!
FYI here is a result of my short experiment.

  1. implement event redirector class.
class WASAPIEventRedirector : public IAudioSessionEvents
{
public:
	class Listener
	{
	public:
		virtual ~Listener() {}
		virtual void audioSessionDisconnected(AudioSessionDisconnectReason DisconnectReason) = 0;
		// todo: declare interface for the other events
	};

	WASAPIEventRedirector(Listener* listener_) : listener(listener_) {};
    virtual ~WASAPIEventRedirector() {};

    ULONG __stdcall AddRef()
    {
        return InterlockedIncrement(&m_cRef);
    };

    ULONG __stdcall Release()
    {
        DWORD dwRef = InterlockedDecrement(&m_cRef);
        if (dwRef == 0)
        {
            delete this;
        }
        return dwRef;
    };

    HRESULT __stdcall QueryInterface(REFIID iid, VOID **pRetVal)
    {
        if (IID_IUnknown == iid)
        {
            AddRef();
            *pRetVal = (IUnknown*)this;
        }
        else if (__uuidof(IAudioSessionEvents) == iid)
        {
            AddRef();
            *pRetVal = (IAudioSessionEvents*)this;
        }
        else
        {
            *pRetVal = NULL;
            return(E_NOINTERFACE);
        }
        return(S_OK);
    };

    HRESULT __stdcall OnDisplayNameChanged(LPCWSTR NewDisplayName, LPCGUID EventContext)
    {
        return S_OK;
    };

    HRESULT __stdcall OnIconPathChanged(LPCWSTR NewIconPath, LPCGUID EventContext)
    {
        return S_OK;
    };

    HRESULT __stdcall OnSimpleVolumeChanged(float NewVolume, BOOL NewMute,
                                            LPCGUID EventContext)
    {
        return S_OK;
    };

    HRESULT __stdcall OnChannelVolumeChanged(DWORD ChannelCount,
                                             float NewChannelVolumeArray[],
                                             DWORD ChangedChannel,
                                             LPCGUID EventContext)
    {
        return S_OK;
    };

    HRESULT __stdcall OnGroupingParamChanged(LPCGUID NewGroupingParam,
                                             LPCGUID EventContext)
    {
        return S_OK;
    };

    HRESULT __stdcall OnStateChanged(AudioSessionState NewState)
    {
        return S_OK;
    };

    HRESULT __stdcall OnSessionDisconnected(AudioSessionDisconnectReason DisconnectReason)
    {
		listener->audioSessionDisconnected(DisconnectReason);
        return S_OK;
    };

private:
    LONG m_cRef;
	Listener* listener;
};
  1. add related objects to WASAPIDeviceBase.
	ComSmartPtr <IAudioSessionControl> sessionControl;
	ComSmartPtr <WASAPIEventRedirector> eventRedirector;
  1. create redirector on WASAPIDeviceBase ctor.
		eventRedirector = new WASAPIEventRedirector(this);
  1. register redirector to IAudioSessionControl on WASAPIDeviceBase::openClient().
        if (client != 0
             && (tryInitialisingWithFormat (true, 4) || tryInitialisingWithFormat (false, 4)
                  || tryInitialisingWithFormat (false, 3) || tryInitialisingWithFormat (false, 2)))
        {
			IAudioSessionControl* newSessionControl = 0;
			client->GetService(__uuidof(IAudioSessionControl), (void**)&newSessionControl);
			sessionControl = newSessionControl;
			if (sessionControl != 0)
			{
				sessionControl->RegisterAudioSessionNotification(eventRedirector);
			}
  1. implement a callback.
	void audioSessionDisconnected(AudioSessionDisconnectReason DisconnectReason)
	{
		DBG(String::formatted(T("disconnected: %08x"), DisconnectReason));
	}
  1. then we can see “disconnected: 00000002” after sample rate changed. (00000002 is DisconnectReasonFormatChanged.)

#4

Oops, coding rules are mixtured because I have made the event redirector class from WinAudio sample… Sorry for my messy code.


#5

Cool, thanks for the tips! I’ll try to take a look at this asap!


#6

It would seem you are quite skilled at WASAPI development :slight_smile:


#7

Hmm, I just hacked together an implementation that will catch these callbacks, but then realised I’m not really sure what to do when it happens… I got as far as making the audio thread aware when the rate changes, but then had to stop and need to think about what it should do. It’s a start, anyway - I need to work on some other stuff now, but you might want to see if what I’ve done actually works.


#8

Thanks, I’ve tried the latest tip then callback works.

I’m not sure about what it should do, either… but for our application, reopening the device with its current settings (like as ASIOAudioIODevice::timerCallback()) seems to work fine. Then the application have only to handle audioDeviceStopped() and audioDeviceAboutToStart() correctly.

Moreover, I think it might be not bad for other DisconnectReason.
e.g. there are devices that disconnect session when headphone is disconnected because of DisconnectReasonDeviceRemoval. It will fail to reopen, and only AudioIODeviceCallback::audioDeviceStopped() will be called.

TheVinn:
Wow, I am a newbie of WASAPI! I have just known about this matter :slight_smile: