Skip to content

Commit 74445d8

Browse files
Working single-app capture
1 parent e6f2524 commit 74445d8

File tree

2 files changed

+42
-83
lines changed

2 files changed

+42
-83
lines changed

VBAudioRouter.Capture/AudioProcessCapture.cs

Lines changed: 39 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -36,28 +36,14 @@ public AudioProcessCapture(Process process, bool include = true)
3636
WaveFormat format;
3737

3838
// 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)
4040
{
41-
const uint AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM = 0x80000000;
42-
4341
var audioNode = graph.CreateFrameInputNode();
44-
client = new(await ActivateAudioClient());
4542

4643
var nodeFormat = audioNode.EncodingProperties;
4744
int blockAlign = (int)(nodeFormat.ChannelCount * nodeFormat.BitsPerSample / 8);
4845
format = WaveFormat.CreateCustomFormat(WaveFormatEncoding.IeeeFloat, (int)nodeFormat.SampleRate, (int)nodeFormat.ChannelCount, (int)nodeFormat.SampleRate * blockAlign, blockAlign, (int)nodeFormat.BitsPerSample);
4946

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-
6147
audioNode.QuantumStarted += AudioNode_QuantumStarted;
6248
return audioNode;
6349
}
@@ -67,6 +53,24 @@ unsafe void AudioNode_QuantumStarted(AudioFrameInputNode sender, FrameInputNodeQ
6753
if (disposed)
6854
return;
6955

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+
7074
int availablePackages = captureClient.GetNextPacketSize();
7175
if (availablePackages != 0)
7276
{
@@ -75,10 +79,13 @@ unsafe void AudioNode_QuantumStarted(AudioFrameInputNode sender, FrameInputNodeQ
7579
using (AudioBuffer buffer = frame.LockBuffer(AudioBufferAccessMode.Write))
7680
using (var reference = buffer.CreateReference())
7781
{
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+
}
8289
}
8390
sender.AddFrame(frame);
8491
}
@@ -94,7 +101,7 @@ void IDisposable.Dispose()
94101
captureClient = null;
95102
}
96103

97-
async Task<IAudioClient> ActivateAudioClient()
104+
IAudioClient ActivateAudioClientInternal()
98105
{
99106
const string VIRTUAL_AUDIO_DEVICE_PROCESS_LOOPBACK = @"VAD\Process_Loopback";
100107

@@ -115,16 +122,17 @@ async Task<IAudioClient> ActivateAudioClient()
115122
propVariant.blobVal.Data = ptr;
116123
propVariant.blobVal.Length = size;
117124

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;
121129
}
122130

123131
[PreserveSig]
124132
[DllImport("Mmdevapi", SetLastError = true, CharSet = CharSet.Unicode)]
125133
static extern int ActivateAudioInterfaceAsync(
126134
[MarshalAs(UnmanagedType.LPWStr)] string deviceInterfacePath,
127-
Guid riid,
135+
Guid riid,
128136
ref PropVariant activationParams,
129137
IActivateAudioInterfaceCompletionHandler completionHandler,
130138
out IActivateAudioInterfaceAsyncOperation result
@@ -139,7 +147,8 @@ out IActivateAudioInterfaceAsyncOperation result
139147
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
140148
public interface IActivateAudioInterfaceAsyncOperation
141149
{
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);
143152
}
144153

145154
/// <summary>
@@ -157,64 +166,14 @@ public interface IActivateAudioInterfaceCompletionHandler
157166
void ActivateCompleted(IActivateAudioInterfaceAsyncOperation activateOperation);
158167
}
159168

160-
public class ActivateAudioInterfaceCompletionHandler<T> : IActivateAudioInterfaceCompletionHandler
169+
public class ActivateAudioInterfaceCompletionHandler<T> : IActivateAudioInterfaceCompletionHandler where T : class
161170
{
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);
180172
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();
191174

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();
218177
}
219178

220179
namespace AudioClientHelper

VBAudioRouter.UWP/Controls/Nodes/OutputNodeControl.xaml.vb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,9 @@ Namespace Controls.Nodes
4444
If Not result.Status = AudioDeviceNodeCreationStatus.Success Then Throw result.ExtendedError
4545
_BaseAudioNode = result.DeviceOutputNode
4646

47-
'Dim processCapture As New AudioProcessCapture(Process.GetProcessesByName("Microsoft.Media.Player")(0))
48-
'Dim audioNode = Await processCapture.CreateAudioNode(graph)
49-
'audioNode.AddOutgoingConnection(BaseAudioNode)
47+
Dim processCapture As New AudioProcessCapture(Process.GetProcessesByName("Microsoft.Media.Player")(0))
48+
Dim audioNode = processCapture.CreateAudioNode(graph)
49+
audioNode.AddOutgoingConnection(BaseAudioNode)
5050
End Function
5151

5252
Public Sub OnStateChanged(state As GraphState) Implements IAudioNodeControl.OnStateChanged : End Sub

0 commit comments

Comments
 (0)