-
Notifications
You must be signed in to change notification settings - Fork 52
/
AudioDeviceManager.cs
231 lines (194 loc) · 8.74 KB
/
AudioDeviceManager.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
// -----------------------------------------------------------------------
// Copyright (c) David Kean. All rights reserved.
// -----------------------------------------------------------------------
// This source file was altered for use in AudioSwitcher.
/*
LICENSE
-------
Copyright (C) 2007 Ray Molenkamp
This source code is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this source code or the software it produces.
Permission is granted to anyone to use this source code for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this source code must not be misrepresented; you must not
claim that you wrote the original source code. If you use this source code
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original source code.
3. This notice may not be removed or altered from any source distribution.
*/
using System;
using System.ComponentModel.Composition;
using System.Runtime.InteropServices;
using System.Threading;
using AudioSwitcher.Audio.Interop;
using AudioSwitcher.Interop;
namespace AudioSwitcher.Audio
{
[Export(typeof(AudioDeviceManager))]
internal class AudioDeviceManager : IMMNotificationClient, IDisposable
{
private readonly IMMDeviceEnumerator _deviceEnumerator = (IMMDeviceEnumerator)new MMDeviceEnumerator();
private readonly SynchronizationContext _synchronizationContext;
public AudioDeviceManager()
{
_synchronizationContext = SynchronizationContext.Current;
int hr = _deviceEnumerator.RegisterEndpointNotificationCallback(this);
if (hr != HResult.OK)
throw Marshal.GetExceptionForHR(hr);
}
public event EventHandler<AudioDeviceEventArgs> DeviceAdded;
public event EventHandler<AudioDeviceRemovedEventArgs> DeviceRemoved;
public event EventHandler<AudioDeviceEventArgs> DevicePropertyChanged;
public event EventHandler<DefaultAudioDeviceEventArgs> DefaultDeviceChanged;
public event EventHandler<AudioDeviceStateEventArgs> DeviceStateChanged;
public AudioDeviceCollection GetAudioDevices(AudioDeviceKind kind, AudioDeviceState state)
{
int hr = _deviceEnumerator.EnumAudioEndpoints(kind, state, out IMMDeviceCollection underlyingCollection);
if (hr == HResult.OK)
return new AudioDeviceCollection(underlyingCollection);
throw Marshal.GetExceptionForHR(hr);
}
public void SetDefaultAudioDevice(AudioDevice device)
{
if (device == null)
throw new ArgumentNullException("device");
SetDefaultAudioDevice(device, AudioDeviceRole.Multimedia);
SetDefaultAudioDevice(device, AudioDeviceRole.Communications);
SetDefaultAudioDevice(device, AudioDeviceRole.Console);
}
public void SetDefaultAudioDevice(AudioDevice device, AudioDeviceRole role)
{
if (device == null)
throw new ArgumentNullException("device");
// BADNESS: The following code uses undocumented interfaces provided by the Audio SDK. This is completely
// unsupported, and should be used for amusement purposes only. This is *extremely likely* to be broken
// in future updates and/or versions of Windows. If Larry Osterman was dead, he would be rolling over
// in his grave if he knew you were using this for nefarious purposes.
var config = new PolicyConfig();
int hr;
if (config is IPolicyConfig2 config2)
{ // Windows 7 -> Windows 8.1
hr = config2.SetDefaultEndpoint(device.Id, role);
}
else
{ // Windows 10+
hr = ((IPolicyConfig3)config).SetDefaultEndpoint(device.Id, role);
}
if (hr != HResult.OK)
throw Marshal.GetExceptionForHR(hr);
}
public bool IsDefaultAudioDevice(AudioDevice device, AudioDeviceRole role)
{
if (device == null)
throw new ArgumentNullException("device");
AudioDevice defaultDevice = GetDefaultAudioDevice(device.Kind, role);
if (defaultDevice == null)
return false;
return string.Equals(defaultDevice.Id, device.Id, StringComparison.OrdinalIgnoreCase);
}
public AudioDevice GetDefaultAudioDevice(AudioDeviceKind kind, AudioDeviceRole role)
{
int hr = _deviceEnumerator.GetDefaultAudioEndpoint(kind, role, out IMMDevice underlyingDevice);
if (hr == HResult.OK)
return new AudioDevice(underlyingDevice);
if (hr == HResult.NotFound || hr == HResult.FileNotFound) // See #33
return null;
throw Marshal.GetExceptionForHR(hr);
}
public AudioDevice GetDevice(string id)
{
if (id == null)
throw new ArgumentNullException("id");
int hr = _deviceEnumerator.GetDevice(id, out IMMDevice underlyingDevice);
if (hr == HResult.OK)
return new AudioDevice(underlyingDevice);
if (hr == HResult.NotFound)
return null;
throw Marshal.GetExceptionForHR(hr);
}
void IMMNotificationClient.OnDeviceStateChanged(string deviceId, AudioDeviceState newState)
{
InvokeOnSynchronizationContext(() =>
{
EventHandler<AudioDeviceStateEventArgs> handler = DeviceStateChanged;
if (handler != null)
{
AudioDevice device = GetDevice(deviceId);
if (device == null)
return; // Device was already removed by the time we got here
handler(this, new AudioDeviceStateEventArgs(device, newState));
}
});
}
void IMMNotificationClient.OnDeviceAdded(string deviceId)
{
InvokeOnSynchronizationContext(() =>
{
EventHandler<AudioDeviceEventArgs> handler = DeviceAdded;
if (handler != null)
{
AudioDevice device = GetDevice(deviceId);
if (device == null)
return; // Device was already removed by the time we got here
handler(this, new AudioDeviceEventArgs(device));
}
});
}
void IMMNotificationClient.OnDeviceRemoved(string deviceId)
{
InvokeOnSynchronizationContext(() =>
{
DeviceRemoved?.Invoke(this, new AudioDeviceRemovedEventArgs(deviceId));
});
}
void IMMNotificationClient.OnDefaultDeviceChanged(AudioDeviceKind kind, AudioDeviceRole role, string deviceId)
{
InvokeOnSynchronizationContext(() =>
{
EventHandler<DefaultAudioDeviceEventArgs> handler = DefaultDeviceChanged;
if (handler != null)
{
AudioDevice device = null;
if (deviceId != null)
device = GetDevice(deviceId);
handler(this, new DefaultAudioDeviceEventArgs(device, kind, role));
}
});
}
void IMMNotificationClient.OnPropertyValueChanged(string deviceId, PropertyKey key)
{
InvokeOnSynchronizationContext(() =>
{
EventHandler<AudioDeviceEventArgs> handler = DevicePropertyChanged;
if (handler != null)
{
AudioDevice device = GetDevice(deviceId);
if (device == null)
return; // Device was already removed by the time I got here
handler(this, new AudioDeviceEventArgs(device));
}
});
}
public void Dispose()
{
int hr = _deviceEnumerator.UnregisterEndpointNotificationCallback(this);
if (hr != HResult.OK)
throw Marshal.GetExceptionForHR(hr);
}
private void InvokeOnSynchronizationContext(Action action)
{
if (_synchronizationContext == null)
{
action();
}
else
{
_synchronizationContext.Post(state => { action(); }, null);
}
}
}
}