Fixed session info retrieval

This commit is contained in:
EonaCat 2025-07-22 21:43:23 +02:00
parent c2043acbd3
commit 050327f56d
10 changed files with 206 additions and 207 deletions

View File

@ -99,15 +99,11 @@ class Program
{ {
Console.WriteLine($"Error: {ex.Message}"); Console.WriteLine($"Error: {ex.Message}");
} }
}
// Microphone management example
using (var micManager = new MicrophoneManager())
{
try try
{ {
// Get all microphones // Get all microphones
var microphones = await micManager.GetMicrophonesAsync(); var microphones = await volumeMixer.GetMicrophonesAsync();
Console.WriteLine($"Found {microphones.Count} microphones:"); Console.WriteLine($"Found {microphones.Count} microphones:");
foreach (var mic in microphones) foreach (var mic in microphones)
@ -119,7 +115,7 @@ class Program
} }
// Default microphone // Default microphone
using (var defaultMic = await micManager.GetDefaultMicrophoneAsync()) using (var defaultMic = await volumeMixer.GetDefaultMicrophoneAsync())
{ {
Console.WriteLine($"\nSetting default microphone volume to 1%"); Console.WriteLine($"\nSetting default microphone volume to 1%");
bool success = await defaultMic.SetMasterVolumeAsync(0.1f); 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 volume: {await volumeMixer.GetMicrophoneVolumeAsync():P0}");
Console.WriteLine($"Default mic muted: {await micManager.GetMicrophoneMuteAsync()}"); Console.WriteLine($"Default mic muted: {await volumeMixer.GetMicrophoneMuteAsync()}");
// Set microphone volume to 60% // 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}"); Console.WriteLine($"Set microphone volume to 60%: {result}");
// Set specific microphone by name // 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}"); Console.WriteLine($"Set USB microphone volume to 70%: {result}");
} }

View File

@ -18,9 +18,9 @@
<PackageTags>EonaCat, Audio, Volume, Mixer .NET Standard, Jeroen, Saey</PackageTags> <PackageTags>EonaCat, Audio, Volume, Mixer .NET Standard, Jeroen, Saey</PackageTags>
<PackageReleaseNotes></PackageReleaseNotes> <PackageReleaseNotes></PackageReleaseNotes>
<Description>EonaCat VolumeMixer</Description> <Description>EonaCat VolumeMixer</Description>
<Version>1.0.0</Version> <Version>1.0.3</Version>
<AssemblyVersion>1.0.0.0</AssemblyVersion> <AssemblyVersion>1.0.0.3</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion> <FileVersion>1.0.0.3</FileVersion>
<PackageIcon>icon.png</PackageIcon> <PackageIcon>icon.png</PackageIcon>
</PropertyGroup> </PropertyGroup>

View File

@ -11,6 +11,7 @@ namespace EonaCat.VolumeMixer.Interfaces
internal interface IAudioSessionEnumerator internal interface IAudioSessionEnumerator
{ {
int GetCount(out int SessionCount); int GetCount(out int SessionCount);
int GetSession(int SessionNumber, out IAudioSessionControlExtended Session); int GetSession(int SessionNumber, out IAudioSessionControl Session);
} }
} }

View File

@ -8,11 +8,11 @@ namespace EonaCat.VolumeMixer.Interfaces
[ComImport] [ComImport]
[Guid("87CE5498-68D6-44E5-9215-6DA47EF883D8")] [Guid("87CE5498-68D6-44E5-9215-6DA47EF883D8")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IAudioVolume interface IAudioVolume
{ {
int SetMasterVolume(float fLevel, ref Guid EventContext); int SetMasterVolume(float fLevel, ref Guid EventContext);
int GetMasterVolume(out float pfLevel); int GetMasterVolume(out float pfLevel);
int SetMute([MarshalAs(UnmanagedType.Bool)] bool bMute, ref Guid EventContext); int SetMute(bool bMute, ref Guid EventContext);
int GetMute([MarshalAs(UnmanagedType.Bool)] out bool pbMute); int GetMute(out bool pbMute);
} }
} }

View File

