@@ -36,28 +36,14 @@ public AudioProcessCapture(Process process, bool include = true)
36
36
WaveFormat format ;
37
37
38
38
// https://github.dev/microsoft/Windows-classic-samples/blob/main/Samples/ApplicationLoopback/cpp/LoopbackCapture.cpp
39
- public async Task < AudioFrameInputNode > CreateAudioNode ( AudioGraph graph )
39
+ public AudioFrameInputNode CreateAudioNode ( AudioGraph graph )
40
40
{
41
- const uint AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM = 0x80000000 ;
42
-
43
41
var audioNode = graph . CreateFrameInputNode ( ) ;
44
- client = new ( await ActivateAudioClient ( ) ) ;
45
42
46
43
var nodeFormat = audioNode . EncodingProperties ;
47
44
int blockAlign = ( int ) ( nodeFormat . ChannelCount * nodeFormat . BitsPerSample / 8 ) ;
48
45
format = WaveFormat . CreateCustomFormat ( WaveFormatEncoding . IeeeFloat , ( int ) nodeFormat . SampleRate , ( int ) nodeFormat . ChannelCount , ( int ) nodeFormat . SampleRate * blockAlign , blockAlign , ( int ) nodeFormat . BitsPerSample ) ;
49
46
50
- client . Initialize (
51
- AudioClientShareMode . Shared ,
52
- AudioClientStreamFlags . Loopback ,
53
- 5 * 10_000_000 ,
54
- AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM ,
55
- format ,
56
- Guid . Empty ) ;
57
-
58
- captureClient = client . AudioCaptureClient ;
59
- client . Start ( ) ;
60
-
61
47
audioNode . QuantumStarted += AudioNode_QuantumStarted ;
62
48
return audioNode ;
63
49
}
@@ -67,6 +53,24 @@ unsafe void AudioNode_QuantumStarted(AudioFrameInputNode sender, FrameInputNodeQ
67
53
if ( disposed )
68
54
return ;
69
55
56
+ // Client need to be activated in here so that we don't run into threading problems
57
+ // This method get's called on an MTA worker thread from native code
58
+ if ( client == null )
59
+ {
60
+ const uint AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM = 0x80000000 ;
61
+ client = new ( ActivateAudioClientInternal ( ) ) ;
62
+ client . Initialize (
63
+ AudioClientShareMode . Shared ,
64
+ AudioClientStreamFlags . Loopback ,
65
+ 5 * 10_000_000 ,
66
+ AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM ,
67
+ format ,
68
+ Guid . Empty ) ;
69
+
70
+ captureClient = client . AudioCaptureClient ;
71
+ client . Start ( ) ;
72
+ }
73
+
70
74
int availablePackages = captureClient . GetNextPacketSize ( ) ;
71
75
if ( availablePackages != 0 )
72
76
{
@@ -75,10 +79,13 @@ unsafe void AudioNode_QuantumStarted(AudioFrameInputNode sender, FrameInputNodeQ
75
79
using ( AudioBuffer buffer = frame . LockBuffer ( AudioBufferAccessMode . Write ) )
76
80
using ( var reference = buffer . CreateReference ( ) )
77
81
{
78
- ( ( IMemoryBufferByteAccess ) reference ) . GetBuffer ( out byte * targetBuffer , out _ ) ;
79
- byte * srcBuffer = ( byte * ) captureClient . GetBuffer ( out var numFrames , out _ ) ;
80
- Buffer . MemoryCopy ( srcBuffer , targetBuffer , bytesToCapture , bytesToCapture ) ;
81
- captureClient . ReleaseBuffer ( numFrames ) ;
82
+ unsafe
83
+ {
84
+ ( ( IMemoryBufferByteAccess ) reference ) . GetBuffer ( out byte * targetBuffer , out _ ) ;
85
+ byte * srcBuffer = ( byte * ) captureClient . GetBuffer ( out var numFrames , out _ ) ;
86
+ Buffer . MemoryCopy ( srcBuffer , targetBuffer , bytesToCapture , bytesToCapture ) ;
87
+ captureClient . ReleaseBuffer ( numFrames ) ;
88
+ }
82
89
}
83
90
sender . AddFrame ( frame ) ;
84
91
}
@@ -94,7 +101,7 @@ void IDisposable.Dispose()
94
101
captureClient = null ;
95
102
}
96
103
97
- async Task < IAudioClient > ActivateAudioClient ( )
104
+ IAudioClient ActivateAudioClientInternal ( )
98
105
{
99
106
const string VIRTUAL_AUDIO_DEVICE_PROCESS_LOOPBACK = @"VAD\Process_Loopback" ;
100
107
@@ -115,16 +122,17 @@ async Task<IAudioClient> ActivateAudioClient()
115
122
propVariant . blobVal . Data = ptr ;
116
123
propVariant . blobVal . Length = size ;
117
124
118
- Marshal . ThrowExceptionForHR ( ActivateAudioInterfaceAsync ( VIRTUAL_AUDIO_DEVICE_PROCESS_LOOPBACK , typeof ( IAudioClient ) . GUID , ref propVariant , completionHandler , out _ ) ) ;
119
- await completionHandler ;
120
- return completionHandler . GetResult ( ) ;
125
+ Marshal . ThrowExceptionForHR ( ActivateAudioInterfaceAsync ( VIRTUAL_AUDIO_DEVICE_PROCESS_LOOPBACK , typeof ( IAudioClient ) . GUID , ref propVariant , completionHandler , out var resultHandler ) ) ;
126
+ completionHandler . WaitForCompletion ( ) ;
127
+ Marshal . ThrowExceptionForHR ( resultHandler . GetActivateResult ( out _ , out var result ) ) ;
128
+ return ( IAudioClient ) result ;
121
129
}
122
130
123
131
[ PreserveSig ]
124
132
[ DllImport ( "Mmdevapi" , SetLastError = true , CharSet = CharSet . Unicode ) ]
125
133
static extern int ActivateAudioInterfaceAsync (
126
134
[ MarshalAs ( UnmanagedType . LPWStr ) ] string deviceInterfacePath ,
127
- Guid riid ,
135
+ Guid riid ,
128
136
ref PropVariant activationParams ,
129
137
IActivateAudioInterfaceCompletionHandler completionHandler ,
130
138
out IActivateAudioInterfaceAsyncOperation result
@@ -139,7 +147,8 @@ out IActivateAudioInterfaceAsyncOperation result
139
147
[ InterfaceType ( ComInterfaceType . InterfaceIsIUnknown ) ]
140
148
public interface IActivateAudioInterfaceAsyncOperation
141
149
{
142
- void GetActivateResult ( out int activateResult , [ MarshalAs ( UnmanagedType . IUnknown ) ] out object activatedInterface ) ;
150
+ [ PreserveSig ]
151
+ int GetActivateResult ( out int activateResult , [ MarshalAs ( UnmanagedType . IUnknown ) ] out object activatedInterface ) ;
143
152
}
144
153
145
154
/// <summary>
@@ -157,64 +166,14 @@ public interface IActivateAudioInterfaceCompletionHandler
157
166
void ActivateCompleted ( IActivateAudioInterfaceAsyncOperation activateOperation ) ;
158
167
}
159
168
160
- public class ActivateAudioInterfaceCompletionHandler < T > : IActivateAudioInterfaceCompletionHandler
169
+ public class ActivateAudioInterfaceCompletionHandler < T > : IActivateAudioInterfaceCompletionHandler where T : class
161
170
{
162
- public ActivateAudioInterfaceCompletionHandler ( )
163
- {
164
- globalInterfaceTable = ( ComExt . IGlobalInterfaceTable ) ( Activator . CreateInstance ( Type . GetTypeFromCLSID ( ComExt . CLSID_StdGlobalInterfaceTable ) ) ) ;
165
- }
166
-
167
- ComExt . IGlobalInterfaceTable globalInterfaceTable ;
168
-
169
- private TaskCompletionSource < T > taskFactory = new TaskCompletionSource < T > ( ) ;
170
- public TaskAwaiter < T > GetAwaiter ( )
171
- => taskFactory . Task . GetAwaiter ( ) ;
172
-
173
- public T GetResult ( )
174
- {
175
- Marshal . ThrowExceptionForHR ( globalInterfaceTable . GetInterfaceFromGlobal ( cookie , ComExt . IID_IUnknown , out var result ) ) ;
176
- return ( T ) result ;
177
- }
178
-
179
- uint cookie ;
171
+ AutoResetEvent _completionEvent = new ( false ) ;
180
172
void IActivateAudioInterfaceCompletionHandler . ActivateCompleted ( IActivateAudioInterfaceAsyncOperation activateOperation )
181
- {
182
- activateOperation . GetActivateResult ( out var hr , out object result ) ;
183
- if ( hr == 0 )
184
- {
185
- Marshal . ThrowExceptionForHR ( globalInterfaceTable . RegisterInterfaceInGlobal ( result , ComExt . IID_IUnknown , out cookie ) ) ;
186
- taskFactory . SetResult ( ( T ) result ) ;
187
- }
188
- else
189
- taskFactory . SetException ( new Win32Exception ( hr ) ) ;
190
- }
173
+ => _completionEvent . Set ( ) ;
191
174
192
- private static class ComExt
193
- {
194
- static public readonly Guid CLSID_StdGlobalInterfaceTable = new Guid ( "00000323-0000-0000-c000-000000000046" ) ;
195
- static public readonly Guid IID_IUnknown = new Guid ( "00000000-0000-0000-C000-000000000046" ) ;
196
-
197
- [ InterfaceType ( ComInterfaceType . InterfaceIsIUnknown ) , Guid ( "00000146-0000-0000-C000-000000000046" ) ]
198
- public interface IGlobalInterfaceTable
199
- {
200
- [ PreserveSig ]
201
- int RegisterInterfaceInGlobal (
202
- [ MarshalAs ( UnmanagedType . IUnknown ) ] object pUnk ,
203
- [ In , MarshalAs ( UnmanagedType . LPStruct ) ] Guid riid ,
204
- out uint cookie
205
- ) ;
206
-
207
- [ PreserveSig ]
208
- int RevokeInterfaceFromGlobal ( uint dwCookie ) ;
209
-
210
- [ PreserveSig ]
211
- int GetInterfaceFromGlobal (
212
- uint dwCookie ,
213
- Guid riid ,
214
- [ MarshalAs ( UnmanagedType . IUnknown ) ] out object ppv
215
- ) ;
216
- }
217
- }
175
+ public void WaitForCompletion ( )
176
+ => _completionEvent . WaitOne ( ) ;
218
177
}
219
178
220
179
namespace AudioClientHelper
0 commit comments