Browse Source

Dropping async IAudioClient2 activation down into the WASAPI package

process-audio-capture
Mark Heath 3 years ago
parent
commit
a01a0a6b78
  1. 12
      NAudio.Uap/WasapiCaptureRT.cs
  2. 181
      NAudio.Uap/WasapiOutRT.cs
  3. 54
      NAudio.Wasapi/CoreAudioApi/ActivateAudioInterfaceCompletionHandler.cs
  4. 35
      NAudio.Wasapi/CoreAudioApi/AudioClient.cs
  5. 2
      NAudio.Wasapi/CoreAudioApi/AudioClientBufferFlags.cs
  6. 2
      NAudio.Wasapi/CoreAudioApi/AudioClientProperties.cs
  7. 62
      NAudio.Wasapi/CoreAudioApi/AudioClientStreamFlags.cs
  8. 21
      NAudio.Wasapi/CoreAudioApi/AudioClientStreamOptions.cs
  9. 30
      NAudio.Wasapi/CoreAudioApi/AudioStreamCategory.cs
  10. 14
      NAudio.Wasapi/CoreAudioApi/Interfaces/IActivateAudioInterfaceAsyncOperation.cs
  11. 16
      NAudio.Wasapi/CoreAudioApi/Interfaces/IActivateAudioInterfaceCompletionHandler.cs
  12. 11
      NAudio.Wasapi/CoreAudioApi/Interfaces/IAgileObject.cs
  13. 2
      NAudio.Wasapi/CoreAudioApi/Interfaces/IAudioClient.cs
  14. 42
      NAudio.Wasapi/CoreAudioApi/Interfaces/IAudioClient2.cs
  15. 26
      NAudio.Wasapi/CoreAudioApi/NativeMethods.cs
  16. 42
      NAudio.Wasapi/MediaFoundationReader.cs
  17. 2
      NAudioUniversalDemo/Package.appxmanifest
  18. 2
      RELEASE_NOTES.md

12
NAudio.Uap/WasapiCaptureRT.cs

