Skip to content

Commit 899058b

Browse files
committed
feat(Core): Replace core of the software by the CoreAudio library
Should help with all issue about not switching devices anymore. Fixes #1184 Fixes #1164 Fixes #1175
1 parent 116c942 commit 899058b

37 files changed

+96
-83
lines changed

SoundSwitch.Audio.Manager/AudioSwitcher.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
using System;
33
using System.Diagnostics;
44
using System.Linq;
5-
using NAudio.CoreAudioApi;
5+
using CoreAudio;
66
using SoundSwitch.Audio.Manager.Interop.Client;
77
using SoundSwitch.Audio.Manager.Interop.Com.Threading;
88
using SoundSwitch.Audio.Manager.Interop.Com.User;
@@ -212,7 +212,7 @@ public bool IsDefault(string deviceId, EDataFlow flow, ERole role)
212212
/// <param name="device"></param>
213213
/// <param name="interaction"></param>
214214
/// <typeparam name="T"></typeparam>
215-
public T InteractWithMmDevice<T>(MMDevice device, Func<MMDevice, T> interaction) => ComThread.Invoke(() => interaction(device));
215+
public T InteractWithMmDevice<T>(MMDevice? device, Func<MMDevice?, T> interaction) => ComThread.Invoke(() => interaction(device));
216216

217217
/// <summary>
218218
/// Get the current default endpoint

SoundSwitch.Audio.Manager/Interop/Client/EnumeratorClient.cs

+2-7
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#nullable enable
22
using System;
33
using System.Runtime.InteropServices;
4-
using NAudio.CoreAudioApi;
4+
using CoreAudio;
55
using SoundSwitch.Audio.Manager.Interop.Enum;
66
using SoundSwitch.Audio.Manager.Interop.Interface;
77

@@ -13,12 +13,7 @@ internal class EnumeratorClient
1313

1414
public EnumeratorClient()
1515
{
16-
_enumerator = new MMDeviceEnumerator();
17-
}
18-
19-
~EnumeratorClient()
20-
{
21-
_enumerator.Dispose();
16+
_enumerator = new MMDeviceEnumerator(Guid.NewGuid());
2217
}
2318

2419
public bool IsDefault(string deviceId, EDataFlow flow, ERole role)

SoundSwitch.Audio.Manager/SoundSwitch.Audio.Manager.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<Version>4.0.0</Version>
1111
</PropertyGroup>
1212
<ItemGroup>
13+
<PackageReference Include="CoreAudio" Version="1.27.0" />
1314
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
1415
<PackageReference Include="Microsoft.Windows.CsWinRT" Version="2.0.2" />
1516
<PackageReference Include="Serilog" Version="2.12.0" />

SoundSwitch.Common/Framework/Audio/Collection/DeviceReadOnlyCollection.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System.Collections;
22
using System.Collections.Generic;
33
using System.Linq;
4-
using NAudio.CoreAudioApi;
4+
using CoreAudio;
55
using SoundSwitch.Common.Framework.Audio.Device;
66

77
namespace SoundSwitch.Common.Framework.Audio.Collection

SoundSwitch.Common/Framework/Audio/Device/DeviceFullInfo.cs

+11-5
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
#nullable enable
22
using System;
3-
using NAudio.CoreAudioApi;
3+
using CoreAudio;
44
using Newtonsoft.Json;
55
using SoundSwitch.Common.Framework.Audio.Icon;
66

