Added default device selection

This commit is contained in:
2025-10-08 19:26:01 +02:00
7 changed files with 187 additions and 97 deletions

View File

@@ -70,6 +70,7 @@ namespace EonaCat.VolumeMixer.Tester.WPF
await RefreshSessions();
_refreshTimer.Start();
_pollTimer.Start();
_currentDevice.SetDefaultDevice();
}
private async Task RefreshSessions()

View File

@@ -1,6 +1,7 @@
using EonaCat.VolumeMixer.Managers;
using EonaCat.VolumeMixer.Models;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
class Program
@@ -15,16 +16,6 @@ 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:");

View File

@@ -18,9 +18,9 @@
<PackageTags>EonaCat, Audio, Volume, Mixer .NET Standard, Jeroen, Saey</PackageTags>
<PackageReleaseNotes></PackageReleaseNotes>
<Description>EonaCat VolumeMixer</Description>
<Version>1.0.6</Version>
<AssemblyVersion>1.0.0.6</AssemblyVersion>
<FileVersion>1.0.0.6</FileVersion>
<Version>1.0.7</Version>
<AssemblyVersion>1.0.0.7</AssemblyVersion>
<FileVersion>1.0.0.7</FileVersion>
<PackageIcon>icon.png</PackageIcon>
<RepositoryUrl>https://git.saey.me/EonaCat/EonaCat.VolumeMixer</RepositoryUrl>
<RepositoryType>git</RepositoryType>
@@ -51,4 +51,8 @@
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
</ItemGroup>
</Project>

View File

@@ -1,62 +0,0 @@
using System;
using System.Runtime.InteropServices;
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.
internal static class ComHelper
{
public static T GetInterface<T>(IntPtr ptr) where T : class
{
if (ptr == IntPtr.Zero)
{
return null;
}
try
{
return Marshal.GetObjectForIUnknown(ptr) as T;
}
catch
{
return null;
}
finally
{
if (ptr != IntPtr.Zero)
{
Marshal.Release(ptr);
}
}
}
public static void ReleaseComObject(object comObj)
{
if (comObj == null)
{
return;
}
try
{
while (Marshal.ReleaseComObject(comObj) > 0)
{
// Do nothing
}
}
catch
{
try
{
Marshal.FinalReleaseComObject(comObj);
}
catch
{
// Do nothing
}
}
}
}
}

View File

