-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProcessLoopbackInterop.cs
More file actions
181 lines (156 loc) · 6.7 KB
/
Copy pathProcessLoopbackInterop.cs
File metadata and controls
181 lines (156 loc) · 6.7 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
using System.Runtime.InteropServices;
using NAudio.CoreAudioApi.Interfaces;
namespace MultiAudioRouter.Core;
/// <summary>
/// Windows Process Loopback API の P/Invoke 定義。
/// 仮想デバイス "VAD\Process_Loopback" 経由で IAudioClient を取得し、
/// PROCESS_LOOPBACK_PARAMS で対象 PID とツリー INCLUDE/EXCLUDE モードを指定する。
/// 必要 OS: Windows 10 build 20348 (21H2) / Windows 11 以降。
/// </summary>
internal static class ProcessLoopbackInterop
{
public const string VirtualDevicePath = "VAD\\Process_Loopback";
public const uint AUDCLNT_STREAMFLAGS_LOOPBACK = 0x00020000;
public const uint AUDCLNT_STREAMFLAGS_EVENTCALLBACK = 0x00040000;
public const int AUDCLNT_SHAREMODE_SHARED = 0;
public const int AUDIOCLIENT_ACTIVATION_TYPE_PROCESS_LOOPBACK = 1;
public const int PROCESS_LOOPBACK_MODE_INCLUDE_TARGET_PROCESS_TREE = 0;
public const int PROCESS_LOOPBACK_MODE_EXCLUDE_TARGET_PROCESS_TREE = 1;
// PROPVARIANT VT
public const ushort VT_BLOB = 0x41;
// IID_IAudioClient
public static readonly Guid IID_IAudioClient = new("1CB9AD4C-DBFA-4c32-B178-C2F568A703B2");
public static readonly Guid IID_IAudioCaptureClient = new("C8ADBD64-E71E-48a0-A4DE-185C395CD317");
[Flags]
public enum AudioClientBufferFlags : uint
{
None = 0,
DataDiscontinuity = 0x1,
Silent = 0x2,
TimestampError = 0x4,
}
/// <summary>自前 IAudioCaptureClient (NAudio のは internal なので)。</summary>
[Guid("C8ADBD64-E71E-48a0-A4DE-185C395CD317")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IAudioCaptureClient
{
[PreserveSig]
int GetBuffer(out IntPtr ppData, out int pNumFramesToRead, out AudioClientBufferFlags pdwFlags,
out long pu64DevicePosition, out long pu64QPCPosition);
[PreserveSig]
int ReleaseBuffer(int numFramesRead);
[PreserveSig]
int GetNextPacketSize(out int pNumFramesInNextPacket);
}
[StructLayout(LayoutKind.Sequential)]
public struct AUDIOCLIENT_PROCESS_LOOPBACK_PARAMS
{
public uint TargetProcessId;
public int ProcessLoopbackMode;
}
[StructLayout(LayoutKind.Sequential)]
public struct AUDIOCLIENT_ACTIVATION_PARAMS
{
public int ActivationType;
public AUDIOCLIENT_PROCESS_LOOPBACK_PARAMS ProcessLoopbackParams;
}
/// <summary>
/// PROPVARIANT (x64 想定で 24 バイト) を BLOB として手動構築するための領域。
/// </summary>
[StructLayout(LayoutKind.Sequential, Size = 24)]
public struct PROPVARIANT_BLOB
{
public ushort vt;
public ushort wReserved1;
public ushort wReserved2;
public ushort wReserved3;
public uint cbSize; // BLOB.cbSize
// 4 bytes pad on x64
public uint pad;
public IntPtr pBlobData; // BLOB.pBlobData
}
[Guid("72A22D78-CDE4-431D-B8CC-843A71199B6D")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IActivateAudioInterfaceAsyncOperation
{
[PreserveSig]
int GetActivateResult(out int activateResult,
[MarshalAs(UnmanagedType.IUnknown)] out object activatedInterface);
}
[Guid("41D949AB-9862-444A-80F6-C261334DA5EB")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IActivateAudioInterfaceCompletionHandler
{
[PreserveSig]
int ActivateCompleted(IActivateAudioInterfaceAsyncOperation activateOperation);
}
[DllImport("Mmdevapi.dll", ExactSpelling = true, PreserveSig = false)]
public static extern void ActivateAudioInterfaceAsync(
[MarshalAs(UnmanagedType.LPWStr)] string deviceInterfacePath,
ref Guid riid,
IntPtr activationParams,
IActivateAudioInterfaceCompletionHandler completionHandler,
out IActivateAudioInterfaceAsyncOperation activationOperation);
/// <summary>
/// ManualResetEvent ベースの完了ハンドラ。同期待ちで IAudioClient を受け取る。
/// </summary>
[ComVisible(true)]
public sealed class CompletionHandler : IActivateAudioInterfaceCompletionHandler
{
public ManualResetEventSlim Done { get; } = new(false);
public int ActivateCompleted(IActivateAudioInterfaceAsyncOperation op)
{
Done.Set();
return 0; // S_OK
}
}
/// <summary>
/// IAudioClient (NAudio の interface) を取得するヘルパー。
/// 失敗時は HRESULT を含む Exception を投げる。
/// </summary>
public static IAudioClient ActivateProcessLoopbackAudioClient(uint pid, bool excludeMode)
{
var clientParams = new AUDIOCLIENT_ACTIVATION_PARAMS
{
ActivationType = AUDIOCLIENT_ACTIVATION_TYPE_PROCESS_LOOPBACK,
ProcessLoopbackParams = new AUDIOCLIENT_PROCESS_LOOPBACK_PARAMS
{
TargetProcessId = pid,
ProcessLoopbackMode = excludeMode
? PROCESS_LOOPBACK_MODE_EXCLUDE_TARGET_PROCESS_TREE
: PROCESS_LOOPBACK_MODE_INCLUDE_TARGET_PROCESS_TREE,
},
};
var paramsSize = Marshal.SizeOf<AUDIOCLIENT_ACTIVATION_PARAMS>();
var paramsPtr = Marshal.AllocCoTaskMem(paramsSize);
var propvariantPtr = Marshal.AllocCoTaskMem(Marshal.SizeOf<PROPVARIANT_BLOB>());
try
{
Marshal.StructureToPtr(clientParams, paramsPtr, false);
var pv = new PROPVARIANT_BLOB
{
vt = VT_BLOB,
cbSize = (uint)paramsSize,
pBlobData = paramsPtr,
};
Marshal.StructureToPtr(pv, propvariantPtr, false);
var iid = IID_IAudioClient;
var handler = new CompletionHandler();
ActivateAudioInterfaceAsync(VirtualDevicePath, ref iid, propvariantPtr, handler, out var op);
if (!handler.Done.Wait(TimeSpan.FromSeconds(5)))
throw new TimeoutException("ActivateAudioInterfaceAsync の完了を 5 秒以内に受け取れませんでした。");
var hr = op.GetActivateResult(out var activateResult, out var iface);
if (hr < 0)
throw Marshal.GetExceptionForHR(hr) ?? new InvalidOperationException($"GetActivateResult HRESULT 0x{hr:X8}");
if (activateResult < 0)
throw Marshal.GetExceptionForHR(activateResult)
?? new InvalidOperationException($"アクティベート失敗 HRESULT 0x{activateResult:X8}");
return (IAudioClient)iface;
}
finally
{
Marshal.FreeCoTaskMem(propvariantPtr);
Marshal.FreeCoTaskMem(paramsPtr);
}
}
}