@ -2,12 +2,12 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using NAudio.CoreAudioApi;
using NAudio.CoreAudioApi.Interfaces;
using System.Threading;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Windows.Devices.Enumeration;
using Windows.Media.Devices;
using NAudio.Wasapi.CoreAudioApi;
namespace NAudio.Wave
{
@ -131,10 +131,9 @@ namespace NAudio.Wave
if (captureState == WasapiCaptureState.Disposed) throw new ObjectDisposedException(nameof(WasapiCaptureRT));
if (captureState != WasapiCaptureState.Uninitialized) throw new InvalidOperationException("Already initialized");
var icbh = new ActivateAudioInterfaceCompletionHandler(ac2 => InitializeCaptureDevice((IAudioClient)ac2));
// must be called on UI thread
NativeMethods.ActivateAudioInterfaceAsync(device, IID_IAudioClient2, IntPtr.Zero, icbh, out var activationOperation);
audioClient = new AudioClient((IAudioClient)(await icbh));
audioClient = await AudioClient.ActivateAsync(device, null);
InitializeCaptureDevice();
hEvent = NativeMethods.CreateEventExW(IntPtr.Zero, IntPtr.Zero, 0, EventAccess.EVENT_ALL_ACCESS);
audioClient.SetEventHandle(hEvent);
@ -142,9 +141,8 @@ namespace NAudio.Wave
captureState = WasapiCaptureState.Stopped;
}
private void InitializeCaptureDevice(IAudioClient audioClientInterface)
private void InitializeCaptureDevice()
{
var audioClient = new AudioClient((IAudioClient)audioClientInterface);
if (waveFormat == null)
{
waveFormat = audioClient.MixFormat;

181
NAudio.Uap/WasapiOutRT.cs

@ -1,12 +1,9 @@
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using NAudio.CoreAudioApi;
using NAudio.CoreAudioApi.Interfaces;
using Windows.Media.Devices;
using NAudio.Utils;
using NAudio.Wave.SampleProviders;
namespace NAudio.Wave
@ -99,32 +96,7 @@ namespace NAudio.Wave
};
}
private async Task Activate()
{
var icbh = new ActivateAudioInterfaceCompletionHandler(
ac2 =>
{
if (this.audioClientProperties != null)
{
IntPtr p = Marshal.AllocHGlobal(Marshal.SizeOf(this.audioClientProperties.Value));
Marshal.StructureToPtr(this.audioClientProperties.Value, p, false);
ac2.SetClientProperties(p);
Marshal.FreeHGlobal(p);
// TODO: consider whether we can marshal this without the need for AllocHGlobal
}
/*var wfx = new WaveFormat(44100, 16, 2);
int hr = ac2.Initialize(AudioClientShareMode.Shared,
AudioClientStreamFlags.EventCallback | AudioClientStreamFlags.NoPersist,
10000000, 0, wfx, IntPtr.Zero);*/
});
var IID_IAudioClient2 = new Guid("726778CD-F60A-4eda-82DE-E47610CD78AA");
IActivateAudioInterfaceAsyncOperation activationOperation;
NativeMethods.ActivateAudioInterfaceAsync(device, IID_IAudioClient2, IntPtr.Zero, icbh, out activationOperation);
var audioClient2 = await icbh;
audioClient = new AudioClient((IAudioClient)audioClient2);
}
private static string GetDefaultAudioEndpoint()
{
@ -135,7 +107,7 @@ namespace NAudio.Wave
private async void PlayThread()
{
await Activate();
audioClient = await AudioClient.ActivateAsync(device, audioClientProperties);
var playbackProvider = Init();
bool isClientRunning = false;
try
@ -479,21 +451,6 @@ namespace NAudio.Wave
[DllImport("api-ms-win-core-synch-l1-2-0.dll", ExactSpelling = true, PreserveSig = true, SetLastError = true)]
public static extern int WaitForSingleObjectEx(IntPtr hEvent, int milliseconds, bool bAlertable);
/// <summary>
/// Enables Windows Store apps to access preexisting Component Object Model (COM) interfaces in the WASAPI family.
/// </summary>
/// <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>
/// <param name="riid">The IID of a COM interface in the WASAPI family, such as IAudioClient.</param>
/// <param name="activationParams">Interface-specific activation parameters. For more information, see the pActivationParams parameter in IMMDevice::Activate. </param>
/// <param name="completionHandler"></param>
/// <param name="activationOperation"></param>
[DllImport("Mmdevapi.dll", ExactSpelling = true, PreserveSig = false)]
public static extern void ActivateAudioInterfaceAsync(
[In, MarshalAs(UnmanagedType.LPWStr)] string deviceInterfacePath,
[In, MarshalAs(UnmanagedType.LPStruct)] Guid riid,
[In] IntPtr activationParams, // n.b. is actually a pointer to a PropVariant, but we never need to pass anything but null
[In] IActivateAudioInterfaceCompletionHandler completionHandler,
out IActivateAudioInterfaceAsyncOperation activationOperation);
}
// trying some ideas from Lucian Wischik (ljw1004):
@ -507,143 +464,7 @@ namespace NAudio.Wave
EVENT_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE | 0x3
}
internal class ActivateAudioInterfaceCompletionHandler :
IActivateAudioInterfaceCompletionHandler, IAgileObject
{
private Action<IAudioClient2> initializeAction;
private TaskCompletionSource<IAudioClient2> tcs = new TaskCompletionSource<IAudioClient2>();
public ActivateAudioInterfaceCompletionHandler(
Action<IAudioClient2> initializeAction)
{
this.initializeAction = initializeAction;
}
public void ActivateCompleted(IActivateAudioInterfaceAsyncOperation activateOperation)
{
// First get the activation results, and see if anything bad happened then
int hr = 0;
object unk = null;
activateOperation.GetActivateResult(out hr, out unk);
if (hr != 0)
{
tcs.TrySetException(Marshal.GetExceptionForHR(hr, new IntPtr(-1)));
return;
}
var pAudioClient = (IAudioClient2) unk;
// Next try to call the client's (synchronous, blocking) initialization method.
try
{
initializeAction(pAudioClient);
tcs.SetResult(pAudioClient);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
}
public TaskAwaiter<IAudioClient2> GetAwaiter()
{
return tcs.Task.GetAwaiter();
}
}
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("41D949AB-9862-444A-80F6-C261334DA5EB")]
interface IActivateAudioInterfaceCompletionHandler
{
//virtual HRESULT STDMETHODCALLTYPE ActivateCompleted(/*[in]*/ _In_
// IActivateAudioInterfaceAsyncOperation *activateOperation) = 0;
void ActivateCompleted(IActivateAudioInterfaceAsyncOperation activateOperation);
}
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("72A22D78-CDE4-431D-B8CC-843A71199B6D")]
interface IActivateAudioInterfaceAsyncOperation
{
//virtual HRESULT STDMETHODCALLTYPE GetActivateResult(/*[out]*/ _Out_
// HRESULT *activateResult, /*[out]*/ _Outptr_result_maybenull_ IUnknown **activatedInterface) = 0;
void GetActivateResult([Out] out int activateResult,
[Out, MarshalAs(UnmanagedType.IUnknown)] out object activateInterface);
}
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("726778CD-F60A-4eda-82DE-E47610CD78AA")]
interface IAudioClient2
{
[PreserveSig]
int Initialize(AudioClientShareMode shareMode,
AudioClientStreamFlags streamFlags,
long hnsBufferDuration, // REFERENCE_TIME
long hnsPeriodicity, // REFERENCE_TIME
[In] WaveFormat pFormat,
[In] IntPtr audioSessionGuid);
// ref Guid AudioSessionGuid
/// <summary>
/// The GetBufferSize method retrieves the size (maximum capacity) of the endpoint buffer.
/// </summary>
int GetBufferSize(out uint bufferSize);
[return: MarshalAs(UnmanagedType.I8)]
long GetStreamLatency();
int GetCurrentPadding(out int currentPadding);
[PreserveSig]
int IsFormatSupported(
AudioClientShareMode shareMode,
[In] WaveFormat pFormat,
out IntPtr closestMatchFormat);
int GetMixFormat(out IntPtr deviceFormatPointer);
// REFERENCE_TIME is 64 bit int
int GetDevicePeriod(out long defaultDevicePeriod, out long minimumDevicePeriod);
int Start();
int Stop();
int Reset();
int SetEventHandle(IntPtr eventHandle);
/// <summary>
/// The GetService method accesses additional services from the audio client object.
/// </summary>
/// <param name="interfaceId">The interface ID for the requested service.</param>
/// <param name="interfacePointer">Pointer to a pointer variable into which the method writes the address of an instance of the requested interface. </param>
[PreserveSig]
int GetService([In, MarshalAs(UnmanagedType.LPStruct)] Guid interfaceId,
[Out, MarshalAs(UnmanagedType.IUnknown)] out object interfacePointer);
//virtual HRESULT STDMETHODCALLTYPE IsOffloadCapable(/*[in]*/ _In_
// AUDIO_STREAM_CATEGORY Category, /*[in]*/ _Out_ BOOL *pbOffloadCapable) = 0;
void IsOffloadCapable(int category, out bool pbOffloadCapable);
//virtual HRESULT STDMETHODCALLTYPE SetClientProperties(/*[in]*/ _In_
// const AudioClientProperties *pProperties) = 0;
void SetClientProperties([In] IntPtr pProperties);
// TODO: try this: void SetClientProperties([In, MarshalAs(UnmanagedType.LPStruct)] AudioClientProperties pProperties);
//virtual HRESULT STDMETHODCALLTYPE GetBufferSizeLimits(/*[in]*/ _In_
// const WAVEFORMATEX *pFormat, /*[in]*/ _In_ BOOL bEventDriven, /*[in]*/
// _Out_ REFERENCE_TIME *phnsMinBufferDuration, /*[in]*/ _Out_
// REFERENCE_TIME *phnsMaxBufferDuration) = 0;
void GetBufferSizeLimits(IntPtr pFormat, bool bEventDriven,
out long phnsMinBufferDuration, out long phnsMaxBufferDuration);
}
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("94ea2b94-e9cc-49e0-c0ff-ee64ca8f5b90")]
interface IAgileObject
{
}
}

