Fixed com disposal

This commit is contained in:
Jeroen Saey 2025-09-01 16:00:13 +02:00
parent be67abaf64
commit 6f639622a5
4 changed files with 404 additions and 505 deletions

View File

@ -15,6 +15,29 @@ class Program
{ {
try 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 // Get all audio PLAYBACK devices
var devices = await volumeMixer.GetAudioDevicesAsync(DataFlow.Output); var devices = await volumeMixer.GetAudioDevicesAsync(DataFlow.Output);
Console.WriteLine($"Found {devices.Count} playback devices:"); Console.WriteLine($"Found {devices.Count} playback devices:");

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace EonaCat.VolumeMixer.Helpers namespace EonaCat.VolumeMixer.Helpers
{ {
@ -31,12 +32,22 @@ namespace EonaCat.VolumeMixer.Helpers
} }
} }
public static void ReleaseComObject(object obj) public static async Task ReleaseComObject(object obj)
{ {
if (obj != null && Marshal.IsComObject(obj)) if (obj != null && Marshal.IsComObject(obj))
{ {
if (obj == null || !Marshal.IsComObject(obj))
{
return;
}
Marshal.ReleaseComObject(obj); Marshal.ReleaseComObject(obj);
obj = null;
} }
GC.Collect();
GC.WaitForPendingFinalizers();
} }
} }
} }

View File