@ -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<List<AudioDevice>> GetMicrophonesAsync()
{
return await _volumeMixer.GetAudioDevicesAsync(DataFlow.Input);
}
public async Task<AudioDevice> GetDefaultMicrophoneAsync()
{
return await _volumeMixer.GetDefaultAudioDeviceAsync(DataFlow.Input);
}
public async Task<bool> 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<float> GetMicrophoneVolumeAsync()
{
AudioDevice defaultMic = await GetDefaultMicrophoneAsync();
if (defaultMic == null)
{
return 0f;
}
try
{
return await defaultMic.GetMasterVolumeAsync();
}
finally
{
defaultMic.Dispose();
}
}
public async Task<bool> SetMicrophoneMuteAsync(bool mute)
{
AudioDevice defaultMic = await GetDefaultMicrophoneAsync();
if (defaultMic == null)
{
return false;
}
try
{
return await defaultMic.SetMasterMuteAsync(mute);
}
finally
{
defaultMic.Dispose();
}
}
public async Task<bool> GetMicrophoneMuteAsync()
{
AudioDevice defaultMic = await GetDefaultMicrophoneAsync();
if (defaultMic == null)
{
return false;
}
try
{
return await defaultMic.GetMasterMuteAsync();
}
finally
{
defaultMic.Dispose();
}
}
public async Task<bool> SetMicrophoneVolumeByNameAsync(string microphoneName, float volume)
{
if (string.IsNullOrWhiteSpace(microphoneName) || volume < 0f || volume > 1f)
{
return false;
}
List<AudioDevice> 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();
}
}
}
}

View File