54
NAudio.Wasapi/CoreAudioApi/ActivateAudioInterfaceCompletionHandler.cs

@ -0,0 +1,54 @@
using NAudio.CoreAudioApi.Interfaces;
using NAudio.Wasapi.CoreAudioApi.Interfaces;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace NAudio.Wasapi.CoreAudioApi
{
internal class ActivateAudioInterfaceCompletionHandler :
IActivateAudioInterfaceCompletionHandler, IAgileObject
{
private Action<IAudioClient2> initializeAction;
private TaskCompletionSource<IAudioClient2> tcs = new TaskCompletionSource<IAudioClient2>();
public ActivateAudioInterfaceCompletionHandler(
Action<IAudioClient2> initializeAction)
{
this.initializeAction = initializeAction;
}
public void ActivateCompleted(IActivateAudioInterfaceAsyncOperation activateOperation)
{
// First get the activation results, and see if anything bad happened then
activateOperation.GetActivateResult(out int hr, out object unk);
if (hr != 0)
{
tcs.TrySetException(Marshal.GetExceptionForHR(hr, new IntPtr(-1)));
return;
}
var pAudioClient = (IAudioClient2)unk;
// Next try to call the client's (synchronous, blocking) initialization method.
try
{
initializeAction(pAudioClient);
tcs.SetResult(pAudioClient);
}
catch (Exception ex)
{
tcs.TrySetException(ex);
}
}
public TaskAwaiter<IAudioClient2> GetAwaiter()
{
return tcs.Task.GetAwaiter();
}
}
}

