naudio / NAudio

Audio and MIDI library for .NET
MIT License
5.53k stars 1.1k forks source link

Multiple IAudioSessionEventsHandler:OnVolumeChanged events being raised #745

Open nsaxelby opened 3 years ago

nsaxelby commented 3 years ago

Hello, sample code further below.

Expected: When I register a IAudioSessionEventsHandler on an AudioSessionControl, I expect to receive one OnVolumeChanged event when the volume is changed.

Actual: When I start my application, it receives two events per volume adjustment ( no big deal ), but when I unbind that object, and re-bind the AudioSessionControl to a new object, multiple events trigger when the Session volume is adjusted.

You can see in the sample application provided, multiple events are firing after AudioSessionControl is unregistered, and registered to a IAudioSessionEventsHandler object.

Curretly using build : 63c7011a7559cb55719e306dd3ab716b25e26d6a ( current version of Master branch ). Console application using .NET Framework 4.7.2

    class Program
    {
        static void Main(string[] args)
        {
            MMDeviceEnumerator deviceEnumerator = new MMDeviceEnumerator();
            SoundDevice dev = null;

            Console.WriteLine("Started APP  - type 'end' to end or press enter to find chrome, and bind to it");
            while (Console.ReadLine() != "end")
            {
                Console.WriteLine("Finding chrome");
                var chrome = FindChrome(deviceEnumerator);
                if (chrome != null)
                {
                    Console.WriteLine("Chrome found, binding to device");
                    if(dev != null)
                    {
                        dev.Dispose();
                    }
                    dev = new SoundDevice(chrome);
                }
                else
                {
                    Console.WriteLine("Chrome not found");
                }
                Console.WriteLine("-------");
                Console.WriteLine("type 'end' to end or press enter to find chrome, and bind to it");
            }
        }

        private static AudioSessionControl FindChrome(MMDeviceEnumerator deviceEnumerator)
        {
            var coreDevice = deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
            for (int i = 0; i < coreDevice.AudioSessionManager.Sessions.Count; i++)
            {
                var sess = coreDevice.AudioSessionManager.Sessions[i];
                if (sess.IsSystemSoundsSession == false)
                {
                    if (sess.GetSessionIdentifier.Contains("chrome"))
                    {
                        return sess;
                    }
                }
            }
            return null;
        }
    }

    public class SoundDevice : IAudioSessionEventsHandler
    {

        private AudioSessionControl _session;

        public SoundDevice(AudioSessionControl sess)
        {
            _session = sess;
            _session.RegisterEventClient(this);
        }
        public void OnChannelVolumeChanged(uint channelCount, IntPtr newVolumes, uint channelIndex)
        {
            throw new NotImplementedException();
        }

        public void OnDisplayNameChanged(string displayName)
        {
            throw new NotImplementedException();
        }

        public void OnGroupingParamChanged(ref Guid groupingId)
        {
            throw new NotImplementedException();
        }

        public void OnIconPathChanged(string iconPath)
        {
            throw new NotImplementedException();
        }

        public void OnSessionDisconnected(AudioSessionDisconnectReason disconnectReason)
        {
            throw new NotImplementedException();
        }

        public void OnStateChanged(AudioSessionState state)
        {
            throw new NotImplementedException();
        }

        public void OnVolumeChanged(float volume, bool isMuted)
        {
            Console.WriteLine("Vol changed to :" + volume.ToString());
        }

        public void Dispose()
        {
            if(_session != null)
            {
                _session.UnRegisterEventClient(this);
                // I think Dispose calls UnRegisterEventClient anyway.. But belt and braces
                _session.Dispose();
            }
        }
    }

Steps to reproduce:

  1. Paste code into a .NET framework console application.
  2. Import NAudio ( I built the NAudio.dll from source, and imported to project )
  3. Make sure you have Chrome running, and it is playing some audio ( or you can use whatever audio application you like, it just needs a sessionable audio application ).
  4. Run the application, and press the 'enter' key once, it will attempt to find a 'chrome' instance running and bind it to the object which listens to vol changes
  5. Change the Chrome application volume mixer in windows volume mixer - you'll see two Console.WriteLine events being written to console
  6. Press the enter key a few more times, which re-binds the session audio
  7. Change Chrome volume again in Mixer, you'll see many lines ( same volume percentage ) being written to console output, this is the problem.. I would only expect one line to be written per change.

Console output example:

image

nsaxelby commented 3 years ago

Small update, I nulled the _session on my SoundDevice object on the Dispose, and called GC.Collect(). This seemed to limit the number of updates sent at once to 4.

        public void Dispose()
        {
            if(_session != null)
            {
                _session.UnRegisterEventClient(this);
                // I think Dispose calls UnRegisterEventClient anyway.. But belt and braces
                _session.Dispose();
                _session = null;   // null obj
                GC.Collect(); // Force GC collection
            }
        }

Does seem to help a little, max of 4 events raised, even on many re-binds.