From a0df126bcf4a0e9cf2497074e77c561dc9160703 Mon Sep 17 00:00:00 2001 From: Jeroen Saey Date: Mon, 1 Sep 2025 11:54:28 +0200 Subject: [PATCH 1/5] Fixed memory leak --- EonaCat.VolumeMixer.Tester/Program.cs | 8 + .../EonaCat.VolumeMixer.csproj | 6 +- .../Managers/VolumeMixerManager.cs | 138 ++++++++++++------ 3 files changed, 104 insertions(+), 48 deletions(-) diff --git a/EonaCat.VolumeMixer.Tester/Program.cs b/EonaCat.VolumeMixer.Tester/Program.cs index 8afeb67..5b61082 100644 --- a/EonaCat.VolumeMixer.Tester/Program.cs +++ b/EonaCat.VolumeMixer.Tester/Program.cs @@ -19,6 +19,14 @@ class Program var devices = await volumeMixer.GetAudioDevicesAsync(DataFlow.Output); Console.WriteLine($"Found {devices.Count} playback devices:"); + while (true) + { + var test = await volumeMixer.GetDefaultAudioDeviceAsync(DataFlow.Output); + devices = await volumeMixer.GetAudioDevicesAsync(DataFlow.Output); + devices = await volumeMixer.GetAudioDevicesAsync(DataFlow.Input); + await Task.Delay(1000); + } + foreach (var device in devices) { Console.WriteLine($"- {device.Name} (Default: {device.IsDefault})"); diff --git a/EonaCat.VolumeMixer/EonaCat.VolumeMixer.csproj b/EonaCat.VolumeMixer/EonaCat.VolumeMixer.csproj index 9298826..6180a9a 100644 --- a/EonaCat.VolumeMixer/EonaCat.VolumeMixer.csproj +++ b/EonaCat.VolumeMixer/EonaCat.VolumeMixer.csproj @@ -18,9 +18,9 @@ EonaCat, Audio, Volume, Mixer .NET Standard, Jeroen, Saey EonaCat VolumeMixer - 1.0.5 - 1.0.0.5 - 1.0.0.5 + 1.0.6 + 1.0.0.6 + 1.0.0.6 icon.png https://git.saey.me/EonaCat/EonaCat.VolumeMixer git diff --git a/EonaCat.VolumeMixer/Managers/VolumeMixerManager.cs b/EonaCat.VolumeMixer/Managers/VolumeMixerManager.cs index 7e8dadb..f51d5b2 100644 --- a/EonaCat.VolumeMixer/Managers/VolumeMixerManager.cs +++ b/EonaCat.VolumeMixer/Managers/VolumeMixerManager.cs @@ -49,9 +49,12 @@ namespace EonaCat.VolumeMixer.Managers lock (_syncLock) { + IMultiMediaDeviceCollection deviceCollection = null; + IMultiMediaDevice defaultDevice = null; + try { - var result = _deviceEnumerator.EnumAudioEndpoints(dataFlow, DeviceState.Active, out var deviceCollection); + var result = _deviceEnumerator.EnumAudioEndpoints(dataFlow, DeviceState.Active, out deviceCollection); if (result != 0 || deviceCollection == null) { return devices; @@ -60,18 +63,16 @@ namespace EonaCat.VolumeMixer.Managers result = deviceCollection.GetCount(out var count); if (result != 0) { - ComHelper.ReleaseComObject(deviceCollection); return devices; } string defaultId = ""; try { - result = _deviceEnumerator.GetDefaultAudioEndpoint(dataFlow, Role.Multimedia, out var defaultDevice); + result = _deviceEnumerator.GetDefaultAudioEndpoint(dataFlow, Role.Multimedia, out defaultDevice); if (result == 0 && defaultDevice != null) { defaultDevice.GetId(out defaultId); - ComHelper.ReleaseComObject(defaultDevice); } } catch @@ -81,9 +82,11 @@ namespace EonaCat.VolumeMixer.Managers for (uint i = 0; i < count; i++) { + IMultiMediaDevice device = null; + try { - result = deviceCollection.Item(i, out var device); + result = deviceCollection.Item(i, out device); if (result == 0 && device != null) { result = device.GetId(out var id); @@ -94,24 +97,37 @@ namespace EonaCat.VolumeMixer.Managers bool isDefault = id == defaultId; devices.Add(new AudioDevice(device, id, name, isDefault, dataFlow, type)); } - else - { - ComHelper.ReleaseComObject(device); - } } } catch { // Do nothing } + finally + { + if (device != null) + { + ComHelper.ReleaseComObject(device); + } + } } - - ComHelper.ReleaseComObject(deviceCollection); } catch { // Do nothing } + finally + { + if (deviceCollection != null) + { + ComHelper.ReleaseComObject(deviceCollection); + } + + if (defaultDevice != null) + { + ComHelper.ReleaseComObject(defaultDevice); + } + } } return devices; @@ -129,9 +145,11 @@ namespace EonaCat.VolumeMixer.Managers lock (_syncLock) { + IMultiMediaDevice device = null; + try { - var result = _deviceEnumerator.GetDefaultAudioEndpoint(dataFlow, Role.Multimedia, out var device); + var result = _deviceEnumerator.GetDefaultAudioEndpoint(dataFlow, Role.Multimedia, out device); if (result == 0 && device != null) { result = device.GetId(out var id); @@ -141,58 +159,81 @@ namespace EonaCat.VolumeMixer.Managers var type = GetDeviceType(device); return new AudioDevice(device, id, name, true, dataFlow, type); } - - ComHelper.ReleaseComObject(device); } } catch { // Do nothing } + finally + { + if (device != null) + { + ComHelper.ReleaseComObject(device); + } + } return null; } }); - } + } + + [DllImport("ole32.dll")] + private static extern int PropVariantClear(ref PropVariant pvar); + + private string GetDeviceName(IMultiMediaDevice device) + { + IPropertyStore? propertyStore = null; + PropVariant propVariant = new PropVariant(); + + try + { + var result = device.OpenPropertyStore(0, out propertyStore); + if (result == 0 && propertyStore != null) + { + var propertyKey = PKEY_Device_FriendlyName; + result = propertyStore.GetValue(ref propertyKey, out propVariant); + if (result == 0) + { + // Get string + string name = Marshal.PtrToStringUni(propVariant.data1); + return !string.IsNullOrEmpty(name) ? name : "Unknown Device"; + } + } + } + catch + { + // Do nothing + } + finally + { + // Clear memory + PropVariantClear(ref propVariant); + + if (propertyStore != null) + { + ComHelper.ReleaseComObject(propertyStore); + } + } + + return "Unknown Device"; + } - private string GetDeviceName(IMultiMediaDevice device) - { - try - { - var result = device.OpenPropertyStore(0, out var propertyStore); - if (result == 0 && propertyStore != null) - { - var propertyKey = PKEY_Device_FriendlyName; - result = propertyStore.GetValue(ref propertyKey, out var propVariant); - if (result == 0 && propVariant.data1 != IntPtr.Zero) - { - string name = Marshal.PtrToStringUni(propVariant.data1); - ComHelper.ReleaseComObject(propertyStore); - return !string.IsNullOrEmpty(name) ? name : "Unknown Device"; - } - - ComHelper.ReleaseComObject(propertyStore); - } - } - catch - { - // Do nothing - } - - return "Unknown Device"; - } private DeviceType GetDeviceType(IMultiMediaDevice device) { + IPropertyStore propertyStore = null; + PropVariant propVariant = new PropVariant(); + try { - int result = device.OpenPropertyStore(0, out var propertyStore); + int result = device.OpenPropertyStore(0, out propertyStore); if (result == 0 && propertyStore != null) { try { var propertyKey = PKEY_AudioEndpoint_FormFactor; - result = propertyStore.GetValue(ref propertyKey, out var propVariant); + result = propertyStore.GetValue(ref propertyKey, out propVariant); // 0x13 == VT_UI4 if (result == 0 && propVariant.vt == 0x13) @@ -227,7 +268,7 @@ namespace EonaCat.VolumeMixer.Managers } finally { - ComHelper.ReleaseComObject(propertyStore); + } } } @@ -235,7 +276,14 @@ namespace EonaCat.VolumeMixer.Managers { // Do nothing } - + finally + { + PropVariantClear(ref propVariant); + if (propertyStore != null) + { + ComHelper.ReleaseComObject(propertyStore); + } + } return DeviceType.Unknown; } From be67abaf64fc4086dac9ade2b29e8f0a7fb50d2a Mon Sep 17 00:00:00 2001 From: Jeroen Saey Date: Mon, 1 Sep 2025 11:54:50 +0200 Subject: [PATCH 2/5] Fixed memory leak --- EonaCat.VolumeMixer.Tester/Program.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/EonaCat.VolumeMixer.Tester/Program.cs b/EonaCat.VolumeMixer.Tester/Program.cs index 5b61082..8afeb67 100644 --- a/EonaCat.VolumeMixer.Tester/Program.cs +++ b/EonaCat.VolumeMixer.Tester/Program.cs @@ -19,14 +19,6 @@ class Program var devices = await volumeMixer.GetAudioDevicesAsync(DataFlow.Output); Console.WriteLine($"Found {devices.Count} playback devices:"); - while (true) - { - var test = await volumeMixer.GetDefaultAudioDeviceAsync(DataFlow.Output); - devices = await volumeMixer.GetAudioDevicesAsync(DataFlow.Output); - devices = await volumeMixer.GetAudioDevicesAsync(DataFlow.Input); - await Task.Delay(1000); - } - foreach (var device in devices) { Console.WriteLine($"- {device.Name} (Default: {device.IsDefault})"); From 6f639622a51bcfca7ca8a6a34fc2800a6ddf1a31 Mon Sep 17 00:00:00 2001 From: Jeroen Saey Date: Mon, 1 Sep 2025 16:00:13 +0200 Subject: [PATCH 3/5] Fixed com disposal --- EonaCat.VolumeMixer.Tester/Program.cs | 23 + EonaCat.VolumeMixer/Helpers/ComHelper.cs | 25 +- .../Managers/VolumeMixerManager.cs | 851 ++++++++---------- EonaCat.VolumeMixer/Models/AudioSession.cs | 10 +- 4 files changed, 404 insertions(+), 505 deletions(-) diff --git a/EonaCat.VolumeMixer.Tester/Program.cs b/EonaCat.VolumeMixer.Tester/Program.cs index 8afeb67..527ed7c 100644 --- a/EonaCat.VolumeMixer.Tester/Program.cs +++ b/EonaCat.VolumeMixer.Tester/Program.cs @@ -15,6 +15,29 @@ class Program { try { + while (true) + { + var input = await volumeMixer.GetAudioDevicesAsync(DataFlow.Input); + var output = await volumeMixer.GetAudioDevicesAsync(DataFlow.Output); + var input2 = await volumeMixer.GetMicrophonesAsync(); + + foreach (var item in input) + { + item.Dispose(); + } + + foreach (var item in input2) + { + item.Dispose(); + } + + foreach (var item in output) + { + item.Dispose(); + } + } + + // Get all audio PLAYBACK devices var devices = await volumeMixer.GetAudioDevicesAsync(DataFlow.Output); Console.WriteLine($"Found {devices.Count} playback devices:"); diff --git a/EonaCat.VolumeMixer/Helpers/ComHelper.cs b/EonaCat.VolumeMixer/Helpers/ComHelper.cs index 46cde05..37632d2 100644 --- a/EonaCat.VolumeMixer/Helpers/ComHelper.cs +++ b/EonaCat.VolumeMixer/Helpers/ComHelper.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using System.Threading.Tasks; namespace EonaCat.VolumeMixer.Helpers { @@ -29,14 +30,24 @@ namespace EonaCat.VolumeMixer.Helpers Marshal.Release(ptr); } } + } + + public static async Task ReleaseComObject(object obj) + { + if (obj != null && Marshal.IsComObject(obj)) + { + if (obj == null || !Marshal.IsComObject(obj)) + { + return; + } + + Marshal.ReleaseComObject(obj); + obj = null; + } + + GC.Collect(); + GC.WaitForPendingFinalizers(); } - public static void ReleaseComObject(object obj) - { - if (obj != null && Marshal.IsComObject(obj)) - { - Marshal.ReleaseComObject(obj); - } - } } } \ No newline at end of file diff --git a/EonaCat.VolumeMixer/Managers/VolumeMixerManager.cs b/EonaCat.VolumeMixer/Managers/VolumeMixerManager.cs index f51d5b2..486ae38 100644 --- a/EonaCat.VolumeMixer/Managers/VolumeMixerManager.cs +++ b/EonaCat.VolumeMixer/Managers/VolumeMixerManager.cs @@ -1,181 +1,158 @@ -using EonaCat.VolumeMixer.Helpers; -using EonaCat.VolumeMixer.Interfaces; -using EonaCat.VolumeMixer.Models; -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Threading.Tasks; - -namespace EonaCat.VolumeMixer.Managers -{ - // This file is part of the EonaCat project(s) which is released under the Apache License. - // See the LICENSE file or go to https://EonaCat.com/License for full license details. - public class VolumeMixerManager : IDisposable - { - private const int VT_UI4 = 0x13; - private readonly IMultiMediaDeviceEnumerator _deviceEnumerator; - private bool _isDisposed = false; - private readonly object _syncLock = new(); - - private static readonly PropertyKey PKEY_Device_FriendlyName = new PropertyKey(new Guid("a45c254e-df1c-4efd-8020-67d146a850e0"), 14); - private static readonly PropertyKey PKEY_AudioEndpoint_FormFactor = new PropertyKey - { - Fmtid = new Guid("1DA5D803-D492-4EDD-8C23-E0C0FFEE7F0E"), - Pid = 0 - }; - - public VolumeMixerManager() - { - try - { - _deviceEnumerator = (IMultiMediaDeviceEnumerator)new MMDeviceEnumerator(); - } - catch (Exception ex) - { - throw new InvalidOperationException("Failed to initialize audio device enumerator. Make sure you're running on Windows Vista or later.", ex); - } - } - - public async Task> GetAudioDevicesAsync(DataFlow dataFlow = DataFlow.Output) - { - return await Task.Run(() => - { - var devices = new List(); - - if (_deviceEnumerator == null) - { - return devices; - } - - lock (_syncLock) - { - IMultiMediaDeviceCollection deviceCollection = null; - IMultiMediaDevice defaultDevice = null; +using EonaCat.VolumeMixer.Helpers; +using EonaCat.VolumeMixer.Interfaces; +using EonaCat.VolumeMixer.Models; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading.Tasks; - try - { - var result = _deviceEnumerator.EnumAudioEndpoints(dataFlow, DeviceState.Active, out deviceCollection); - if (result != 0 || deviceCollection == null) - { - return devices; - } - - result = deviceCollection.GetCount(out var count); - if (result != 0) - { - return devices; - } - - string defaultId = ""; - try - { - result = _deviceEnumerator.GetDefaultAudioEndpoint(dataFlow, Role.Multimedia, out defaultDevice); - if (result == 0 && defaultDevice != null) - { - defaultDevice.GetId(out defaultId); - } - } - catch - { - defaultId = ""; - } - - for (uint i = 0; i < count; i++) - { - IMultiMediaDevice device = null; - - try - { - result = deviceCollection.Item(i, out device); - if (result == 0 && device != null) - { - result = device.GetId(out var id); - if (result == 0 && !string.IsNullOrEmpty(id)) - { - var name = GetDeviceName(device); - var type = GetDeviceType(device); - bool isDefault = id == defaultId; - devices.Add(new AudioDevice(device, id, name, isDefault, dataFlow, type)); - } - } - } - catch - { - // Do nothing - } - finally - { - if (device != null) - { - ComHelper.ReleaseComObject(device); - } - } - } - } - catch - { - // Do nothing - } - finally - { - if (deviceCollection != null) - { - ComHelper.ReleaseComObject(deviceCollection); - } - - if (defaultDevice != null) - { - ComHelper.ReleaseComObject(defaultDevice); - } - } - } - - return devices; - }); - } - - public async Task GetDefaultAudioDeviceAsync(DataFlow dataFlow = DataFlow.Output) - { - return await Task.Run(() => - { - if (_deviceEnumerator == null) - { - return null; - } - - lock (_syncLock) - { - IMultiMediaDevice device = null; - - try - { - var result = _deviceEnumerator.GetDefaultAudioEndpoint(dataFlow, Role.Multimedia, out device); - if (result == 0 && device != null) - { - result = device.GetId(out var id); - if (result == 0 && !string.IsNullOrEmpty(id)) - { - var name = GetDeviceName(device); - var type = GetDeviceType(device); - return new AudioDevice(device, id, name, true, dataFlow, type); - } - } - } - catch - { - // Do nothing - } - finally +namespace EonaCat.VolumeMixer.Managers +{ + // This file is part of the EonaCat project(s) which is released under the Apache License. + // See the LICENSE file or go to https://EonaCat.com/License for full license details. + public class VolumeMixerManager : IDisposable + { + private const int VT_UI4 = 0x13; + private readonly IMultiMediaDeviceEnumerator _deviceEnumerator; + private bool _isDisposed = false; + private readonly object _syncLock = new(); + + private static readonly PropertyKey PKEY_Device_FriendlyName = new PropertyKey(new Guid("a45c254e-df1c-4efd-8020-67d146a850e0"), 14); + private static readonly PropertyKey PKEY_AudioEndpoint_FormFactor = new PropertyKey + { + Fmtid = new Guid("1DA5D803-D492-4EDD-8C23-E0C0FFEE7F0E"), + Pid = 0 + }; + + public VolumeMixerManager() + { + try + { + _deviceEnumerator = (IMultiMediaDeviceEnumerator)new MMDeviceEnumerator(); + } + catch (Exception ex) + { + throw new InvalidOperationException("Failed to initialize audio device enumerator. Make sure you're running on Windows Vista or later.", ex); + } + } + + public Task> GetAudioDevicesAsync(DataFlow dataFlow = DataFlow.Output) + { + var devices = new List(); + + if (_deviceEnumerator == null) + { + return Task.FromResult(devices); + } + + lock (_syncLock) + { + IMultiMediaDeviceCollection deviceCollection = null; + IMultiMediaDevice defaultDevice = null; + + try + { + var result = _deviceEnumerator.EnumAudioEndpoints(dataFlow, DeviceState.Active, out deviceCollection); + if (result != 0 || deviceCollection == null) { - if (device != null) + return Task.FromResult(devices); + } + + result = deviceCollection.GetCount(out var count); + if (result != 0) + { + return Task.FromResult(devices); + } + + string defaultId = ""; + try + { + result = _deviceEnumerator.GetDefaultAudioEndpoint(dataFlow, Role.Multimedia, out defaultDevice); + if (result == 0 && defaultDevice != null) + { + defaultDevice.GetId(out defaultId); + } + } + catch + { + defaultId = ""; + } + + for (uint i = 0; i < count; i++) + { + IMultiMediaDevice device = null; + try + { + result = deviceCollection.Item(i, out device); + if (result == 0 && device != null) + { + result = device.GetId(out var id); + if (result == 0 && !string.IsNullOrEmpty(id)) + { + var name = GetDeviceName(device); + var type = GetDeviceType(device); + bool isDefault = id == defaultId; + + devices.Add(new AudioDevice(device, id, name, isDefault, dataFlow, type)); + device = null; + } + } + } + catch + { + // Do nothing + } + finally { ComHelper.ReleaseComObject(device); } - } - - return null; - } - }); + } + } + finally + { + ComHelper.ReleaseComObject(deviceCollection); + ComHelper.ReleaseComObject(defaultDevice); + } + } + + return Task.FromResult(devices); + } + + public Task GetDefaultAudioDeviceAsync(DataFlow dataFlow = DataFlow.Output) + { + if (_deviceEnumerator == null) + { + return Task.FromResult(null); + } + + lock (_syncLock) + { + IMultiMediaDevice device = null; + + try + { + var result = _deviceEnumerator.GetDefaultAudioEndpoint(dataFlow, Role.Multimedia, out device); + if (result == 0 && device != null) + { + result = device.GetId(out var id); + if (result == 0 && !string.IsNullOrEmpty(id)) + { + var name = GetDeviceName(device); + var type = GetDeviceType(device); + + var audioDevice = new AudioDevice(device, id, name, true, dataFlow, type); + device = null; // ownership transferred + return Task.FromResult(audioDevice); + } + } + } + finally + { + ComHelper.ReleaseComObject(device); + } + + return Task.FromResult(null); + } } [DllImport("ole32.dll")] @@ -183,7 +160,7 @@ namespace EonaCat.VolumeMixer.Managers private string GetDeviceName(IMultiMediaDevice device) { - IPropertyStore? propertyStore = null; + IPropertyStore propertyStore = null; PropVariant propVariant = new PropVariant(); try @@ -193,339 +170,229 @@ namespace EonaCat.VolumeMixer.Managers { var propertyKey = PKEY_Device_FriendlyName; result = propertyStore.GetValue(ref propertyKey, out propVariant); - if (result == 0) + if (result == 0 && + (propVariant.vt == (ushort)VarEnum.VT_LPWSTR || propVariant.vt == (ushort)VarEnum.VT_BSTR)) { - // Get string string name = Marshal.PtrToStringUni(propVariant.data1); return !string.IsNullOrEmpty(name) ? name : "Unknown Device"; } } } - catch - { - // Do nothing - } finally { - // Clear memory PropVariantClear(ref propVariant); - - if (propertyStore != null) - { - ComHelper.ReleaseComObject(propertyStore); - } + ComHelper.ReleaseComObject(propertyStore); } return "Unknown Device"; } - - - private DeviceType GetDeviceType(IMultiMediaDevice device) - { + + private DeviceType GetDeviceType(IMultiMediaDevice device) + { IPropertyStore propertyStore = null; - PropVariant propVariant = new PropVariant(); - - try - { - int result = device.OpenPropertyStore(0, out propertyStore); - if (result == 0 && propertyStore != null) - { - try - { - var propertyKey = PKEY_AudioEndpoint_FormFactor; - result = propertyStore.GetValue(ref propertyKey, out propVariant); - - // 0x13 == VT_UI4 - if (result == 0 && propVariant.vt == 0x13) - { - int formFactor = propVariant.data1.ToInt32(); - - return formFactor switch - { - 0 => DeviceType.Unknown, - 1 => DeviceType.Speakers, - 2 => DeviceType.LineLevel, - 3 => DeviceType.Headphones, - 4 => DeviceType.Microphone, - 5 => DeviceType.Headset, - 6 => DeviceType.Handset, - 7 => DeviceType.UnknownDigitalPassthrough, - 8 => DeviceType.SPDIF, - 9 => DeviceType.DigitalAudioDisplayDevice, - 10 => DeviceType.UnknownFormFactor, - 11 => DeviceType.FMRadio, - 12 => DeviceType.VideoPhone, - 13 => DeviceType.RCA, - 14 => DeviceType.Bluetooth, - 15 => DeviceType.SPDIFOut, - 16 => DeviceType.HDMI, - 17 => DeviceType.DisplayAudio, - 18 => DeviceType.UnknownFormFactor2, - 19 => DeviceType.Other, - _ => DeviceType.Unknown, - }; - } - } - finally - { - - } - } - } - catch - { - // Do nothing - } - finally + PropVariant propVariant = new PropVariant(); + + try + { + int result = device.OpenPropertyStore(0, out propertyStore); + if (result == 0 && propertyStore != null) + { + var propertyKey = PKEY_AudioEndpoint_FormFactor; + result = propertyStore.GetValue(ref propertyKey, out propVariant); + + if (result == 0 && propVariant.vt == VT_UI4) + { + int formFactor = propVariant.data1.ToInt32(); + + return formFactor switch + { + 0 => DeviceType.Unknown, + 1 => DeviceType.Speakers, + 2 => DeviceType.LineLevel, + 3 => DeviceType.Headphones, + 4 => DeviceType.Microphone, + 5 => DeviceType.Headset, + 6 => DeviceType.Handset, + 7 => DeviceType.UnknownDigitalPassthrough, + 8 => DeviceType.SPDIF, + 9 => DeviceType.DigitalAudioDisplayDevice, + 10 => DeviceType.UnknownFormFactor, + 11 => DeviceType.FMRadio, + 12 => DeviceType.VideoPhone, + 13 => DeviceType.RCA, + 14 => DeviceType.Bluetooth, + 15 => DeviceType.SPDIFOut, + 16 => DeviceType.HDMI, + 17 => DeviceType.DisplayAudio, + 18 => DeviceType.UnknownFormFactor2, + 19 => DeviceType.Other, + _ => DeviceType.Unknown, + }; + } + } + } + finally { PropVariantClear(ref propVariant); - if (propertyStore != null) + ComHelper.ReleaseComObject(propertyStore); + } + + return DeviceType.Unknown; + } + + public async Task SetSystemVolumeAsync(float volume) + { + if (volume < 0f || volume > 1f) + { + return false; + } + + using var defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output); + if (defaultDevice == null) + { + return false; + } + + return await defaultDevice.SetMasterVolumeAsync(volume); + } + + public async Task GetSystemVolumeAsync() + { + using var defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output); + return defaultDevice == null ? 0f : await defaultDevice.GetMasterVolumeAsync(); + } + + public async Task SetSystemMuteAsync(bool mute) + { + using var defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output); + return defaultDevice != null && await defaultDevice.SetMasterMuteAsync(mute); + } + + public async Task GetSystemMuteAsync() + { + using var defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output); + return defaultDevice != null && await defaultDevice.GetMasterMuteAsync(); + } + + public async Task SetMicrophoneVolumeAsync(float volume) + { + if (volume < 0f || volume > 1f) + { + return false; + } + + using var defaultMic = await GetDefaultAudioDeviceAsync(DataFlow.Input); + return defaultMic != null && await defaultMic.SetMasterVolumeAsync(volume); + } + + public async Task GetMicrophoneVolumeAsync() + { + using var defaultMic = await GetDefaultAudioDeviceAsync(DataFlow.Input); + return defaultMic == null ? 0f : await defaultMic.GetMasterVolumeAsync(); + } + + public async Task SetMicrophoneMuteAsync(bool mute) + { + using var defaultMic = await GetDefaultAudioDeviceAsync(DataFlow.Input); + return defaultMic != null && await defaultMic.SetMasterMuteAsync(mute); + } + + public async Task GetMicrophoneMuteAsync() + { + using var defaultMic = await GetDefaultAudioDeviceAsync(DataFlow.Input); + return defaultMic != null && await defaultMic.GetMasterMuteAsync(); + } + + public async Task SetMicrophoneVolumeByNameAsync(string microphoneName, float volume) + { + if (string.IsNullOrWhiteSpace(microphoneName) || volume < 0f || volume > 1f) + { + return false; + } + + List microphones = null; + + try + { + microphones = await GetAudioDevicesAsync(DataFlow.Input); + + foreach (var mic in microphones) { - ComHelper.ReleaseComObject(propertyStore); + using (mic) + { + if (mic.Name.IndexOf(microphoneName, StringComparison.OrdinalIgnoreCase) >= 0) + { + return await mic.SetMasterVolumeAsync(volume); + } + } } - } - return DeviceType.Unknown; - } - - - - public async Task SetSystemVolumeAsync(float volume) - { - if (volume < 0f || volume > 1f) - { - return false; - } - - AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output); - - if (defaultDevice == null) - { - return false; - } - - try - { - return await defaultDevice.SetMasterVolumeAsync(volume); - } - finally - { - defaultDevice.Dispose(); - } - } - - public async Task GetSystemVolumeAsync() - { - AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output); - - if (defaultDevice == null) - { - return 0f; - } - - try - { - return await defaultDevice.GetMasterVolumeAsync(); - } - finally - { - defaultDevice.Dispose(); - } - } - - public async Task SetSystemMuteAsync(bool mute) - { - AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output); - - if (defaultDevice == null) - { - return false; - } - - try - { - return await defaultDevice.SetMasterMuteAsync(mute); - } - finally - { - defaultDevice.Dispose(); - } - } - - public async Task GetSystemMuteAsync() - { - AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output); - - if (defaultDevice == null) - { - return false; - } - - try - { - return await defaultDevice.GetMasterMuteAsync(); - } - finally - { - defaultDevice.Dispose(); - } - } - - public async Task SetMicrophoneVolumeAsync(float volume) - { - if (volume < 0f || volume > 1f) - { - return false; - } - - AudioDevice defaultMic = await GetDefaultAudioDeviceAsync(DataFlow.Input); - - if (defaultMic == null) - { - return false; - } - - try - { - return await defaultMic.SetMasterVolumeAsync(volume); - } - finally - { - defaultMic.Dispose(); - } - } - - public async Task GetMicrophoneVolumeAsync() - { - AudioDevice defaultMic = await GetDefaultAudioDeviceAsync(DataFlow.Input); - - if (defaultMic == null) - { - return 0f; - } - - try - { - return await defaultMic.GetMasterVolumeAsync(); - } - finally - { - defaultMic.Dispose(); - } - } - - public async Task SetMicrophoneMuteAsync(bool mute) - { - AudioDevice defaultMic = await GetDefaultAudioDeviceAsync(DataFlow.Input); - - if (defaultMic == null) - { - return false; - } - - try - { - return await defaultMic.SetMasterMuteAsync(mute); - } - finally - { - defaultMic.Dispose(); - } - } - - public async Task GetMicrophoneMuteAsync() - { - AudioDevice defaultMic = await GetDefaultAudioDeviceAsync(DataFlow.Input); - - if (defaultMic == null) - { - return false; - } - - try - { - return await defaultMic.GetMasterMuteAsync(); - } - finally - { - defaultMic.Dispose(); - } - } - - public async Task SetMicrophoneVolumeByNameAsync(string microphoneName, float volume) - { - if (string.IsNullOrWhiteSpace(microphoneName) || volume < 0f || volume > 1f) - { - return false; - } - - List microphones = await GetAudioDevicesAsync(DataFlow.Input); - - foreach (var mic in microphones) - { - try - { - if (mic.Name.IndexOf(microphoneName, StringComparison.OrdinalIgnoreCase) >= 0) - { - return await mic.SetMasterVolumeAsync(volume); - } - } - finally - { - mic.Dispose(); - } - } - - return false; - } - - public async Task> GetAllActiveSessionsAsync() - { - return await Task.Run(async () => - { - var allSessions = new List(); - - List devices = await GetAudioDevicesAsync(DataFlow.Output); - - foreach (var device in devices) - { - try - { - allSessions.AddRange(await device.GetAudioSessionsAsync()); - } - catch - { - // Do nothing - } - finally - { - device.Dispose(); - } - } - - return allSessions; - }); - } - - public async Task> GetMicrophonesAsync() - { - return await GetAudioDevicesAsync(DataFlow.Input); - } - - public async Task GetDefaultMicrophoneAsync() - { - return await GetDefaultAudioDeviceAsync(DataFlow.Input); - } - - public void Dispose() - { - lock (_syncLock) - { - if (!_isDisposed) - { - ComHelper.ReleaseComObject(_deviceEnumerator); - _isDisposed = true; - } - } - } - } -} + } + finally + { + // Release all resources + if (microphones != null) + { + foreach (var mic in microphones) + { + mic.Dispose(); + } + } + } + + return false; + } + + public async Task> GetAllActiveSessionsAsync() + { + var allSessions = new List(); + List devices = await GetAudioDevicesAsync(DataFlow.Output); + + foreach (var device in devices) + { + using (device) + { + try + { + allSessions.AddRange(await device.GetAudioSessionsAsync()); + } + catch + { + // Do nothing + } + } + } + + // Release resources + foreach (var device in devices) + { + device.Dispose(); + } + devices.Clear(); + devices = null; + + return allSessions; + } + + public Task> GetMicrophonesAsync() + { + return GetAudioDevicesAsync(DataFlow.Input); + } + + public Task GetDefaultMicrophoneAsync() + { + return GetDefaultAudioDeviceAsync(DataFlow.Input); + } + + public void Dispose() + { + lock (_syncLock) + { + if (!_isDisposed) + { + ComHelper.ReleaseComObject(_deviceEnumerator); + _isDisposed = true; + } + } + } + } +} diff --git a/EonaCat.VolumeMixer/Models/AudioSession.cs b/EonaCat.VolumeMixer/Models/AudioSession.cs index b2d9b82..c617531 100644 --- a/EonaCat.VolumeMixer/Models/AudioSession.cs +++ b/EonaCat.VolumeMixer/Models/AudioSession.cs @@ -258,13 +258,11 @@ namespace EonaCat.VolumeMixer.Models return; } - if (_audioVolume != null) - { - Marshal.ReleaseComObject(_audioVolume); - _audioVolume = null; - } - ComHelper.ReleaseComObject(_sessionControl); + Marshal.ReleaseComObject(_audioVolume); + _audioVolume = null; + ComHelper.ReleaseComObject(_sessionControl); + _isDisposed = true; } } From 28087b7730bced1f87feffc970b55b5e1ea96423 Mon Sep 17 00:00:00 2001 From: Jeroen Saey Date: Mon, 1 Sep 2025 16:00:55 +0200 Subject: [PATCH 4/5] upped version --- EonaCat.VolumeMixer/EonaCat.VolumeMixer.csproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/EonaCat.VolumeMixer/EonaCat.VolumeMixer.csproj b/EonaCat.VolumeMixer/EonaCat.VolumeMixer.csproj index 6180a9a..fd7d7a8 100644 --- a/EonaCat.VolumeMixer/EonaCat.VolumeMixer.csproj +++ b/EonaCat.VolumeMixer/EonaCat.VolumeMixer.csproj @@ -18,9 +18,9 @@ EonaCat, Audio, Volume, Mixer .NET Standard, Jeroen, Saey EonaCat VolumeMixer - 1.0.6 - 1.0.0.6 - 1.0.0.6 + 1.0.7 + 1.0.0.7 + 1.0.0.7 icon.png https://git.saey.me/EonaCat/EonaCat.VolumeMixer git From 145dcb6148881e7801e6ab54ff7e7c593a6dc1dc Mon Sep 17 00:00:00 2001 From: Jeroen Saey Date: Mon, 1 Sep 2025 16:02:37 +0200 Subject: [PATCH 5/5] Removed test code for disposal --- EonaCat.VolumeMixer.Tester/Program.cs | 355 ++++++++++++-------------- 1 file changed, 166 insertions(+), 189 deletions(-) diff --git a/EonaCat.VolumeMixer.Tester/Program.cs b/EonaCat.VolumeMixer.Tester/Program.cs index 527ed7c..a33a924 100644 --- a/EonaCat.VolumeMixer.Tester/Program.cs +++ b/EonaCat.VolumeMixer.Tester/Program.cs @@ -1,199 +1,176 @@ -using EonaCat.VolumeMixer.Managers; -using EonaCat.VolumeMixer.Models; -using System; -using System.Threading.Tasks; - -class Program -{ - // This file is part of the EonaCat project(s) which is released under the Apache License. - // See the LICENSE file or go to https://EonaCat.com/License for full license details. - [STAThread] - static async Task Main() - { - // Playback management example - using (var volumeMixer = new VolumeMixerManager()) - { - try - { - while (true) +using EonaCat.VolumeMixer.Managers; +using EonaCat.VolumeMixer.Models; +using System; +using System.Threading.Tasks; + +class Program +{ + // This file is part of the EonaCat project(s) which is released under the Apache License. + // See the LICENSE file or go to https://EonaCat.com/License for full license details. + [STAThread] + static async Task Main() + { + // Playback management example + using (var volumeMixer = new VolumeMixerManager()) + { + try + { + // Get all audio PLAYBACK devices + var devices = await volumeMixer.GetAudioDevicesAsync(DataFlow.Output); + Console.WriteLine($"Found {devices.Count} playback devices:"); + + foreach (var device in devices) { - var input = await volumeMixer.GetAudioDevicesAsync(DataFlow.Input); - var output = await volumeMixer.GetAudioDevicesAsync(DataFlow.Output); - var input2 = await volumeMixer.GetMicrophonesAsync(); + Console.WriteLine($"- {device.Name} (Default: {device.IsDefault})"); + Console.WriteLine($" Volume: {await device.GetMasterVolumeAsync():P0}"); + Console.WriteLine($" Muted: {await device.GetMasterMuteAsync()}"); + device.Dispose(); + } - foreach (var item in input) + // Default playback device + using (var defaultDevice = await volumeMixer.GetDefaultAudioDeviceAsync(DataFlow.Output)) + { + if (defaultDevice != null) { - item.Dispose(); + Console.WriteLine($"\nDefault playback device: {defaultDevice.Name}"); + + // Get all audio sessions + var sessions = await defaultDevice.GetAudioSessionsAsync(); + Console.WriteLine($"Active sessions: {sessions.Count}"); + + foreach (var session in sessions) + { + Console.WriteLine($"- {session.DisplayName} ({await session.GetProcessNameAsync()})"); + + if ((await session.GetProcessNameAsync()).Equals("msedge", StringComparison.OrdinalIgnoreCase)) + { + Console.WriteLine($" Current Volume: {await session.GetVolumeAsync():P0}"); + await session.SetVolumeAsync(1f).ConfigureAwait(false); + Console.WriteLine($" Set to Volume: {await session.GetVolumeAsync():P0}"); + } + else + { + Console.WriteLine($" Volume: {await session.GetVolumeAsync():P0}"); + } + Console.WriteLine($" Muted: {await session.GetMuteAsync()}"); + session.Dispose(); + } } - foreach (var item in input2) + // Example Set volume of default device + if (defaultDevice != null) { - item.Dispose(); + // Unmute the device if it is muted + if (await defaultDevice.GetMasterMuteAsync()) + { + Console.WriteLine("Unmuting default playback device..."); + await defaultDevice.SetMasterMuteAsync(false); + } + + Console.WriteLine($"\nSetting default playback device volume to 1%"); + bool success = await defaultDevice.SetMasterVolumeAsync(0.1f); + + // Log the current volume + Console.WriteLine($"Current volume: {await defaultDevice.GetMasterVolumeAsync():P0}"); + + for (int i = 0; i < 50; i++) + { + if (await defaultDevice.GetMasterVolumeAsync() >= 1f) + { + break; + } + + // Increment volume by 2% each step + success = await defaultDevice.StepUpAsync(); + Console.WriteLine($"Current step volume: {await defaultDevice.GetMasterVolumeAsync():P0}"); + } + + if (success) + { + Console.WriteLine("Volume increased by 95% successfully."); + } + else + { + Console.WriteLine("Failed to increase volume."); + } + + // Toggle mute + // bool currentMute = await defaultDevice.GetMasterMuteAsync(); + // success = await defaultDevice.SetMasterMuteAsync(!currentMute); + // Console.WriteLine($"Mute toggled: {success}"); + } + else + { + Console.WriteLine("No default playback device found"); + } + } + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + } + + try + { + // Get all microphones + var microphones = await volumeMixer.GetMicrophonesAsync(); + Console.WriteLine($"Found {microphones.Count} microphones:"); + + foreach (var mic in microphones) + { + Console.WriteLine($"- {mic.Name} (Default: {mic.IsDefault})"); + Console.WriteLine($" Volume: {await mic.GetMasterVolumeAsync():P0}"); + Console.WriteLine($" Muted: {await mic.GetMasterMuteAsync()}"); + mic.Dispose(); + } + + // Default microphone + using (var defaultMic = await volumeMixer.GetDefaultMicrophoneAsync()) + { + Console.WriteLine($"\nSetting default microphone volume to 1%"); + bool success = await defaultMic.SetMasterVolumeAsync(0.1f); + + // Log the current volume + Console.WriteLine($"Current volume: {await defaultMic.GetMasterVolumeAsync():P0}"); + + for (int i = 0; i < 50; i++) + { + if (await defaultMic.GetMasterVolumeAsync() >= 1f) + { + break; + } + + // Increment volume by 2% each step + success = await defaultMic.StepUpAsync(); + Console.WriteLine($"Current step volume: {await defaultMic.GetMasterVolumeAsync():P0}"); } - foreach (var item in output) + if (success) { - item.Dispose(); + Console.WriteLine("Volume increased by 95% successfully."); } - } - - - // Get all audio PLAYBACK devices - var devices = await volumeMixer.GetAudioDevicesAsync(DataFlow.Output); - Console.WriteLine($"Found {devices.Count} playback devices:"); - - foreach (var device in devices) - { - Console.WriteLine($"- {device.Name} (Default: {device.IsDefault})"); - Console.WriteLine($" Volume: {await device.GetMasterVolumeAsync():P0}"); - Console.WriteLine($" Muted: {await device.GetMasterMuteAsync()}"); - device.Dispose(); - } - - // Default playback device - using (var defaultDevice = await volumeMixer.GetDefaultAudioDeviceAsync(DataFlow.Output)) - { - if (defaultDevice != null) - { - Console.WriteLine($"\nDefault playback device: {defaultDevice.Name}"); - - // Get all audio sessions - var sessions = await defaultDevice.GetAudioSessionsAsync(); - Console.WriteLine($"Active sessions: {sessions.Count}"); - - foreach (var session in sessions) - { - Console.WriteLine($"- {session.DisplayName} ({await session.GetProcessNameAsync()})"); - - if ((await session.GetProcessNameAsync()).Equals("msedge", StringComparison.OrdinalIgnoreCase)) - { - Console.WriteLine($" Current Volume: {await session.GetVolumeAsync():P0}"); - await session.SetVolumeAsync(1f).ConfigureAwait(false); - Console.WriteLine($" Set to Volume: {await session.GetVolumeAsync():P0}"); - } - else - { - Console.WriteLine($" Volume: {await session.GetVolumeAsync():P0}"); - } - Console.WriteLine($" Muted: {await session.GetMuteAsync()}"); - session.Dispose(); - } - } - - // Example Set volume of default device - if (defaultDevice != null) - { - // Unmute the device if it is muted - if (await defaultDevice.GetMasterMuteAsync()) - { - Console.WriteLine("Unmuting default playback device..."); - await defaultDevice.SetMasterMuteAsync(false); - } - - Console.WriteLine($"\nSetting default playback device volume to 1%"); - bool success = await defaultDevice.SetMasterVolumeAsync(0.1f); - - // Log the current volume - Console.WriteLine($"Current volume: {await defaultDevice.GetMasterVolumeAsync():P0}"); - - for (int i = 0; i < 50; i++) - { - if (await defaultDevice.GetMasterVolumeAsync() >= 1f) - { - break; - } - - // Increment volume by 2% each step - success = await defaultDevice.StepUpAsync(); - Console.WriteLine($"Current step volume: {await defaultDevice.GetMasterVolumeAsync():P0}"); - } - - if (success) - { - Console.WriteLine("Volume increased by 95% successfully."); - } - else - { - Console.WriteLine("Failed to increase volume."); - } - - // Toggle mute - // bool currentMute = await defaultDevice.GetMasterMuteAsync(); - // success = await defaultDevice.SetMasterMuteAsync(!currentMute); - // Console.WriteLine($"Mute toggled: {success}"); - } - else - { - Console.WriteLine("No default playback device found"); - } - } - } - catch (Exception ex) - { - Console.WriteLine($"Error: {ex.Message}"); - } - - try - { - // Get all microphones - var microphones = await volumeMixer.GetMicrophonesAsync(); - Console.WriteLine($"Found {microphones.Count} microphones:"); - - foreach (var mic in microphones) - { - Console.WriteLine($"- {mic.Name} (Default: {mic.IsDefault})"); - Console.WriteLine($" Volume: {await mic.GetMasterVolumeAsync():P0}"); - Console.WriteLine($" Muted: {await mic.GetMasterMuteAsync()}"); - mic.Dispose(); - } - - // Default microphone - using (var defaultMic = await volumeMixer.GetDefaultMicrophoneAsync()) - { - Console.WriteLine($"\nSetting default microphone volume to 1%"); - bool success = await defaultMic.SetMasterVolumeAsync(0.1f); - - // Log the current volume - Console.WriteLine($"Current volume: {await defaultMic.GetMasterVolumeAsync():P0}"); - - for (int i = 0; i < 50; i++) - { - if (await defaultMic.GetMasterVolumeAsync() >= 1f) - { - break; - } - - // Increment volume by 2% each step - success = await defaultMic.StepUpAsync(); - Console.WriteLine($"Current step volume: {await defaultMic.GetMasterVolumeAsync():P0}"); - } - - if (success) - { - Console.WriteLine("Volume increased by 95% successfully."); - } - else - { - Console.WriteLine("Failed to increase volume."); - } - } - - Console.WriteLine($"Default mic volume: {await volumeMixer.GetMicrophoneVolumeAsync():P0}"); - Console.WriteLine($"Default mic muted: {await volumeMixer.GetMicrophoneMuteAsync()}"); - - // Set microphone volume to 60% - bool result = await volumeMixer.SetMicrophoneVolumeAsync(0.6f); - Console.WriteLine($"Set microphone volume to 60%: {result}"); - - // Set specific microphone by name - result = await volumeMixer.SetMicrophoneVolumeByNameAsync("USB", 0.7f); - Console.WriteLine($"Set USB microphone volume to 70%: {result}"); - - } - catch (Exception exception) - { - Console.WriteLine($"Error: {exception.Message}"); - } - } - } -} + else + { + Console.WriteLine("Failed to increase volume."); + } + } + + Console.WriteLine($"Default mic volume: {await volumeMixer.GetMicrophoneVolumeAsync():P0}"); + Console.WriteLine($"Default mic muted: {await volumeMixer.GetMicrophoneMuteAsync()}"); + + // Set microphone volume to 60% + bool result = await volumeMixer.SetMicrophoneVolumeAsync(0.6f); + Console.WriteLine($"Set microphone volume to 60%: {result}"); + + // Set specific microphone by name + result = await volumeMixer.SetMicrophoneVolumeByNameAsync("USB", 0.7f); + Console.WriteLine($"Set USB microphone volume to 70%: {result}"); + + } + catch (Exception exception) + { + Console.WriteLine($"Error: {exception.Message}"); + } + } + } +}