35
NAudio.Wasapi/CoreAudioApi/AudioClient.cs

@ -2,6 +2,8 @@
using NAudio.CoreAudioApi.Interfaces;
using System.Runtime.InteropServices;
using NAudio.Wave;
using System.Threading.Tasks;
using NAudio.Wasapi.CoreAudioApi;
namespace NAudio.CoreAudioApi
{
@ -18,6 +20,39 @@ namespace NAudio.CoreAudioApi
private AudioStreamVolume audioStreamVolume;
private AudioClientShareMode shareMode;
public static async Task<AudioClient> ActivateAsync(string deviceInterfacePath, AudioClientProperties? audioClientProperties)
{
var icbh = new ActivateAudioInterfaceCompletionHandler(
ac2 =>
{
if (audioClientProperties != null)
{
IntPtr p = Marshal.AllocHGlobal(Marshal.SizeOf(audioClientProperties.Value));
try
{
// TODO: consider whether we can marshal this without the need for AllocHGlobal
Marshal.StructureToPtr(audioClientProperties.Value, p, false);
ac2.SetClientProperties(p);
}
finally
{
Marshal.FreeHGlobal(p);
}
}
/*var wfx = new WaveFormat(44100, 16, 2);
int hr = ac2.Initialize(AudioClientShareMode.Shared,
AudioClientStreamFlags.EventCallback | AudioClientStreamFlags.NoPersist,
10000000, 0, wfx, IntPtr.Zero);*/
});
var IID_IAudioClient2 = new Guid("726778CD-F60A-4eda-82DE-E47610CD78AA");
NativeMethods.ActivateAudioInterfaceAsync(deviceInterfacePath, IID_IAudioClient2, IntPtr.Zero, icbh, out var activationOperation);
var audioClient2 = await icbh;
return new AudioClient((IAudioClient)audioClient2);
}
public AudioClient(IAudioClient audioClientInterface)
{
this.audioClientInterface = audioClientInterface;

2
NAudio.Wasapi/CoreAudioApi/AudioClientBufferFlags.cs

@ -25,5 +25,5 @@ namespace NAudio.CoreAudioApi
/// </summary>
TimestampError = 0x4
}
}
}

2
NAudio.Wasapi/CoreAudioApi/AudioClientProperties.cs