@ -30,6 +30,7 @@ namespace EonaCat.VolumeMixer.Managers
} }
} }
// --- Get Devices ---
public async Task<List<AudioDevice>> GetAudioDevicesAsync(DataFlow dataFlow = DataFlow.Output) public async Task<List<AudioDevice>> GetAudioDevicesAsync(DataFlow dataFlow = DataFlow.Output)
{ {
return await Task.Run(() => return await Task.Run(() =>
@ -37,9 +38,7 @@ namespace EonaCat.VolumeMixer.Managers
var devices = new List<AudioDevice>(); var devices = new List<AudioDevice>();
if (_deviceEnumerator == null) if (_deviceEnumerator == null)
{
return devices; return devices;
}
lock (_syncLock) lock (_syncLock)
{ {
@ -47,13 +46,12 @@ namespace EonaCat.VolumeMixer.Managers
{ {
var result = _deviceEnumerator.EnumAudioEndpoints(dataFlow, DeviceState.Active, out var deviceCollection); var result = _deviceEnumerator.EnumAudioEndpoints(dataFlow, DeviceState.Active, out var deviceCollection);
if (result != 0 || deviceCollection == null) if (result != 0 || deviceCollection == null)
{
return devices; return devices;
}
result = deviceCollection.GetCount(out var count); result = deviceCollection.GetCount(out var count);
if (result != 0) if (result != 0)
{ {
ComHelper.ReleaseComObject(deviceCollection);
return devices; return devices;
} }
@ -110,14 +108,13 @@ namespace EonaCat.VolumeMixer.Managers
}); });
} }
// --- Get Default Device ---
public async Task<AudioDevice> GetDefaultAudioDeviceAsync(DataFlow dataFlow = DataFlow.Output) public async Task<AudioDevice> GetDefaultAudioDeviceAsync(DataFlow dataFlow = DataFlow.Output)
{ {
return await Task.Run(() => return await Task.Run(() =>
{ {
if (_deviceEnumerator == null) if (_deviceEnumerator == null)
{
return null; return null;
}
lock (_syncLock) lock (_syncLock)
{ {
@ -173,19 +170,17 @@ namespace EonaCat.VolumeMixer.Managers
return "Unknown Device"; return "Unknown Device";
} }
// --- System (Output) Volume and Mute ---
public async Task<bool> SetSystemVolumeAsync(float volume) public async Task<bool> SetSystemVolumeAsync(float volume)
{ {
if (volume < 0f || volume > 1f) if (volume < 0f || volume > 1f)
{
return false; return false;
}
AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(); AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output);
if (defaultDevice == null) if (defaultDevice == null)
{
return false; return false;
}
try try
{ {
@ -199,12 +194,10 @@ namespace EonaCat.VolumeMixer.Managers
public async Task<float> GetSystemVolumeAsync() public async Task<float> GetSystemVolumeAsync()
{ {
AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(); AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output);
if (defaultDevice == null) if (defaultDevice == null)
{
return 0f; return 0f;
}
try try
{ {
@ -218,12 +211,10 @@ namespace EonaCat.VolumeMixer.Managers
public async Task<bool> SetSystemMuteAsync(bool mute) public async Task<bool> SetSystemMuteAsync(bool mute)
{ {
AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(); AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output);
if (defaultDevice == null) if (defaultDevice == null)
{
return false; return false;
}
try try
{ {
@ -237,12 +228,10 @@ namespace EonaCat.VolumeMixer.Managers
public async Task<bool> GetSystemMuteAsync() public async Task<bool> GetSystemMuteAsync()
{ {
AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(); AudioDevice defaultDevice = await GetDefaultAudioDeviceAsync(DataFlow.Output);
if (defaultDevice == null) if (defaultDevice == null)
{
return false; return false;
}
try try
{ {
@ -254,13 +243,113 @@ namespace EonaCat.VolumeMixer.Managers
} }
} }
// --- Microphone (Input) Volume and Mute ---
public async Task<bool> 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<float> GetMicrophoneVolumeAsync()
{
AudioDevice defaultMic = await GetDefaultAudioDeviceAsync(DataFlow.Input);
if (defaultMic == null)
return 0f;
try
{
return await defaultMic.GetMasterVolumeAsync();
}
finally
{
defaultMic.Dispose();
}
}
public async Task<bool> 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<bool> GetMicrophoneMuteAsync()
{
AudioDevice defaultMic = await GetDefaultAudioDeviceAsync(DataFlow.Input);
if (defaultMic == null)
return false;
try
{
return await defaultMic.GetMasterMuteAsync();
}
finally
{
defaultMic.Dispose();
}
}
public async Task<bool> SetMicrophoneVolumeByNameAsync(string microphoneName, float volume)
{
if (string.IsNullOrWhiteSpace(microphoneName) || volume < 0f || volume > 1f)
return false;
List<AudioDevice> 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<List<AudioSession>> GetAllActiveSessionsAsync() public async Task<List<AudioSession>> GetAllActiveSessionsAsync()
{ {
return await Task.Run(async () => return await Task.Run(async () =>
{ {
var allSessions = new List<AudioSession>(); var allSessions = new List<AudioSession>();
List<AudioDevice> devices = await GetAudioDevicesAsync(); List<AudioDevice> devices = await GetAudioDevicesAsync(DataFlow.Output);
foreach (var device in devices) foreach (var device in devices)
{ {
@ -282,6 +371,18 @@ namespace EonaCat.VolumeMixer.Managers
}); });
} }
public async Task<List<AudioDevice>> GetMicrophonesAsync()
{
return await GetAudioDevicesAsync(DataFlow.Input);
}
public async Task<AudioDevice> GetDefaultMicrophoneAsync()
{
return await GetDefaultAudioDeviceAsync(DataFlow.Input);
}
// --- Dispose ---
public void Dispose() public void Dispose()
{ {
lock (_syncLock) lock (_syncLock)

View File

@ -2,6 +2,7 @@
using EonaCat.VolumeMixer.Interfaces; using EonaCat.VolumeMixer.Interfaces;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace EonaCat.VolumeMixer.Models 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. // See the LICENSE file or go to https://EonaCat.com/License for full license details.
public class AudioDevice : IDisposable public class AudioDevice : IDisposable
{ {
internal Guid AudioController2Guid = new Guid("bfb7ff88-7239-4fc9-8fa2-07c950be9c6d");
private readonly IMultiMediaDevice _device; private readonly IMultiMediaDevice _device;
private IAudioEndpointVolume _endpointVolume; private IAudioEndpointVolume _endpointVolume;
private IAudioSessionManager _sessionManager; private IAudioSessionManager _sessionManager;
@ -79,8 +81,8 @@ namespace EonaCat.VolumeMixer.Models
try try
{ {
var hr = _endpointVolume.GetMasterVolumeLevelScalar(out var volume); var result = _endpointVolume.GetMasterVolumeLevelScalar(out var volume);
return hr == 0 ? volume : 0f; return result == 0 ? volume : 0f;
} }
catch catch
{ {
@ -214,7 +216,15 @@ namespace EonaCat.VolumeMixer.Models
result = sessionEnum.GetSession(i, out var sessionControl); result = sessionEnum.GetSession(i, out var sessionControl);
if (result == 0 && sessionControl != null) 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 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<bool> StepUpAsync(int delayMs = 20) public async Task<bool> StepUpAsync(int delayMs = 20)
{ {
bool success = false; bool success = false;

View File

@ -2,12 +2,11 @@
using EonaCat.VolumeMixer.Interfaces; using EonaCat.VolumeMixer.Interfaces;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace EonaCat.VolumeMixer.Models 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 public class AudioSession : IDisposable
{ {
private readonly IAudioSessionControlExtended _sessionControl; private readonly IAudioSessionControlExtended _sessionControl;
@ -24,24 +23,27 @@ namespace EonaCat.VolumeMixer.Models
{ {
_sessionControl = sessionControl; _sessionControl = sessionControl;
LoadSessionInfo(); LoadSessionInfo();
InitializeVolume(sessionManager); InitializeSimpleAudioVolume();
} }
private void InitializeVolume(IAudioSessionManager sessionManager) private void InitializeSimpleAudioVolume()
{ {
lock (_syncLock) lock (_syncLock)
{ {
try try
{ {
var result = _sessionControl.GetGroupingParam(out var guid); var sessionControlUnknown = Marshal.GetIUnknownForObject(_sessionControl);
if (result == 0) 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); _audioVolume = (IAudioVolume)Marshal.GetObjectForIUnknown(simpleAudioVolumePtr);
if (result == 0 && ptr != IntPtr.Zero) Marshal.Release(simpleAudioVolumePtr);
{
_audioVolume = ComHelper.GetInterface<IAudioVolume>(ptr);
}
} }
Marshal.Release(sessionControlUnknown);
} }
catch catch
{ {
@ -91,7 +93,7 @@ namespace EonaCat.VolumeMixer.Models
try try
{ {
var result = _audioVolume.GetMasterVolume(out var volume); int result = _audioVolume.GetMasterVolume(out var volume);
return result == 0 ? volume : 0f; return result == 0 ? volume : 0f;
} }
catch catch
@ -109,7 +111,7 @@ namespace EonaCat.VolumeMixer.Models
return false; return false;
} }
IAudioVolume audioVolCopy; IAudioVolume simpleAudioVolCopy;
lock (_syncLock) lock (_syncLock)
{ {
if (_isDisposed || _audioVolume == null) if (_isDisposed || _audioVolume == null)
@ -117,7 +119,7 @@ namespace EonaCat.VolumeMixer.Models
return false; return false;
} }
audioVolCopy = _audioVolume; simpleAudioVolCopy = _audioVolume;
} }
var guid = Guid.Empty; var guid = Guid.Empty;
@ -126,8 +128,8 @@ namespace EonaCat.VolumeMixer.Models
{ {
try try
{ {
var hr = audioVolCopy.SetMasterVolume(volume, ref guid); var result = simpleAudioVolCopy.SetMasterVolume(volume, ref guid);
if (hr == 0) if (result == 0)
{ {
await Task.Delay(delayMs); await Task.Delay(delayMs);
@ -175,7 +177,7 @@ namespace EonaCat.VolumeMixer.Models
public async Task<bool> SetMuteAsync(bool mute) public async Task<bool> SetMuteAsync(bool mute)
{ {
IAudioVolume audioVolCopy; IAudioVolume simpleAudioVolCopy;
lock (_syncLock) lock (_syncLock)
{ {
if (_isDisposed || _audioVolume == null) if (_isDisposed || _audioVolume == null)
@ -183,13 +185,13 @@ namespace EonaCat.VolumeMixer.Models
return false; return false;
} }
audioVolCopy = _audioVolume; simpleAudioVolCopy = _audioVolume;
} }
try try
{ {
var guid = Guid.Empty; 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 catch
{ {
@ -221,6 +223,13 @@ namespace EonaCat.VolumeMixer.Models
}); });
} }
public async Task<float> GetEffectiveVolumeAsync(AudioDevice device)
{
var deviceVolume = await device.GetMasterVolumeAsync();
var sessionVolume = await GetVolumeAsync();
return deviceVolume * sessionVolume;
}
public void Dispose() public void Dispose()
{ {
lock (_syncLock) lock (_syncLock)
@ -230,7 +239,11 @@ namespace EonaCat.VolumeMixer.Models
return; return;
} }
ComHelper.ReleaseComObject(_audioVolume); if (_audioVolume != null)
{
Marshal.ReleaseComObject(_audioVolume);
_audioVolume = null;
}
ComHelper.ReleaseComObject(_sessionControl); ComHelper.ReleaseComObject(_sessionControl);
_isDisposed = true; _isDisposed = true;
} }