@@ -41,7 +41,10 @@ namespace EonaCat.VolumeMixer.Managers
return await Task.Run(() =>
{
var devices = new List<AudioDevice>();
if (_isDisposed || _deviceEnumerator == null) return devices;
if (_isDisposed || _deviceEnumerator == null)
{
return devices;
}
lock (_syncLock)
{
@@ -51,10 +54,16 @@ namespace EonaCat.VolumeMixer.Managers
try
{
int hr = _deviceEnumerator.EnumAudioEndpoints(dataFlow, DeviceState.Active, out collection);
if (hr != 0 || collection == null) return devices;
if (hr != 0 || collection == null)
{
return devices;
}
hr = collection.GetCount(out var count);
if (hr != 0) return devices;
if (hr != 0)
{
return devices;
}
// Get default device
string defaultId = string.Empty;
@@ -77,9 +86,15 @@ namespace EonaCat.VolumeMixer.Managers
IMultiMediaDevice device = null;
try
{
if (collection.Item(i, out device) != 0 || device == null) continue;
if (collection.Item(i, out device) != 0 || device == null)
{
continue;
}
if (device.GetId(out var id) != 0 || string.IsNullOrEmpty(id)) continue;
if (device.GetId(out var id) != 0 || string.IsNullOrEmpty(id))
{
continue;
}
string name = GetDeviceName(device);
DeviceType type = GetDeviceType(device);
@@ -131,7 +146,10 @@ namespace EonaCat.VolumeMixer.Managers
public async Task<AudioDevice> GetDefaultAudioDeviceAsync(DataFlow dataFlow = DataFlow.Output)
{
if (_isDisposed || _deviceEnumerator == null) return null;
if (_isDisposed || _deviceEnumerator == null)
{
return null;
}
return await Task.Run(() =>
{
@@ -165,7 +183,11 @@ namespace EonaCat.VolumeMixer.Managers
try
{
if (device.OpenPropertyStore(0, out store) != 0 || store == null) return "Unknown Device";
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)
{
@@ -190,7 +212,11 @@ namespace EonaCat.VolumeMixer.Managers
try
{
if (device.OpenPropertyStore(0, out store) != 0 || store == null) return DeviceType.Unknown;
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)
{
return prop.uintValue switch
@@ -425,7 +451,11 @@ namespace EonaCat.VolumeMixer.Managers
{
lock (_syncLock)
{
if (_isDisposed) return;
if (_isDisposed)
{
return;
}
_isDisposed = true;
SafeRelease(ref _deviceEnumerator);
}

View File

@@ -110,7 +110,10 @@ namespace EonaCat.VolumeMixer.Models
public async Task<bool> SetMasterVolumeAsync(float volume)
{
if (_isDisposed || _endpointVolume == null || volume < 0f || volume > 1f) return false;
if (_isDisposed || _endpointVolume == null || volume < 0f || volume > 1f)
{
return false;
}
var guid = Guid.Empty;
lock (_syncLock)
@@ -123,7 +126,11 @@ namespace EonaCat.VolumeMixer.Models
{
lock (_syncLock)
{
if (_isDisposed || _endpointVolume == null) return Task.FromResult(false);
if (_isDisposed || _endpointVolume == null)
{
return Task.FromResult(false);
}
try
{
int hr = _endpointVolume.GetMute(out bool mute);
@@ -135,7 +142,10 @@ namespace EonaCat.VolumeMixer.Models
public Task<bool> SetMasterMuteAsync(bool mute)
{
if (_isDisposed || _endpointVolume == null) return Task.FromResult(false);
if (_isDisposed || _endpointVolume == null)
{
return Task.FromResult(false);
}
var guid = Guid.Empty;
lock (_syncLock)
@@ -147,19 +157,28 @@ namespace EonaCat.VolumeMixer.Models
public async Task<List<AudioSession>> GetAudioSessionsAsync()
{
var sessions = new List<AudioSession>();
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;
if (hr != 0 || sessionEnum == null)
{
return sessions;
}
try
{
hr = sessionEnum.GetCount(out int count);
if (hr != 0) return sessions;
if (hr != 0)
{
return sessions;
}
for (int i = 0; i < count; i++)
{
@@ -195,7 +214,10 @@ namespace EonaCat.VolumeMixer.Models
private IAudioSessionControlExtended GetSessionControl2(object sessionControl)
{
if (sessionControl == null) return null;
if (sessionControl == null)
{
return null;
}
IntPtr unknownPtr = IntPtr.Zero;
IntPtr sessionControl2Ptr = IntPtr.Zero;
@@ -211,8 +233,15 @@ namespace EonaCat.VolumeMixer.Models
catch (Exception ex) { Debug.WriteLine($"GetSessionControl2 failed: {ex}"); }
finally
{
if (sessionControl2Ptr != IntPtr.Zero) Marshal.Release(sessionControl2Ptr);
if (unknownPtr != IntPtr.Zero) Marshal.Release(unknownPtr);
if (sessionControl2Ptr != IntPtr.Zero)
{
Marshal.Release(sessionControl2Ptr);
}
if (unknownPtr != IntPtr.Zero)
{
Marshal.Release(unknownPtr);
}
}
return null;
}
@@ -273,7 +302,11 @@ namespace EonaCat.VolumeMixer.Models
{
lock (_syncLock)
{
if (_isDisposed) return;
if (_isDisposed)
{
return;
}
_isDisposed = true;
SafeRelease(ref _endpointVolume);
@@ -282,6 +315,28 @@ namespace EonaCat.VolumeMixer.Models
}
}
public bool SetDefaultDevice()
{
try
{
var policyConfig = new PolicyConfigClient();
policyConfig.SetDefaultEndpoint(Id, Role.Multimedia);
policyConfig.SetDefaultEndpoint(Id, Role.Console);
if (DeviceType == DeviceType.Microphone)
{
policyConfig.SetDefaultEndpoint(Id, Role.Communications);
}
return true;
}
catch (Exception ex)
{
Console.WriteLine($"Error setting default device: {ex.Message}");
return false;
}
}
private static void SafeRelease<T>(ref T comObj) where T : class
{
if (comObj != null)
@@ -290,5 +345,66 @@ namespace EonaCat.VolumeMixer.Models
comObj = null;
}
}
internal void SendException(Exception exception)
{
OnException?.Invoke(this, exception);
}
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("F8679F50-850A-41CF-9C72-430F290290C8")]
internal interface IPolicyConfig
{
[PreserveSig]
int GetMixFormat(string pstrDeviceName, IntPtr ppFormat);
[PreserveSig]
int GetDeviceFormat(string pstrDeviceName, bool bDefault, IntPtr ppFormat);
[PreserveSig]
int ResetDeviceFormat(string pstrDeviceName);
[PreserveSig]
int SetDeviceFormat(string pstrDeviceName, IntPtr pEndpointFormat, IntPtr MixFormat);
[PreserveSig]
int GetProcessingPeriod(string pstrDeviceName, bool bDefault, IntPtr pmftDefaultPeriod, IntPtr pmftMinimumPeriod);
[PreserveSig]
int SetProcessingPeriod(string pstrDeviceName, IntPtr pmftPeriod);
[PreserveSig]
int GetShareMode(string pstrDeviceName, IntPtr pMode);
[PreserveSig]
int SetShareMode(string pstrDeviceName, IntPtr mode);
[PreserveSig]
int GetPropertyValue(string pstrDeviceName, bool bFxStore, ref PropertyKey key, IntPtr pv);
[PreserveSig]
int SetPropertyValue(string pstrDeviceName, bool bFxStore, ref PropertyKey key, IntPtr pv);
[PreserveSig]
int SetDefaultEndpoint(string pstrDeviceName, Role role);
[PreserveSig]
int SetEndpointVisibility(string pstrDeviceName, bool bVisible);
}
//[ComImport, Guid("870AF99C-171D-4F9E-AF0D-E63DF40C2BC9")]
internal class PolicyConfigClient
{
private readonly IPolicyConfig policyConfig;
public PolicyConfigClient()
{
policyConfig = (IPolicyConfig)Activator.CreateInstance(Type.GetTypeFromCLSID(new Guid("870AF99C-171D-4F9E-AF0D-E63DF40C2BC9")));
}
public void SetDefaultEndpoint(string deviceId, Role role)
{
policyConfig.SetDefaultEndpoint(deviceId, role);
}
}
}
}

View File

@@ -30,7 +30,10 @@ namespace EonaCat.VolumeMixer.Models
{
lock (_syncLock)
{
if (_sessionControl == null) return;
if (_sessionControl == null)
{
return;
}
IntPtr unknownPtr = IntPtr.Zero;
try
@@ -48,7 +51,10 @@ namespace EonaCat.VolumeMixer.Models
catch { _audioVolume = null; }
finally
{
if (unknownPtr != IntPtr.Zero) Marshal.Release(unknownPtr);
if (unknownPtr != IntPtr.Zero)
{
Marshal.Release(unknownPtr);
}
}
}
}
@@ -255,7 +261,11 @@ namespace EonaCat.VolumeMixer.Models
{
lock (_syncLock)
{
if (_isDisposed) return;
if (_isDisposed)
{
return;
}
_isDisposed = true;
SafeRelease(ref _audioVolume);