@ -6,7 +6,7 @@ namespace NAudio.CoreAudioApi
/// <summary>
/// The AudioClientProperties structure is used to set the parameters that describe the properties of the client's audio stream.
/// </summary>
/// <remarks>http://msdn.microsoft.com/en-us/library/windows/desktop/hh968105(v=vs.85).aspx</remarks>
/// <remarks>https://docs.microsoft.com/en-us/windows/win32/api/audioclient/ns-audioclient-audioclientproperties-r1</remarks>
[StructLayout(LayoutKind.Sequential)]
public struct AudioClientProperties
{

62
NAudio.Wasapi/CoreAudioApi/AudioClientStreamFlags.cs

@ -53,4 +53,66 @@ namespace NAudio.CoreAudioApi
AutoConvertPcm = 0x80000000,
}
/// <summary>
/// AUDIOCLIENT_ACTIVATION_PARAMS
/// https://docs.microsoft.com/en-us/windows/win32/api/audioclientactivationparams/ns-audioclientactivationparams-audioclient_activation_params
/// </summary>
struct AudioClientActivationParams
{
public AudioClientActivationType ActivationType;
public AudioClientProcessLoopbackParams ProcessLoopbackParams;
}
/// <summary>
/// AUDIOCLIENT_PROCESS_LOOPBACK_PARAMS
/// https://docs.microsoft.com/en-us/windows/win32/api/audioclientactivationparams/ns-audioclientactivationparams-audioclient_process_loopback_params
/// </summary>
struct AudioClientProcessLoopbackParams
{
/// <summary>
/// AUDIOCLIENT_PROCESS_LOOPBACK_PARAMS
/// The ID of the process for which the render streams, and the render streams of its child processes, will be included or excluded when activating the process loopback stream.
/// </summary>
public uint TargetProcessId;
public ProcessLoopbackMode ProcessLoopbackMode;
}
/// <summary>
/// PROCESS_LOOPBACK_MODE
/// https://docs.microsoft.com/en-us/windows/win32/api/audioclientactivationparams/ne-audioclientactivationparams-process_loopback_mode
/// </summary>
enum ProcessLoopbackMode
{
/// <summary>
/// PROCESS_LOOPBACK_MODE_INCLUDE_TARGET_PROCESS_TREE
/// Render streams from the specified process and its child processes are included in the activated process loopback stream.
/// </summary>
IncludeTargetProcessTree,
/// <summary>
/// PROCESS_LOOPBACK_MODE_EXCLUDE_TARGET_PROCESS_TREE
/// Render streams from the specified process and its child processes are excluded from the activated process loopback stream.
/// </summary>
ExcludeTargetProcessTree
}
/// <summary>
/// AUDIOCLIENT_ACTIVATION_TYPE
/// https://docs.microsoft.com/en-us/windows/win32/api/audioclientactivationparams/ne-audioclientactivationparams-audioclient_activation_type
/// </summary>
enum AudioClientActivationType
{
/// <summary>
/// AUDIOCLIENT_ACTIVATION_TYPE_DEFAULT
/// Default activation.
/// </summary>
Default,
/// <summary>
/// AUDIOCLIENT_ACTIVATION_TYPE_PROCESS_LOOPBACK
/// Process loopback activation, allowing for the inclusion or exclusion of audio rendered by the specified process and its child processes.
/// </summary>
ProcessLoopback
};
}

21
NAudio.Wasapi/CoreAudioApi/AudioClientStreamOptions.cs

@ -1,17 +1,34 @@
namespace NAudio.CoreAudioApi
using System;
namespace NAudio.CoreAudioApi
{
/// <summary>
/// Defines values that describe the characteristics of an audio stream.
/// AUDCLNT_STREAMOPTIONS
/// https://docs.microsoft.com/en-us/windows/win32/api/audioclient/ne-audioclient-audclnt_streamoptions
/// </summary>
[Flags]
public enum AudioClientStreamOptions
{
/// <summary>
/// AUDCLNT_STREAMOPTIONS_NONE
/// No stream options.
/// </summary>
None = 0,
/// <summary>
/// AUDCLNT_STREAMOPTIONS_RAW
/// The audio stream is a 'raw' stream that bypasses all signal processing except for endpoint specific, always-on processing in the APO, driver, and hardware.
/// </summary>
Raw = 0x1
Raw = 0x1,
/// <summary>
/// AUDCLNT_STREAMOPTIONS_MATCH_FORMAT
/// The audio client is requesting that the audio engine match the format proposed by the client. The audio engine
/// will match this format only if the format is supported by the audio driver and associated APOs.
/// </summary>
MatchFormat = 0x2,
/// <summary>
/// AUDCLNT_STREAMOPTIONS_AMBISONICS
/// </summary>
Ambisonics = 0x4,
}
}

30
NAudio.Wasapi/CoreAudioApi/AudioStreamCategory.cs

@ -2,6 +2,8 @@ namespace NAudio.CoreAudioApi
{
/// <summary>
/// Specifies the category of an audio stream.
/// https://docs.microsoft.com/en-us/windows/win32/api/audiosessiontypes/ne-audiosessiontypes-audio_stream_category
/// AUDIO_STREAM_CATEGORY
/// </summary>
public enum AudioStreamCategory
{
@ -37,5 +39,33 @@ namespace NAudio.CoreAudioApi
/// Background audio for games.
/// </summary>
GameMedia,
/// <summary>
/// Game chat audio. Similar to AudioCategory_Communications except that AudioCategory_GameChat will not attenuate other streams.
/// </summary>
GameChat,
/// <summary>
/// Speech
/// </summary>
Speech,
/// <summary>
/// Stream that includes audio with dialog.
/// </summary>
Movie,
/// <summary>
/// Stream that includes audio without dialog.
/// </summary>
Media,
/// <summary>
/// Media is audio captured with the intent of capturing voice sources located in the ‘far field’. (Far away from the microphone.)
/// </summary>
FarFieldSpeech,
/// <summary>
/// Media is captured audio that requires consistent speech processing for the captured audio stream across all Windows devices. Used by applications that process speech data using machine learning algorithms.
/// </summary>
UniformSpeech,
/// <summary>
/// Media is audio captured with the intent of enabling dictation or typing by voice.
/// </summary>
VoiceTyping
}
}

