diff --git a/EonaCat.VolumeMixer.Tester/Program.cs b/EonaCat.VolumeMixer.Tester/Program.cs
index 8afeb67..c378f03 100644
--- a/EonaCat.VolumeMixer.Tester/Program.cs
+++ b/EonaCat.VolumeMixer.Tester/Program.cs
@@ -15,6 +15,16 @@ class Program
{
try
{
+ while (true)
+ {
+ var devicesTest = await volumeMixer.GetAudioDevicesAsync(DataFlow.Output);
+ var micrphonesTest = await volumeMixer.GetMicrophonesAsync();
+ Console.WriteLine($"Found {devicesTest.Count} playback devices");
+ Console.WriteLine($"Found {micrphonesTest.Count} microphones");
+ await Task.Delay(100);
+ }
+
+
// Get all audio PLAYBACK devices
var devices = await volumeMixer.GetAudioDevicesAsync(DataFlow.Output);
Console.WriteLine($"Found {devices.Count} playback devices:");
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/Helpers/ComHelper.cs b/EonaCat.VolumeMixer/Helpers/ComHelper.cs
index 46cde05..16ee5a2 100644
--- a/EonaCat.VolumeMixer/Helpers/ComHelper.cs
+++ b/EonaCat.VolumeMixer/Helpers/ComHelper.cs
@@ -31,12 +31,32 @@ namespace EonaCat.VolumeMixer.Helpers
}
}
- public static void ReleaseComObject(object obj)
+ public static void ReleaseComObject(object comObj)
{
- if (obj != null && Marshal.IsComObject(obj))
+ if (comObj == null)
{
- Marshal.ReleaseComObject(obj);
+ return;
+ }
+
+ try
+ {
+ while (Marshal.ReleaseComObject(comObj) > 0)
+ {
+ // Do nothing
+ }
+ }
+ catch
+ {
+ try
+ {
+ Marshal.FinalReleaseComObject(comObj);
+ }
+ catch
+ {
+ // Do nothing
+ }
}
}
+
}
}
\ No newline at end of file
diff --git a/EonaCat.VolumeMixer/Interfaces/IPropertyStore.cs b/EonaCat.VolumeMixer/Interfaces/IPropertyStore.cs
index 184b4ce..8a45dd4 100644
--- a/EonaCat.VolumeMixer/Interfaces/IPropertyStore.cs
+++ b/EonaCat.VolumeMixer/Interfaces/IPropertyStore.cs
@@ -1,4 +1,5 @@
-using EonaCat.VolumeMixer.Models;
+using EonaCat.VolumeMixer.Helpers;
+using EonaCat.VolumeMixer.Models;
using System;
using System.Runtime.InteropServices;
@@ -13,8 +14,8 @@ namespace EonaCat.VolumeMixer.Interfaces
{
int GetCount(out uint cProps);
int GetAt(uint iProp, out PropertyKey pkey);
- int GetValue(ref PropertyKey key, out PropVariant pv);
- int SetValue(ref PropertyKey key, ref PropVariant propvar);
+ int GetValue(ref PropertyKey key, out PROPVARIANT pv);
+ int SetValue(ref PropertyKey key, ref PROPVARIANT propvar);
int Commit();
}
}
\ No newline at end of file
diff --git a/EonaCat.VolumeMixer/Managers/VolumeMixerManager.cs b/EonaCat.VolumeMixer/Managers/VolumeMixerManager.cs
index 7e8dadb..23b6b7c 100644
--- a/EonaCat.VolumeMixer/Managers/VolumeMixerManager.cs
+++ b/EonaCat.VolumeMixer/Managers/VolumeMixerManager.cs
@@ -8,17 +8,16 @@ 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 IMultiMediaDeviceEnumerator _deviceEnumerator;
+ private bool _isDisposed;
private readonly object _syncLock = new();
+ public event EventHandler OnException;
- 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
+ private static PropertyKey PKEY_Device_FriendlyName = new(new Guid("a45c254e-df1c-4efd-8020-67d146a850e0"), 14);
+
+ private static PropertyKey PKEY_AudioEndpoint_FormFactor = new PropertyKey
{
Fmtid = new Guid("1DA5D803-D492-4EDD-8C23-E0C0FFEE7F0E"),
Pid = 0
@@ -32,7 +31,8 @@ namespace EonaCat.VolumeMixer.Managers
}
catch (Exception ex)
{
- throw new InvalidOperationException("Failed to initialize audio device enumerator. Make sure you're running on Windows Vista or later.", ex);
+ throw new InvalidOperationException(
+ "Failed to initialize audio device enumerator. Make sure you're running on Windows Vista or later.", ex);
}
}
@@ -41,76 +41,87 @@ namespace EonaCat.VolumeMixer.Managers
return await Task.Run(() =>
{
var devices = new List();
-
- if (_deviceEnumerator == null)
- {
- return devices;
- }
+ if (_isDisposed || _deviceEnumerator == null) return devices;
lock (_syncLock)
{
+ IMultiMediaDeviceCollection collection = null;
+ IMultiMediaDevice defaultDevice = null;
+
try
{
- var result = _deviceEnumerator.EnumAudioEndpoints(dataFlow, DeviceState.Active, out var deviceCollection);
- if (result != 0 || deviceCollection == null)
- {
- return devices;
- }
+ int hr = _deviceEnumerator.EnumAudioEndpoints(dataFlow, DeviceState.Active, out collection);
+ if (hr != 0 || collection == null) return devices;
- result = deviceCollection.GetCount(out var count);
- if (result != 0)
- {
- ComHelper.ReleaseComObject(deviceCollection);
- return devices;
- }
+ hr = collection.GetCount(out var count);
+ if (hr != 0) return devices;
- string defaultId = "";
+ // Get default device
+ string defaultId = string.Empty;
try
{
- result = _deviceEnumerator.GetDefaultAudioEndpoint(dataFlow, Role.Multimedia, out var defaultDevice);
- if (result == 0 && defaultDevice != null)
+ hr = _deviceEnumerator.GetDefaultAudioEndpoint(dataFlow, Role.Multimedia, out defaultDevice);
+ if (hr == 0 && defaultDevice != null)
{
defaultDevice.GetId(out defaultId);
- ComHelper.ReleaseComObject(defaultDevice);
}
}
- catch
+ catch (Exception ex)
{
- defaultId = "";
+ OnException?.Invoke(this, ex);
}
+ // Enumerate devices
for (uint i = 0; i < count; i++)
{
+ IMultiMediaDevice device = null;
try
{
- result = deviceCollection.Item(i, out var device);
- if (result == 0 && device != null)
+ if (collection.Item(i, out device) != 0 || device == null) continue;
+
+ if (device.GetId(out var id) != 0 || string.IsNullOrEmpty(id)) continue;
+
+ string name = GetDeviceName(device);
+ DeviceType type = GetDeviceType(device);
+ bool isDefault = id == defaultId;
+
+ // Increase ref count before passing to AudioDevice
+ Marshal.AddRef(Marshal.GetIUnknownForObject(device));
+
+ devices.Add(new AudioDevice(device, id, name, isDefault, dataFlow, type));
+ device = null;
+ }
+ catch (Exception ex)
+ {
+ OnException?.Invoke(this, ex);
+ }
+ finally
+ {
+ if (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));
- }
- else
- {
- ComHelper.ReleaseComObject(device);
- }
+ Marshal.ReleaseComObject(device);
+ device = null;
}
}
- catch
- {
- // Do nothing
- }
+ }
+ }
+ catch (Exception ex)
+ {
+ OnException?.Invoke(this, ex);
+ }
+ finally
+ {
+ if (collection != null)
+ {
+ Marshal.ReleaseComObject(collection);
+ collection = null;
}
- ComHelper.ReleaseComObject(deviceCollection);
- }
- catch
- {
- // Do nothing
+ if (defaultDevice != null)
+ {
+ Marshal.ReleaseComObject(defaultDevice);
+ defaultDevice = null;
+ }
}
}
@@ -120,36 +131,28 @@ namespace EonaCat.VolumeMixer.Managers
public async Task GetDefaultAudioDeviceAsync(DataFlow dataFlow = DataFlow.Output)
{
+ if (_isDisposed || _deviceEnumerator == null) return null;
+
return await Task.Run(() =>
{
- if (_deviceEnumerator == null)
- {
- return null;
- }
-
lock (_syncLock)
{
+ IMultiMediaDevice device = null;
try
{
- var result = _deviceEnumerator.GetDefaultAudioEndpoint(dataFlow, Role.Multimedia, out var device);
- if (result == 0 && device != null)
+ int hr = _deviceEnumerator.GetDefaultAudioEndpoint(dataFlow, Role.Multimedia, out device);
+ if (hr == 0 && device != null)
{
- result = device.GetId(out var id);
- if (result == 0 && !string.IsNullOrEmpty(id))
+ if (device.GetId(out var id) == 0 && !string.IsNullOrEmpty(id))
{
- var name = GetDeviceName(device);
- var type = GetDeviceType(device);
+ string name = GetDeviceName(device);
+ DeviceType type = GetDeviceType(device);
return new AudioDevice(device, id, name, true, dataFlow, type);
}
-
- ComHelper.ReleaseComObject(device);
}
}
- catch
- {
- // Do nothing
- }
-
+ catch (Exception ex) { OnException?.Invoke(this, ex); }
+ finally { SafeRelease(ref device); }
return null;
}
});
@@ -157,26 +160,24 @@ namespace EonaCat.VolumeMixer.Managers
private string GetDeviceName(IMultiMediaDevice device)
{
+ IPropertyStore store = null;
+ PROPVARIANT prop = default;
+
try
{
- var result = device.OpenPropertyStore(0, out var propertyStore);
- if (result == 0 && propertyStore != null)
+ if (device.OpenPropertyStore(0, out store) != 0 || store == null) return "Unknown Device";
+ if (store.GetValue(ref PKEY_Device_FriendlyName, out prop) == 0 &&
+ prop.vt == VarEnumConstants.VT_LPWSTR && prop.pointerValue != IntPtr.Zero)
{
- 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);
+ string name = Marshal.PtrToStringUni(prop.pointerValue);
+ return string.IsNullOrEmpty(name) ? "Unknown Device" : name;
}
}
- catch
+ catch (Exception ex) { OnException?.Invoke(this, ex); }
+ finally
{
- // Do nothing
+ try { PropVariantHelper.PropVariantClear(ref prop); } catch { }
+ SafeRelease(ref store);
}
return "Unknown Device";
@@ -184,63 +185,43 @@ namespace EonaCat.VolumeMixer.Managers
private DeviceType GetDeviceType(IMultiMediaDevice device)
{
+ IPropertyStore store = null;
+ PROPVARIANT prop = default;
+
try
{
- int result = device.OpenPropertyStore(0, out var propertyStore);
- if (result == 0 && propertyStore != null)
+ if (device.OpenPropertyStore(0, out store) != 0 || store == null) return DeviceType.Unknown;
+ if (store.GetValue(ref PKEY_AudioEndpoint_FormFactor, out prop) == 0 && prop.vt == VarEnumConstants.VT_UI4)
{
- try
+ return prop.uintValue switch
{
- var propertyKey = PKEY_AudioEndpoint_FormFactor;
- result = propertyStore.GetValue(ref propertyKey, out var 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
- {
- ComHelper.ReleaseComObject(propertyStore);
- }
+ 1 => DeviceType.Speakers,
+ 2 => DeviceType.LineLevel,
+ 3 => DeviceType.Headphones,
+ 4 => DeviceType.Microphone,
+ 5 => DeviceType.Headset,
+ 6 => DeviceType.Handset,
+ 8 => DeviceType.SPDIF,
+ 9 => DeviceType.DigitalAudioDisplayDevice,
+ 14 => DeviceType.Bluetooth,
+ 15 => DeviceType.SPDIFOut,
+ 16 => DeviceType.HDMI,
+ 17 => DeviceType.DisplayAudio,
+ 19 => DeviceType.Other,
+ _ => DeviceType.Unknown
+ };
}
}
- catch
+ catch (Exception ex) { OnException?.Invoke(this, ex); }
+ finally
{
- // Do nothing
+ try { PropVariantHelper.PropVariantClear(ref prop); } catch { }
+ SafeRelease(ref store);
}
return DeviceType.Unknown;
}
-
-
public async Task SetSystemVolumeAsync(float volume)
{
if (volume < 0f || volume > 1f)
@@ -430,34 +411,6 @@ namespace EonaCat.VolumeMixer.Managers
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);
@@ -472,11 +425,18 @@ namespace EonaCat.VolumeMixer.Managers
{
lock (_syncLock)
{
- if (!_isDisposed)
- {
- ComHelper.ReleaseComObject(_deviceEnumerator);
- _isDisposed = true;
- }
+ if (_isDisposed) return;
+ _isDisposed = true;
+ SafeRelease(ref _deviceEnumerator);
+ }
+ }
+
+ private static void SafeRelease(ref T comObj) where T : class
+ {
+ if (comObj != null)
+ {
+ try { Marshal.FinalReleaseComObject(comObj); } catch { }
+ comObj = null;
}
}
}
diff --git a/EonaCat.VolumeMixer/Models/AudioDevice.cs b/EonaCat.VolumeMixer/Models/AudioDevice.cs
index 519a443..85a0d3b 100644
--- a/EonaCat.VolumeMixer/Models/AudioDevice.cs
+++ b/EonaCat.VolumeMixer/Models/AudioDevice.cs
@@ -8,267 +8,212 @@ using System.Threading.Tasks;
namespace EonaCat.VolumeMixer.Models
{
- // 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 AudioDevice : IDisposable
{
- internal Guid AudioController2Guid = new Guid("bfb7ff88-7239-4fc9-8fa2-07c950be9c6d");
- private readonly IMultiMediaDevice _device;
+ internal Guid AudioController2Guid = new("bfb7ff88-7239-4fc9-8fa2-07c950be9c6d");
+ private IMultiMediaDevice _device;
private IAudioEndpointVolume _endpointVolume;
private IAudioSessionManager _sessionManager;
- private bool _isDisposed = false;
- private readonly object _syncLock = new object();
+ private bool _isDisposed;
+ private readonly object _syncLock = new();
- public string Id { get; private set; }
- public string Name { get; private set; }
- public DeviceType DeviceType { get; private set; }
- public bool IsDefault { get; private set; }
- public DataFlow DataFlow { get; private set; }
+ public string Id { get; }
+ public string Name { get; }
+ public DeviceType DeviceType { get; }
+ public bool IsDefault { get; }
+ public DataFlow DataFlow { get; }
+
+ public event EventHandler OnException;
internal AudioDevice(IMultiMediaDevice device, string id, string name, bool isDefault, DataFlow dataFlow, DeviceType deviceType)
{
- _device = device;
+ _device = device ?? throw new ArgumentNullException(nameof(device));
Id = id;
Name = name;
IsDefault = isDefault;
DataFlow = dataFlow;
DeviceType = deviceType;
+
InitializeEndpointVolume();
InitializeSessionManager();
}
private void InitializeEndpointVolume()
{
+ IntPtr ptr = IntPtr.Zero;
try
{
- var audioEndpointGuid = typeof(IAudioEndpointVolume).GUID;
- var result = _device.Activate(ref audioEndpointGuid, 0, IntPtr.Zero, out var ptr);
- if (result == 0 && ptr != IntPtr.Zero)
+ var guid = typeof(IAudioEndpointVolume).GUID;
+ int hr = _device.Activate(ref guid, 0, IntPtr.Zero, out ptr);
+ if (hr == 0 && ptr != IntPtr.Zero)
{
- _endpointVolume = ComHelper.GetInterface(ptr);
+ _endpointVolume = (IAudioEndpointVolume)Marshal.GetObjectForIUnknown(ptr);
}
}
catch (Exception ex)
{
- Debug.WriteLine($"Failed to initialize endpoint volume: {ex}");
+ Debug.WriteLine($"InitializeEndpointVolume failed: {ex}");
_endpointVolume = null;
}
+ finally
+ {
+ if (ptr != IntPtr.Zero)
+ {
+ Marshal.Release(ptr);
+ }
+ }
}
private void InitializeSessionManager()
{
+ IntPtr ptr = IntPtr.Zero;
try
{
- var audioSessionGuid = typeof(IAudioSessionManager).GUID;
- var result = _device.Activate(ref audioSessionGuid, 0, IntPtr.Zero, out var ptr);
- if (result == 0 && ptr != IntPtr.Zero)
+ var guid = typeof(IAudioSessionManager).GUID;
+ int hr = _device.Activate(ref guid, 0, IntPtr.Zero, out ptr);
+ if (hr == 0 && ptr != IntPtr.Zero)
{
- _sessionManager = ComHelper.GetInterface(ptr);
+ _sessionManager = (IAudioSessionManager)Marshal.GetObjectForIUnknown(ptr);
}
}
catch (Exception ex)
{
- Debug.WriteLine($"Failed to initialize session manager: {ex}");
+ Debug.WriteLine($"InitializeSessionManager failed: {ex}");
_sessionManager = null;
}
- }
-
- public async Task GetMasterVolumeAsync()
- {
- return await Task.Run(() =>
+ finally
{
- lock (_syncLock)
+ if (ptr != IntPtr.Zero)
{
- if (_isDisposed || _endpointVolume == null)
- {
- return 0f;
- }
-
- try
- {
- var result = _endpointVolume.GetMasterVolumeLevelScalar(out var volume);
- return result == 0 ? volume : 0f;
- }
- catch (Exception ex)
- {
- Debug.WriteLine($"Error getting master volume: {ex}");
- return 0f;
- }
+ Marshal.Release(ptr);
}
- }).ConfigureAwait(false);
+ }
}
- public async Task SetMasterVolumeAsync(float volume, int maxRetries = 5, int delayMs = 20)
+ public Task GetMasterVolumeAsync()
{
- if (_isDisposed || _endpointVolume == null || volume < 0f || volume > 1f)
+ lock (_syncLock)
{
- return false;
- }
+ if (_isDisposed || _endpointVolume == null)
+ {
+ return Task.FromResult(0f);
+ }
- var guid = Guid.Empty;
-
- for (int attempt = 0; attempt <= maxRetries; attempt++)
- {
try
{
- int result;
- lock (_syncLock)
- {
- result = _endpointVolume.SetMasterVolumeLevelScalar(volume, ref guid);
- }
-
- if (result == 0)
- {
- await Task.Delay(delayMs).ConfigureAwait(false);
- var currentVolume = await GetMasterVolumeAsync().ConfigureAwait(false);
- if (Math.Abs(currentVolume - volume) < 0.01f)
- {
- return true;
- }
- }
+ int hr = _endpointVolume.GetMasterVolumeLevelScalar(out float volume);
+ return Task.FromResult(hr == 0 ? volume : 0f);
}
- catch (Exception ex)
- {
- Debug.WriteLine($"Volume set failed on attempt {attempt + 1}: {ex}");
- }
-
- await Task.Delay(delayMs).ConfigureAwait(false);
+ catch { return Task.FromResult(0f); }
}
-
- return false;
}
- public async Task GetMasterMuteAsync()
+ public async Task SetMasterVolumeAsync(float volume)
{
- return await Task.Run(() =>
- {
- lock (_syncLock)
- {
- if (_isDisposed || _endpointVolume == null)
- {
- return false;
- }
+ if (_isDisposed || _endpointVolume == null || volume < 0f || volume > 1f) return false;
- try
- {
- var result = _endpointVolume.GetMute(out var mute);
- return result == 0 && mute;
- }
- catch (Exception ex)
- {
- Debug.WriteLine($"Error getting mute: {ex}");
- return false;
- }
- }
- }).ConfigureAwait(false);
+ var guid = Guid.Empty;
+ lock (_syncLock)
+ {
+ return _endpointVolume.SetMasterVolumeLevelScalar(volume, ref guid) == 0;
+ }
}
- public async Task SetMasterMuteAsync(bool mute)
+ public Task GetMasterMuteAsync()
{
- if (_isDisposed || _endpointVolume == null)
+ lock (_syncLock)
{
- return false;
+ if (_isDisposed || _endpointVolume == null) return Task.FromResult(false);
+ try
+ {
+ int hr = _endpointVolume.GetMute(out bool mute);
+ return Task.FromResult(hr == 0 && mute);
+ }
+ catch { return Task.FromResult(false); }
}
+ }
- try
+ public Task SetMasterMuteAsync(bool mute)
+ {
+ if (_isDisposed || _endpointVolume == null) return Task.FromResult(false);
+
+ var guid = Guid.Empty;
+ lock (_syncLock)
{
- var guid = Guid.Empty;
- return await Task.Run(() => _endpointVolume.SetMute(mute, ref guid) == 0).ConfigureAwait(false);
- }
- catch (Exception ex)
- {
- Debug.WriteLine($"Error setting mute: {ex}");
- return false;
+ return Task.FromResult(_endpointVolume.SetMute(mute, ref guid) == 0);
}
}
public async Task> GetAudioSessionsAsync()
{
- return await Task.Run(() =>
- {
- lock (_syncLock)
- {
- var sessions = new List();
+ var sessions = new List();
+ if (_isDisposed || _sessionManager == null) return sessions;
- if (_isDisposed || _sessionManager == null)
- {
- return sessions;
- }
+ lock (_syncLock)
+ {
+ try
+ {
+ int hr = _sessionManager.GetSessionEnumerator(out var sessionEnum);
+ if (hr != 0 || sessionEnum == null) return sessions;
try
{
- var result = _sessionManager.GetSessionEnumerator(out var sessionEnum);
- if (result != 0 || sessionEnum == null)
- {
- return sessions;
- }
-
- result = sessionEnum.GetCount(out var count);
- if (result != 0)
- {
- return sessions;
- }
+ hr = sessionEnum.GetCount(out int count);
+ if (hr != 0) return sessions;
for (int i = 0; i < count; i++)
{
try
{
- result = sessionEnum.GetSession(i, out var sessionControl);
- if (result == 0 && sessionControl != null)
+ hr = sessionEnum.GetSession(i, out var sessionControl);
+ if (hr == 0 && sessionControl != null)
{
var sessionControl2 = GetSessionControl2(sessionControl);
if (sessionControl2 != null)
{
sessions.Add(new AudioSession(sessionControl2, _sessionManager));
- Marshal.ReleaseComObject(sessionControl2);
}
-
- // Release the original sessionControl COM object
Marshal.ReleaseComObject(sessionControl);
}
}
- catch
- {
- // Do nothing
- }
+ catch (Exception ex) { OnException?.Invoke(this, ex); }
}
}
- catch
+ finally
{
- // Do nothing
+ Marshal.ReleaseComObject(sessionEnum);
}
-
- return sessions;
}
- });
+ catch (Exception ex)
+ {
+ OnException?.Invoke(this, ex);
+ }
+ }
+
+ return sessions;
}
private IAudioSessionControlExtended GetSessionControl2(object sessionControl)
{
- if (sessionControl == null)
- {
- return null;
- }
+ if (sessionControl == null) return null;
- var unknownPtr = Marshal.GetIUnknownForObject(sessionControl);
+ IntPtr unknownPtr = IntPtr.Zero;
+ IntPtr sessionControl2Ptr = IntPtr.Zero;
try
{
- IntPtr sessionControl2Ptr;
- int result = Marshal.QueryInterface(unknownPtr, ref AudioController2Guid, out sessionControl2Ptr);
- if (result == 0 && sessionControl2Ptr != IntPtr.Zero)
+ unknownPtr = Marshal.GetIUnknownForObject(sessionControl);
+ int hr = Marshal.QueryInterface(unknownPtr, ref AudioController2Guid, out sessionControl2Ptr);
+ if (hr == 0 && sessionControl2Ptr != IntPtr.Zero)
{
return (IAudioSessionControlExtended)Marshal.GetObjectForIUnknown(sessionControl2Ptr);
}
}
- catch (Exception ex)
- {
- Debug.WriteLine($"Error querying sessionControl2: {ex}");
- }
+ catch (Exception ex) { Debug.WriteLine($"GetSessionControl2 failed: {ex}"); }
finally
{
- Marshal.Release(unknownPtr);
+ if (sessionControl2Ptr != IntPtr.Zero) Marshal.Release(sessionControl2Ptr);
+ if (unknownPtr != IntPtr.Zero) Marshal.Release(unknownPtr);
}
-
return null;
}
@@ -328,17 +273,21 @@ namespace EonaCat.VolumeMixer.Models
{
lock (_syncLock)
{
- if (_isDisposed)
- {
- return;
- }
-
- ComHelper.ReleaseComObject(_endpointVolume);
- ComHelper.ReleaseComObject(_sessionManager);
- ComHelper.ReleaseComObject(_device);
+ if (_isDisposed) return;
_isDisposed = true;
- GC.SuppressFinalize(this);
+ SafeRelease(ref _endpointVolume);
+ SafeRelease(ref _sessionManager);
+ SafeRelease(ref _device);
+ }
+ }
+
+ private static void SafeRelease(ref T comObj) where T : class
+ {
+ if (comObj != null)
+ {
+ try { Marshal.FinalReleaseComObject(comObj); } catch { }
+ comObj = null;
}
}
}
diff --git a/EonaCat.VolumeMixer/Models/AudioSession.cs b/EonaCat.VolumeMixer/Models/AudioSession.cs
index b2d9b82..7449868 100644
--- a/EonaCat.VolumeMixer/Models/AudioSession.cs
+++ b/EonaCat.VolumeMixer/Models/AudioSession.cs
@@ -9,9 +9,9 @@ namespace EonaCat.VolumeMixer.Models
{
public class AudioSession : IDisposable
{
- private readonly IAudioSessionControlExtended _sessionControl;
+ private IAudioSessionControlExtended _sessionControl;
private IAudioVolume _audioVolume;
- private bool _isDisposed = false;
+ private bool _isDisposed;
private readonly object _syncLock = new();
public string DisplayName { get; private set; }
@@ -19,7 +19,7 @@ namespace EonaCat.VolumeMixer.Models
public uint ProcessId { get; private set; }
public AudioSessionState State { get; private set; }
- internal AudioSession(IAudioSessionControlExtended sessionControl, IAudioSessionManager sessionManager)
+ internal AudioSession(IAudioSessionControlExtended sessionControl, IAudioSessionManager manager)
{
_sessionControl = sessionControl ?? throw new ArgumentNullException(nameof(sessionControl));
InitializeSimpleAudioVolume();
@@ -30,23 +30,25 @@ namespace EonaCat.VolumeMixer.Models
{
lock (_syncLock)
{
+ if (_sessionControl == null) return;
+
+ IntPtr unknownPtr = IntPtr.Zero;
try
{
- var sessionControlUnknown = Marshal.GetIUnknownForObject(_sessionControl);
- var iidSimpleAudioVolume = typeof(IAudioVolume).GUID;
- IntPtr simpleAudioVolumePtr = IntPtr.Zero;
+ unknownPtr = Marshal.GetIUnknownForObject(_sessionControl);
+ IntPtr volumePtr = IntPtr.Zero;
- int result = Marshal.QueryInterface(sessionControlUnknown, ref iidSimpleAudioVolume, out simpleAudioVolumePtr);
- if (result == 0 && simpleAudioVolumePtr != IntPtr.Zero)
+ var iid = typeof(IAudioVolume).GUID;
+ int hr = Marshal.QueryInterface(unknownPtr, ref iid, out volumePtr);
+ if (hr == 0 && volumePtr != IntPtr.Zero)
{
- _audioVolume = (IAudioVolume)Marshal.GetObjectForIUnknown(simpleAudioVolumePtr);
- Marshal.Release(simpleAudioVolumePtr);
+ _audioVolume = (IAudioVolume)Marshal.GetObjectForIUnknown(volumePtr);
}
- Marshal.Release(sessionControlUnknown);
}
- catch
+ catch { _audioVolume = null; }
+ finally
{
- _audioVolume = null;
+ if (unknownPtr != IntPtr.Zero) Marshal.Release(unknownPtr);
}
}
}
@@ -253,19 +255,20 @@ namespace EonaCat.VolumeMixer.Models
{
lock (_syncLock)
{
- if (_isDisposed)
- {
- return;
- }
-
- if (_audioVolume != null)
- {
- Marshal.ReleaseComObject(_audioVolume);
- _audioVolume = null;
- }
- ComHelper.ReleaseComObject(_sessionControl);
-
+ if (_isDisposed) return;
_isDisposed = true;
+
+ SafeRelease(ref _audioVolume);
+ SafeRelease(ref _sessionControl);
+ }
+ }
+
+ private static void SafeRelease(ref T comObj) where T : class
+ {
+ if (comObj != null)
+ {
+ try { Marshal.FinalReleaseComObject(comObj); } catch { }
+ comObj = null;
}
}
}
diff --git a/EonaCat.VolumeMixer/Models/PropVariant.cs b/EonaCat.VolumeMixer/Models/PropVariant.cs
index 5cb979a..c32e098 100644
--- a/EonaCat.VolumeMixer/Models/PropVariant.cs
+++ b/EonaCat.VolumeMixer/Models/PropVariant.cs
@@ -1,18 +1,46 @@
using System;
using System.Runtime.InteropServices;
-namespace EonaCat.VolumeMixer.Models
+namespace EonaCat.VolumeMixer.Helpers
{
// 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.
- [StructLayout(LayoutKind.Explicit)]
- internal struct PropVariant
+ internal static class PropVariantHelper
{
- [FieldOffset(0)] public ushort vt;
+ ///
+ /// Calls the native PropVariantClear function in ole32.dll to free memory allocated inside a PROPVARIANT.
+ ///
+ /// Reference to the PROPVARIANT structure to clear.
+ /// HRESULT — 0 (S_OK) if successful.
+ [DllImport("ole32.dll", PreserveSig = true)]
+ internal static extern int PropVariantClear(ref PROPVARIANT pvar);
+ }
+
+ ///
+ /// Managed definition of the native PROPVARIANT structure used by Windows COM APIs.
+ /// Only includes fields required for strings (VT_LPWSTR) and integers (VT_UI4).
+ ///
+ [StructLayout(LayoutKind.Explicit)]
+ internal struct PROPVARIANT
+ {
+ [FieldOffset(0)] public ushort vt; // Variant type
[FieldOffset(2)] public ushort wReserved1;
[FieldOffset(4)] public ushort wReserved2;
[FieldOffset(6)] public ushort wReserved3;
- [FieldOffset(8)] public IntPtr data1;
- [FieldOffset(16)] public IntPtr data2;
+
+ // Union data starts at offset 8
+ [FieldOffset(8)] public IntPtr pointerValue; // For VT_LPWSTR (LPWSTR)
+ [FieldOffset(8)] public uint uintValue; // For VT_UI4
+ [FieldOffset(8)] public int intValue; // For VT_I4
}
-}
\ No newline at end of file
+
+ ///
+ /// VARIANT type constants (from WinNT.h).
+ ///
+ internal static class VarEnumConstants
+ {
+ public const ushort VT_EMPTY = 0;
+ public const ushort VT_UI4 = 0x13; // Unsigned 4-byte integer
+ public const ushort VT_LPWSTR = 0x1F; // Wide string (LPWSTR)
+ }
+}