From 050327f56dcf0356fb2b08c2268068597047ee00 Mon Sep 17 00:00:00 2001 From: EonaCat Date: Tue, 22 Jul 2025 21:43:23 +0200 Subject: [PATCH] Fixed session info retrieval --- EonaCat.VolumeMixer.Tester/Program.cs | 16 +- .../EonaCat.VolumeMixer.csproj | 6 +- ...olExtended.cs => IAudioSessionControl2.cs} | 0 .../Interfaces/IAudioSessionEnumerator.cs | 3 +- ...ionManager.cs => IAudioSessionManager2.cs} | 0 .../Interfaces/IAudioVolume.cs | 6 +- .../Managers/MicrophoneManager.cs | 146 ------------------ .../Managers/VolumeMixerManager.cs | 143 ++++++++++++++--- EonaCat.VolumeMixer/Models/AudioDevice.cs | 40 ++++- EonaCat.VolumeMixer/Models/AudioSession.cs | 53 ++++--- 10 files changed, 206 insertions(+), 207 deletions(-) rename EonaCat.VolumeMixer/Interfaces/{IAudioSessionControlExtended.cs => IAudioSessionControl2.cs} (100%) rename EonaCat.VolumeMixer/Interfaces/{IAudioSessionManager.cs => IAudioSessionManager2.cs} (100%) delete mode 100644 EonaCat.VolumeMixer/Managers/MicrophoneManager.cs diff --git a/EonaCat.VolumeMixer.Tester/Program.cs b/EonaCat.VolumeMixer.Tester/Program.cs index 81b7ab6..dc69901 100644 --- a/EonaCat.VolumeMixer.Tester/Program.cs +++ b/EonaCat.VolumeMixer.Tester/Program.cs @@ -99,15 +99,11 @@ class Program { Console.WriteLine($"Error: {ex.Message}"); } - } - // Microphone management example - using (var micManager = new MicrophoneManager()) - { try { // Get all microphones - var microphones = await micManager.GetMicrophonesAsync(); + var microphones = await volumeMixer.GetMicrophonesAsync(); Console.WriteLine($"Found {microphones.Count} microphones:"); foreach (var mic in microphones) @@ -119,7 +115,7 @@ class Program } // Default microphone - using (var defaultMic = await micManager.GetDefaultMicrophoneAsync()) + using (var defaultMic = await volumeMixer.GetDefaultMicrophoneAsync()) { Console.WriteLine($"\nSetting default microphone volume to 1%"); bool success = await defaultMic.SetMasterVolumeAsync(0.1f); @@ -149,15 +145,15 @@ class Program } } - Console.WriteLine($"Default mic volume: {await micManager.GetMicrophoneVolumeAsync():P0}"); - Console.WriteLine($"Default mic muted: {await micManager.GetMicrophoneMuteAsync()}"); + 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 micManager.SetMicrophoneVolumeAsync(0.6f); + bool result = await volumeMixer.SetMicrophoneVolumeAsync(0.6f); Console.WriteLine($"Set microphone volume to 60%: {result}"); // Set specific microphone by name - result = await micManager.SetMicrophoneVolumeByNameAsync("USB", 0.7f); + result = await volumeMixer.SetMicrophoneVolumeByNameAsync("USB", 0.7f); Console.WriteLine($"Set USB microphone volume to 70%: {result}"); } diff --git a/EonaCat.VolumeMixer/EonaCat.VolumeMixer.csproj b/EonaCat.VolumeMixer/EonaCat.VolumeMixer.csproj index 09be1ff..2fafa65 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.0 - 1.0.0.0 - 1.0.0.0 + 1.0.3 + 1.0.0.3 + 1.0.0.3 icon.png diff --git a/EonaCat.VolumeMixer/Interfaces/IAudioSessionControlExtended.cs b/EonaCat.VolumeMixer/Interfaces/IAudioSessionControl2.cs similarity index 100% rename from EonaCat.VolumeMixer/Interfaces/IAudioSessionControlExtended.cs rename to EonaCat.VolumeMixer/Interfaces/IAudioSessionControl2.cs diff --git a/EonaCat.VolumeMixer/Interfaces/IAudioSessionEnumerator.cs b/EonaCat.VolumeMixer/Interfaces/IAudioSessionEnumerator.cs index 632e641..cd8373f 100644 --- a/EonaCat.VolumeMixer/Interfaces/IAudioSessionEnumerator.cs +++ b/EonaCat.VolumeMixer/Interfaces/IAudioSessionEnumerator.cs @@ -11,6 +11,7 @@ namespace EonaCat.VolumeMixer.Interfaces internal interface IAudioSessionEnumerator { int GetCount(out int SessionCount); - int GetSession(int SessionNumber, out IAudioSessionControlExtended Session); + int GetSession(int SessionNumber, out IAudioSessionControl Session); } + } \ No newline at end of file diff --git a/EonaCat.VolumeMixer/Interfaces/IAudioSessionManager.cs b/EonaCat.VolumeMixer/Interfaces/IAudioSessionManager2.cs similarity index 100% rename from EonaCat.VolumeMixer/Interfaces/IAudioSessionManager.cs rename to EonaCat.VolumeMixer/Interfaces/IAudioSessionManager2.cs diff --git a/EonaCat.VolumeMixer/Interfaces/IAudioVolume.cs b/EonaCat.VolumeMixer/Interfaces/IAudioVolume.cs index 6791d91..ba24589 100644 --- a/EonaCat.VolumeMixer/Interfaces/IAudioVolume.cs +++ b/EonaCat.VolumeMixer/Interfaces/IAudioVolume.cs @@ -8,11 +8,11 @@ namespace EonaCat.VolumeMixer.Interfaces [ComImport] [Guid("87CE5498-68D6-44E5-9215-6DA47EF883D8")] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] - internal interface IAudioVolume + interface IAudioVolume { int SetMasterVolume(float fLevel, ref Guid EventContext); int GetMasterVolume(out float pfLevel); - int SetMute([MarshalAs(UnmanagedType.Bool)] bool bMute, ref Guid EventContext); - int GetMute([MarshalAs(UnmanagedType.Bool)] out bool pbMute); + int SetMute(bool bMute, ref Guid EventContext); + int GetMute(out bool pbMute); } } \ No newline at end of file diff --git a/EonaCat.VolumeMixer/Managers/MicrophoneManager.cs b/EonaCat.VolumeMixer/Managers/MicrophoneManager.cs deleted file mode 100644 index b50b1b7..0000000 --- a/EonaCat.VolumeMixer/Managers/MicrophoneManager.cs +++ /dev/null @@ -1,146 +0,0 @@ -using EonaCat.VolumeMixer.Models; -using System; -using System.Collections.Generic; -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 MicrophoneManager : IDisposable - { - private readonly VolumeMixerManager _volumeMixer; - private readonly object _syncLock = new(); - - public MicrophoneManager() - { - _volumeMixer = new VolumeMixerManager(); - } - - public async Task> GetMicrophonesAsync() - { - return await _volumeMixer.GetAudioDevicesAsync(DataFlow.Input); - } - - public async Task GetDefaultMicrophoneAsync() - { - return await _volumeMixer.GetDefaultAudioDeviceAsync(DataFlow.Input); - } - - public async Task SetMicrophoneVolumeAsync(float volume) - { - if (volume < 0f || volume > 1f) - { - return false; - } - - AudioDevice defaultMic = await GetDefaultMicrophoneAsync(); - - if (defaultMic == null) - { - return false; - } - - try - { - return await defaultMic.SetMasterVolumeAsync(volume); - } - finally - { - defaultMic.Dispose(); - } - } - - public async Task GetMicrophoneVolumeAsync() - { - AudioDevice defaultMic = await GetDefaultMicrophoneAsync(); - - if (defaultMic == null) - { - return 0f; - } - - try - { - return await defaultMic.GetMasterVolumeAsync(); - } - finally - { - defaultMic.Dispose(); - } - } - - public async Task SetMicrophoneMuteAsync(bool mute) - { - AudioDevice defaultMic = await GetDefaultMicrophoneAsync(); - - if (defaultMic == null) - { - return false; - } - - try - { - return await defaultMic.SetMasterMuteAsync(mute); - } - finally - { - defaultMic.Dispose(); - } - } - - public async Task GetMicrophoneMuteAsync() - { - AudioDevice defaultMic = await GetDefaultMicrophoneAsync(); - - 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 GetMicrophonesAsync(); - - 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 void Dispose() - { - lock (_syncLock) - { - _volumeMixer?.Dispose(); - } - } - } -} diff --git a/EonaCat.VolumeMixer/Managers/VolumeMixerManager.cs b/EonaCat.VolumeMixer/Managers/VolumeMixerManager.cs index ae0040b..e1c576c 100644 --- a/EonaCat.VolumeMixer/Managers/VolumeMixerManager.cs +++ b/EonaCat.VolumeMixer/Managers/VolumeMixerManager.cs @@ -30,6 +30,7 @@ namespace EonaCat.VolumeMixer.Managers } } + // --- Get Devices --- public async Task> GetAudioDevicesAsync(DataFlow dataFlow = DataFlow.Output) { return await Task.Run(() => @@ -37,9 +38,7 @@ namespace EonaCat.VolumeMixer.Managers var devices = new List(); if (_deviceEnumerator == null) - { return devices; - } lock (_syncLock) { @@ -47,13 +46,12 @@ namespace EonaCat.VolumeMixer.Managers { var result = _deviceEnumerator.EnumAudioEndpoints(dataFlow, DeviceState.Active, out var deviceCollection); if (result != 0 || deviceCollection == null) - { return devices; - } result = deviceCollection.GetCount(out var count); if (result != 0) { + ComHelper.ReleaseComObject(deviceCollection); return devices; } @@ -110,14 +108,13 @@ namespace EonaCat.VolumeMixer.Managers }); } + // --- Get Default Device --- public async Task GetDefaultAudioDeviceAsync(DataFlow dataFlow = DataFlow.Output) { return await Task.Run(() => { if (_deviceEnumerator == null) - { return null; - } lock (_syncLock) { @@ -173,19 +170,17 @@ namespace EonaCat.VolumeMixer.Managers return "Unknown Device"; } + // --- System (Output) Volume and Mute --- + public async Task SetSystemVolumeAsync(float volume) { if (volume < 0f || volume > 1f) - { return false; - } - AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(); + AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output); if (defaultDevice == null) - { return false; - } try { @@ -199,12 +194,10 @@ namespace EonaCat.VolumeMixer.Managers public async Task GetSystemVolumeAsync() { - AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(); + AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output); if (defaultDevice == null) - { return 0f; - } try { @@ -218,12 +211,10 @@ namespace EonaCat.VolumeMixer.Managers public async Task SetSystemMuteAsync(bool mute) { - AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(); + AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output); if (defaultDevice == null) - { return false; - } try { @@ -237,12 +228,10 @@ namespace EonaCat.VolumeMixer.Managers public async Task GetSystemMuteAsync() { - AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(); + AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output); if (defaultDevice == null) - { return false; - } try { @@ -254,13 +243,113 @@ namespace EonaCat.VolumeMixer.Managers } } + // --- Microphone (Input) Volume and Mute --- + + 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; + } + + // --- Audio Sessions (All devices) --- + public async Task> GetAllActiveSessionsAsync() { return await Task.Run(async () => { var allSessions = new List(); - List devices = await GetAudioDevicesAsync(); + List devices = await GetAudioDevicesAsync(DataFlow.Output); foreach (var device in devices) { @@ -282,6 +371,18 @@ namespace EonaCat.VolumeMixer.Managers }); } + public async Task> GetMicrophonesAsync() + { + return await GetAudioDevicesAsync(DataFlow.Input); + } + + public async Task GetDefaultMicrophoneAsync() + { + return await GetDefaultAudioDeviceAsync(DataFlow.Input); + } + + // --- Dispose --- + public void Dispose() { lock (_syncLock) diff --git a/EonaCat.VolumeMixer/Models/AudioDevice.cs b/EonaCat.VolumeMixer/Models/AudioDevice.cs index ec5c711..c950a29 100644 --- a/EonaCat.VolumeMixer/Models/AudioDevice.cs +++ b/EonaCat.VolumeMixer/Models/AudioDevice.cs @@ -2,6 +2,7 @@ using EonaCat.VolumeMixer.Interfaces; using System; using System.Collections.Generic; +using System.Runtime.InteropServices; using System.Threading.Tasks; namespace EonaCat.VolumeMixer.Models @@ -10,6 +11,7 @@ namespace EonaCat.VolumeMixer.Models // 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; private IAudioEndpointVolume _endpointVolume; private IAudioSessionManager _sessionManager; @@ -79,8 +81,8 @@ namespace EonaCat.VolumeMixer.Models try { - var hr = _endpointVolume.GetMasterVolumeLevelScalar(out var volume); - return hr == 0 ? volume : 0f; + var result = _endpointVolume.GetMasterVolumeLevelScalar(out var volume); + return result == 0 ? volume : 0f; } catch { @@ -214,7 +216,15 @@ namespace EonaCat.VolumeMixer.Models result = sessionEnum.GetSession(i, out var sessionControl); if (result == 0 && sessionControl != null) { - sessions.Add(new AudioSession(sessionControl, _sessionManager)); + 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 @@ -233,6 +243,30 @@ namespace EonaCat.VolumeMixer.Models }); } + private IAudioSessionControlExtended GetSessionControl2(object sessionControl) + { + if (sessionControl == null) + return null; + + var unknownPtr = Marshal.GetIUnknownForObject(sessionControl); + try + { + IntPtr sessionControl2Ptr; + int result = Marshal.QueryInterface(unknownPtr, ref AudioController2Guid, out sessionControl2Ptr); + if (result == 0 && sessionControl2Ptr != IntPtr.Zero) + { + var sessionControl2 = (IAudioSessionControlExtended)Marshal.GetObjectForIUnknown(sessionControl2Ptr); + Marshal.Release(sessionControl2Ptr); + return sessionControl2; + } + } + finally + { + Marshal.Release(unknownPtr); + } + return null; + } + public async Task StepUpAsync(int delayMs = 20) { bool success = false; diff --git a/EonaCat.VolumeMixer/Models/AudioSession.cs b/EonaCat.VolumeMixer/Models/AudioSession.cs index c25328f..db0d648 100644 --- a/EonaCat.VolumeMixer/Models/AudioSession.cs +++ b/EonaCat.VolumeMixer/Models/AudioSession.cs @@ -2,12 +2,11 @@ using EonaCat.VolumeMixer.Interfaces; using System; using System.Diagnostics; +using System.Runtime.InteropServices; 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 AudioSession : IDisposable { private readonly IAudioSessionControlExtended _sessionControl; @@ -24,24 +23,27 @@ namespace EonaCat.VolumeMixer.Models { _sessionControl = sessionControl; LoadSessionInfo(); - InitializeVolume(sessionManager); + InitializeSimpleAudioVolume(); } - private void InitializeVolume(IAudioSessionManager sessionManager) + private void InitializeSimpleAudioVolume() { lock (_syncLock) { try { - var result = _sessionControl.GetGroupingParam(out var guid); - if (result == 0) + var sessionControlUnknown = Marshal.GetIUnknownForObject(_sessionControl); + var iidSimpleAudioVolume = typeof(IAudioVolume).GUID; + IntPtr simpleAudioVolumePtr = IntPtr.Zero; + + int result = Marshal.QueryInterface(sessionControlUnknown, ref iidSimpleAudioVolume, out simpleAudioVolumePtr); + if (result == 0 && simpleAudioVolumePtr != IntPtr.Zero) { - result = sessionManager.GetAudioVolume(ref guid, 0, out var ptr); - if (result == 0 && ptr != IntPtr.Zero) - { - _audioVolume = ComHelper.GetInterface(ptr); - } + _audioVolume = (IAudioVolume)Marshal.GetObjectForIUnknown(simpleAudioVolumePtr); + Marshal.Release(simpleAudioVolumePtr); } + + Marshal.Release(sessionControlUnknown); } catch { @@ -91,7 +93,7 @@ namespace EonaCat.VolumeMixer.Models try { - var result = _audioVolume.GetMasterVolume(out var volume); + int result = _audioVolume.GetMasterVolume(out var volume); return result == 0 ? volume : 0f; } catch @@ -109,7 +111,7 @@ namespace EonaCat.VolumeMixer.Models return false; } - IAudioVolume audioVolCopy; + IAudioVolume simpleAudioVolCopy; lock (_syncLock) { if (_isDisposed || _audioVolume == null) @@ -117,7 +119,7 @@ namespace EonaCat.VolumeMixer.Models return false; } - audioVolCopy = _audioVolume; + simpleAudioVolCopy = _audioVolume; } var guid = Guid.Empty; @@ -126,8 +128,8 @@ namespace EonaCat.VolumeMixer.Models { try { - var hr = audioVolCopy.SetMasterVolume(volume, ref guid); - if (hr == 0) + var result = simpleAudioVolCopy.SetMasterVolume(volume, ref guid); + if (result == 0) { await Task.Delay(delayMs); @@ -175,7 +177,7 @@ namespace EonaCat.VolumeMixer.Models public async Task SetMuteAsync(bool mute) { - IAudioVolume audioVolCopy; + IAudioVolume simpleAudioVolCopy; lock (_syncLock) { if (_isDisposed || _audioVolume == null) @@ -183,13 +185,13 @@ namespace EonaCat.VolumeMixer.Models return false; } - audioVolCopy = _audioVolume; + simpleAudioVolCopy = _audioVolume; } try { var guid = Guid.Empty; - return await Task.Run(() => audioVolCopy.SetMute(mute, ref guid) == 0); + return await Task.Run(() => simpleAudioVolCopy.SetMute(mute, ref guid) == 0); } catch { @@ -221,6 +223,13 @@ namespace EonaCat.VolumeMixer.Models }); } + public async Task GetEffectiveVolumeAsync(AudioDevice device) + { + var deviceVolume = await device.GetMasterVolumeAsync(); + var sessionVolume = await GetVolumeAsync(); + return deviceVolume * sessionVolume; + } + public void Dispose() { lock (_syncLock) @@ -230,7 +239,11 @@ namespace EonaCat.VolumeMixer.Models return; } - ComHelper.ReleaseComObject(_audioVolume); + if (_audioVolume != null) + { + Marshal.ReleaseComObject(_audioVolume); + _audioVolume = null; + } ComHelper.ReleaseComObject(_sessionControl); _isDisposed = true; }