14
NAudio.Wasapi/CoreAudioApi/Interfaces/IActivateAudioInterfaceAsyncOperation.cs

@ -0,0 +1,14 @@
using System;
using System.Runtime.InteropServices;
namespace NAudio.Wasapi.CoreAudioApi.Interfaces
{
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("72A22D78-CDE4-431D-B8CC-843A71199B6D")]
public interface IActivateAudioInterfaceAsyncOperation
{
//virtual HRESULT STDMETHODCALLTYPE GetActivateResult(/*[out]*/ _Out_
// HRESULT *activateResult, /*[out]*/ _Outptr_result_maybenull_ IUnknown **activatedInterface) = 0;
void GetActivateResult([Out] out int activateResult,
[Out, MarshalAs(UnmanagedType.IUnknown)] out object activateInterface);
}
}

16
NAudio.Wasapi/CoreAudioApi/Interfaces/IActivateAudioInterfaceCompletionHandler.cs

@ -0,0 +1,16 @@
using NAudio.CoreAudioApi.Interfaces;
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
namespace NAudio.Wasapi.CoreAudioApi.Interfaces
{
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("41D949AB-9862-444A-80F6-C261334DA5EB")]
public interface IActivateAudioInterfaceCompletionHandler
{
//virtual HRESULT STDMETHODCALLTYPE ActivateCompleted(/*[in]*/ _In_
// IActivateAudioInterfaceAsyncOperation *activateOperation) = 0;
void ActivateCompleted(IActivateAudioInterfaceAsyncOperation activateOperation);
}
}

11
NAudio.Wasapi/CoreAudioApi/Interfaces/IAgileObject.cs

@ -0,0 +1,11 @@
using System;
using System.Runtime.InteropServices;
namespace NAudio.Wasapi.CoreAudioApi.Interfaces
{
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("94ea2b94-e9cc-49e0-c0ff-ee64ca8f5b90")]
interface IAgileObject
{
}
}

2
NAudio.Wasapi/CoreAudioApi/Interfaces/IAudioClient.cs

@ -35,7 +35,7 @@ namespace NAudio.CoreAudioApi.Interfaces
int IsFormatSupported(
AudioClientShareMode shareMode,
[In] WaveFormat pFormat,
IntPtr closestMatchFormat);
IntPtr closestMatchFormat); // or outIntPtr??
int GetMixFormat(out IntPtr deviceFormatPointer);

42
NAudio.Wasapi/CoreAudioApi/Interfaces/IAudioClient2.cs

