Repository navigation
Expand file tree
/
Copy pathAudioSessionGuard.cs
More file actions
287 lines (264 loc) · 9.63 KB
/
Copy pathAudioSessionGuard.cs
File metadata and controls
287 lines (264 loc) · 9.63 KB
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
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using NAudio.CoreAudioApi;
using NAudio.CoreAudioApi.Interfaces;
namespace WpfApp1
{
/// <summary>
/// 音频会话守护:开启后持续静音本进程以外所有程序的声音,防止杂音进入广播输出。
/// 覆盖三类来源:
/// 1. 已在运行的程序 —— 开启时全量扫描并静音其音频会话;
/// 2. 未来启动的程序 —— 通过 OnSessionCreated 会话创建回调即时静音;
/// 3. 兜底 —— 周期全量重扫,覆盖新插入的设备和自行解除静音的程序。
/// 注意:无法拦截绕过系统混音器的程序(WASAPI 独占模式/ASIO),
/// 且首次出现的新程序在回调静音生效前可能漏出极短的声音,
/// 因此本守护应与设备隔离(系统默认设备不接功放)配合使用。
/// </summary>
public class AudioSessionGuard : IDisposable
{
private readonly uint _ownProcessId = (uint)Environment.ProcessId;
private readonly object _lock = new object();
// 按设备ID缓存已挂接 OnSessionCreated 回调的设备,避免重复注册
private readonly Dictionary<string, MMDevice> _hookedDevices = new Dictionary<string, MMDevice>();
private readonly System.Timers.Timer _sweepTimer;
private MMDeviceEnumerator? _deviceEnumerator;
private volatile bool _isRunning;
/// <summary>
/// 守护是否正在运行
/// </summary>
public bool IsRunning => _isRunning;
public AudioSessionGuard()
{
_sweepTimer = new System.Timers.Timer(2000) { AutoReset = true };
_sweepTimer.Elapsed += (s, e) => Sweep();
}
/// <summary>
/// 开启守护:立即静音所有其他程序的声音,并持续监控新会话
/// </summary>
public void Start()
{
lock (_lock)
{
if (_isRunning)
{
return;
}
_isRunning = true;
}
_sweepTimer.Start();
// 立即执行首次静音,不等定时器
Task.Run(Sweep);
}
/// <summary>
/// 停止守护
/// </summary>
/// <param name="restoreOthers">是否同时解除当前所有会话的静音</param>
public void Stop(bool restoreOthers)
{
_sweepTimer.Stop();
lock (_lock)
{
_isRunning = false;
// 解除回调并释放缓存的设备
foreach (var device in _hookedDevices.Values)
{
try
{
device.AudioSessionManager.OnSessionCreated -= SessionCreatedHandler;
device.Dispose();
}
catch
{
// 设备可能已被拔出,忽略
}
}
_hookedDevices.Clear();
}
if (restoreOthers)
{
RestoreAllSessions();
}
}
/// <summary>
/// 解除所有输出设备上全部音频会话的静音。
/// 只能恢复当前存在的会话:已退出的程序其静音状态会被 Windows 记住,
/// 需等该程序再次运行并发声后重新调用本方法才能恢复。
/// </summary>
public void RestoreAllSessions()
{
try
{
using var enumerator = new MMDeviceEnumerator();
foreach (var device in enumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active))
{
try
{
using (device)
{
var manager = device.AudioSessionManager;
manager.RefreshSessions();
var sessions = manager.Sessions;
for (int i = 0; i < sessions.Count; i++)
{
try
{
sessions[i].SimpleAudioVolume.Mute = false;
}
catch
{
// 单个会话恢复失败不影响其他会话
}
}
}
}
catch
{
// 单个设备失败不影响其他设备
}
}
}
catch
{
// 无可用音频设备时忽略
}
}
/// <summary>
/// 全量扫描:静音所有设备上的外部会话,并为新设备挂接会话创建回调
/// </summary>
private void Sweep()
{
if (!_isRunning)
{
return;
}
lock (_lock)
{
if (!_isRunning)
{
return;
}
try
{
_deviceEnumerator ??= new MMDeviceEnumerator();
var activeIds = new HashSet<string>();
foreach (var device in _deviceEnumerator.EnumerateAudioEndPoints(DataFlow.Render, DeviceState.Active))
{
string id;
try
{
id = device.ID;
}
catch
{
device.Dispose();
continue;
}
activeIds.Add(id);
if (_hookedDevices.TryGetValue(id, out var hookedDevice))
{
// 已挂接过回调的设备,复用缓存实例,丢弃本次枚举出的副本
device.Dispose();
MuteForeignSessions(hookedDevice);
}
else
{
try
{
device.AudioSessionManager.OnSessionCreated += SessionCreatedHandler;
_hookedDevices[id] = device;
MuteForeignSessions(device);
}
catch
{
device.Dispose();
}
}
}
// 清理已拔出或禁用的设备
foreach (var goneId in _hookedDevices.Keys.Where(k => !activeIds.Contains(k)).ToList())
{
try
{
_hookedDevices[goneId].Dispose();
}
catch
{
// 忽略
}
_hookedDevices.Remove(goneId);
}
}
catch
{
// 单次扫描失败不影响下一轮
}
}
}
/// <summary>
/// 静音指定设备上所有不属于本进程的音频会话
/// </summary>
private void MuteForeignSessions(MMDevice device)
{
try
{
var manager = device.AudioSessionManager;
manager.RefreshSessions();
var sessions = manager.Sessions;
for (int i = 0; i < sessions.Count; i++)
{
try
{
var session = sessions[i];
if (session.GetProcessID != _ownProcessId)
{
session.SimpleAudioVolume.Mute = true;
}
}
catch
{
// 会话可能刚好过期,忽略
}
}
}
catch
{
// 设备可能已被拔出,等下一轮扫描清理
}
}
/// <summary>
/// 新音频会话创建回调:其他程序一发声立即静音
/// </summary>
private void SessionCreatedHandler(object sender, IAudioSessionControl newSession)
{
if (!_isRunning)
{
return;
}
try
{
var session = new AudioSessionControl(newSession);
if (session.GetProcessID != _ownProcessId)
{
session.SimpleAudioVolume.Mute = true;
}
}
catch
{
// 失败时由周期扫描兜底
}
}
/// <summary>
/// 释放资源。刻意不恢复其他程序的静音状态:
/// 广播机上程序退出后保持静音是更安全的默认行为,需要恢复时使用界面上的"恢复全部声音"
/// </summary>
public void Dispose()
{
Stop(restoreOthers: false);
_sweepTimer.Dispose();
_deviceEnumerator?.Dispose();
_deviceEnumerator = null;
}
}
}