77
namespace SoundSwitch.Common.Framework.Audio.Device
88
{
99
public class DeviceFullInfo : DeviceInfo, IDisposable
1010
{
11-
private readonly MMDevice _device;
11+
private readonly MMDevice? _device;
1212
public string IconPath { get; }
1313
public DeviceState State { get; }
1414

@@ -28,7 +28,7 @@ public DeviceFullInfo(string name, string id, DataFlow type, string iconPath, De
2828
State = state;
2929
}
3030

31-
public DeviceFullInfo(MMDevice device) : base(device)
31+
public DeviceFullInfo(MMDevice? device) : base(device)
3232
{
3333
_device = device;
3434
IconPath = device.IconPath;
@@ -39,6 +39,12 @@ public DeviceFullInfo(MMDevice device) : base(device)
3939
if (device.State == DeviceState.Active)
4040
{
4141
var deviceAudioEndpointVolume = device.AudioEndpointVolume;
42+
if (deviceAudioEndpointVolume == null)
43+
{
44+
Volume = 0;
45+
return;
46+
}
47+
4248
Volume = (int)Math.Round(deviceAudioEndpointVolume.MasterVolumeLevelScalar * 100);
4349
deviceAudioEndpointVolume.OnVolumeNotification += DeviceAudioEndpointVolumeOnOnVolumeNotification;
4450
}
@@ -58,8 +64,8 @@ public void Dispose()
5864
{
5965
try
6066
{
61-
62-
_device.AudioEndpointVolume.OnVolumeNotification -= DeviceAudioEndpointVolumeOnOnVolumeNotification;
67+
if (_device.AudioEndpointVolume != null)
68+
_device.AudioEndpointVolume.OnVolumeNotification -= DeviceAudioEndpointVolumeOnOnVolumeNotification;
6369
_device.Dispose();
6470
}
6571
catch (Exception)

SoundSwitch.Common/Framework/Audio/Device/DeviceInfo.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System;
22
using System.Text.RegularExpressions;
3-
using NAudio.CoreAudioApi;
3+
using CoreAudio;
44
using Newtonsoft.Json;
55

66
namespace SoundSwitch.Common.Framework.Audio.Device
@@ -47,11 +47,11 @@ public string NameClean
4747

4848
public DeviceInfo(MMDevice device)
4949
{
50-
Name = device.FriendlyName;
50+
Name = device.DeviceFriendlyName;
5151
Id = device.ID;
5252
Type = device.DataFlow;
5353
var deviceProperties = device.Properties;
54-
var enumerator = deviceProperties.Contains(PropertyKeys.DEVPKEY_Device_EnumeratorName) ? (string)deviceProperties[PropertyKeys.DEVPKEY_Device_EnumeratorName].Value : "";
54+
var enumerator = deviceProperties?.Contains(PropertyKeys.DEVPKEY_Device_EnumeratorName) ?? false ? (string)deviceProperties[PropertyKeys.DEVPKEY_Device_EnumeratorName]?.Value : "";
5555
IsUsb = enumerator == "USB";
5656
}
5757

SoundSwitch.Common/Framework/Audio/Device/PropertyKeys.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using System;
2-
using NAudio.CoreAudioApi;
2+
using CoreAudio;
33

44
namespace SoundSwitch.Common.Framework.Audio.Device
55
{

SoundSwitch.Common/Framework/Audio/Icon/AudioDeviceIconExtractor.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
********************************************************************/
1414

1515
using System;
16+
using CoreAudio;
1617
using Microsoft.Extensions.Caching.Memory;
17-
using NAudio.CoreAudioApi;
1818
using Serilog;
1919
using SoundSwitch.Common.Framework.Icon;
2020
using SoundSwitch.Common.Properties;

SoundSwitch.Common/SoundSwitch.Common.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
<Content Include="Resources\defaultSpeakers.ico" />
1919
</ItemGroup>
2020
<ItemGroup>
21-
<PackageReference Include="NAudio" Version="2.1.0" />
21+
<PackageReference Include="CoreAudio" Version="1.27.0" />
2222
<PackageReference Include="NAudio.Wasapi" Version="2.1.0" />
2323
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
2424
<PackageReference Include="Serilog" Version="2.12.0" />

SoundSwitch/Framework/Audio/Lister/CachedAudioDeviceLister.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
using System.Collections.Generic;
1717
using System.Linq;
1818
using System.Threading;
19-
using NAudio.CoreAudioApi;
19+
using CoreAudio;
2020
using Serilog;
2121
using SoundSwitch.Common.Framework.Audio.Collection;
2222
using SoundSwitch.Common.Framework.Audio.Device;
@@ -25,6 +25,7 @@
2525
using SoundSwitch.Framework.NotificationManager;
2626
using SoundSwitch.Framework.Threading;
2727
using SoundSwitch.Model;
28+
using MMNotificationClient = SoundSwitch.Framework.NotificationManager.MMNotificationClient;
2829

2930
namespace SoundSwitch.Framework.Audio.Lister
3031
{
@@ -90,8 +91,7 @@ public void Refresh(CancellationToken cancellationToken = default)
9091
try
9192
{
9293
_context.Information("Refreshing all devices");
93-
var enumerator = new MMDeviceEnumerator();
94-
using var _ = enumerator.DisposeOnCancellation(cancellationToken);
94+
var enumerator = new MMDeviceEnumerator(Guid.NewGuid());
9595
foreach (var endPoint in enumerator.EnumerateAudioEndPoints(DataFlow.All, _state))
9696
{
9797
cancellationToken.ThrowIfCancellationRequested();

SoundSwitch/Framework/Audio/Lister/Job/DebounceRefreshJob.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
using System;
22
using System.Threading;
33
using System.Threading.Tasks;
4+
using CoreAudio;
45
using Job.Scheduler.Job;
56
using Job.Scheduler.Job.Action;
67
using Job.Scheduler.Job.Exception;
7-
using NAudio.CoreAudioApi;
88
using Serilog;
99
using SoundSwitch.Model;
1010

SoundSwitch/Framework/Configuration/SoundSwitchConfiguration.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
using System.Collections.Generic;
1717
using System.Linq;
1818
using System.Windows.Forms;
19-
using NAudio.CoreAudioApi;
19+
using CoreAudio;
2020
using Newtonsoft.Json;
2121
using Serilog;
2222
using SoundSwitch.Common.Framework.Audio.Device;

SoundSwitch/Framework/DeviceCyclerManager/DeviceCycler/ADeviceCycler.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
using System.Collections.Generic;
1616
using System.Linq;
17-
using NAudio.CoreAudioApi;
17+
using CoreAudio;
1818
using Serilog;
1919
using SoundSwitch.Audio.Manager;
2020
using SoundSwitch.Audio.Manager.Interop.Enum;

SoundSwitch/Framework/DeviceCyclerManager/DeviceCycler/DeviceCyclerAll.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
using System;
1616
using System.Collections.Generic;
17-
using NAudio.CoreAudioApi;
17+
using CoreAudio;
1818
using SoundSwitch.Common.Framework.Audio.Device;
1919
using SoundSwitch.Localization;
2020
using SoundSwitch.Model;

SoundSwitch/Framework/DeviceCyclerManager/DeviceCycler/DeviceCyclerAvailable.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
using System;
1616
using System.Collections.Generic;
17-
using NAudio.CoreAudioApi;
17+
using CoreAudio;
1818
using SoundSwitch.Common.Framework.Audio.Device;
1919
using SoundSwitch.Localization;
2020
using SoundSwitch.Model;

SoundSwitch/Framework/DeviceCyclerManager/DeviceCycler/IDeviceCycler.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* GNU General Public License for more details.
1313
********************************************************************/
1414

15-
using NAudio.CoreAudioApi;
15+
using CoreAudio;
1616
using SoundSwitch.Common.Framework.Audio.Device;
1717
using SoundSwitch.Framework.Factory;
1818

SoundSwitch/Framework/DeviceCyclerManager/DeviceCyclerManager.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* GNU General Public License for more details.
1313
********************************************************************/
1414

15-
using NAudio.CoreAudioApi;
15+
using CoreAudio;
1616
using SoundSwitch.Common.Framework.Audio.Device;
1717
using SoundSwitch.Framework.Configuration;
1818

SoundSwitch/Framework/NotificationManager/MMNotificationClient.cs

+40-31
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,24 @@
33
using System.Linq;
44
using System.Threading;
55
using System.Threading.Tasks;
6+
using CoreAudio;
67
using Job.Scheduler.Job;
78
using Job.Scheduler.Job.Action;
89
using Job.Scheduler.Job.Exception;
9-
using NAudio.CoreAudioApi;
10-
using NAudio.CoreAudioApi.Interfaces;
1110
using Serilog;
1211
using SoundSwitch.Audio.Manager;
13-
using SoundSwitch.Audio.Manager.Interop.Enum;
1412
using SoundSwitch.Common.Framework.Audio.Device;
1513
using SoundSwitch.Framework.Threading;
1614
using SoundSwitch.Model;
1715
using PropertyKeys = NAudio.CoreAudioApi.PropertyKeys;
1816

1917
namespace SoundSwitch.Framework.NotificationManager
2018
{
21-
public class MMNotificationClient : IMMNotificationClient, IDisposable
19+
public class MMNotificationClient : IDisposable
2220
{
2321
private record struct DeviceRole(DataFlow Flow, Role Role);
2422

2523
public static MMNotificationClient Instance { get; } = new();
26-
private MMDeviceEnumerator _enumerator;
2724

2825
private readonly Dictionary<DeviceRole, string> _lastRoleDevice = new();
2926

@@ -33,6 +30,9 @@ private record struct DeviceRole(DataFlow Flow, Role Role);
3330

3431
private readonly TaskScheduler _taskScheduler = new LimitedConcurrencyLevelTaskScheduler(1);
3532

33+
private CoreAudio.MMNotificationClient _notificationClient;
34+
private MMDeviceEnumerator _mmDeviceEnumerator;
35+
3636
private class DeviceChangedJob : IJob
3737
{
3838
private readonly MMNotificationClient _notificationClient;
@@ -104,40 +104,43 @@ public Task OnFailure(JobException exception)
104104
/// </summary>
105105
public void Register()
106106
{
107-
_enumerator = new MMDeviceEnumerator();
108-
_enumerator.RegisterEndpointNotificationCallback(this);
107+
_mmDeviceEnumerator = new MMDeviceEnumerator(Guid.NewGuid());
108+
_notificationClient = new CoreAudio.MMNotificationClient(_mmDeviceEnumerator);
109+
_notificationClient.DeviceAdded += OnDeviceAdded;
110+
_notificationClient.DeviceRemoved += OnDeviceRemoved;
111+
_notificationClient.DevicePropertyChanged += OnPropertyValueChanged;
112+
_notificationClient.DeviceStateChanged += OnDeviceStateChanged;
113+
_notificationClient.DefaultDeviceChanged += OnDefaultDeviceChanged;
109114
foreach (var flow in Enum.GetValues<DataFlow>().Where(flow => flow != DataFlow.All))
110115
{
111-
foreach (var role in Enum.GetValues<Role>())
116+
foreach (var role in Enum.GetValues<Role>().Where(role => role != Role.EnumCount))
112117
{
113-
var device = AudioSwitcher.Instance.GetDefaultAudioEndpoint((EDataFlow)flow, (ERole)role);
114-
if (device == null)
115-
continue;
116-
117-
_lastRoleDevice[new DeviceRole(flow, role)] = device.Id;
118+
var device = _mmDeviceEnumerator.GetDefaultAudioEndpoint(flow, role);
119+
_lastRoleDevice[new DeviceRole(flow, role)] = device.ID;
118120
}
119121
}
120122
}
121123

122-
public void OnDeviceStateChanged(string deviceId, DeviceState newState)
124+
public void OnDeviceStateChanged(object sender, DeviceStateChangedEventArgs deviceStateChangedEventArgs)
123125
{
124-
JobScheduler.Instance.ScheduleJob(new DeviceChangedJob(this, deviceId), CancellationToken.None, _taskScheduler);
126+
JobScheduler.Instance.ScheduleJob(new DeviceChangedJob(this, deviceStateChangedEventArgs.DeviceId), CancellationToken.None, _taskScheduler);
125127
}
126128

127-
public void OnDeviceAdded(string deviceId)
129+
public void OnDeviceAdded(object sender, DeviceNotificationEventArgs deviceNotificationEventArgs)
128130
{
129-
JobScheduler.Instance.ScheduleJob(new DeviceChangedJob(this, deviceId, true), CancellationToken.None, _taskScheduler);
131+
JobScheduler.Instance.ScheduleJob(new DeviceChangedJob(this, deviceNotificationEventArgs.DeviceId, true), CancellationToken.None, _taskScheduler);
130132
}
131133

132-
public void OnDeviceRemoved(string deviceId)
134+
public void OnDeviceRemoved(object sender, DeviceNotificationEventArgs deviceNotificationEventArgs)
133135
{
134-
JobScheduler.Instance.ScheduleJob(new DeviceChangedJob(this, deviceId), CancellationToken.None, _taskScheduler);
136+
JobScheduler.Instance.ScheduleJob(new DeviceChangedJob(this, deviceNotificationEventArgs.DeviceId), CancellationToken.None, _taskScheduler);
135137
}
136138

137-
public void OnDefaultDeviceChanged(DataFlow flow, Role role, string deviceId)
139+
public void OnDefaultDeviceChanged(object sender, DefaultDeviceChangedEventArgs defaultDeviceChangedEventArgs)
138140
{
139-
if (deviceId == null)
140-
return;
141+
var deviceId = defaultDeviceChangedEventArgs.DeviceId;
142+
var flow = defaultDeviceChangedEventArgs.DataFlow;
143+
var role = defaultDeviceChangedEventArgs.Role;
141144

142145
var deviceRole = new DeviceRole(flow, role);
143146
if (_lastRoleDevice.TryGetValue(deviceRole, out var oldDeviceId) && oldDeviceId == deviceId)
@@ -149,24 +152,30 @@ public void OnDefaultDeviceChanged(DataFlow flow, Role role, string deviceId)
149152
JobScheduler.Instance.ScheduleJob(new DefaultDeviceChangedJob(this, deviceId, role), CancellationToken.None, _taskScheduler);
150153
}
151154

152-
public void OnPropertyValueChanged(string pwstrDeviceId, PropertyKey key)
155+
public void OnPropertyValueChanged(object sender, DevicePropertyChangedEventArgs devicePropertyChangedEventArgs)
153156
{
154-
if (PropertyKeys.PKEY_DeviceInterface_FriendlyName.formatId != key.formatId
155-
&& PropertyKeys.PKEY_AudioEndpoint_GUID.formatId != key.formatId
156-
&& PropertyKeys.PKEY_Device_IconPath.formatId != key.formatId
157-
&& PropertyKeys.PKEY_Device_FriendlyName.formatId != key.formatId
157+
var key = devicePropertyChangedEventArgs.PropertyKey;
158+
if (PropertyKeys.PKEY_DeviceInterface_FriendlyName.formatId != key.fmtId
159+
&& PropertyKeys.PKEY_AudioEndpoint_GUID.formatId != key.fmtId
160+
&& PropertyKeys.PKEY_Device_IconPath.formatId != key.fmtId
161+
&& PropertyKeys.PKEY_Device_FriendlyName.formatId != key.fmtId
158162
)
159163
{
160164
return;
161165
}
162166

163-
JobScheduler.Instance.ScheduleJob(new DeviceChangedJob(this, pwstrDeviceId), CancellationToken.None, _taskScheduler);
167+
JobScheduler.Instance.ScheduleJob(new DeviceChangedJob(this, devicePropertyChangedEventArgs.DeviceId), CancellationToken.None, _taskScheduler);
164168
}
165169

166170
public void Dispose()
167171
{
168-
_enumerator.UnregisterEndpointNotificationCallback(this);
169-
_enumerator?.Dispose();
172+
_notificationClient.DeviceAdded -= OnDeviceAdded;
173+
_notificationClient.DeviceRemoved -= OnDeviceRemoved;
174+
_notificationClient.DevicePropertyChanged -= OnPropertyValueChanged;
175+
_notificationClient.DeviceStateChanged -= OnDeviceStateChanged;
176+
_notificationClient.DefaultDeviceChanged -= OnDefaultDeviceChanged;
177+
_notificationClient = null;
178+
_mmDeviceEnumerator = null;
170179
}
171180
}
172-
}
181+
}

SoundSwitch/Framework/NotificationManager/Notification/NotificationBanner.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
using System.Drawing;
1717
using System.IO;
1818
using System.Linq;
19-
using NAudio.CoreAudioApi;
19+
using CoreAudio;
2020
using SoundSwitch.Common.Framework.Audio.Device;
2121
using SoundSwitch.Framework.Audio;
2222
using SoundSwitch.Framework.Banner;

0 commit comments

Comments
 (0)