@ -0,0 +1,42 @@
using System;
using System.Runtime.InteropServices;
namespace NAudio.CoreAudioApi.Interfaces
{
/// <summary>
/// https://docs.microsoft.com/en-us/windows/win32/api/audioclient/nn-audioclient-iaudioclient2
/// </summary>
[ComImport, InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("726778CD-F60A-4eda-82DE-E47610CD78AA")]
public interface IAudioClient2 : IAudioClient
{
//virtual HRESULT STDMETHODCALLTYPE IsOffloadCapable(/*[in]*/ _In_
// AUDIO_STREAM_CATEGORY Category, /*[in]*/ _Out_ BOOL *pbOffloadCapable) = 0;
/// <summary>
/// The IsOffloadCapable method retrieves information about whether or not the endpoint on which a stream is created is capable of supporting an offloaded audio stream.
/// </summary>
/// <param name="category">An enumeration that specifies the category of an audio stream.</param>
/// <param name="pbOffloadCapable">A pointer to a Boolean value. TRUE indicates that the endpoint is offload-capable. FALSE indicates that the endpoint is not offload-capable.</param>
void IsOffloadCapable(AudioStreamCategory category, out bool pbOffloadCapable);
//virtual HRESULT STDMETHODCALLTYPE SetClientProperties(/*[in]*/ _In_
// const AudioClientProperties *pProperties) = 0;
/// <summary>
/// Pointer to an AudioClientProperties structure.
/// </summary>
/// <param name="pProperties"></param>
void SetClientProperties([In] IntPtr pProperties);
// TODO: try this: void SetClientProperties([In, MarshalAs(UnmanagedType.LPStruct)] AudioClientProperties pProperties);
//virtual HRESULT STDMETHODCALLTYPE GetBufferSizeLimits(/*[in]*/ _In_
// const WAVEFORMATEX *pFormat, /*[in]*/ _In_ BOOL bEventDriven, /*[in]*/
// _Out_ REFERENCE_TIME *phnsMinBufferDuration, /*[in]*/ _Out_
// REFERENCE_TIME *phnsMaxBufferDuration) = 0;
/// <summary>
/// The GetBufferSizeLimits method returns the buffer size limits of the hardware audio engine in 100-nanosecond units.
/// </summary>
/// <param name="pFormat">A pointer to the target format that is being queried for the buffer size limit.</param>
/// <param name="bEventDriven">Boolean value to indicate whether or not the stream can be event-driven.</param>
/// <param name="phnsMinBufferDuration">Returns a pointer to the minimum buffer size (in 100-nanosecond units) that is required for the underlying hardware audio engine to operate at the format specified in the pFormat parameter, without frequent audio glitching.</param>
/// <param name="phnsMaxBufferDuration">Returns a pointer to the maximum buffer size (in 100-nanosecond units) that the underlying hardware audio engine can support for the format specified in the pFormat parameter.</param>
void GetBufferSizeLimits(IntPtr pFormat, bool bEventDriven,
out long phnsMinBufferDuration, out long phnsMaxBufferDuration);
}
}

26
NAudio.Wasapi/CoreAudioApi/NativeMethods.cs

@ -0,0 +1,26 @@
using NAudio.Wasapi.CoreAudioApi.Interfaces;
using System;
using System.Runtime.InteropServices;
namespace NAudio.Wasapi.CoreAudioApi
{
static class NativeMethods
{
/// <summary>
/// Enables Windows Store apps to access preexisting Component Object Model (COM) interfaces in the WASAPI family.
/// </summary>
/// <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>
/// <param name="riid">The IID of a COM interface in the WASAPI family, such as IAudioClient.</param>
/// <param name="activationParams">Interface-specific activation parameters. For more information, see the pActivationParams parameter in IMMDevice::Activate. </param>
/// <param name="completionHandler"></param>
/// <param name="activationOperation"></param>
[DllImport("Mmdevapi.dll", ExactSpelling = true, PreserveSig = false)]
public static extern void ActivateAudioInterfaceAsync(
[In, MarshalAs(UnmanagedType.LPWStr)] string deviceInterfacePath,
[In, MarshalAs(UnmanagedType.LPStruct)] Guid riid,
[In] IntPtr activationParams, // n.b. is actually a pointer to a PropVariant, but we never need to pass anything but null
[In] IActivateAudioInterfaceCompletionHandler completionHandler,
out IActivateAudioInterfaceAsyncOperation activationOperation);
}
}

42
NAudio.Wasapi/MediaFoundationReader.cs