@ -36,15 +36,13 @@ namespace EonaCat.VolumeMixer.Managers
} }
} }
public async Task<List<AudioDevice>> GetAudioDevicesAsync(DataFlow dataFlow = DataFlow.Output) public Task<List<AudioDevice>> GetAudioDevicesAsync(DataFlow dataFlow = DataFlow.Output)
{
return await Task.Run(() =>
{ {
var devices = new List<AudioDevice>(); var devices = new List<AudioDevice>();
if (_deviceEnumerator == null) if (_deviceEnumerator == null)
{ {
return devices; return Task.FromResult(devices);
} }
lock (_syncLock) lock (_syncLock)
@ -57,13 +55,13 @@ namespace EonaCat.VolumeMixer.Managers
var result = _deviceEnumerator.EnumAudioEndpoints(dataFlow, DeviceState.Active, out deviceCollection); var result = _deviceEnumerator.EnumAudioEndpoints(dataFlow, DeviceState.Active, out deviceCollection);
if (result != 0 || deviceCollection == null) if (result != 0 || deviceCollection == null)
{ {
return devices; return Task.FromResult(devices);
} }
result = deviceCollection.GetCount(out var count); result = deviceCollection.GetCount(out var count);
if (result != 0) if (result != 0)
{ {
return devices; return Task.FromResult(devices);
} }
string defaultId = ""; string defaultId = "";
@ -83,7 +81,6 @@ namespace EonaCat.VolumeMixer.Managers
for (uint i = 0; i < count; i++) for (uint i = 0; i < count; i++)
{ {
IMultiMediaDevice device = null; IMultiMediaDevice device = null;
try try
{ {
result = deviceCollection.Item(i, out device); result = deviceCollection.Item(i, out device);
@ -95,7 +92,9 @@ namespace EonaCat.VolumeMixer.Managers
var name = GetDeviceName(device); var name = GetDeviceName(device);
var type = GetDeviceType(device); var type = GetDeviceType(device);
bool isDefault = id == defaultId; bool isDefault = id == defaultId;
devices.Add(new AudioDevice(device, id, name, isDefault, dataFlow, type)); devices.Add(new AudioDevice(device, id, name, isDefault, dataFlow, type));
device = null;
} }
} }
} }
@ -104,43 +103,26 @@ namespace EonaCat.VolumeMixer.Managers
// Do nothing // Do nothing
} }
finally finally
{
if (device != null)
{ {
ComHelper.ReleaseComObject(device); ComHelper.ReleaseComObject(device);
} }
} }
} }
}
catch
{
// Do nothing
}
finally finally
{
if (deviceCollection != null)
{ {
ComHelper.ReleaseComObject(deviceCollection); ComHelper.ReleaseComObject(deviceCollection);
}
if (defaultDevice != null)
{
ComHelper.ReleaseComObject(defaultDevice); ComHelper.ReleaseComObject(defaultDevice);
} }
} }
return Task.FromResult(devices);
} }
return devices; public Task<AudioDevice> GetDefaultAudioDeviceAsync(DataFlow dataFlow = DataFlow.Output)
});
}
public async Task<AudioDevice> GetDefaultAudioDeviceAsync(DataFlow dataFlow = DataFlow.Output)
{
return await Task.Run(() =>
{ {
if (_deviceEnumerator == null) if (_deviceEnumerator == null)
{ {
return null; return Task.FromResult<AudioDevice>(null);
} }
lock (_syncLock) lock (_syncLock)
@ -157,25 +139,20 @@ namespace EonaCat.VolumeMixer.Managers
{ {
var name = GetDeviceName(device); var name = GetDeviceName(device);
var type = GetDeviceType(device); var type = GetDeviceType(device);
return new AudioDevice(device, id, name, true, dataFlow, type);
var audioDevice = new AudioDevice(device, id, name, true, dataFlow, type);
device = null; // ownership transferred
return Task.FromResult(audioDevice);
} }
} }
} }
catch
{
// Do nothing
}
finally finally
{
if (device != null)
{ {
ComHelper.ReleaseComObject(device); ComHelper.ReleaseComObject(device);
} }
}
return null; return Task.FromResult<AudioDevice>(null);
} }
});
} }
[DllImport("ole32.dll")] [DllImport("ole32.dll")]
@ -183,7 +160,7 @@ namespace EonaCat.VolumeMixer.Managers
private string GetDeviceName(IMultiMediaDevice device) private string GetDeviceName(IMultiMediaDevice device)
{ {
IPropertyStore? propertyStore = null; IPropertyStore propertyStore = null;
PropVariant propVariant = new PropVariant(); PropVariant propVariant = new PropVariant();
try try
@ -193,33 +170,23 @@ namespace EonaCat.VolumeMixer.Managers
{ {
var propertyKey = PKEY_Device_FriendlyName; var propertyKey = PKEY_Device_FriendlyName;
result = propertyStore.GetValue(ref propertyKey, out propVariant); 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); string name = Marshal.PtrToStringUni(propVariant.data1);
return !string.IsNullOrEmpty(name) ? name : "Unknown Device"; return !string.IsNullOrEmpty(name) ? name : "Unknown Device";
} }
} }
} }
catch
{
// Do nothing
}
finally finally
{ {
// Clear memory
PropVariantClear(ref propVariant); PropVariantClear(ref propVariant);
if (propertyStore != null)
{
ComHelper.ReleaseComObject(propertyStore); ComHelper.ReleaseComObject(propertyStore);
} }
}
return "Unknown Device"; return "Unknown Device";
} }
private DeviceType GetDeviceType(IMultiMediaDevice device) private DeviceType GetDeviceType(IMultiMediaDevice device)
{ {
IPropertyStore propertyStore = null; IPropertyStore propertyStore = null;
@ -229,14 +196,11 @@ namespace EonaCat.VolumeMixer.Managers
{ {
int result = device.OpenPropertyStore(0, out propertyStore); int result = device.OpenPropertyStore(0, out propertyStore);
if (result == 0 && propertyStore != null) if (result == 0 && propertyStore != null)
{
try
{ {
var propertyKey = PKEY_AudioEndpoint_FormFactor; var propertyKey = PKEY_AudioEndpoint_FormFactor;
result = propertyStore.GetValue(ref propertyKey, out propVariant); result = propertyStore.GetValue(ref propertyKey, out propVariant);
// 0x13 == VT_UI4 if (result == 0 && propVariant.vt == VT_UI4)
if (result == 0 && propVariant.vt == 0x13)
{ {
int formFactor = propVariant.data1.ToInt32(); int formFactor = propVariant.data1.ToInt32();
@ -266,29 +230,16 @@ namespace EonaCat.VolumeMixer.Managers
}; };
} }
} }
finally
{
}
}
}
catch
{
// Do nothing
} }
finally finally
{ {
PropVariantClear(ref propVariant); PropVariantClear(ref propVariant);
if (propertyStore != null)
{
ComHelper.ReleaseComObject(propertyStore); ComHelper.ReleaseComObject(propertyStore);
} }
}
return DeviceType.Unknown; return DeviceType.Unknown;
} }
public async Task<bool> SetSystemVolumeAsync(float volume) public async Task<bool> SetSystemVolumeAsync(float volume)
{ {
if (volume < 0f || volume > 1f) if (volume < 0f || volume > 1f)
@ -296,78 +247,31 @@ namespace EonaCat.VolumeMixer.Managers
return false; return false;
} }
AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output); using var defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output);
if (defaultDevice == null) if (defaultDevice == null)
{ {
return false; return false;
} }
try
{
return await defaultDevice.SetMasterVolumeAsync(volume); return await defaultDevice.SetMasterVolumeAsync(volume);
} }
finally
{
defaultDevice.Dispose();
}
}
public async Task<float> GetSystemVolumeAsync() public async Task<float> GetSystemVolumeAsync()
{ {
AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output); using var defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output);
return defaultDevice == null ? 0f : await defaultDevice.GetMasterVolumeAsync();
if (defaultDevice == null)
{
return 0f;
}
try
{
return await defaultDevice.GetMasterVolumeAsync();
}
finally
{
defaultDevice.Dispose();
}
} }
public async Task<bool> SetSystemMuteAsync(bool mute) public async Task<bool> SetSystemMuteAsync(bool mute)
{ {
AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output); using var defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output);
return defaultDevice != null && await defaultDevice.SetMasterMuteAsync(mute);
if (defaultDevice == null)
{
return false;
}
try
{
return await defaultDevice.SetMasterMuteAsync(mute);
}
finally
{
defaultDevice.Dispose();
}
} }
public async Task<bool> GetSystemMuteAsync() public async Task<bool> GetSystemMuteAsync()
{ {
AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output); using var defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output);
return defaultDevice != null && await defaultDevice.GetMasterMuteAsync();
if (defaultDevice == null)
{
return false;
}
try
{
return await defaultDevice.GetMasterMuteAsync();
}
finally
{
defaultDevice.Dispose();
}
} }
public async Task<bool> SetMicrophoneVolumeAsync(float volume) public async Task<bool> SetMicrophoneVolumeAsync(float volume)
@ -377,78 +281,26 @@ namespace EonaCat.VolumeMixer.Managers
return false; return false;
} }
AudioDevice defaultMic = await GetDefaultAudioDeviceAsync(DataFlow.Input); using var defaultMic = await GetDefaultAudioDeviceAsync(DataFlow.Input);
return defaultMic != null && await defaultMic.SetMasterVolumeAsync(volume);
if (defaultMic == null)
{
return false;
}
try
{
return await defaultMic.SetMasterVolumeAsync(volume);
}
finally
{
defaultMic.Dispose();
}
} }
public async Task<float> GetMicrophoneVolumeAsync() public async Task<float> GetMicrophoneVolumeAsync()
{ {
AudioDevice defaultMic = await GetDefaultAudioDeviceAsync(DataFlow.Input); using var defaultMic = await GetDefaultAudioDeviceAsync(DataFlow.Input);
return defaultMic == null ? 0f : await defaultMic.GetMasterVolumeAsync();
if (defaultMic == null)
{
return 0f;
}
try
{
return await defaultMic.GetMasterVolumeAsync();
}
finally
{
defaultMic.Dispose();
}
} }
public async Task<bool> SetMicrophoneMuteAsync(bool mute) public async Task<bool> SetMicrophoneMuteAsync(bool mute)
{ {
AudioDevice defaultMic = await GetDefaultAudioDeviceAsync(DataFlow.Input); using var defaultMic = await GetDefaultAudioDeviceAsync(DataFlow.Input);
return defaultMic != null && await defaultMic.SetMasterMuteAsync(mute);
if (defaultMic == null)
{
return false;
}
try
{
return await defaultMic.SetMasterMuteAsync(mute);
}
finally
{
defaultMic.Dispose();
}
} }
public async Task<bool> GetMicrophoneMuteAsync() public async Task<bool> GetMicrophoneMuteAsync()
{ {
AudioDevice defaultMic = await GetDefaultAudioDeviceAsync(DataFlow.Input); using var defaultMic = await GetDefaultAudioDeviceAsync(DataFlow.Input);
return defaultMic != null && await defaultMic.GetMasterMuteAsync();
if (defaultMic == null)
{
return false;
}
try
{
return await defaultMic.GetMasterMuteAsync();
}
finally
{
defaultMic.Dispose();
}
} }
public async Task<bool> SetMicrophoneVolumeByNameAsync(string microphoneName, float volume) public async Task<bool> SetMicrophoneVolumeByNameAsync(string microphoneName, float volume)
@ -458,35 +310,46 @@ namespace EonaCat.VolumeMixer.Managers
return false; return false;
} }
List<AudioDevice> microphones = await GetAudioDevicesAsync(DataFlow.Input); List<AudioDevice> microphones = null;
try
{
microphones = await GetAudioDevicesAsync(DataFlow.Input);
foreach (var mic in microphones) foreach (var mic in microphones)
{ {
try using (mic)
{ {
if (mic.Name.IndexOf(microphoneName, StringComparison.OrdinalIgnoreCase) >= 0) if (mic.Name.IndexOf(microphoneName, StringComparison.OrdinalIgnoreCase) >= 0)
{ {
return await mic.SetMasterVolumeAsync(volume); return await mic.SetMasterVolumeAsync(volume);
} }
} }
}
}
finally finally
{
// Release all resources
if (microphones != null)
{
foreach (var mic in microphones)
{ {
mic.Dispose(); mic.Dispose();
} }
} }
}
return false; return false;
} }
public async Task<List<AudioSession>> GetAllActiveSessionsAsync() public async Task<List<AudioSession>> GetAllActiveSessionsAsync()
{
return await Task.Run(async () =>
{ {
var allSessions = new List<AudioSession>(); var allSessions = new List<AudioSession>();
List<AudioDevice> devices = await GetAudioDevicesAsync(DataFlow.Output); List<AudioDevice> devices = await GetAudioDevicesAsync(DataFlow.Output);
foreach (var device in devices) foreach (var device in devices)
{
using (device)
{ {
try try
{ {
@ -496,24 +359,28 @@ namespace EonaCat.VolumeMixer.Managers
{ {
// Do nothing // Do nothing
} }
finally }
}
// Release resources
foreach (var device in devices)
{ {
device.Dispose(); device.Dispose();
} }
} devices.Clear();
devices = null;
return allSessions; return allSessions;
});
} }
public async Task<List<AudioDevice>> GetMicrophonesAsync() public Task<List<AudioDevice>> GetMicrophonesAsync()
{ {
return await GetAudioDevicesAsync(DataFlow.Input); return GetAudioDevicesAsync(DataFlow.Input);
} }
public async Task<AudioDevice> GetDefaultMicrophoneAsync() public Task<AudioDevice> GetDefaultMicrophoneAsync()
{ {
return await GetDefaultAudioDeviceAsync(DataFlow.Input); return GetDefaultAudioDeviceAsync(DataFlow.Input);
} }
public void Dispose() public void Dispose()

View File

@ -258,11 +258,9 @@ namespace EonaCat.VolumeMixer.Models
return; return;
} }
if (_audioVolume != null)
{
Marshal.ReleaseComObject(_audioVolume); Marshal.ReleaseComObject(_audioVolume);
_audioVolume = null; _audioVolume = null;
}
ComHelper.ReleaseComObject(_sessionControl); ComHelper.ReleaseComObject(_sessionControl);
_isDisposed = true; _isDisposed = true;