Skip to content

Commit a01a0a6

Browse files
committed
Dropping async IAudioClient2 activation down into the WASAPI package
1 parent 16d5432 commit a01a0a6

18 files changed

+339
-217
lines changed

NAudio.Uap/WasapiCaptureRT.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@
22
using System.Collections.Generic;
33
using System.Threading.Tasks;
44
using NAudio.CoreAudioApi;
5-
using NAudio.CoreAudioApi.Interfaces;
65
using System.Threading;
76
using System.Diagnostics;
87
using System.Runtime.InteropServices;
98
using Windows.Devices.Enumeration;
109
using Windows.Media.Devices;
10+
using NAudio.Wasapi.CoreAudioApi;
1111

1212
namespace NAudio.Wave
1313
{
@@ -131,20 +131,18 @@ public async Task InitAsync()
131131
if (captureState == WasapiCaptureState.Disposed) throw new ObjectDisposedException(nameof(WasapiCaptureRT));
132132
if (captureState != WasapiCaptureState.Uninitialized) throw new InvalidOperationException("Already initialized");
133133

134-
var icbh = new ActivateAudioInterfaceCompletionHandler(ac2 => InitializeCaptureDevice((IAudioClient)ac2));
135-
// must be called on UI thread
136-
NativeMethods.ActivateAudioInterfaceAsync(device, IID_IAudioClient2, IntPtr.Zero, icbh, out var activationOperation);
137-
audioClient = new AudioClient((IAudioClient)(await icbh));
134+
audioClient = await AudioClient.ActivateAsync(device, null);
135+
InitializeCaptureDevice();
136+
138137

139138
hEvent = NativeMethods.CreateEventExW(IntPtr.Zero, IntPtr.Zero, 0, EventAccess.EVENT_ALL_ACCESS);
140139
audioClient.SetEventHandle(hEvent);
141140

142141
captureState = WasapiCaptureState.Stopped;
143142
}
144143