@ -109,8 +109,7 @@ namespace NAudio.Wave
private WaveFormat GetCurrentWaveFormat(IMFSourceReader reader)
{
IMFMediaType uncompressedMediaType;
reader.GetCurrentMediaType(MediaFoundationInterop.MF_SOURCE_READER_FIRST_AUDIO_STREAM, out uncompressedMediaType);
reader.GetCurrentMediaType(MediaFoundationInterop.MF_SOURCE_READER_FIRST_AUDIO_STREAM, out IMFMediaType uncompressedMediaType);
// Two ways to query it, first is to ask for properties (second is to convert into WaveFormatEx using MFCreateWaveFormatExFromMFMediaType)
var outputMediaType = new MediaType(uncompressedMediaType);
@ -126,13 +125,12 @@ namespace NAudio.Wave
if (audioSubType == AudioSubtypes.MFAudioFormat_Float)
return WaveFormat.CreateIeeeFloatWaveFormat(sampleRate, channels);
var subTypeDescription = FieldDescriptionHelper.Describe(typeof (AudioSubtypes), audioSubType);
throw new InvalidDataException(String.Format("Unsupported audio sub Type {0}", subTypeDescription));
throw new InvalidDataException($"Unsupported audio sub Type {subTypeDescription}");
}
private static MediaType GetCurrentMediaType(IMFSourceReader reader)
{
IMFMediaType mediaType;
reader.GetCurrentMediaType(MediaFoundationInterop.MF_SOURCE_READER_FIRST_AUDIO_STREAM, out mediaType);
reader.GetCurrentMediaType(MediaFoundationInterop.MF_SOURCE_READER_FIRST_AUDIO_STREAM, out IMFMediaType mediaType);
return new MediaType(mediaType);
}
@ -249,11 +247,8 @@ namespace NAudio.Wave
while (bytesWritten < count)
{
IMFSample pSample;
MF_SOURCE_READER_FLAG dwFlags;
ulong timestamp;
int actualStreamIndex;
pReader.ReadSample(MediaFoundationInterop.MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0, out actualStreamIndex, out dwFlags, out timestamp, out pSample);
pReader.ReadSample(MediaFoundationInterop.MF_SOURCE_READER_FIRST_AUDIO_STREAM, 0,
out int actualStreamIndex, out MF_SOURCE_READER_FLAG dwFlags, out ulong timestamp, out IMFSample pSample);
if ((dwFlags & MF_SOURCE_READER_FLAG.MF_SOURCE_READERF_ENDOFSTREAM) != 0)
{
// reached the end of the stream
@ -267,15 +262,11 @@ namespace NAudio.Wave
}
else if (dwFlags != 0)
{
throw new InvalidOperationException(String.Format("MediaFoundationReadError {0}", dwFlags));
throw new InvalidOperationException($"MediaFoundationReadError {dwFlags}");
}
IMFMediaBuffer pBuffer;
pSample.ConvertToContiguousBuffer(out pBuffer);
IntPtr pAudioData;
int cbBuffer;
int pcbMaxLength;
pBuffer.Lock(out pAudioData, out pcbMaxLength, out cbBuffer);
pSample.ConvertToContiguousBuffer(out IMFMediaBuffer pBuffer);
pBuffer.Lock(out IntPtr pAudioData, out int pcbMaxLength, out int cbBuffer);
EnsureBuffer(cbBuffer);
Marshal.Copy(pAudioData, decoderOutputBuffer, 0, cbBuffer);
decoderOutputOffset = 0;
@ -283,7 +274,6 @@ namespace NAudio.Wave
bytesWritten += ReadFromDecoderBuffer(buffer, offset + bytesWritten, count - bytesWritten);
pBuffer.Unlock();
Marshal.ReleaseComObject(pBuffer);
Marshal.ReleaseComObject(pSample);
@ -353,11 +343,17 @@ namespace NAudio.Wave
long nsPosition = (10000000L * repositionTo) / waveFormat.AverageBytesPerSecond;
var pv = PropVariant.FromLong(nsPosition);
var ptr = Marshal.AllocHGlobal(Marshal.SizeOf(pv));
Marshal.StructureToPtr(pv,ptr,false);
// should pass in a variant of type VT_I8 which is a long containing time in 100nanosecond units
pReader.SetCurrentPosition(Guid.Empty, ptr);
Marshal.FreeHGlobal(ptr);
try
{
Marshal.StructureToPtr(pv, ptr, false);
// should pass in a variant of type VT_I8 which is a long containing time in 100nanosecond units
pReader.SetCurrentPosition(Guid.Empty, ptr);
}
finally
{
Marshal.FreeHGlobal(ptr);
}
decoderOutputCount = 0;
decoderOutputOffset = 0;
position = desiredPosition;

2
NAudioUniversalDemo/Package.appxmanifest

@ -45,6 +45,6 @@
<Capabilities>
<Capability Name="internetClient" />
<DeviceCapability Name="microphone" />
<DeviceCapability Name="microphone"/>
</Capabilities>
</Package>

2
RELEASE_NOTES.md

@ -2,7 +2,7 @@
* Minimum supported Win 10 version is now uap10.0.18362 (SDK version 1903)
* `IWavePlayer` now has an `OuputWaveFormat` property
* `WasapiCapture` supports sample rate conversion so you can capture at a sample rate of your choice
* `WasapiCapture` and `WasapiLoopbackCapture` support sample rate conversion so you can capture at a sample rate of your choice
* `WasapiOut` supports built-in sample rate conversion in shared mode
* `MediaFoundationEncoder` allows you to encode to a `Stream`

Loading…
Cancel
Save