Skip to content

Commit 04298ab

Browse files
committed
1.8.2
[New] - Audio actions are back, using code from AudioSwitcher (https://github.com/xenolightning/AudioSwitcher) [Bug fixes} - Nvapi causes high cpu usage ( 1.8.1 has high cpu usage #75) - Refresh rate changing not working for some users (v1.8.1+: Refresh rate switching is broken again #74)
1 parent 47a5c8e commit 04298ab

File tree

101 files changed

+6123
-492
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

101 files changed

+6123
-492
lines changed
Lines changed: 360 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,360 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Linq.Expressions;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using AudioSwitcher.AudioApi.CoreAudio.Interfaces;
8+
using AudioSwitcher.AudioApi.CoreAudio.Threading;
9+
10+
namespace AudioSwitcher.AudioApi.CoreAudio
11+
{
12+
/// <summary>
13+
/// Enumerates Windows System Devices.
14+
/// Stores the current devices in memory to avoid calling the COM library when not required
15+
/// </summary>
16+
public sealed class CoreAudioController : AudioController<CoreAudioDevice>, ISystemAudioEventClient
17+
{
18+
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
19+
20+
private IMMDeviceEnumerator _innerEnumerator;
21+
private HashSet<CoreAudioDevice> _deviceCache = new HashSet<CoreAudioDevice>();
22+
23+
public CoreAudioController()
24+
{
25+
ComThread.Invoke(() =>
26+
{
27+
// ReSharper disable once SuspiciousTypeConversion.Global
28+
_innerEnumerator = new MMDeviceEnumeratorComObject() as IMMDeviceEnumerator;
29+
30+
if (_innerEnumerator == null)
31+
return;
32+
33+
_notificationClient = new MMNotificationClient(this);
34+
_innerEnumerator.RegisterEndpointNotificationCallback(_notificationClient);
35+
});
36+
37+
RefreshSystemDevices();
38+
}
39+
40+
~CoreAudioController()
41+
{
42+
Dispose(false);
43+
}
44+
45+
protected override void Dispose(bool disposing)
46+
{
47+
if (_innerEnumerator != null)
48+
{
49+
ComThread.BeginInvoke(() =>
50+
{
51+
_innerEnumerator.UnregisterEndpointNotificationCallback(_notificationClient);
52+
_notificationClient = null;
53+
_innerEnumerator = null;
54+
});
55+
}
56+
57+
if (_deviceCache != null)
58+
_deviceCache.Clear();
59+
60+
if (_lock != null)
61+
_lock.Dispose();
62+
63+
GC.SuppressFinalize(this);
64+
}
65+
66+
public override CoreAudioDevice GetDevice(Guid id, DeviceState state)
67+
{
68+
var acquiredLock = _lock.AcquireReadLockNonReEntrant();
69+
70+
try
71+
{
72+
return _deviceCache.FirstOrDefault(x => x.Id == id && state.HasFlag(x.State));
73+
}
74+
finally
75+
{
76+
if (acquiredLock)
77+
_lock.ExitReadLock();
78+
}
79+
}
80+
81+
private CoreAudioDevice GetDevice(string realId)
82+
{
83+
var acquiredLock = _lock.AcquireReadLockNonReEntrant();
84+
85+
try
86+
{
87+
return _deviceCache.FirstOrDefault(x => String.Equals(x.RealId, realId, StringComparison.InvariantCultureIgnoreCase));
88+
}
89+
finally
90+
{
91+
if (acquiredLock)
92+
_lock.ExitReadLock();
93+
}
94+
}
95+
96+
private void RefreshSystemDevices()
97+
{
98+
ComThread.Invoke(() =>
99+
{
100+
_deviceCache = new HashSet<CoreAudioDevice>();
101+
IMMDeviceCollection collection;
102+
_innerEnumerator.EnumAudioEndpoints(EDataFlow.All, EDeviceState.All, out collection);
103+
104+
using (var coll = new MMDeviceCollection(collection))
105+
{
106+
foreach (var mDev in coll)
107+
CacheDevice(mDev);
108+
}
109+
});
110+
}
111+
112+
private CoreAudioDevice GetOrAddDeviceFromRealId(string deviceId)
113+
{
114+
//This pre-check here may prevent more com objects from being created
115+
var device = GetDevice(deviceId);
116+
if (device != null)
117+
return device;
118+
119+
return ComThread.Invoke(() =>
120+
{
121+
IMMDevice mDevice;
122+
_innerEnumerator.GetDevice(deviceId, out mDevice);
123+
124+
if (mDevice == null)
125+
return null;
126+
127+
return CacheDevice(mDevice);
128+
});
129+
}
130+
131+
private IEnumerable<CoreAudioDevice> RemoveFromRealId(string deviceId)
132+
{
133+
var lockAcquired = _lock.AcquireWriteLockNonReEntrant();
134+
try
135+
{
136+
var devicesToRemove =
137+
_deviceCache.Where(x => String.Equals(x.RealId, deviceId, StringComparison.InvariantCultureIgnoreCase)).ToList();
138+
139+
_deviceCache.RemoveWhere(x => String.Equals(x.RealId, deviceId, StringComparison.InvariantCultureIgnoreCase));
140+
141+
return devicesToRemove;
142+
}
143+
finally
144+
{
145+
if (lockAcquired)
146+
_lock.ExitWriteLock();
147+
}
148+
}
149+
150+
private CoreAudioDevice CacheDevice(IMMDevice mDevice)
151+
{
152+
if (!DeviceIsValid(mDevice))
153+
return null;
154+
155+
string id;
156+
mDevice.GetId(out id);
157+
var device = GetDevice(id);
158+
159+
if (device != null)
160+
return device;
161+
162+
var lockAcquired = _lock.AcquireWriteLockNonReEntrant();
163+
164+
try
165+
{
166+
device = new CoreAudioDevice(mDevice, this);
167+
_deviceCache.Add(device);
168+
169+
return device;
170+
}
171+
finally
172+
{
173+
if (lockAcquired)
174+
_lock.ExitWriteLock();
175+
}
176+
}
177+
178+
private static bool DeviceIsValid(IMMDevice device)
179+
{
180+
try
181+
{
182+
string id;
183+
EDeviceState state;
184+
device.GetId(out id);
185+
device.GetState(out state);
186+
187+
return true;
188+
}
189+
catch
190+
{
191+
return false;
192+
}
193+
}
194+
195+
private void RaiseAudioDeviceChanged(DeviceChangedEventArgs e)
196+
{
197+
OnAudioDeviceChanged(this, e);
198+
}
199+
200+
public override bool SetDefaultDevice(CoreAudioDevice dev)
201+
{
202+
if (dev == null)
203+
return false;
204+
205+
var oldDefault = dev.IsPlaybackDevice ? DefaultPlaybackDevice : DefaultCaptureDevice;
206+
207+
try
208+
{
209+
PolicyConfig.SetDefaultEndpoint(dev.RealId, ERole.Console | ERole.Multimedia);
210+
return dev.IsDefaultDevice;
211+
}
212+
catch
213+
{
214+
return false;
215+
}
216+
finally
217+
{
218+
//Raise the default changed event on the old device
219+
if (oldDefault != null && !oldDefault.IsDefaultDevice)
220+
RaiseAudioDeviceChanged(new DefaultDeviceChangedEventArgs(oldDefault));
221+
}
222+
}
223+
224+
public override bool SetDefaultCommunicationsDevice(CoreAudioDevice dev)
225+
{
226+
if (dev == null)
227+
return false;
228+
229+
var oldDefault = dev.IsPlaybackDevice ? DefaultPlaybackCommunicationsDevice : DefaultCaptureCommunicationsDevice;
230+
231+
try
232+
{
233+
PolicyConfig.SetDefaultEndpoint(dev.RealId, ERole.Communications);
234+
235+
return dev.IsDefaultCommunicationsDevice;
236+
}
237+
catch
238+
{
239+
return false;
240+
}
241+
finally
242+
{
243+
//Raise the default changed event on the old device
244+
if (oldDefault != null && !oldDefault.IsDefaultCommunicationsDevice)
245+
RaiseAudioDeviceChanged(new DefaultDeviceChangedEventArgs(oldDefault, true));
246+
}
247+
}
248+
249+
public override CoreAudioDevice GetDefaultDevice(DeviceType deviceType, Role role)
250+
{
251+
var acquiredLock = _lock.AcquireReadLockNonReEntrant();
252+
253+
try
254+
{
255+
IMMDevice dev;
256+
_innerEnumerator.GetDefaultAudioEndpoint(deviceType.AsEDataFlow(), role.AsERole(), out dev);
257+
if (dev == null)
258+
return null;
259+
260+
string devId;
261+
dev.GetId(out devId);
262+
if (String.IsNullOrEmpty(devId))
263+
return null;
264+
265+
return _deviceCache.FirstOrDefault(x => x.RealId == devId);
266+
}
267+
finally
268+
{
269+
if (acquiredLock)
270+
_lock.ExitReadLock();
271+
}
272+
}
273+
274+
public override IEnumerable<CoreAudioDevice> GetDevices(DeviceType deviceType, DeviceState state)
275+
{
276+
var acquiredLock = _lock.AcquireReadLockNonReEntrant();
277+
278+
try
279+
{
280+
return _deviceCache.Where(x =>
281+
(x.DeviceType == deviceType || deviceType == DeviceType.All)
282+
&& state.HasFlag(x.State)).ToList();
283+
}
284+
finally
285+
{
286+
if (acquiredLock)
287+
_lock.ExitReadLock();
288+
}
289+
}
290+
291+
private MMNotificationClient _notificationClient;
292+
293+
void ISystemAudioEventClient.OnDeviceStateChanged(string deviceId, EDeviceState newState)
294+
{
295+
var dev = GetOrAddDeviceFromRealId(deviceId);
296+
297+
if (dev != null)
298+
RaiseAudioDeviceChanged(new DeviceStateChangedEventArgs(dev, newState.AsDeviceState()));
299+
}
300+
301+
void ISystemAudioEventClient.OnDeviceAdded(string deviceId)
302+
{
303+
var dev = GetOrAddDeviceFromRealId(deviceId);
304+
305+
if (dev != null)
306+
RaiseAudioDeviceChanged(new DeviceAddedEventArgs(dev));
307+
}
308+
309+
void ISystemAudioEventClient.OnDeviceRemoved(string deviceId)
310+
{
311+
var devicesRemoved = RemoveFromRealId(deviceId);
312+
313+
foreach (var dev in devicesRemoved)
314+
RaiseAudioDeviceChanged(new DeviceRemovedEventArgs(dev));
315+
}
316+
317+
void ISystemAudioEventClient.OnDefaultDeviceChanged(EDataFlow flow, ERole role, string deviceId)
318+
{
319+
//Ignore multimedia, it seems to fire a console event anyway
320+
if (role == ERole.Multimedia)
321+
return;
322+
323+
var dev = GetOrAddDeviceFromRealId(deviceId);
324+
325+
if (dev == null)
326+
return;
327+
328+
Task.Factory.StartNew(() =>
329+
{
330+
RaiseAudioDeviceChanged(new DefaultDeviceChangedEventArgs(dev, role == ERole.Communications));
331+
});
332+
}
333+
334+
private static readonly Dictionary<PropertyKey, Expression<Func<IDevice, object>>> PropertykeyToLambdaMap = new Dictionary<PropertyKey, Expression<Func<IDevice, object>>>
335+
{
336+
{PropertyKeys.PKEY_DEVICE_INTERFACE_FRIENDLY_NAME, x => x.InterfaceName},
337+
{PropertyKeys.PKEY_DEVICE_DESCRIPTION, x => x.Name},
338+
{PropertyKeys.PKEY_DEVICE_FRIENDLY_NAME, x => x.FullName},
339+
{PropertyKeys.PKEY_DEVICE_ICON, x => x.Icon},
340+
};
341+
342+
void ISystemAudioEventClient.OnPropertyValueChanged(string deviceId, PropertyKey key)
343+
{
344+
var dev = GetOrAddDeviceFromRealId(deviceId);
345+
346+
if(dev == null)
347+
return;
348+
349+
if (PropertykeyToLambdaMap.ContainsKey(key))
350+
{
351+
RaiseAudioDeviceChanged(DevicePropertyChangedEventArgs.FromExpression(dev, PropertykeyToLambdaMap[key]));
352+
return;
353+
}
354+
355+
//Unknown property changed
356+
RaiseAudioDeviceChanged(new DevicePropertyChangedEventArgs(dev));
357+
}
358+
359+
}
360+
}

0 commit comments

Comments
 (0)