145-
private void InitializeCaptureDevice(IAudioClient audioClientInterface)
144+
private void InitializeCaptureDevice()
146145
{
147-
var audioClient = new AudioClient((IAudioClient)audioClientInterface);
148146
if (waveFormat == null)
149147
{
150148
waveFormat = audioClient.MixFormat;

NAudio.Uap/WasapiOutRT.cs

Lines changed: 1 addition & 180 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
using System;
2-
using System.Runtime.CompilerServices;
32
using System.Runtime.InteropServices;
43
using System.Threading;
54
using System.Threading.Tasks;
65
using NAudio.CoreAudioApi;
7-
using NAudio.CoreAudioApi.Interfaces;
86
using Windows.Media.Devices;
9-
using NAudio.Utils;
107
using NAudio.Wave.SampleProviders;
118

129
namespace NAudio.Wave
@@ -99,32 +96,7 @@ public void SetClientProperties(bool useHardwareOffload, AudioStreamCategory cat
9996
};
10097
}
10198

102-
private async Task Activate()
103-
{
104-
var icbh = new ActivateAudioInterfaceCompletionHandler(
105-
ac2 =>
106-
{
107-
108-
if (this.audioClientProperties != null)
109-
{
110-
IntPtr p = Marshal.AllocHGlobal(Marshal.SizeOf(this.audioClientProperties.Value));
111-
Marshal.StructureToPtr(this.audioClientProperties.Value, p, false);
112-
ac2.SetClientProperties(p);
113-
Marshal.FreeHGlobal(p);
114-
// TODO: consider whether we can marshal this without the need for AllocHGlobal
115-
}
11699

117-
/*var wfx = new WaveFormat(44100, 16, 2);
118-
int hr = ac2.Initialize(AudioClientShareMode.Shared,
119-
AudioClientStreamFlags.EventCallback | AudioClientStreamFlags.NoPersist,
120-
10000000, 0, wfx, IntPtr.Zero);*/
121-
});
122-
var IID_IAudioClient2 = new Guid("726778CD-F60A-4eda-82DE-E47610CD78AA");
123-
IActivateAudioInterfaceAsyncOperation activationOperation;
124-
NativeMethods.ActivateAudioInterfaceAsync(device, IID_IAudioClient2, IntPtr.Zero, icbh, out activationOperation);
125-
var audioClient2 = await icbh;
126-
audioClient = new AudioClient((IAudioClient)audioClient2);
127-
}
128100

129101
private static string GetDefaultAudioEndpoint()
130102
{
@@ -135,7 +107,7 @@ private static string GetDefaultAudioEndpoint()
135107

136108
private async void PlayThread()
137109
{
138-
await Activate();
110+
audioClient = await AudioClient.ActivateAsync(device, audioClientProperties);
139111
var playbackProvider = Init();
140112
bool isClientRunning = false;
141113
try
@@ -479,21 +451,6 @@ internal static extern IntPtr CreateEventExW(IntPtr lpEventAttributes, IntPtr lp
479451
[DllImport("api-ms-win-core-synch-l1-2-0.dll", ExactSpelling = true, PreserveSig = true, SetLastError = true)]
480452
public static extern int WaitForSingleObjectEx(IntPtr hEvent, int milliseconds, bool bAlertable);
481453

482-
/// <summary>
483-
/// Enables Windows Store apps to access preexisting Component Object Model (COM) interfaces in the WASAPI family.
484-
/// </summary>
485-
/// <param name="deviceInterfacePath">A device interface ID for an audio device. This is normally retrieved from a DeviceInformation object or one of the methods of the MediaDevice class.</param>
486-
/// <param name="riid">The IID of a COM interface in the WASAPI family, such as IAudioClient.</param>
487-
/// <param name="activationParams">Interface-specific activation parameters. For more information, see the pActivationParams parameter in IMMDevice::Activate. </param>
488-
/// <param name="completionHandler"></param>
489-
/// <param name="activationOperation"></param>
490-
[DllImport("Mmdevapi.dll", ExactSpelling = true, PreserveSig = false)]
491-
public static extern void ActivateAudioInterfaceAsync(
492-
[In, MarshalAs(UnmanagedType.LPWStr)] string deviceInterfacePath,
493-
[In, MarshalAs(UnmanagedType.LPStruct)] Guid riid,
494-
[In] IntPtr activationParams, // n.b. is actually a pointer to a PropVariant, but we never need to pass anything but null
495-
[In] IActivateAudioInterfaceCompletionHandler completionHandler,
496-
out IActivateAudioInterfaceAsyncOperation activationOperation);
497454
}
498455

499456
// trying some ideas from Lucian Wischik (ljw1004):
@@ -507,143 +464,7 @@ internal enum EventAccess
507464
EVENT_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x3
508465
}
509466

510-
internal class ActivateAudioInterfaceCompletionHandler :
511-
IActivateAudioInterfaceCompletionHandler, IAgileObject
512-
{
513-
private Action<IAudioClient2> initializeAction;
514-
private TaskCompletionSource<IAudioClient2> tcs = new TaskCompletionSource<IAudioClient2>();
515-
516-
public ActivateAudioInterfaceCompletionHandler(
517-
Action<IAudioClient2> initializeAction)
518-
{
519-
this.initializeAction = initializeAction;
520-
}
521-
522-
public void ActivateCompleted(IActivateAudioInterfaceAsyncOperation activateOperation)
523-
{
524-
// First get the activation results, and see if anything bad happened then
525-
int hr = 0;
526-
object unk = null;
527-
activateOperation.GetActivateResult(out hr, out unk);
528-
if (hr != 0)
529-
{
530-
tcs.TrySetException(Marshal.GetExceptionForHR(hr, new IntPtr(-1)));
531-
return;
532-
}
533-
534-
var pAudioClient = (IAudioClient2) unk;
535-
536-
// Next try to call the client's (synchronous, blocking) initialization method.
537-
try
538-
{
539-
initializeAction(pAudioClient);
540-
tcs.SetResult(pAudioClient);
541-
}
542-
catch (Exception ex)
543-
{
544-
tcs.TrySetException(ex);
545-
}
546-
547-
548-
}
549-
550-
551-
public TaskAwaiter<IAudioClient2> GetAwaiter()
552-
{
553-
return tcs.Task.GetAwaiter();
554-
}
555-
}
556467

557-
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("41D949AB-9862-444A-80F6-C261334DA5EB")]
558-
interface IActivateAudioInterfaceCompletionHandler
559-
{
560-
//virtual HRESULT STDMETHODCALLTYPE ActivateCompleted(/*[in]*/ _In_
561-
// IActivateAudioInterfaceAsyncOperation *activateOperation) = 0;
562-
void ActivateCompleted(IActivateAudioInterfaceAsyncOperation activateOperation);
563-
}
564-
565-
566-
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("72A22D78-CDE4-431D-B8CC-843A71199B6D")]
567-
interface IActivateAudioInterfaceAsyncOperation
568-
{
569-
//virtual HRESULT STDMETHODCALLTYPE GetActivateResult(/*[out]*/ _Out_
570-
// HRESULT *activateResult, /*[out]*/ _Outptr_result_maybenull_ IUnknown **activatedInterface) = 0;
571-
void GetActivateResult([Out] out int activateResult,
572-
[Out, MarshalAs(UnmanagedType.IUnknown)] out object activateInterface);
573-
}
574-
575-
576-
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("726778CD-F60A-4eda-82DE-E47610CD78AA")]
577-
interface IAudioClient2
578-
{
579-
[PreserveSig]
580-
int Initialize(AudioClientShareMode shareMode,
581-
AudioClientStreamFlags streamFlags,
582-
long hnsBufferDuration, // REFERENCE_TIME
583-
long hnsPeriodicity, // REFERENCE_TIME
584-
[In] WaveFormat pFormat,
585-
[In] IntPtr audioSessionGuid);
586-
587-
// ref Guid AudioSessionGuid
588-
589-
/// <summary>
590-
/// The GetBufferSize method retrieves the size (maximum capacity) of the endpoint buffer.
591-
/// </summary>
592-
int GetBufferSize(out uint bufferSize);
593-
594-
[return: MarshalAs(UnmanagedType.I8)]
595-
long GetStreamLatency();
596-
597-
int GetCurrentPadding(out int currentPadding);
598-
599-
[PreserveSig]
600-
int IsFormatSupported(
601-
AudioClientShareMode shareMode,
602-
[In] WaveFormat pFormat,
603-
out IntPtr closestMatchFormat);
604-
605-
int GetMixFormat(out IntPtr deviceFormatPointer);
606-
607-
// REFERENCE_TIME is 64 bit int
608-
int GetDevicePeriod(out long defaultDevicePeriod, out long minimumDevicePeriod);
609-
610-
int Start();
611-
612-
int Stop();
613-
614-
int Reset();
615-
616-
int SetEventHandle(IntPtr eventHandle);
617-
618-
/// <summary>
619-
/// The GetService method accesses additional services from the audio client object.
620-
/// </summary>
621-
/// <param name="interfaceId">The interface ID for the requested service.</param>
622-
/// <param name="interfacePointer">Pointer to a pointer variable into which the method writes the address of an instance of the requested interface. </param>
623-
[PreserveSig]
624-
int GetService([In, MarshalAs(UnmanagedType.LPStruct)] Guid interfaceId,
625-
[Out, MarshalAs(UnmanagedType.IUnknown)] out object interfacePointer);
626-
627-
//virtual HRESULT STDMETHODCALLTYPE IsOffloadCapable(/*[in]*/ _In_
628-
// AUDIO_STREAM_CATEGORY Category, /*[in]*/ _Out_ BOOL *pbOffloadCapable) = 0;
629-
void IsOffloadCapable(int category, out bool pbOffloadCapable);
630-
//virtual HRESULT STDMETHODCALLTYPE SetClientProperties(/*[in]*/ _In_
631-
// const AudioClientProperties *pProperties) = 0;
632-
void SetClientProperties([In] IntPtr pProperties);
633-
// TODO: try this: void SetClientProperties([In, MarshalAs(UnmanagedType.LPStruct)] AudioClientProperties pProperties);
634-
//virtual HRESULT STDMETHODCALLTYPE GetBufferSizeLimits(/*[in]*/ _In_
635-
// const WAVEFORMATEX *pFormat, /*[in]*/ _In_ BOOL bEventDriven, /*[in]*/
636-
// _Out_ REFERENCE_TIME *phnsMinBufferDuration, /*[in]*/ _Out_
637-
// REFERENCE_TIME *phnsMaxBufferDuration) = 0;
638-
void GetBufferSizeLimits(IntPtr pFormat, bool bEventDriven,
639-
out long phnsMinBufferDuration, out long phnsMaxBufferDuration);
640-
}
641-
642-
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("94ea2b94-e9cc-49e0-c0ff-ee64ca8f5b90")]
643-
interface IAgileObject
644-
{
645-
646-
}
647468

