# HG changeset patch # User StephaneLenclud # Date 1483816902 -3600 # Node ID 71ba0dd622a5394dbd4119bf002a0015b4b4d68a # Parent 7cd495550d5f559d9a20a75bb4507ad6e8c2955f Created Audio Manager class. Clean up CScore audio usage. Fixing broken audio device change handler. Fixed various audio Dispose deadlock due to Invoke usage. Thus now using BeginInvoke instead. diff -r 7cd495550d5f -r 71ba0dd622a5 Server/AudioManager.cs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Server/AudioManager.cs Sat Jan 07 20:21:42 2017 +0100 @@ -0,0 +1,184 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +//CSCore +using CSCore; +using CSCore.Win32; +using CSCore.DSP; +using CSCore.Streams; +using CSCore.CoreAudioAPI; +using CSCore.SoundIn; +// Visualization +using Visualization; + + +namespace SharpDisplayManager +{ + class AudioManager + { + // Volume management + private MMDeviceEnumerator iMultiMediaDeviceEnumerator; + private MMNotificationClient iMultiMediaNotificationClient; + private MMDevice iMultiMediaDevice; + private AudioEndpointVolume iAudioEndpointVolume; + private AudioEndpointVolumeCallback iAudioEndpointVolumeCallback; + EventHandler iDefaultDeviceChangedHandler; + EventHandler iVolumeChangedHandler; + + // Audio visualization + private WasapiCapture iSoundIn; + private IWaveSource iWaveSource; + private LineSpectrum iLineSpectrum; + + + public LineSpectrum Spectrum { get { return iLineSpectrum; } } + public AudioEndpointVolume Volume { get { return iAudioEndpointVolume; } } + public MMDevice DefaultDevice { get { return iMultiMediaDevice; } } + + /// + /// + /// + /// + /// + public void Open( EventHandler aDefaultDeviceChangedHandler, + EventHandler aVolumeChangedHandler) + { + //Create device and register default device change notification + iMultiMediaDeviceEnumerator = new MMDeviceEnumerator(); + iMultiMediaNotificationClient = new MMNotificationClient(iMultiMediaDeviceEnumerator); + iMultiMediaNotificationClient.DefaultDeviceChanged += iDefaultDeviceChangedHandler = aDefaultDeviceChangedHandler; + iMultiMediaDevice = iMultiMediaDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render,Role.Multimedia); + //Register to get volume modifications + iAudioEndpointVolume = AudioEndpointVolume.FromDevice(iMultiMediaDevice); + iAudioEndpointVolumeCallback = new AudioEndpointVolumeCallback(); + iAudioEndpointVolumeCallback.NotifyRecived += iVolumeChangedHandler = aVolumeChangedHandler; + iAudioEndpointVolume.RegisterControlChangeNotify(iAudioEndpointVolumeCallback); + + StartAudioVisualization(); + } + + /// + /// + /// + public void Close() + { + StopAudioVisualization(); + + // Client up our MM objects in reverse order + if (iAudioEndpointVolumeCallback != null && iAudioEndpointVolume != null) + { + iAudioEndpointVolume.UnregisterControlChangeNotify(iAudioEndpointVolumeCallback); + } + + if (iAudioEndpointVolumeCallback != null) + { + iAudioEndpointVolumeCallback.NotifyRecived -= iVolumeChangedHandler; + iAudioEndpointVolumeCallback = null; + } + + if (iAudioEndpointVolume != null) + { + iAudioEndpointVolume.Dispose(); + iAudioEndpointVolume = null; + } + + if (iMultiMediaDevice != null) + { + iMultiMediaDevice.Dispose(); + iMultiMediaDevice = null; + } + + if (iMultiMediaNotificationClient != null) + { + iMultiMediaNotificationClient.DefaultDeviceChanged -= iDefaultDeviceChangedHandler; + iMultiMediaNotificationClient.Dispose(); + iMultiMediaNotificationClient = null; + } + + if (iMultiMediaDeviceEnumerator != null) + { + iMultiMediaDeviceEnumerator.Dispose(); + iMultiMediaDeviceEnumerator = null; + } + + } + + + /// + /// + /// + private void StartAudioVisualization() + { + //Open the default device + iSoundIn = new WasapiLoopbackCapture(); + //Our loopback capture opens the default render device by default so the following is not needed + //iSoundIn.Device = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Console); + iSoundIn.Initialize(); + + SoundInSource soundInSource = new SoundInSource(iSoundIn); + ISampleSource source = soundInSource.ToSampleSource(); + + const FftSize fftSize = FftSize.Fft2048; + //create a spectrum provider which provides fft data based on some input + BasicSpectrumProvider spectrumProvider = new BasicSpectrumProvider(source.WaveFormat.Channels, source.WaveFormat.SampleRate, fftSize); + + //linespectrum and voiceprint3dspectrum used for rendering some fft data + //in oder to get some fft data, set the previously created spectrumprovider + iLineSpectrum = new LineSpectrum(fftSize) + { + SpectrumProvider = spectrumProvider, + UseAverage = false, + BarCount = 16, + BarSpacing = 1, + IsXLogScale = true, + ScalingStrategy = ScalingStrategy.Decibel + }; + + + //the SingleBlockNotificationStream is used to intercept the played samples + var notificationSource = new SingleBlockNotificationStream(source); + //pass the intercepted samples as input data to the spectrumprovider (which will calculate a fft based on them) + notificationSource.SingleBlockRead += (s, a) => spectrumProvider.Add(a.Left, a.Right); + + iWaveSource = notificationSource.ToWaveSource(16); + + + // We need to read from our source otherwise SingleBlockRead is never called and our spectrum provider is not populated + byte[] buffer = new byte[iWaveSource.WaveFormat.BytesPerSecond / 2]; + soundInSource.DataAvailable += (s, aEvent) => + { + int read; + while ((read = iWaveSource.Read(buffer, 0, buffer.Length)) > 0) ; + }; + + + //Start recording + iSoundIn.Start(); + } + + /// + /// + /// + private void StopAudioVisualization() + { + if (iWaveSource != null) + { + iWaveSource.Dispose(); + iWaveSource = null; + } + + if (iSoundIn != null) + { + iSoundIn.Stop(); + iSoundIn.Dispose(); + iSoundIn = null; + } + + } + + + } +} diff -r 7cd495550d5f -r 71ba0dd622a5 Server/FormMain.Designer.cs --- a/Server/FormMain.Designer.cs Fri Jan 06 18:27:19 2017 +0100 +++ b/Server/FormMain.Designer.cs Sat Jan 07 20:21:42 2017 +0100 @@ -88,7 +88,7 @@ this.checkBoxInverseColors = new System.Windows.Forms.CheckBox(); this.checkBoxReverseScreen = new System.Windows.Forms.CheckBox(); this.tabPageAudio = new System.Windows.Forms.TabPage(); - this.labelDefaultAudioDevice = new System.Windows.Forms.Label(); + this.iLabelDefaultAudioDevice = new System.Windows.Forms.Label(); this.checkBoxShowVolumeLabel = new System.Windows.Forms.CheckBox(); this.checkBoxMute = new System.Windows.Forms.CheckBox(); this.trackBarMasterVolume = new System.Windows.Forms.TrackBar(); @@ -708,7 +708,7 @@ // // tabPageAudio // - this.tabPageAudio.Controls.Add(this.labelDefaultAudioDevice); + this.tabPageAudio.Controls.Add(this.iLabelDefaultAudioDevice); this.tabPageAudio.Controls.Add(this.checkBoxShowVolumeLabel); this.tabPageAudio.Controls.Add(this.checkBoxMute); this.tabPageAudio.Controls.Add(this.trackBarMasterVolume); @@ -720,15 +720,15 @@ this.tabPageAudio.Text = "Audio"; this.tabPageAudio.UseVisualStyleBackColor = true; // - // labelDefaultAudioDevice + // iLabelDefaultAudioDevice // - this.labelDefaultAudioDevice.AutoSize = true; - this.labelDefaultAudioDevice.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.labelDefaultAudioDevice.Location = new System.Drawing.Point(3, 6); - this.labelDefaultAudioDevice.Name = "labelDefaultAudioDevice"; - this.labelDefaultAudioDevice.Size = new System.Drawing.Size(120, 13); - this.labelDefaultAudioDevice.TabIndex = 19; - this.labelDefaultAudioDevice.Text = "Audio Device Unknown"; + this.iLabelDefaultAudioDevice.AutoSize = true; + this.iLabelDefaultAudioDevice.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.iLabelDefaultAudioDevice.Location = new System.Drawing.Point(3, 6); + this.iLabelDefaultAudioDevice.Name = "iLabelDefaultAudioDevice"; + this.iLabelDefaultAudioDevice.Size = new System.Drawing.Size(120, 13); + this.iLabelDefaultAudioDevice.TabIndex = 19; + this.iLabelDefaultAudioDevice.Text = "Audio Device Unknown"; // // checkBoxShowVolumeLabel // @@ -1361,7 +1361,7 @@ private System.Windows.Forms.TrackBar trackBarMasterVolume; private System.Windows.Forms.CheckBox checkBoxMute; private System.Windows.Forms.CheckBox checkBoxShowVolumeLabel; - private System.Windows.Forms.Label labelDefaultAudioDevice; + private System.Windows.Forms.Label iLabelDefaultAudioDevice; private System.Windows.Forms.OpenFileDialog openFileDialog; private System.Windows.Forms.TabPage tabPageCec; private System.Windows.Forms.CheckBox iCheckBoxCecEnabled; diff -r 7cd495550d5f -r 71ba0dd622a5 Server/FormMain.cs --- a/Server/FormMain.cs Fri Jan 06 18:27:19 2017 +0100 +++ b/Server/FormMain.cs Sat Jan 07 20:21:42 2017 +0100 @@ -79,15 +79,13 @@ public delegate void SetClientPriorityDelegate(string aSessionId, uint aPriority); - public delegate void PlainUpdateDelegate(); - public delegate void WndProcDelegate(ref Message aMessage); /// /// Our Display manager main form /// [System.ComponentModel.DesignerCategory("Form")] - public partial class FormMain : FormMainHid, IMMNotificationClient + public partial class FormMain : FormMainHid { //public Manager iManager = new Manager(); DateTime LastTickTime; @@ -111,15 +109,8 @@ CoordinateTranslationDelegate iScreenX; //Function pointer for pixel Y coordinate intercept CoordinateTranslationDelegate iScreenY; - //CSCore - // Volume management - private MMDeviceEnumerator iMultiMediaDeviceEnumerator; - private MMDevice iMultiMediaDevice; - private AudioEndpointVolume iAudioEndpointVolume; - // Audio visualization - private WasapiCapture iSoundIn; - private IWaveSource iWaveSource; - private LineSpectrum iLineSpectrum; + //Audio + private AudioManager iAudioManager; //Network private NetworkManager iNetworkManager; @@ -232,9 +223,7 @@ } //CSCore - iMultiMediaDeviceEnumerator = new MMDeviceEnumerator(); - iMultiMediaDeviceEnumerator.RegisterEndpointNotificationCallback(this); - UpdateAudioDeviceAndMasterVolumeThreadSafe(); + CreateAudioManager(); //Network iNetworkManager = new NetworkManager(); @@ -291,6 +280,54 @@ } } + + private void CreateAudioManager() + { + iAudioManager = new AudioManager(); + iAudioManager.Open(OnDefaultMultiMediaDeviceChanged, OnVolumeNotification); + UpdateAudioDeviceAndMasterVolumeThreadSafe(); + } + + private void DestroyAudioManager() + { + if (iAudioManager != null) + { + iAudioManager.Close(); + iAudioManager = null; + } + } + + /// + /// + /// + /// + /// + public void OnDefaultMultiMediaDeviceChanged(object sender, DefaultDeviceChangedEventArgs aEvent) + { + if (aEvent.DataFlow == DataFlow.Render && aEvent.Role == Role.Multimedia) + { + ResetAudioManagerThreadSafe(); + } + } + + /// + /// + /// + private void ResetAudioManagerThreadSafe() + { + if (InvokeRequired) + { + //Not in the proper thread, invoke ourselves + BeginInvoke(new Action((sender) => { ResetAudioManagerThreadSafe(); }), this); + return; + } + + //Proper thread, go ahead + DestroyAudioManager(); + CreateAudioManager(); + + } + /// /// Called when our display is opened. /// @@ -511,7 +548,7 @@ /// Receive volume change notification and reflect changes on our slider. /// /// - public void OnVolumeNotificationThreadSafe(object sender, AudioEndpointVolumeCallbackEventArgs aEvent) + public void OnVolumeNotification(object sender, AudioEndpointVolumeCallbackEventArgs aEvent) { UpdateMasterVolumeThreadSafe(); } @@ -524,9 +561,9 @@ private void trackBarMasterVolume_Scroll(object sender, EventArgs e) { //Just like Windows Volume Mixer we unmute if the volume is adjusted - iAudioEndpointVolume.IsMuted = false; + iAudioManager.Volume.IsMuted = false; //Set volume level according to our volume slider new position - iAudioEndpointVolume.MasterVolumeLevelScalar = trackBarMasterVolume.Value/100.0f; + iAudioManager.Volume.MasterVolumeLevelScalar = trackBarMasterVolume.Value/100.0f; } @@ -537,54 +574,9 @@ /// private void checkBoxMute_CheckedChanged(object sender, EventArgs e) { - iAudioEndpointVolume.IsMuted = checkBoxMute.Checked; + iAudioManager.Volume.IsMuted = checkBoxMute.Checked; } - /// - /// Device State Changed - /// - public void OnDeviceStateChanged([MarshalAs(UnmanagedType.LPWStr)] string deviceId, - [MarshalAs(UnmanagedType.I4)] DeviceState newState) - { - } - - /// - /// Device Added - /// - public void OnDeviceAdded([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId) - { - } - - /// - /// Device Removed - /// - public void OnDeviceRemoved([MarshalAs(UnmanagedType.LPWStr)] string deviceId) - { - } - - /// - /// Default Device Changed - /// - public void OnDefaultDeviceChanged(DataFlow flow, Role role, - [MarshalAs(UnmanagedType.LPWStr)] string defaultDeviceId) - { - if (role == Role.Multimedia && flow == DataFlow.Render) - { - UpdateAudioDeviceAndMasterVolumeThreadSafe(); - } - } - - /// - /// Property Value Changed - /// - /// - /// - public void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, PropertyKey key) - { - } - - - /// /// Update master volume indicators based our current system states. @@ -592,19 +584,18 @@ /// private void UpdateMasterVolumeThreadSafe() { - if (this.InvokeRequired) + if (InvokeRequired) { //Not in the proper thread, invoke ourselves - PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateMasterVolumeThreadSafe); - this.Invoke(d, new object[] {}); + BeginInvoke(new Action((sender) => { UpdateMasterVolumeThreadSafe(); }), this); return; } //Update volume slider - float volumeLevelScalar = iAudioEndpointVolume.MasterVolumeLevelScalar; + float volumeLevelScalar = iAudioManager.Volume.MasterVolumeLevelScalar; trackBarMasterVolume.Value = Convert.ToInt32(volumeLevelScalar*100); //Update mute checkbox - checkBoxMute.Checked = iAudioEndpointVolume.IsMuted; + checkBoxMute.Checked = iAudioManager.Volume.IsMuted; //If our display connection is open we need to update its icons if (iDisplay.IsOpen()) @@ -646,83 +637,11 @@ } //Take care of our mute icon - iDisplay.SetIconOnOff(MiniDisplay.IconType.Mute, iAudioEndpointVolume.IsMuted); + iDisplay.SetIconOnOff(MiniDisplay.IconType.Mute, iAudioManager.Volume.IsMuted); } } - /// - /// - /// - private void StartAudioVisualization() - { - StopAudioVisualization(); - //Open the default device - iSoundIn = new WasapiLoopbackCapture(); - //Our loopback capture opens the default render device by default so the following is not needed - //iSoundIn.Device = MMDeviceEnumerator.DefaultAudioEndpoint(DataFlow.Render, Role.Console); - iSoundIn.Initialize(); - - SoundInSource soundInSource = new SoundInSource(iSoundIn); - ISampleSource source = soundInSource.ToSampleSource(); - - const FftSize fftSize = FftSize.Fft2048; - //create a spectrum provider which provides fft data based on some input - BasicSpectrumProvider spectrumProvider = new BasicSpectrumProvider(source.WaveFormat.Channels, source.WaveFormat.SampleRate, fftSize); - - //linespectrum and voiceprint3dspectrum used for rendering some fft data - //in oder to get some fft data, set the previously created spectrumprovider - iLineSpectrum = new LineSpectrum(fftSize) - { - SpectrumProvider = spectrumProvider, - UseAverage = false, - BarCount = 16, - BarSpacing = 1, - IsXLogScale = true, - ScalingStrategy = ScalingStrategy.Decibel - }; - - - //the SingleBlockNotificationStream is used to intercept the played samples - var notificationSource = new SingleBlockNotificationStream(source); - //pass the intercepted samples as input data to the spectrumprovider (which will calculate a fft based on them) - notificationSource.SingleBlockRead += (s, a) => spectrumProvider.Add(a.Left, a.Right); - - iWaveSource = notificationSource.ToWaveSource(16); - - - // We need to read from our source otherwise SingleBlockRead is never called and our spectrum provider is not populated - byte[] buffer = new byte[iWaveSource.WaveFormat.BytesPerSecond / 2]; - soundInSource.DataAvailable += (s, aEvent) => - { - int read; - while ((read = iWaveSource.Read(buffer, 0, buffer.Length)) > 0) ; - }; - - - //Start recording - iSoundIn.Start(); - } - - /// - /// - /// - private void StopAudioVisualization() - { - - if (iSoundIn != null) - { - iSoundIn.Stop(); - iSoundIn.Dispose(); - iSoundIn = null; - } - if (iWaveSource != null) - { - iWaveSource.Dispose(); - iWaveSource = null; - } - - } /// @@ -737,7 +656,7 @@ } // Update our math - if (!iLineSpectrum.Update()) + if (iAudioManager==null || !iAudioManager.Spectrum.Update()) { //Nothing changed no need to render return; @@ -755,7 +674,7 @@ if (ctrl is PictureBox) { PictureBox pb = (PictureBox)ctrl; - if (iLineSpectrum.Render(pb.Image, Color.Black, Color.Black, Color.White, false)) + if (iAudioManager.Spectrum.Render(pb.Image, Color.Black, Color.Black, Color.White, false)) { pb.Invalidate(); } @@ -769,35 +688,24 @@ /// /// private void UpdateAudioDeviceAndMasterVolumeThreadSafe() - { - if (this.InvokeRequired) + { + if (InvokeRequired) { //Not in the proper thread, invoke ourselves - PlainUpdateDelegate d = new PlainUpdateDelegate(UpdateAudioDeviceAndMasterVolumeThreadSafe); - this.Invoke(d, new object[] {}); + BeginInvoke(new Action((sender) => { UpdateAudioDeviceAndMasterVolumeThreadSafe(); }), this); return; } //We are in the correct thread just go ahead. try { - //Get our master volume - iMultiMediaDevice = iMultiMediaDeviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia); - iAudioEndpointVolume = AudioEndpointVolume.FromDevice(iMultiMediaDevice); - + //Update our label - labelDefaultAudioDevice.Text = iMultiMediaDevice.FriendlyName; + iLabelDefaultAudioDevice.Text = iAudioManager.DefaultDevice.FriendlyName; //Show our volume in our track bar UpdateMasterVolumeThreadSafe(); - //Register to get volume modifications - AudioEndpointVolumeCallback callback = new AudioEndpointVolumeCallback(); - callback.NotifyRecived += OnVolumeNotificationThreadSafe; - // Do we need to unregister? - iAudioEndpointVolume.RegisterControlChangeNotify(callback); - // - StartAudioVisualization(); // trackBarMasterVolume.Enabled = true; } @@ -1573,10 +1481,9 @@ private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { - //TODO: discard other CSCore audio objects - StopAudioVisualization(); iCecManager.Stop(); iNetworkManager.Dispose(); + DestroyAudioManager(); CloseDisplayConnection(); StopServer(); e.Cancel = iClosing; diff -r 7cd495550d5f -r 71ba0dd622a5 Server/SharpDisplayManager.csproj --- a/Server/SharpDisplayManager.csproj Fri Jan 06 18:27:19 2017 +0100 +++ b/Server/SharpDisplayManager.csproj Sat Jan 07 20:21:42 2017 +0100 @@ -174,6 +174,7 @@ +