648469

649470
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using NAudio.CoreAudioApi.Interfaces;
2+
using NAudio.Wasapi.CoreAudioApi.Interfaces;
3+
using System;
4+
using System.Runtime.CompilerServices;
5+
using System.Runtime.InteropServices;
6+
using System.Threading.Tasks;
7+
8+
namespace NAudio.Wasapi.CoreAudioApi
9+
{
10+
internal class ActivateAudioInterfaceCompletionHandler :
11+
IActivateAudioInterfaceCompletionHandler, IAgileObject
12+
{
13+
private Action<IAudioClient2> initializeAction;
14+
private TaskCompletionSource<IAudioClient2> tcs = new TaskCompletionSource<IAudioClient2>();
15+
16+
public ActivateAudioInterfaceCompletionHandler(
17+
Action<IAudioClient2> initializeAction)
18+
{
19+
this.initializeAction = initializeAction;
20+
}
21+
22+
public void ActivateCompleted(IActivateAudioInterfaceAsyncOperation activateOperation)
23+
{
24+
// First get the activation results, and see if anything bad happened then
25+
activateOperation.GetActivateResult(out int hr, out object unk);
26+
if (hr != 0)
27+
{
28+
tcs.TrySetException(Marshal.GetExceptionForHR(hr, new IntPtr(-1)));
29+
return;
30+
}
31+
32+
var pAudioClient = (IAudioClient2)unk;
33+
34+
// Next try to call the client's (synchronous, blocking) initialization method.
35+
try
36+
{
37+
initializeAction(pAudioClient);
38+
tcs.SetResult(pAudioClient);
39+
}
40+
catch (Exception ex)
41+
{
42+
tcs.TrySetException(ex);
43+
}
44+
45+
46+
}
47+
48+
49+
public TaskAwaiter<IAudioClient2> GetAwaiter()
50+
{
51+
return tcs.Task.GetAwaiter();
52+
}
53+
}
54+
}

NAudio.Wasapi/CoreAudioApi/AudioClient.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
using NAudio.CoreAudioApi.Interfaces;
33
using System.Runtime.InteropServices;
44
using NAudio.Wave;
5+
using System.Threading.Tasks;
6+
using NAudio.Wasapi.CoreAudioApi;
57

68
namespace NAudio.CoreAudioApi
79
{
@@ -18,6 +20,39 @@ public class AudioClient : IDisposable
1820
private AudioStreamVolume audioStreamVolume;
1921
private AudioClientShareMode shareMode;
2022

23+
public static async Task<AudioClient> ActivateAsync(string deviceInterfacePath, AudioClientProperties? audioClientProperties)
24+
{
25+
var icbh = new ActivateAudioInterfaceCompletionHandler(
26+
ac2 =>
27+
{
28+
29+
if (audioClientProperties != null)
30+
{
31+
IntPtr p = Marshal.AllocHGlobal(Marshal.SizeOf(audioClientProperties.Value));
32+
try
33+
{
34+
// TODO: consider whether we can marshal this without the need for AllocHGlobal
35+
Marshal.StructureToPtr(audioClientProperties.Value, p, false);
36+
ac2.SetClientProperties(p);
37+
}
38+
finally
39+
{
40+
Marshal.FreeHGlobal(p);
41+
42+
}
43+
}
44+
45+
/*var wfx = new WaveFormat(44100, 16, 2);
46+
int hr = ac2.Initialize(AudioClientShareMode.Shared,
47+
AudioClientStreamFlags.EventCallback | AudioClientStreamFlags.NoPersist,
48+
10000000, 0, wfx, IntPtr.Zero);*/
49+
});
50+
var IID_IAudioClient2 = new Guid("726778CD-F60A-4eda-82DE-E47610CD78AA");
51+
NativeMethods.ActivateAudioInterfaceAsync(deviceInterfacePath, IID_IAudioClient2, IntPtr.Zero, icbh, out var activationOperation);
52+
var audioClient2 = await icbh;
53+
return new AudioClient((IAudioClient)audioClient2);
54+
}
55+
2156
public AudioClient(IAudioClient audioClientInterface)
2257
{
2358
this.audioClientInterface = audioClientInterface;

NAudio.Wasapi/CoreAudioApi/AudioClientBufferFlags.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,5 @@ public enum AudioClientBufferFlags
2525
/// </summary>
2626
TimestampError = 0x4
2727

28-
}
28+
}
2929
}

NAudio.Wasapi/CoreAudioApi/AudioClientProperties.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace NAudio.CoreAudioApi
66
/// <summary>
77
/// The AudioClientProperties structure is used to set the parameters that describe the properties of the client's audio stream.
88
/// </summary>
9-
/// <remarks>http://msdn.microsoft.com/en-us/library/windows/desktop/hh968105(v=vs.85).aspx</remarks>
9+
/// <remarks>https://docs.microsoft.com/en-us/windows/win32/api/audioclient/ns-audioclient-audioclientproperties-r1</remarks>
1010
[StructLayout(LayoutKind.Sequential)]
1111
public struct AudioClientProperties
1212
{

0 commit comments

Comments
 (0)