diff options
Diffstat (limited to 'portaudio/src/hostapi/wasapi/pa_win_wasapi.c')
-rw-r--r-- | portaudio/src/hostapi/wasapi/pa_win_wasapi.c | 6534 |
1 files changed, 0 insertions, 6534 deletions
diff --git a/portaudio/src/hostapi/wasapi/pa_win_wasapi.c b/portaudio/src/hostapi/wasapi/pa_win_wasapi.c deleted file mode 100644 index c76f302..0000000 --- a/portaudio/src/hostapi/wasapi/pa_win_wasapi.c +++ /dev/null @@ -1,6534 +0,0 @@ -/* - * Portable Audio I/O Library WASAPI implementation - * Copyright (c) 2006-2010 David Viens - * Copyright (c) 2010-2019 Dmitry Kostjuchenko - * - * Based on the Open Source API proposed by Ross Bencina - * Copyright (c) 1999-2019 Ross Bencina, Phil Burk - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files - * (the "Software"), to deal in the Software without restriction, - * including without limitation the rights to use, copy, modify, merge, - * publish, distribute, sublicense, and/or sell copies of the Software, - * and to permit persons to whom the Software is furnished to do so, - * subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR - * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF - * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -/* - * The text above constitutes the entire PortAudio license; however, - * the PortAudio community also makes the following non-binding requests: - * - * Any person wishing to distribute modifications to the Software is - * requested to send the modifications to the original developer so that - * they can be incorporated into the canonical version. It is also - * requested that these non-binding requests be included along with the - * license above. - */ - -/** @file - @ingroup hostapi_src - @brief WASAPI implementation of support for a host API. - @note pa_wasapi currently requires minimum VC 2005, and the latest Vista SDK -*/ - -#include <windows.h> -#include <stdio.h> -#include <process.h> -#include <assert.h> - -// Max device count (if defined) causes max constant device count in the device list that -// enables PaWasapi_UpdateDeviceList() API and makes it possible to update WASAPI list dynamically -#ifndef PA_WASAPI_MAX_CONST_DEVICE_COUNT - #define PA_WASAPI_MAX_CONST_DEVICE_COUNT 0 // Force basic behavior by defining 0 if not defined by user -#endif - -// Fallback from Event to the Polling method in case if latency is higher than 21.33ms, as it allows to use -// 100% of CPU inside the PA's callback. -// Note: Some USB DAC drivers are buggy when Polling method is forced in Exclusive mode, audio output becomes -// unstable with a lot of interruptions, therefore this define is optional. The default behavior is to -// not change the Event mode to Polling and use the mode which user provided. -//#define PA_WASAPI_FORCE_POLL_IF_LARGE_BUFFER - -//! Poll mode time slots logging. -//#define PA_WASAPI_LOG_TIME_SLOTS - -// WinRT -#if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) - #define PA_WINRT - #define INITGUID -#endif - -// WASAPI -// using adjustments for MinGW build from @mgeier/MXE -// https://github.com/mxe/mxe/commit/f4bbc45682f021948bdaefd9fd476e2a04c4740f -#include <mmreg.h> // must be before other Wasapi headers -#if defined(_MSC_VER) && (_MSC_VER >= 1400) || defined(__MINGW64_VERSION_MAJOR) - #include <avrt.h> - #define COBJMACROS - #include <audioclient.h> - #include <endpointvolume.h> - #define INITGUID // Avoid additional linkage of static libs, excessive code will be optimized out by the compiler -#ifndef _MSC_VER - #include <functiondiscoverykeys_devpkey.h> -#endif - #include <functiondiscoverykeys.h> - #include <mmdeviceapi.h> - #include <devicetopology.h> // Used to get IKsJackDescription interface - #undef INITGUID -// Visual Studio 2010 does not support the inline keyword -#if (_MSC_VER <= 1600) - #define inline _inline -#endif -#endif -#ifndef __MWERKS__ - #include <malloc.h> - #include <memory.h> -#endif -#ifndef PA_WINRT - #include <mmsystem.h> -#endif - -#include "pa_util.h" -#include "pa_allocation.h" -#include "pa_hostapi.h" -#include "pa_stream.h" -#include "pa_cpuload.h" -#include "pa_process.h" -#include "pa_win_wasapi.h" -#include "pa_debugprint.h" -#include "pa_ringbuffer.h" -#include "pa_win_coinitialize.h" - -#if !defined(NTDDI_VERSION) || (defined(__GNUC__) && (__GNUC__ <= 6) && !defined(__MINGW64__)) - - #undef WINVER - #undef _WIN32_WINNT - #define WINVER 0x0600 // VISTA - #define _WIN32_WINNT WINVER - - #ifndef WINAPI - #define WINAPI __stdcall - #endif - - #ifndef __unaligned - #define __unaligned - #endif - - #ifndef __C89_NAMELESS - #define __C89_NAMELESS - #endif - - #ifndef _AVRT_ //<< fix MinGW dummy compile by defining missing type: AVRT_PRIORITY - typedef enum _AVRT_PRIORITY - { - AVRT_PRIORITY_LOW = -1, - AVRT_PRIORITY_NORMAL, - AVRT_PRIORITY_HIGH, - AVRT_PRIORITY_CRITICAL - } AVRT_PRIORITY, *PAVRT_PRIORITY; - #endif - - #include <basetyps.h> // << for IID/CLSID - #include <rpcsal.h> - #include <sal.h> - - #ifndef __LPCGUID_DEFINED__ - #define __LPCGUID_DEFINED__ - typedef const GUID *LPCGUID; - #endif - typedef GUID IID; - typedef GUID CLSID; - - #ifndef PROPERTYKEY_DEFINED - #define PROPERTYKEY_DEFINED - typedef struct _tagpropertykey - { - GUID fmtid; - DWORD pid; - } PROPERTYKEY; - #endif - - #ifdef __midl_proxy - #define __MIDL_CONST - #else - #define __MIDL_CONST const - #endif - - #ifdef WIN64 - #include <wtypes.h> - #define FASTCALL - #include <oleidl.h> - #include <objidl.h> - #else - typedef struct _BYTE_BLOB - { - unsigned long clSize; - unsigned char abData[ 1 ]; - } BYTE_BLOB; - typedef /* [unique] */ __RPC_unique_pointer BYTE_BLOB *UP_BYTE_BLOB; - typedef LONGLONG REFERENCE_TIME; - #define NONAMELESSUNION - #endif - - #ifndef NT_SUCCESS - typedef LONG NTSTATUS; - #endif - - #ifndef WAVE_FORMAT_IEEE_FLOAT - #define WAVE_FORMAT_IEEE_FLOAT 0x0003 // 32-bit floating-point - #endif - - #ifndef __MINGW_EXTENSION - #if defined(__GNUC__) || defined(__GNUG__) - #define __MINGW_EXTENSION __extension__ - #else - #define __MINGW_EXTENSION - #endif - #endif - - #include <sdkddkver.h> - #include <propkeydef.h> - #define COBJMACROS - #define INITGUID // Avoid additional linkage of static libs, excessive code will be optimized out by the compiler - #include <audioclient.h> - #include <mmdeviceapi.h> - #include <endpointvolume.h> - #include <functiondiscoverykeys.h> - #include <devicetopology.h> // Used to get IKsJackDescription interface - #undef INITGUID - -#endif // NTDDI_VERSION - -// Missing declarations for WinRT -#ifdef PA_WINRT - - #define DEVICE_STATE_ACTIVE 0x00000001 - - typedef enum _EDataFlow - { - eRender = 0, - eCapture = ( eRender + 1 ) , - eAll = ( eCapture + 1 ) , - EDataFlow_enum_count = ( eAll + 1 ) - } - EDataFlow; - - typedef enum _EndpointFormFactor - { - RemoteNetworkDevice = 0, - Speakers = ( RemoteNetworkDevice + 1 ) , - LineLevel = ( Speakers + 1 ) , - Headphones = ( LineLevel + 1 ) , - Microphone = ( Headphones + 1 ) , - Headset = ( Microphone + 1 ) , - Handset = ( Headset + 1 ) , - UnknownDigitalPassthrough = ( Handset + 1 ) , - SPDIF = ( UnknownDigitalPassthrough + 1 ) , - HDMI = ( SPDIF + 1 ) , - UnknownFormFactor = ( HDMI + 1 ) - } - EndpointFormFactor; - -#endif - -#ifndef GUID_SECT - #define GUID_SECT -#endif - -#define __DEFINE_GUID(n,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) static const GUID n GUID_SECT = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}} -#define __DEFINE_IID(n,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) static const IID n GUID_SECT = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}} -#define __DEFINE_CLSID(n,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) static const CLSID n GUID_SECT = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}} -#define PA_DEFINE_CLSID(className, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ - __DEFINE_CLSID(pa_CLSID_##className, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8) -#define PA_DEFINE_IID(interfaceName, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \ - __DEFINE_IID(pa_IID_##interfaceName, 0x##l, 0x##w1, 0x##w2, 0x##b1, 0x##b2, 0x##b3, 0x##b4, 0x##b5, 0x##b6, 0x##b7, 0x##b8) - -// "1CB9AD4C-DBFA-4c32-B178-C2F568A703B2" -PA_DEFINE_IID(IAudioClient, 1cb9ad4c, dbfa, 4c32, b1, 78, c2, f5, 68, a7, 03, b2); -// "726778CD-F60A-4EDA-82DE-E47610CD78AA" -PA_DEFINE_IID(IAudioClient2, 726778cd, f60a, 4eda, 82, de, e4, 76, 10, cd, 78, aa); -// "7ED4EE07-8E67-4CD4-8C1A-2B7A5987AD42" -PA_DEFINE_IID(IAudioClient3, 7ed4ee07, 8e67, 4cd4, 8c, 1a, 2b, 7a, 59, 87, ad, 42); -// "1BE09788-6894-4089-8586-9A2A6C265AC5" -PA_DEFINE_IID(IMMEndpoint, 1be09788, 6894, 4089, 85, 86, 9a, 2a, 6c, 26, 5a, c5); -// "A95664D2-9614-4F35-A746-DE8DB63617E6" -PA_DEFINE_IID(IMMDeviceEnumerator, a95664d2, 9614, 4f35, a7, 46, de, 8d, b6, 36, 17, e6); -// "BCDE0395-E52F-467C-8E3D-C4579291692E" -PA_DEFINE_CLSID(IMMDeviceEnumerator,bcde0395, e52f, 467c, 8e, 3d, c4, 57, 92, 91, 69, 2e); -// "F294ACFC-3146-4483-A7BF-ADDCA7C260E2" -PA_DEFINE_IID(IAudioRenderClient, f294acfc, 3146, 4483, a7, bf, ad, dc, a7, c2, 60, e2); -// "C8ADBD64-E71E-48a0-A4DE-185C395CD317" -PA_DEFINE_IID(IAudioCaptureClient, c8adbd64, e71e, 48a0, a4, de, 18, 5c, 39, 5c, d3, 17); -// *2A07407E-6497-4A18-9787-32F79BD0D98F* Or this?? -PA_DEFINE_IID(IDeviceTopology, 2A07407E, 6497, 4A18, 97, 87, 32, f7, 9b, d0, d9, 8f); -// *AE2DE0E4-5BCA-4F2D-AA46-5D13F8FDB3A9* -PA_DEFINE_IID(IPart, AE2DE0E4, 5BCA, 4F2D, aa, 46, 5d, 13, f8, fd, b3, a9); -// *4509F757-2D46-4637-8E62-CE7DB944F57B* -PA_DEFINE_IID(IKsJackDescription, 4509F757, 2D46, 4637, 8e, 62, ce, 7d, b9, 44, f5, 7b); - -// Media formats: -__DEFINE_GUID(pa_KSDATAFORMAT_SUBTYPE_PCM, 0x00000001, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 ); -__DEFINE_GUID(pa_KSDATAFORMAT_SUBTYPE_ADPCM, 0x00000002, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 ); -__DEFINE_GUID(pa_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, 0x00000003, 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 ); - -#ifdef __IAudioClient2_INTERFACE_DEFINED__ -typedef enum _pa_AUDCLNT_STREAMOPTIONS { - pa_AUDCLNT_STREAMOPTIONS_NONE = 0x00, - pa_AUDCLNT_STREAMOPTIONS_RAW = 0x01, - pa_AUDCLNT_STREAMOPTIONS_MATCH_FORMAT = 0x02 -} pa_AUDCLNT_STREAMOPTIONS; -typedef struct _pa_AudioClientProperties { - UINT32 cbSize; - BOOL bIsOffload; - AUDIO_STREAM_CATEGORY eCategory; - pa_AUDCLNT_STREAMOPTIONS Options; -} pa_AudioClientProperties; -#define PA_AUDIOCLIENTPROPERTIES_SIZE_CATEGORY (sizeof(pa_AudioClientProperties) - sizeof(pa_AUDCLNT_STREAMOPTIONS)) -#define PA_AUDIOCLIENTPROPERTIES_SIZE_OPTIONS sizeof(pa_AudioClientProperties) -#endif // __IAudioClient2_INTERFACE_DEFINED__ - -/* use CreateThread for CYGWIN/Windows Mobile, _beginthreadex for all others */ -#if !defined(__CYGWIN__) && !defined(_WIN32_WCE) - #define CREATE_THREAD(PROC) (HANDLE)_beginthreadex( NULL, 0, (PROC), stream, 0, &stream->dwThreadId ) - #define PA_THREAD_FUNC static unsigned WINAPI - #define PA_THREAD_ID unsigned -#else - #define CREATE_THREAD(PROC) CreateThread( NULL, 0, (PROC), stream, 0, &stream->dwThreadId ) - #define PA_THREAD_FUNC static DWORD WINAPI - #define PA_THREAD_ID DWORD -#endif - -// Thread function forward decl. -PA_THREAD_FUNC ProcThreadEvent(void *param); -PA_THREAD_FUNC ProcThreadPoll(void *param); - -// Error codes (available since Windows 7) -#ifndef AUDCLNT_E_BUFFER_ERROR - #define AUDCLNT_E_BUFFER_ERROR AUDCLNT_ERR(0x018) -#endif -#ifndef AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED - #define AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED AUDCLNT_ERR(0x019) -#endif -#ifndef AUDCLNT_E_INVALID_DEVICE_PERIOD - #define AUDCLNT_E_INVALID_DEVICE_PERIOD AUDCLNT_ERR(0x020) -#endif - -// Stream flags (available since Windows 7) -#ifndef AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY - #define AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY 0x08000000 -#endif -#ifndef AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM - #define AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM 0x80000000 -#endif - -#define PA_WASAPI_DEVICE_ID_LEN 256 -#define PA_WASAPI_DEVICE_NAME_LEN 128 -#ifdef PA_WINRT - #define PA_WASAPI_DEVICE_MAX_COUNT 16 -#endif - -enum { S_INPUT = 0, S_OUTPUT = 1, S_COUNT = 2, S_FULLDUPLEX = 0 }; - -// Number of packets which compose single contignous buffer. With trial and error it was calculated -// that WASAPI Input sub-system uses 6 packets per whole buffer. Please provide more information -// or corrections if available. -enum { WASAPI_PACKETS_PER_INPUT_BUFFER = 6 }; - -#define STATIC_ARRAY_SIZE(array) (sizeof(array)/sizeof(array[0])) - -#define PRINT(x) PA_DEBUG(x); - -#define PA_SKELETON_SET_LAST_HOST_ERROR( errorCode, errorText ) \ - PaUtil_SetLastHostErrorInfo( paWASAPI, errorCode, errorText ) - -#define PA_WASAPI__IS_FULLDUPLEX(STREAM) ((STREAM)->in.clientProc && (STREAM)->out.clientProc) - -#ifndef IF_FAILED_JUMP -#define IF_FAILED_JUMP(hr, label) if(FAILED(hr)) goto label; -#endif - -#ifndef IF_FAILED_INTERNAL_ERROR_JUMP -#define IF_FAILED_INTERNAL_ERROR_JUMP(hr, error, label) if(FAILED(hr)) { error = paInternalError; goto label; } -#endif - -#define SAFE_CLOSE(h) if ((h) != NULL) { CloseHandle((h)); (h) = NULL; } -#define SAFE_RELEASE(punk) if ((punk) != NULL) { (punk)->lpVtbl->Release((punk)); (punk) = NULL; } - -// Mixer function -typedef void (*MixMonoToStereoF) (void *__to, const void *__from, UINT32 count); - -// AVRT is the new "multimedia scheduling stuff" -#ifndef PA_WINRT -typedef BOOL (WINAPI *FAvRtCreateThreadOrderingGroup) (PHANDLE,PLARGE_INTEGER,GUID*,PLARGE_INTEGER); -typedef BOOL (WINAPI *FAvRtDeleteThreadOrderingGroup) (HANDLE); -typedef BOOL (WINAPI *FAvRtWaitOnThreadOrderingGroup) (HANDLE); -typedef HANDLE (WINAPI *FAvSetMmThreadCharacteristics) (LPCSTR,LPDWORD); -typedef BOOL (WINAPI *FAvRevertMmThreadCharacteristics)(HANDLE); -typedef BOOL (WINAPI *FAvSetMmThreadPriority) (HANDLE,AVRT_PRIORITY); -static HMODULE hDInputDLL = 0; -FAvRtCreateThreadOrderingGroup pAvRtCreateThreadOrderingGroup = NULL; -FAvRtDeleteThreadOrderingGroup pAvRtDeleteThreadOrderingGroup = NULL; -FAvRtWaitOnThreadOrderingGroup pAvRtWaitOnThreadOrderingGroup = NULL; -FAvSetMmThreadCharacteristics pAvSetMmThreadCharacteristics = NULL; -FAvRevertMmThreadCharacteristics pAvRevertMmThreadCharacteristics = NULL; -FAvSetMmThreadPriority pAvSetMmThreadPriority = NULL; -#endif - -#define _GetProc(fun, type, name) { \ - fun = (type) GetProcAddress(hDInputDLL,name); \ - if (fun == NULL) { \ - PRINT(("GetProcAddr failed for %s" ,name)); \ - return FALSE; \ - } \ - } \ - -// ------------------------------------------------------------------------------------------ -/* prototypes for functions declared in this file */ -#ifdef __cplusplus -extern "C" -{ -#endif /* __cplusplus */ -PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); -#ifdef __cplusplus -} -#endif /* __cplusplus */ -// dummy entry point for other compilers and sdks -// currently built using RC1 SDK (5600) -//#if _MSC_VER < 1400 -//PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) -//{ - //return paNoError; -//} -//#else - -// ------------------------------------------------------------------------------------------ -static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); -static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, - const PaStreamParameters *inputParameters, - const PaStreamParameters *outputParameters, - double sampleRate ); -static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, - PaStream** s, - const PaStreamParameters *inputParameters, - const PaStreamParameters *outputParameters, - double sampleRate, - unsigned long framesPerBuffer, - PaStreamFlags streamFlags, - PaStreamCallback *streamCallback, - void *userData ); -static PaError CloseStream( PaStream* stream ); -static PaError StartStream( PaStream *stream ); -static PaError StopStream( PaStream *stream ); -static PaError AbortStream( PaStream *stream ); -static PaError IsStreamStopped( PaStream *s ); -static PaError IsStreamActive( PaStream *stream ); -static PaTime GetStreamTime( PaStream *stream ); -static double GetStreamCpuLoad( PaStream* stream ); -static PaError ReadStream( PaStream* stream, void *buffer, unsigned long frames ); -static PaError WriteStream( PaStream* stream, const void *buffer, unsigned long frames ); -static signed long GetStreamReadAvailable( PaStream* stream ); -static signed long GetStreamWriteAvailable( PaStream* stream ); - -// ------------------------------------------------------------------------------------------ -/* - These are fields that can be gathered from IDevice and IAudioDevice PRIOR to Initialize, and - done in first pass i assume that neither of these will cause the Driver to "load", but again, - who knows how they implement their stuff - */ -typedef struct PaWasapiDeviceInfo -{ - // Device -#ifndef PA_WINRT - IMMDevice *device; -#endif - - // device Id - WCHAR deviceId[PA_WASAPI_DEVICE_ID_LEN]; - - // from GetState - DWORD state; - - // Fields filled from IAudioDevice (_prior_ to Initialize) - // from GetDevicePeriod( - REFERENCE_TIME DefaultDevicePeriod; - REFERENCE_TIME MinimumDevicePeriod; - - // Default format (setup through Control Panel by user) - WAVEFORMATEXTENSIBLE DefaultFormat; - - // Mix format (internal format used by WASAPI audio engine) - WAVEFORMATEXTENSIBLE MixFormat; - - // Fields filled from IMMEndpoint'sGetDataFlow - EDataFlow flow; - - // Form-factor - EndpointFormFactor formFactor; -} -PaWasapiDeviceInfo; - -// ------------------------------------------------------------------------------------------ -/* PaWasapiHostApiRepresentation - host api datastructure specific to this implementation */ -typedef struct -{ - PaUtilHostApiRepresentation inheritedHostApiRep; - PaUtilStreamInterface callbackStreamInterface; - PaUtilStreamInterface blockingStreamInterface; - - PaUtilAllocationGroup *allocations; - - /* implementation specific data goes here */ - - PaWinUtilComInitializationResult comInitializationResult; - - // this is the REAL number of devices, whether they are useful to PA or not! - UINT32 deviceCount; - - PaWasapiDeviceInfo *devInfo; - - // is TRUE when WOW64 Vista/7 Workaround is needed - BOOL useWOW64Workaround; -} -PaWasapiHostApiRepresentation; - -// ------------------------------------------------------------------------------------------ -/* PaWasapiAudioClientParams - audio client parameters */ -typedef struct PaWasapiAudioClientParams -{ - PaWasapiDeviceInfo *device_info; - PaStreamParameters stream_params; - PaWasapiStreamInfo wasapi_params; - UINT32 frames_per_buffer; - double sample_rate; - BOOL blocking; - BOOL full_duplex; - BOOL wow64_workaround; -} -PaWasapiAudioClientParams; - -// ------------------------------------------------------------------------------------------ -/* PaWasapiStream - a stream data structure specifically for this implementation */ -typedef struct PaWasapiSubStream -{ - IAudioClient *clientParent; -#ifndef PA_WINRT - IStream *clientStream; -#endif - IAudioClient *clientProc; - - WAVEFORMATEXTENSIBLE wavex; - UINT32 bufferSize; - REFERENCE_TIME deviceLatency; - REFERENCE_TIME period; - double latencySeconds; - UINT32 framesPerHostCallback; - AUDCLNT_SHAREMODE shareMode; - UINT32 streamFlags; // AUDCLNT_STREAMFLAGS_EVENTCALLBACK, ... - UINT32 flags; - PaWasapiAudioClientParams params; //!< parameters - - // Buffers - UINT32 buffers; //!< number of buffers used (from host side) - UINT32 framesPerBuffer; //!< number of frames per 1 buffer - BOOL userBufferAndHostMatch; - - // Used for Mono >> Stereo workaround, if driver does not support it - // (in Exclusive mode WASAPI usually refuses to operate with Mono (1-ch) - void *monoBuffer; //!< pointer to buffer - UINT32 monoBufferSize; //!< buffer size in bytes - MixMonoToStereoF monoMixer; //!< pointer to mixer function - - PaUtilRingBuffer *tailBuffer; //!< buffer with trailing sample for blocking mode operations (only for Input) - void *tailBufferMemory; //!< tail buffer memory region -} -PaWasapiSubStream; - -// ------------------------------------------------------------------------------------------ -/* PaWasapiHostProcessor - redirects processing data */ -typedef struct PaWasapiHostProcessor -{ - PaWasapiHostProcessorCallback processor; - void *userData; -} -PaWasapiHostProcessor; - -// ------------------------------------------------------------------------------------------ -typedef struct PaWasapiStream -{ - /* IMPLEMENT ME: rename this */ - PaUtilStreamRepresentation streamRepresentation; - PaUtilCpuLoadMeasurer cpuLoadMeasurer; - PaUtilBufferProcessor bufferProcessor; - - // input - PaWasapiSubStream in; - IAudioCaptureClient *captureClientParent; -#ifndef PA_WINRT - IStream *captureClientStream; -#endif - IAudioCaptureClient *captureClient; - IAudioEndpointVolume *inVol; - - // output - PaWasapiSubStream out; - IAudioRenderClient *renderClientParent; -#ifndef PA_WINRT - IStream *renderClientStream; -#endif - IAudioRenderClient *renderClient; - IAudioEndpointVolume *outVol; - - // event handles for event-driven processing mode - HANDLE event[S_COUNT]; - - // buffer mode - PaUtilHostBufferSizeMode bufferMode; - - // must be volatile to avoid race condition on user query while - // thread is being started - volatile BOOL running; - - PA_THREAD_ID dwThreadId; - HANDLE hThread; - HANDLE hCloseRequest; - HANDLE hThreadStart; //!< signalled by thread on start - HANDLE hThreadExit; //!< signalled by thread on exit - HANDLE hBlockingOpStreamRD; - HANDLE hBlockingOpStreamWR; - - // Host callback Output overrider - PaWasapiHostProcessor hostProcessOverrideOutput; - - // Host callback Input overrider - PaWasapiHostProcessor hostProcessOverrideInput; - - // Defines blocking/callback interface used - BOOL bBlocking; - - // Av Task (MM thread management) - HANDLE hAvTask; - - // Thread priority level - PaWasapiThreadPriority nThreadPriority; - - // State handler - PaWasapiStreamStateCallback fnStateHandler; - void *pStateHandlerUserData; -} -PaWasapiStream; - -// COM marshaling -static HRESULT MarshalSubStreamComPointers(PaWasapiSubStream *substream); -static HRESULT MarshalStreamComPointers(PaWasapiStream *stream); -static HRESULT UnmarshalSubStreamComPointers(PaWasapiSubStream *substream); -static HRESULT UnmarshalStreamComPointers(PaWasapiStream *stream); -static void ReleaseUnmarshaledSubComPointers(PaWasapiSubStream *substream); -static void ReleaseUnmarshaledComPointers(PaWasapiStream *stream); - -// Local methods -static void _StreamOnStop(PaWasapiStream *stream); -static void _StreamFinish(PaWasapiStream *stream); -static void _StreamCleanup(PaWasapiStream *stream); -static HRESULT _PollGetOutputFramesAvailable(PaWasapiStream *stream, UINT32 *available); -static HRESULT _PollGetInputFramesAvailable(PaWasapiStream *stream, UINT32 *available); -static void *PaWasapi_ReallocateMemory(void *prev, size_t size); -static void PaWasapi_FreeMemory(void *ptr); -static PaSampleFormat WaveToPaFormat(const WAVEFORMATEXTENSIBLE *fmtext); - -// WinRT (UWP) device list -#ifdef PA_WINRT -typedef struct PaWasapiWinrtDeviceInfo -{ - WCHAR id[PA_WASAPI_DEVICE_ID_LEN]; - WCHAR name[PA_WASAPI_DEVICE_NAME_LEN]; - EndpointFormFactor formFactor; -} -PaWasapiWinrtDeviceInfo; -typedef struct PaWasapiWinrtDeviceListRole -{ - WCHAR defaultId[PA_WASAPI_DEVICE_ID_LEN]; - PaWasapiWinrtDeviceInfo devices[PA_WASAPI_DEVICE_MAX_COUNT]; - UINT32 deviceCount; -} -PaWasapiWinrtDeviceListRole; -typedef struct PaWasapiWinrtDeviceList -{ - PaWasapiWinrtDeviceListRole render; - PaWasapiWinrtDeviceListRole capture; -} -PaWasapiWinrtDeviceList; -static PaWasapiWinrtDeviceList g_DeviceListInfo = { 0 }; -#endif - -// WinRT (UWP) device list context -#ifdef PA_WINRT -typedef struct PaWasapiWinrtDeviceListContextEntry -{ - PaWasapiWinrtDeviceInfo *info; - EDataFlow flow; -} -PaWasapiWinrtDeviceListContextEntry; -typedef struct PaWasapiWinrtDeviceListContext -{ - PaWasapiWinrtDeviceListContextEntry devices[PA_WASAPI_DEVICE_MAX_COUNT * 2]; -} -PaWasapiWinrtDeviceListContext; -#endif - -// ------------------------------------------------------------------------------------------ -#define LogHostError(HRES) __LogHostError(HRES, __FUNCTION__, __FILE__, __LINE__) -static HRESULT __LogHostError(HRESULT res, const char *func, const char *file, int line) -{ - const char *text = NULL; - switch (res) - { - case S_OK: return res; - case E_POINTER :text ="E_POINTER"; break; - case E_INVALIDARG :text ="E_INVALIDARG"; break; - - case AUDCLNT_E_NOT_INITIALIZED :text ="AUDCLNT_E_NOT_INITIALIZED"; break; - case AUDCLNT_E_ALREADY_INITIALIZED :text ="AUDCLNT_E_ALREADY_INITIALIZED"; break; - case AUDCLNT_E_WRONG_ENDPOINT_TYPE :text ="AUDCLNT_E_WRONG_ENDPOINT_TYPE"; break; - case AUDCLNT_E_DEVICE_INVALIDATED :text ="AUDCLNT_E_DEVICE_INVALIDATED"; break; - case AUDCLNT_E_NOT_STOPPED :text ="AUDCLNT_E_NOT_STOPPED"; break; - case AUDCLNT_E_BUFFER_TOO_LARGE :text ="AUDCLNT_E_BUFFER_TOO_LARGE"; break; - case AUDCLNT_E_OUT_OF_ORDER :text ="AUDCLNT_E_OUT_OF_ORDER"; break; - case AUDCLNT_E_UNSUPPORTED_FORMAT :text ="AUDCLNT_E_UNSUPPORTED_FORMAT"; break; - case AUDCLNT_E_INVALID_SIZE :text ="AUDCLNT_E_INVALID_SIZE"; break; - case AUDCLNT_E_DEVICE_IN_USE :text ="AUDCLNT_E_DEVICE_IN_USE"; break; - case AUDCLNT_E_BUFFER_OPERATION_PENDING :text ="AUDCLNT_E_BUFFER_OPERATION_PENDING"; break; - case AUDCLNT_E_THREAD_NOT_REGISTERED :text ="AUDCLNT_E_THREAD_NOT_REGISTERED"; break; - case AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED :text ="AUDCLNT_E_EXCLUSIVE_MODE_NOT_ALLOWED"; break; - case AUDCLNT_E_ENDPOINT_CREATE_FAILED :text ="AUDCLNT_E_ENDPOINT_CREATE_FAILED"; break; - case AUDCLNT_E_SERVICE_NOT_RUNNING :text ="AUDCLNT_E_SERVICE_NOT_RUNNING"; break; - case AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED :text ="AUDCLNT_E_EVENTHANDLE_NOT_EXPECTED"; break; - case AUDCLNT_E_EXCLUSIVE_MODE_ONLY :text ="AUDCLNT_E_EXCLUSIVE_MODE_ONLY"; break; - case AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL :text ="AUDCLNT_E_BUFDURATION_PERIOD_NOT_EQUAL"; break; - case AUDCLNT_E_EVENTHANDLE_NOT_SET :text ="AUDCLNT_E_EVENTHANDLE_NOT_SET"; break; - case AUDCLNT_E_INCORRECT_BUFFER_SIZE :text ="AUDCLNT_E_INCORRECT_BUFFER_SIZE"; break; - case AUDCLNT_E_BUFFER_SIZE_ERROR :text ="AUDCLNT_E_BUFFER_SIZE_ERROR"; break; - case AUDCLNT_E_CPUUSAGE_EXCEEDED :text ="AUDCLNT_E_CPUUSAGE_EXCEEDED"; break; - case AUDCLNT_E_BUFFER_ERROR :text ="AUDCLNT_E_BUFFER_ERROR"; break; - case AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED :text ="AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED"; break; - case AUDCLNT_E_INVALID_DEVICE_PERIOD :text ="AUDCLNT_E_INVALID_DEVICE_PERIOD"; break; - -#ifdef AUDCLNT_E_INVALID_STREAM_FLAG - case AUDCLNT_E_INVALID_STREAM_FLAG :text ="AUDCLNT_E_INVALID_STREAM_FLAG"; break; -#endif -#ifdef AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE - case AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE :text ="AUDCLNT_E_ENDPOINT_OFFLOAD_NOT_CAPABLE"; break; -#endif -#ifdef AUDCLNT_E_OUT_OF_OFFLOAD_RESOURCES - case AUDCLNT_E_OUT_OF_OFFLOAD_RESOURCES :text ="AUDCLNT_E_OUT_OF_OFFLOAD_RESOURCES"; break; -#endif -#ifdef AUDCLNT_E_OFFLOAD_MODE_ONLY - case AUDCLNT_E_OFFLOAD_MODE_ONLY :text ="AUDCLNT_E_OFFLOAD_MODE_ONLY"; break; -#endif -#ifdef AUDCLNT_E_NONOFFLOAD_MODE_ONLY - case AUDCLNT_E_NONOFFLOAD_MODE_ONLY :text ="AUDCLNT_E_NONOFFLOAD_MODE_ONLY"; break; -#endif -#ifdef AUDCLNT_E_RESOURCES_INVALIDATED - case AUDCLNT_E_RESOURCES_INVALIDATED :text ="AUDCLNT_E_RESOURCES_INVALIDATED"; break; -#endif -#ifdef AUDCLNT_E_RAW_MODE_UNSUPPORTED - case AUDCLNT_E_RAW_MODE_UNSUPPORTED :text ="AUDCLNT_E_RAW_MODE_UNSUPPORTED"; break; -#endif -#ifdef AUDCLNT_E_ENGINE_PERIODICITY_LOCKED - case AUDCLNT_E_ENGINE_PERIODICITY_LOCKED :text ="AUDCLNT_E_ENGINE_PERIODICITY_LOCKED"; break; -#endif -#ifdef AUDCLNT_E_ENGINE_FORMAT_LOCKED - case AUDCLNT_E_ENGINE_FORMAT_LOCKED :text ="AUDCLNT_E_ENGINE_FORMAT_LOCKED"; break; -#endif - - case AUDCLNT_S_BUFFER_EMPTY :text ="AUDCLNT_S_BUFFER_EMPTY"; break; - case AUDCLNT_S_THREAD_ALREADY_REGISTERED :text ="AUDCLNT_S_THREAD_ALREADY_REGISTERED"; break; - case AUDCLNT_S_POSITION_STALLED :text ="AUDCLNT_S_POSITION_STALLED"; break; - - // other windows common errors: - case CO_E_NOTINITIALIZED :text ="CO_E_NOTINITIALIZED: you must call CoInitialize() before Pa_OpenStream()"; break; - - default: - text = "UNKNOWN ERROR"; - } - PRINT(("WASAPI ERROR HRESULT: 0x%X : %s\n [FUNCTION: %s FILE: %s {LINE: %d}]\n", res, text, func, file, line)); -#ifndef PA_ENABLE_DEBUG_OUTPUT - (void)func; (void)file; (void)line; -#endif - PA_SKELETON_SET_LAST_HOST_ERROR(res, text); - return res; -} - -// ------------------------------------------------------------------------------------------ -#define LogPaError(PAERR) __LogPaError(PAERR, __FUNCTION__, __FILE__, __LINE__) -static PaError __LogPaError(PaError err, const char *func, const char *file, int line) -{ - if (err == paNoError) - return err; - - PRINT(("WASAPI ERROR PAERROR: %i : %s\n [FUNCTION: %s FILE: %s {LINE: %d}]\n", err, Pa_GetErrorText(err), func, file, line)); -#ifndef PA_ENABLE_DEBUG_OUTPUT - (void)func; (void)file; (void)line; -#endif - return err; -} - -// ------------------------------------------------------------------------------------------ -/*! \class ThreadSleepScheduler - Allows to emulate thread sleep of less than 1 millisecond under Windows. Scheduler - calculates number of times the thread must run until next sleep of 1 millisecond. - It does not make thread sleeping for real number of microseconds but rather controls - how many of imaginary microseconds the thread task can allow thread to sleep. -*/ -typedef struct ThreadIdleScheduler -{ - UINT32 m_idle_microseconds; //!< number of microseconds to sleep - UINT32 m_next_sleep; //!< next sleep round - UINT32 m_i; //!< current round iterator position - UINT32 m_resolution; //!< resolution in number of milliseconds -} -ThreadIdleScheduler; - -//! Setup scheduler. -static void ThreadIdleScheduler_Setup(ThreadIdleScheduler *sched, UINT32 resolution, UINT32 microseconds) -{ - assert(microseconds != 0); - assert(resolution != 0); - assert((resolution * 1000) >= microseconds); - - memset(sched, 0, sizeof(*sched)); - - sched->m_idle_microseconds = microseconds; - sched->m_resolution = resolution; - sched->m_next_sleep = (resolution * 1000) / microseconds; -} - -//! Iterate and check if can sleep. -static inline UINT32 ThreadIdleScheduler_NextSleep(ThreadIdleScheduler *sched) -{ - // advance and check if thread can sleep - if (++sched->m_i == sched->m_next_sleep) - { - sched->m_i = 0; - return sched->m_resolution; - } - return 0; -} - -// ------------------------------------------------------------------------------------------ -typedef struct _SystemTimer -{ - INT32 granularity; - -} SystemTimer; -static LARGE_INTEGER g_SystemTimerFrequency; -static BOOL g_SystemTimerUseQpc = FALSE; - -//! Set granularity of the system timer. -static BOOL SystemTimer_SetGranularity(SystemTimer *timer, UINT32 granularity) -{ -#ifndef PA_WINRT - TIMECAPS caps; - - timer->granularity = granularity; - - if (timeGetDevCaps(&caps, sizeof(caps)) == MMSYSERR_NOERROR) - { - if (timer->granularity < (INT32)caps.wPeriodMin) - timer->granularity = (INT32)caps.wPeriodMin; - } - - if (timeBeginPeriod(timer->granularity) != TIMERR_NOERROR) - { - PRINT(("SetSystemTimer: timeBeginPeriod(1) failed!\n")); - - timer->granularity = 10; - return FALSE; - } -#else - (void)granularity; - - // UWP does not support increase of the timer precision change and thus calling WaitForSingleObject with anything - // below 10 milliseconds will cause underruns for input and output stream. - timer->granularity = 10; -#endif - - return TRUE; -} - -//! Restore granularity of the system timer. -static void SystemTimer_RestoreGranularity(SystemTimer *timer) -{ -#ifndef PA_WINRT - if (timer->granularity != 0) - { - if (timeEndPeriod(timer->granularity) != TIMERR_NOERROR) - { - PRINT(("RestoreSystemTimer: timeEndPeriod(1) failed!\n")); - } - } -#else - (void)timer; -#endif -} - -//! Initialize high-resolution time getter. -static void SystemTimer_InitializeTimeGetter() -{ - g_SystemTimerUseQpc = QueryPerformanceFrequency(&g_SystemTimerFrequency); -} - -//! Get high-resolution time in milliseconds (using QPC by default). -static inline LONGLONG SystemTimer_GetTime(SystemTimer *timer) -{ - (void)timer; - - // QPC: https://docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps - if (g_SystemTimerUseQpc) - { - LARGE_INTEGER now; - QueryPerformanceCounter(&now); - return (now.QuadPart * 1000LL) / g_SystemTimerFrequency.QuadPart; - } - else - { - #ifdef PA_WINRT - return GetTickCount64(); - #else - return timeGetTime(); - #endif - } -} - -// ------------------------------------------------------------------------------------------ -/*static double nano100ToMillis(REFERENCE_TIME ref) -{ - // 1 nano = 0.000000001 seconds - //100 nano = 0.0000001 seconds - //100 nano = 0.0001 milliseconds - return ((double)ref) * 0.0001; -}*/ - -// ------------------------------------------------------------------------------------------ -static double nano100ToSeconds(REFERENCE_TIME ref) -{ - // 1 nano = 0.000000001 seconds - //100 nano = 0.0000001 seconds - //100 nano = 0.0001 milliseconds - return ((double)ref) * 0.0000001; -} - -// ------------------------------------------------------------------------------------------ -/*static REFERENCE_TIME MillisTonano100(double ref) -{ - // 1 nano = 0.000000001 seconds - //100 nano = 0.0000001 seconds - //100 nano = 0.0001 milliseconds - return (REFERENCE_TIME)(ref / 0.0001); -}*/ - -// ------------------------------------------------------------------------------------------ -static REFERENCE_TIME SecondsTonano100(double ref) -{ - // 1 nano = 0.000000001 seconds - //100 nano = 0.0000001 seconds - //100 nano = 0.0001 milliseconds - return (REFERENCE_TIME)(ref / 0.0000001); -} - -// ------------------------------------------------------------------------------------------ -// Makes Hns period from frames and sample rate -static REFERENCE_TIME MakeHnsPeriod(UINT32 nFrames, DWORD nSamplesPerSec) -{ - return (REFERENCE_TIME)((10000.0 * 1000 / nSamplesPerSec * nFrames) + 0.5); -} - -// ------------------------------------------------------------------------------------------ -// Converts PaSampleFormat to bits per sample value -// Note: paCustomFormat stands for 8.24 format (24-bits inside 32-bit containers) -static WORD PaSampleFormatToBitsPerSample(PaSampleFormat format_id) -{ - switch (format_id & ~paNonInterleaved) - { - case paFloat32: - case paInt32: return 32; - case paCustomFormat: - case paInt24: return 24; - case paInt16: return 16; - case paInt8: - case paUInt8: return 8; - } - return 0; -} - -// ------------------------------------------------------------------------------------------ -// Convert PaSampleFormat to valid sample format for I/O, e.g. if paCustomFormat is specified -// it will be converted to paInt32, other formats pass through -// Note: paCustomFormat stands for 8.24 format (24-bits inside 32-bit containers) -static PaSampleFormat GetSampleFormatForIO(PaSampleFormat format_id) -{ - return ((format_id & ~paNonInterleaved) == paCustomFormat ? - (paInt32 | (format_id & paNonInterleaved ? paNonInterleaved : 0)) : format_id); -} - -// ------------------------------------------------------------------------------------------ -// Converts Hns period into number of frames -static UINT32 MakeFramesFromHns(REFERENCE_TIME hnsPeriod, UINT32 nSamplesPerSec) -{ - UINT32 nFrames = (UINT32)( // frames = - 1.0 * hnsPeriod * // hns * - nSamplesPerSec / // (frames / s) / - 1000 / // (ms / s) / - 10000 // (hns / s) / - + 0.5 // rounding - ); - return nFrames; -} - -// Aligning function type -typedef UINT32 (*ALIGN_FUNC) (UINT32 v, UINT32 align); - -// ------------------------------------------------------------------------------------------ -// Aligns 'v' backwards -static UINT32 ALIGN_BWD(UINT32 v, UINT32 align) -{ - return ((v - (align ? v % align : 0))); -} - -// ------------------------------------------------------------------------------------------ -// Aligns 'v' forward -static UINT32 ALIGN_FWD(UINT32 v, UINT32 align) -{ - UINT32 remainder = (align ? (v % align) : 0); - if (remainder == 0) - return v; - return v + (align - remainder); -} - -// ------------------------------------------------------------------------------------------ -// Get next value power of 2 -static UINT32 ALIGN_NEXT_POW2(UINT32 v) -{ - UINT32 v2 = 1; - while (v > (v2 <<= 1)) { } - v = v2; - return v; -} - -// ------------------------------------------------------------------------------------------ -// Aligns WASAPI buffer to 128 byte packet boundary. HD Audio will fail to play if buffer -// is misaligned. This problem was solved in Windows 7 were AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED -// is thrown although we must align for Vista anyway. -static UINT32 AlignFramesPerBuffer(UINT32 nFrames, UINT32 nBlockAlign, ALIGN_FUNC pAlignFunc) -{ -#define HDA_PACKET_SIZE (128) - - UINT32 bytes = nFrames * nBlockAlign; - UINT32 packets; - - // align to a HD Audio packet size - bytes = pAlignFunc(bytes, HDA_PACKET_SIZE); - - // atlest 1 frame must be available - if (bytes < HDA_PACKET_SIZE) - bytes = HDA_PACKET_SIZE; - - packets = bytes / HDA_PACKET_SIZE; - bytes = packets * HDA_PACKET_SIZE; - nFrames = bytes / nBlockAlign; - - // WASAPI frames are always aligned to at least 8 - nFrames = ALIGN_FWD(nFrames, 8); - - return nFrames; - -#undef HDA_PACKET_SIZE -} - -// ------------------------------------------------------------------------------------------ -static UINT32 GetFramesSleepTime(REFERENCE_TIME nFrames, REFERENCE_TIME nSamplesPerSec) -{ - REFERENCE_TIME nDuration; - if (nSamplesPerSec == 0) - return 0; - -#define REFTIMES_PER_SEC 10000000LL -#define REFTIMES_PER_MILLISEC 10000LL - - // Calculate the actual duration of the allocated buffer. - nDuration = (REFTIMES_PER_SEC * nFrames) / nSamplesPerSec; - return (UINT32)(nDuration / REFTIMES_PER_MILLISEC); - -#undef REFTIMES_PER_SEC -#undef REFTIMES_PER_MILLISEC -} - -// ------------------------------------------------------------------------------------------ -static UINT32 GetFramesSleepTimeMicroseconds(REFERENCE_TIME nFrames, REFERENCE_TIME nSamplesPerSec) -{ - REFERENCE_TIME nDuration; - if (nSamplesPerSec == 0) - return 0; - -#define REFTIMES_PER_SEC 10000000LL -#define REFTIMES_PER_MILLISEC 10000LL - - // Calculate the actual duration of the allocated buffer. - nDuration = (REFTIMES_PER_SEC * nFrames) / nSamplesPerSec; - return (UINT32)(nDuration / 10); - -#undef REFTIMES_PER_SEC -#undef REFTIMES_PER_MILLISEC -} - -// ------------------------------------------------------------------------------------------ -#ifndef PA_WINRT -static BOOL SetupAVRT() -{ - hDInputDLL = LoadLibraryA("avrt.dll"); - if (hDInputDLL == NULL) - return FALSE; - - _GetProc(pAvRtCreateThreadOrderingGroup, FAvRtCreateThreadOrderingGroup, "AvRtCreateThreadOrderingGroup"); - _GetProc(pAvRtDeleteThreadOrderingGroup, FAvRtDeleteThreadOrderingGroup, "AvRtDeleteThreadOrderingGroup"); - _GetProc(pAvRtWaitOnThreadOrderingGroup, FAvRtWaitOnThreadOrderingGroup, "AvRtWaitOnThreadOrderingGroup"); - _GetProc(pAvSetMmThreadCharacteristics, FAvSetMmThreadCharacteristics, "AvSetMmThreadCharacteristicsA"); - _GetProc(pAvRevertMmThreadCharacteristics,FAvRevertMmThreadCharacteristics,"AvRevertMmThreadCharacteristics"); - _GetProc(pAvSetMmThreadPriority, FAvSetMmThreadPriority, "AvSetMmThreadPriority"); - - return pAvRtCreateThreadOrderingGroup && - pAvRtDeleteThreadOrderingGroup && - pAvRtWaitOnThreadOrderingGroup && - pAvSetMmThreadCharacteristics && - pAvRevertMmThreadCharacteristics && - pAvSetMmThreadPriority; -} -#endif - -// ------------------------------------------------------------------------------------------ -static void CloseAVRT() -{ -#ifndef PA_WINRT - if (hDInputDLL != NULL) - FreeLibrary(hDInputDLL); - hDInputDLL = NULL; -#endif -} - -// ------------------------------------------------------------------------------------------ -static BOOL IsWow64() -{ -#ifndef PA_WINRT - - // http://msdn.microsoft.com/en-us/library/ms684139(VS.85).aspx - - typedef BOOL (WINAPI *LPFN_ISWOW64PROCESS) (HANDLE, PBOOL); - LPFN_ISWOW64PROCESS fnIsWow64Process; - - BOOL bIsWow64 = FALSE; - - // IsWow64Process is not available on all supported versions of Windows. - // Use GetModuleHandle to get a handle to the DLL that contains the function - // and GetProcAddress to get a pointer to the function if available. - - fnIsWow64Process = (LPFN_ISWOW64PROCESS) GetProcAddress( - GetModuleHandleA("kernel32"), "IsWow64Process"); - - if (fnIsWow64Process == NULL) - return FALSE; - - if (!fnIsWow64Process(GetCurrentProcess(), &bIsWow64)) - return FALSE; - - return bIsWow64; - -#else - - return FALSE; - -#endif -} - -// ------------------------------------------------------------------------------------------ -typedef enum EWindowsVersion -{ - WINDOWS_UNKNOWN = 0, - WINDOWS_VISTA_SERVER2008, - WINDOWS_7_SERVER2008R2, - WINDOWS_8_SERVER2012, - WINDOWS_8_1_SERVER2012R2, - WINDOWS_10_SERVER2016, - WINDOWS_FUTURE -} -EWindowsVersion; -// Alternative way for checking Windows version (allows to check version on Windows 8.1 and up) -#ifndef PA_WINRT -static BOOL IsWindowsVersionOrGreater(WORD wMajorVersion, WORD wMinorVersion, WORD wServicePackMajor) -{ - typedef ULONGLONG (NTAPI *LPFN_VERSETCONDITIONMASK)(ULONGLONG ConditionMask, DWORD TypeMask, BYTE Condition); - typedef BOOL (WINAPI *LPFN_VERIFYVERSIONINFO)(LPOSVERSIONINFOEXA lpVersionInformation, DWORD dwTypeMask, DWORDLONG dwlConditionMask); - - LPFN_VERSETCONDITIONMASK fnVerSetConditionMask; - LPFN_VERIFYVERSIONINFO fnVerifyVersionInfo; - OSVERSIONINFOEXA osvi = { sizeof(osvi), 0, 0, 0, 0, {0}, 0, 0 }; - DWORDLONG dwlConditionMask; - - fnVerSetConditionMask = (LPFN_VERSETCONDITIONMASK)GetProcAddress(GetModuleHandleA("kernel32"), "VerSetConditionMask"); - fnVerifyVersionInfo = (LPFN_VERIFYVERSIONINFO)GetProcAddress(GetModuleHandleA("kernel32"), "VerifyVersionInfoA"); - - if ((fnVerSetConditionMask == NULL) || (fnVerifyVersionInfo == NULL)) - return FALSE; - - dwlConditionMask = fnVerSetConditionMask( - fnVerSetConditionMask( - fnVerSetConditionMask( - 0, VER_MAJORVERSION, VER_GREATER_EQUAL), - VER_MINORVERSION, VER_GREATER_EQUAL), - VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); - - osvi.dwMajorVersion = wMajorVersion; - osvi.dwMinorVersion = wMinorVersion; - osvi.wServicePackMajor = wServicePackMajor; - - return (fnVerifyVersionInfo(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, dwlConditionMask) != FALSE); -} -#endif -// Get Windows version -static EWindowsVersion GetWindowsVersion() -{ -#ifndef PA_WINRT - static EWindowsVersion version = WINDOWS_UNKNOWN; - - if (version == WINDOWS_UNKNOWN) - { - DWORD dwMajorVersion = 0xFFFFFFFFU, dwMinorVersion = 0, dwBuild = 0; - - // RTL_OSVERSIONINFOW equals OSVERSIONINFOW but it is missing inb MinGW winnt.h header, - // thus use OSVERSIONINFOW for greater portability - typedef NTSTATUS (WINAPI *LPFN_RTLGETVERSION)(POSVERSIONINFOW lpVersionInformation); - LPFN_RTLGETVERSION fnRtlGetVersion; - - #define NTSTATUS_SUCCESS ((NTSTATUS)0x00000000L) - - // RtlGetVersion must be able to provide true Windows version (Windows 10 may be reported as Windows 8 - // by GetVersion API) - if ((fnRtlGetVersion = (LPFN_RTLGETVERSION)GetProcAddress(GetModuleHandleA("ntdll"), "RtlGetVersion")) != NULL) - { - OSVERSIONINFOW ver = { sizeof(OSVERSIONINFOW), 0, 0, 0, 0, {0} }; - - PRINT(("WASAPI: getting Windows version with RtlGetVersion()\n")); - - if (fnRtlGetVersion(&ver) == NTSTATUS_SUCCESS) - { - dwMajorVersion = ver.dwMajorVersion; - dwMinorVersion = ver.dwMinorVersion; - dwBuild = ver.dwBuildNumber; - } - } - - #undef NTSTATUS_SUCCESS - - // fallback to GetVersion if RtlGetVersion is missing - if (dwMajorVersion == 0xFFFFFFFFU) - { - typedef DWORD (WINAPI *LPFN_GETVERSION)(VOID); - LPFN_GETVERSION fnGetVersion; - - if ((fnGetVersion = (LPFN_GETVERSION)GetProcAddress(GetModuleHandleA("kernel32"), "GetVersion")) != NULL) - { - DWORD dwVersion; - - PRINT(("WASAPI: getting Windows version with GetVersion()\n")); - - dwVersion = fnGetVersion(); - - dwMajorVersion = (DWORD)(LOBYTE(LOWORD(dwVersion))); - dwMinorVersion = (DWORD)(HIBYTE(LOWORD(dwVersion))); - - if (dwVersion < 0x80000000) - dwBuild = (DWORD)(HIWORD(dwVersion)); - } - } - - if (dwMajorVersion != 0xFFFFFFFFU) - { - switch (dwMajorVersion) - { - case 0: - case 1: - case 2: - case 3: - case 4: - case 5: - break; // skip lower - case 6: - switch (dwMinorVersion) - { - case 0: version = WINDOWS_VISTA_SERVER2008; break; - case 1: version = WINDOWS_7_SERVER2008R2; break; - case 2: version = WINDOWS_8_SERVER2012; break; - case 3: version = WINDOWS_8_1_SERVER2012R2; break; - default: version = WINDOWS_FUTURE; break; - } - break; - case 10: - switch (dwMinorVersion) - { - case 0: version = WINDOWS_10_SERVER2016; break; - default: version = WINDOWS_FUTURE; break; - } - break; - default: - version = WINDOWS_FUTURE; - break; - } - } - // fallback to VerifyVersionInfo if RtlGetVersion and GetVersion are missing - else - { - PRINT(("WASAPI: getting Windows version with VerifyVersionInfo()\n")); - - if (IsWindowsVersionOrGreater(10, 0, 0)) - version = WINDOWS_10_SERVER2016; - else - if (IsWindowsVersionOrGreater(6, 3, 0)) - version = WINDOWS_8_1_SERVER2012R2; - else - if (IsWindowsVersionOrGreater(6, 2, 0)) - version = WINDOWS_8_SERVER2012; - else - if (IsWindowsVersionOrGreater(6, 1, 0)) - version = WINDOWS_7_SERVER2008R2; - else - if (IsWindowsVersionOrGreater(6, 0, 0)) - version = WINDOWS_VISTA_SERVER2008; - else - version = WINDOWS_FUTURE; - } - - PRINT(("WASAPI: Windows version = %d\n", version)); - } - - return version; -#else - #if (_WIN32_WINNT >= _WIN32_WINNT_WIN10) - return WINDOWS_10_SERVER2016; - #else - return WINDOWS_8_SERVER2012; - #endif -#endif -} - -// ------------------------------------------------------------------------------------------ -static BOOL UseWOW64Workaround() -{ - // note: WOW64 bug is common to Windows Vista x64, thus we fall back to safe Poll-driven - // method. Windows 7 x64 seems has WOW64 bug fixed. - - return (IsWow64() && (GetWindowsVersion() == WINDOWS_VISTA_SERVER2008)); -} - -// ------------------------------------------------------------------------------------------ -static UINT32 GetAudioClientVersion() -{ - if (GetWindowsVersion() >= WINDOWS_10_SERVER2016) - return 3; - else - if (GetWindowsVersion() >= WINDOWS_8_SERVER2012) - return 2; - - return 1; -} - -// ------------------------------------------------------------------------------------------ -static const IID *GetAudioClientIID() -{ - static const IID *cli_iid = NULL; - if (cli_iid == NULL) - { - UINT32 cli_version = GetAudioClientVersion(); - switch (cli_version) - { - case 3: cli_iid = &pa_IID_IAudioClient3; break; - case 2: cli_iid = &pa_IID_IAudioClient2; break; - default: cli_iid = &pa_IID_IAudioClient; break; - } - - PRINT(("WASAPI: IAudioClient version = %d\n", cli_version)); - } - - return cli_iid; -} - -// ------------------------------------------------------------------------------------------ -typedef enum EMixDirection -{ - MIX_DIR__1TO2, //!< mix one channel to L and R - MIX_DIR__2TO1, //!< mix L and R channels to one channel - MIX_DIR__2TO1_L //!< mix only L channel (of total 2 channels) to one channel -} -EMixDirection; - -// ------------------------------------------------------------------------------------------ -#define _WASAPI_MONO_TO_STEREO_MIXER_1_TO_2(TYPE)\ - TYPE * __restrict to = (TYPE *)__to;\ - const TYPE * __restrict from = (const TYPE *)__from;\ - const TYPE * __restrict end = from + count;\ - while (from != end)\ - {\ - to[0] = to[1] = *from ++;\ - to += 2;\ - } - -// ------------------------------------------------------------------------------------------ -#define _WASAPI_MONO_TO_STEREO_MIXER_2_TO_1_FLT32(TYPE)\ - TYPE * __restrict to = (TYPE *)__to;\ - const TYPE * __restrict from = (const TYPE *)__from;\ - const TYPE * __restrict end = to + count;\ - while (to != end)\ - {\ - *to ++ = (TYPE)((float)(from[0] + from[1]) * 0.5f);\ - from += 2;\ - } - -// ------------------------------------------------------------------------------------------ -#define _WASAPI_MONO_TO_STEREO_MIXER_2_TO_1_INT32(TYPE)\ - TYPE * __restrict to = (TYPE *)__to;\ - const TYPE * __restrict from = (const TYPE *)__from;\ - const TYPE * __restrict end = to + count;\ - while (to != end)\ - {\ - *to ++ = (TYPE)(((INT32)from[0] + (INT32)from[1]) >> 1);\ - from += 2;\ - } - -// ------------------------------------------------------------------------------------------ -#define _WASAPI_MONO_TO_STEREO_MIXER_2_TO_1_INT64(TYPE)\ - TYPE * __restrict to = (TYPE *)__to;\ - const TYPE * __restrict from = (const TYPE *)__from;\ - const TYPE * __restrict end = to + count;\ - while (to != end)\ - {\ - *to ++ = (TYPE)(((INT64)from[0] + (INT64)from[1]) >> 1);\ - from += 2;\ - } - -// ------------------------------------------------------------------------------------------ -#define _WASAPI_MONO_TO_STEREO_MIXER_2_TO_1_L(TYPE)\ - TYPE * __restrict to = (TYPE *)__to;\ - const TYPE * __restrict from = (const TYPE *)__from;\ - const TYPE * __restrict end = to + count;\ - while (to != end)\ - {\ - *to ++ = from[0];\ - from += 2;\ - } - -// ------------------------------------------------------------------------------------------ -static void _MixMonoToStereo_1TO2_8(void *__to, const void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_1_TO_2(BYTE); } -static void _MixMonoToStereo_1TO2_16(void *__to, const void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_1_TO_2(short); } -static void _MixMonoToStereo_1TO2_8_24(void *__to, const void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_1_TO_2(int); /* !!! int24 data is contained in 32-bit containers*/ } -static void _MixMonoToStereo_1TO2_32(void *__to, const void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_1_TO_2(int); } -static void _MixMonoToStereo_1TO2_32f(void *__to, const void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_1_TO_2(float); } -static void _MixMonoToStereo_1TO2_24(void *__to, const void *__from, UINT32 count) -{ - const UCHAR * __restrict from = (const UCHAR *)__from; - UCHAR * __restrict to = (UCHAR *)__to; - const UCHAR * __restrict end = to + (count * (2 * 3)); - - while (to != end) - { - to[0] = to[3] = from[0]; - to[1] = to[4] = from[1]; - to[2] = to[5] = from[2]; - - from += 3; - to += (2 * 3); - } -} - -// ------------------------------------------------------------------------------------------ -static void _MixMonoToStereo_2TO1_8(void *__to, const void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_2_TO_1_INT32(BYTE); } -static void _MixMonoToStereo_2TO1_16(void *__to, const void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_2_TO_1_INT32(short); } -static void _MixMonoToStereo_2TO1_8_24(void *__to, const void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_2_TO_1_INT32(int); /* !!! int24 data is contained in 32-bit containers*/ } -static void _MixMonoToStereo_2TO1_32(void *__to, const void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_2_TO_1_INT64(int); } -static void _MixMonoToStereo_2TO1_32f(void *__to, const void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_2_TO_1_FLT32(float); } -static void _MixMonoToStereo_2TO1_24(void *__to, const void *__from, UINT32 count) -{ - const UCHAR * __restrict from = (const UCHAR *)__from; - UCHAR * __restrict to = (UCHAR *)__to; - const UCHAR * __restrict end = to + (count * 3); - PaInt32 tempL, tempR, tempM; - - while (to != end) - { - tempL = (((PaInt32)from[0]) << 8); - tempL = tempL | (((PaInt32)from[1]) << 16); - tempL = tempL | (((PaInt32)from[2]) << 24); - - tempR = (((PaInt32)from[3]) << 8); - tempR = tempR | (((PaInt32)from[4]) << 16); - tempR = tempR | (((PaInt32)from[5]) << 24); - - tempM = (tempL + tempR) >> 1; - - to[0] = (UCHAR)(tempM >> 8); - to[1] = (UCHAR)(tempM >> 16); - to[2] = (UCHAR)(tempM >> 24); - - from += (2 * 3); - to += 3; - } -} - -// ------------------------------------------------------------------------------------------ -static void _MixMonoToStereo_2TO1_8_L(void *__to, const void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_2_TO_1_L(BYTE); } -static void _MixMonoToStereo_2TO1_16_L(void *__to, const void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_2_TO_1_L(short); } -static void _MixMonoToStereo_2TO1_8_24_L(void *__to, const void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_2_TO_1_L(int); /* !!! int24 data is contained in 32-bit containers*/ } -static void _MixMonoToStereo_2TO1_32_L(void *__to, const void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_2_TO_1_L(int); } -static void _MixMonoToStereo_2TO1_32f_L(void *__to, const void *__from, UINT32 count) { _WASAPI_MONO_TO_STEREO_MIXER_2_TO_1_L(float); } -static void _MixMonoToStereo_2TO1_24_L(void *__to, const void *__from, UINT32 count) -{ - const UCHAR * __restrict from = (const UCHAR *)__from; - UCHAR * __restrict to = (UCHAR *)__to; - const UCHAR * __restrict end = to + (count * 3); - - while (to != end) - { - to[0] = from[0]; - to[1] = from[1]; - to[2] = from[2]; - - from += (2 * 3); - to += 3; - } -} - -// ------------------------------------------------------------------------------------------ -static MixMonoToStereoF GetMonoToStereoMixer(const WAVEFORMATEXTENSIBLE *fmtext, EMixDirection dir) -{ - PaSampleFormat format = WaveToPaFormat(fmtext); - - switch (dir) - { - case MIX_DIR__1TO2: - switch (format & ~paNonInterleaved) - { - case paUInt8: return _MixMonoToStereo_1TO2_8; - case paInt16: return _MixMonoToStereo_1TO2_16; - case paInt24: return (fmtext->Format.wBitsPerSample == 32 ? _MixMonoToStereo_1TO2_8_24 : _MixMonoToStereo_1TO2_24); - case paInt32: return _MixMonoToStereo_1TO2_32; - case paFloat32: return _MixMonoToStereo_1TO2_32f; - } - break; - - case MIX_DIR__2TO1: - switch (format & ~paNonInterleaved) - { - case paUInt8: return _MixMonoToStereo_2TO1_8; - case paInt16: return _MixMonoToStereo_2TO1_16; - case paInt24: return (fmtext->Format.wBitsPerSample == 32 ? _MixMonoToStereo_2TO1_8_24 : _MixMonoToStereo_2TO1_24); - case paInt32: return _MixMonoToStereo_2TO1_32; - case paFloat32: return _MixMonoToStereo_2TO1_32f; - } - break; - - case MIX_DIR__2TO1_L: - switch (format & ~paNonInterleaved) - { - case paUInt8: return _MixMonoToStereo_2TO1_8_L; - case paInt16: return _MixMonoToStereo_2TO1_16_L; - case paInt24: return (fmtext->Format.wBitsPerSample == 32 ? _MixMonoToStereo_2TO1_8_24_L : _MixMonoToStereo_2TO1_24_L); - case paInt32: return _MixMonoToStereo_2TO1_32_L; - case paFloat32: return _MixMonoToStereo_2TO1_32f_L; - } - break; - } - - return NULL; -} - -// ------------------------------------------------------------------------------------------ -#ifdef PA_WINRT -typedef struct PaActivateAudioInterfaceCompletionHandler -{ - IActivateAudioInterfaceCompletionHandler parent; - volatile LONG refs; - volatile LONG done; - struct - { - const IID *iid; - void **obj; - } - in; - struct - { - HRESULT hr; - } - out; -} -PaActivateAudioInterfaceCompletionHandler; - -static HRESULT (STDMETHODCALLTYPE PaActivateAudioInterfaceCompletionHandler_QueryInterface)( - IActivateAudioInterfaceCompletionHandler *This, REFIID riid, void **ppvObject) -{ - PaActivateAudioInterfaceCompletionHandler *handler = (PaActivateAudioInterfaceCompletionHandler *)This; - - // From MSDN: - // "The IAgileObject interface is a marker interface that indicates that an object - // is free threaded and can be called from any apartment." - if (IsEqualIID(riid, &IID_IUnknown) || - IsEqualIID(riid, &IID_IAgileObject)) - { - IActivateAudioInterfaceCompletionHandler_AddRef((IActivateAudioInterfaceCompletionHandler *)handler); - (*ppvObject) = handler; - return S_OK; - } - - return E_NOINTERFACE; -} - -static ULONG (STDMETHODCALLTYPE PaActivateAudioInterfaceCompletionHandler_AddRef)( - IActivateAudioInterfaceCompletionHandler *This) -{ - PaActivateAudioInterfaceCompletionHandler *handler = (PaActivateAudioInterfaceCompletionHandler *)This; - - return InterlockedIncrement(&handler->refs); -} - -static ULONG (STDMETHODCALLTYPE PaActivateAudioInterfaceCompletionHandler_Release)( - IActivateAudioInterfaceCompletionHandler *This) -{ - PaActivateAudioInterfaceCompletionHandler *handler = (PaActivateAudioInterfaceCompletionHandler *)This; - ULONG refs; - - if ((refs = InterlockedDecrement(&handler->refs)) == 0) - { - PaUtil_FreeMemory(handler->parent.lpVtbl); - PaUtil_FreeMemory(handler); - } - - return refs; -} - -static HRESULT (STDMETHODCALLTYPE PaActivateAudioInterfaceCompletionHandler_ActivateCompleted)( - IActivateAudioInterfaceCompletionHandler *This, IActivateAudioInterfaceAsyncOperation *activateOperation) -{ - PaActivateAudioInterfaceCompletionHandler *handler = (PaActivateAudioInterfaceCompletionHandler *)This; - - HRESULT hr = S_OK; - HRESULT hrActivateResult = S_OK; - IUnknown *punkAudioInterface = NULL; - - // Check for a successful activation result - hr = IActivateAudioInterfaceAsyncOperation_GetActivateResult(activateOperation, &hrActivateResult, &punkAudioInterface); - if (SUCCEEDED(hr) && SUCCEEDED(hrActivateResult)) - { - // Get pointer to the requested audio interface - IUnknown_QueryInterface(punkAudioInterface, handler->in.iid, handler->in.obj); - if ((*handler->in.obj) == NULL) - hrActivateResult = E_FAIL; - } - SAFE_RELEASE(punkAudioInterface); - - if (SUCCEEDED(hr)) - handler->out.hr = hrActivateResult; - else - handler->out.hr = hr; - - // Got client object, stop busy waiting in ActivateAudioInterface - InterlockedExchange(&handler->done, TRUE); - - return hr; -} - -static IActivateAudioInterfaceCompletionHandler *CreateActivateAudioInterfaceCompletionHandler(const IID *iid, void **client) -{ - PaActivateAudioInterfaceCompletionHandler *handler = PaUtil_AllocateMemory(sizeof(PaActivateAudioInterfaceCompletionHandler)); - - memset(handler, 0, sizeof(*handler)); - - handler->parent.lpVtbl = PaUtil_AllocateMemory(sizeof(*handler->parent.lpVtbl)); - handler->parent.lpVtbl->QueryInterface = &PaActivateAudioInterfaceCompletionHandler_QueryInterface; - handler->parent.lpVtbl->AddRef = &PaActivateAudioInterfaceCompletionHandler_AddRef; - handler->parent.lpVtbl->Release = &PaActivateAudioInterfaceCompletionHandler_Release; - handler->parent.lpVtbl->ActivateCompleted = &PaActivateAudioInterfaceCompletionHandler_ActivateCompleted; - handler->refs = 1; - handler->in.iid = iid; - handler->in.obj = client; - - return (IActivateAudioInterfaceCompletionHandler *)handler; -} -#endif - -// ------------------------------------------------------------------------------------------ -#ifdef PA_WINRT -static HRESULT WinRT_GetDefaultDeviceId(WCHAR *deviceId, UINT32 deviceIdMax, EDataFlow flow) -{ - switch (flow) - { - case eRender: - if (g_DeviceListInfo.render.defaultId[0] != 0) - wcsncpy_s(deviceId, deviceIdMax, g_DeviceListInfo.render.defaultId, wcslen(g_DeviceListInfo.render.defaultId)); - else - StringFromGUID2(&DEVINTERFACE_AUDIO_RENDER, deviceId, deviceIdMax); - break; - case eCapture: - if (g_DeviceListInfo.capture.defaultId[0] != 0) - wcsncpy_s(deviceId, deviceIdMax, g_DeviceListInfo.capture.defaultId, wcslen(g_DeviceListInfo.capture.defaultId)); - else - StringFromGUID2(&DEVINTERFACE_AUDIO_CAPTURE, deviceId, deviceIdMax); - break; - default: - return S_FALSE; - } - - return S_OK; -} -#endif - -// ------------------------------------------------------------------------------------------ -#ifdef PA_WINRT -static HRESULT WinRT_ActivateAudioInterface(const WCHAR *deviceId, const IID *iid, void **client) -{ - PaError result = paNoError; - HRESULT hr = S_OK; - IActivateAudioInterfaceAsyncOperation *asyncOp = NULL; - IActivateAudioInterfaceCompletionHandler *handler = CreateActivateAudioInterfaceCompletionHandler(iid, client); - PaActivateAudioInterfaceCompletionHandler *handlerImpl = (PaActivateAudioInterfaceCompletionHandler *)handler; - UINT32 sleepToggle = 0; - - // Async operation will call back to IActivateAudioInterfaceCompletionHandler::ActivateCompleted - // which must be an agile interface implementation - hr = ActivateAudioInterfaceAsync(deviceId, iid, NULL, handler, &asyncOp); - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - - // Wait in busy loop for async operation to complete - // Use Interlocked API here to ensure that ->done variable is read every time through the loop - while (SUCCEEDED(hr) && !InterlockedOr(&handlerImpl->done, 0)) - { - Sleep(sleepToggle ^= 1); - } - - hr = handlerImpl->out.hr; - -error: - - SAFE_RELEASE(asyncOp); - SAFE_RELEASE(handler); - - return hr; -} -#endif - -// ------------------------------------------------------------------------------------------ -static HRESULT ActivateAudioInterface(const PaWasapiDeviceInfo *deviceInfo, const PaWasapiStreamInfo *streamInfo, - IAudioClient **client) -{ - HRESULT hr; - -#ifndef PA_WINRT - if (FAILED(hr = IMMDevice_Activate(deviceInfo->device, GetAudioClientIID(), CLSCTX_ALL, NULL, (void **)client))) - return hr; -#else - if (FAILED(hr = WinRT_ActivateAudioInterface(deviceInfo->deviceId, GetAudioClientIID(), (void **)client))) - return hr; -#endif - - // Set audio client options (applicable only to IAudioClient2+): options may affect the audio format - // support by IAudioClient implementation and therefore we should set them before GetClosestFormat() - // in order to correctly match the requested format -#ifdef __IAudioClient2_INTERFACE_DEFINED__ - if ((streamInfo != NULL) && (GetAudioClientVersion() >= 2)) - { - pa_AudioClientProperties audioProps = { 0 }; - audioProps.cbSize = sizeof(pa_AudioClientProperties); - audioProps.bIsOffload = FALSE; - audioProps.eCategory = (AUDIO_STREAM_CATEGORY)streamInfo->streamCategory; - switch (streamInfo->streamOption) - { - case eStreamOptionRaw: - if (GetWindowsVersion() >= WINDOWS_8_1_SERVER2012R2) - audioProps.Options = pa_AUDCLNT_STREAMOPTIONS_RAW; - break; - case eStreamOptionMatchFormat: - if (GetWindowsVersion() >= WINDOWS_10_SERVER2016) - audioProps.Options = pa_AUDCLNT_STREAMOPTIONS_MATCH_FORMAT; - break; - } - - if (FAILED(hr = IAudioClient2_SetClientProperties((IAudioClient2 *)(*client), (AudioClientProperties *)&audioProps))) - { - PRINT(("WASAPI: IAudioClient2_SetClientProperties(IsOffload = %d, Category = %d, Options = %d) failed\n", audioProps.bIsOffload, audioProps.eCategory, audioProps.Options)); - LogHostError(hr); - } - else - { - PRINT(("WASAPI: IAudioClient2 set properties: IsOffload = %d, Category = %d, Options = %d\n", audioProps.bIsOffload, audioProps.eCategory, audioProps.Options)); - } - } -#endif - - return S_OK; -} - -// ------------------------------------------------------------------------------------------ -#ifdef PA_WINRT -// Windows 10 SDK 10.0.15063.0 has SignalObjectAndWait defined again (unlike in 10.0.14393.0 and lower) -#if !defined(WDK_NTDDI_VERSION) || (WDK_NTDDI_VERSION < NTDDI_WIN10_RS2) -static DWORD SignalObjectAndWait(HANDLE hObjectToSignal, HANDLE hObjectToWaitOn, DWORD dwMilliseconds, BOOL bAlertable) -{ - SetEvent(hObjectToSignal); - return WaitForSingleObjectEx(hObjectToWaitOn, dwMilliseconds, bAlertable); -} -#endif -#endif - -// ------------------------------------------------------------------------------------------ -static void NotifyStateChanged(PaWasapiStream *stream, UINT32 flags, HRESULT hr) -{ - if (stream->fnStateHandler == NULL) - return; - - if (FAILED(hr)) - flags |= paWasapiStreamStateError; - - stream->fnStateHandler((PaStream *)stream, flags, hr, stream->pStateHandlerUserData); -} - -// ------------------------------------------------------------------------------------------ -static void FillBaseDeviceInfo(PaDeviceInfo *deviceInfo, PaHostApiIndex hostApiIndex) -{ - deviceInfo->structVersion = 2; - deviceInfo->hostApi = hostApiIndex; -} - -// ------------------------------------------------------------------------------------------ -static PaError FillInactiveDeviceInfo(PaWasapiHostApiRepresentation *paWasapi, PaDeviceInfo *deviceInfo) -{ - if (deviceInfo->name == NULL) - deviceInfo->name = (char *)PaUtil_GroupAllocateMemory(paWasapi->allocations, 1); - - if (deviceInfo->name != NULL) - { - ((char *)deviceInfo->name)[0] = 0; - } - else - return paInsufficientMemory; - - return paNoError; -} - -// ------------------------------------------------------------------------------------------ -static PaError FillDeviceInfo(PaWasapiHostApiRepresentation *paWasapi, void *pEndPoints, INT32 index, const WCHAR *defaultRenderId, - const WCHAR *defaultCaptureId, PaDeviceInfo *deviceInfo, PaWasapiDeviceInfo *wasapiDeviceInfo -#ifdef PA_WINRT - , PaWasapiWinrtDeviceListContext *deviceListContext -#endif -) -{ - HRESULT hr; - PaError result; - PaUtilHostApiRepresentation *hostApi = (PaUtilHostApiRepresentation *)paWasapi; -#ifdef PA_WINRT - PaWasapiWinrtDeviceListContextEntry *listEntry = &deviceListContext->devices[index]; - (void)pEndPoints; - (void)defaultRenderId; - (void)defaultCaptureId; -#endif - -#ifndef PA_WINRT - hr = IMMDeviceCollection_Item((IMMDeviceCollection *)pEndPoints, index, &wasapiDeviceInfo->device); - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - - // Get device Id - { - WCHAR *deviceId; - - hr = IMMDevice_GetId(wasapiDeviceInfo->device, &deviceId); - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - - wcsncpy(wasapiDeviceInfo->deviceId, deviceId, PA_WASAPI_DEVICE_ID_LEN - 1); - CoTaskMemFree(deviceId); - } - - // Get state of the device - hr = IMMDevice_GetState(wasapiDeviceInfo->device, &wasapiDeviceInfo->state); - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - if (wasapiDeviceInfo->state != DEVICE_STATE_ACTIVE) - { - PRINT(("WASAPI device: %d is not currently available (state:%d)\n", index, wasapiDeviceInfo->state)); - } - - // Get basic device info - { - IPropertyStore *pProperty; - IMMEndpoint *endpoint; - PROPVARIANT value; - - hr = IMMDevice_OpenPropertyStore(wasapiDeviceInfo->device, STGM_READ, &pProperty); - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - - // "Friendly" Name - { - PropVariantInit(&value); - - hr = IPropertyStore_GetValue(pProperty, &PKEY_Device_FriendlyName, &value); - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - - if ((deviceInfo->name = (char *)PaUtil_GroupAllocateMemory(paWasapi->allocations, PA_WASAPI_DEVICE_NAME_LEN)) == NULL) - { - result = paInsufficientMemory; - PropVariantClear(&value); - goto error; - } - if (value.pwszVal) - WideCharToMultiByte(CP_UTF8, 0, value.pwszVal, (INT32)wcslen(value.pwszVal), (char *)deviceInfo->name, PA_WASAPI_DEVICE_NAME_LEN - 1, 0, 0); - else - _snprintf((char *)deviceInfo->name, PA_WASAPI_DEVICE_NAME_LEN - 1, "baddev%d", index); - - PropVariantClear(&value); - - PA_DEBUG(("WASAPI:%d| name[%s]\n", index, deviceInfo->name)); - } - - // Default format - { - PropVariantInit(&value); - - hr = IPropertyStore_GetValue(pProperty, &PKEY_AudioEngine_DeviceFormat, &value); - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - - memcpy(&wasapiDeviceInfo->DefaultFormat, value.blob.pBlobData, min(sizeof(wasapiDeviceInfo->DefaultFormat), value.blob.cbSize)); - - PropVariantClear(&value); - } - - // Form factor - { - PropVariantInit(&value); - - hr = IPropertyStore_GetValue(pProperty, &PKEY_AudioEndpoint_FormFactor, &value); - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - - // set - #if defined(DUMMYUNIONNAME) && defined(NONAMELESSUNION) - // avoid breaking strict-aliasing rules in such line: (EndpointFormFactor)(*((UINT *)(((WORD *)&value.wReserved3)+1))); - UINT v; - memcpy(&v, (((WORD *)&value.wReserved3) + 1), sizeof(v)); - wasapiDeviceInfo->formFactor = (EndpointFormFactor)v; - #else - wasapiDeviceInfo->formFactor = (EndpointFormFactor)value.uintVal; - #endif - - PA_DEBUG(("WASAPI:%d| form-factor[%d]\n", index, wasapiDeviceInfo->formFactor)); - - PropVariantClear(&value); - } - - // Data flow (Renderer or Capture) - hr = IMMDevice_QueryInterface(wasapiDeviceInfo->device, &pa_IID_IMMEndpoint, (void **)&endpoint); - if (SUCCEEDED(hr)) - { - hr = IMMEndpoint_GetDataFlow(endpoint, &wasapiDeviceInfo->flow); - SAFE_RELEASE(endpoint); - } - - SAFE_RELEASE(pProperty); - } -#else - // Set device Id - wcsncpy(wasapiDeviceInfo->deviceId, listEntry->info->id, PA_WASAPI_DEVICE_ID_LEN - 1); - - // Set device name - if ((deviceInfo->name = (char *)PaUtil_GroupAllocateMemory(paWasapi->allocations, PA_WASAPI_DEVICE_NAME_LEN)) == NULL) - { - result = paInsufficientMemory; - goto error; - } - ((char *)deviceInfo->name)[0] = 0; - if (listEntry->info->name[0] != 0) - WideCharToMultiByte(CP_UTF8, 0, listEntry->info->name, (INT32)wcslen(listEntry->info->name), (char *)deviceInfo->name, PA_WASAPI_DEVICE_NAME_LEN - 1, 0, 0); - if (deviceInfo->name[0] == 0) // fallback if WideCharToMultiByte is failed, or listEntry is nameless - _snprintf((char *)deviceInfo->name, PA_WASAPI_DEVICE_NAME_LEN - 1, "WASAPI_%s:%d", (listEntry->flow == eRender ? "Output" : "Input"), index); - - // Form-factor - wasapiDeviceInfo->formFactor = listEntry->info->formFactor; - - // Set data flow - wasapiDeviceInfo->flow = listEntry->flow; -#endif - - // Set default Output/Input devices - if ((defaultRenderId != NULL) && (wcsncmp(wasapiDeviceInfo->deviceId, defaultRenderId, PA_WASAPI_DEVICE_NAME_LEN - 1) == 0)) - hostApi->info.defaultOutputDevice = hostApi->info.deviceCount; - if ((defaultCaptureId != NULL) && (wcsncmp(wasapiDeviceInfo->deviceId, defaultCaptureId, PA_WASAPI_DEVICE_NAME_LEN - 1) == 0)) - hostApi->info.defaultInputDevice = hostApi->info.deviceCount; - - // Get a temporary IAudioClient for more details - { - IAudioClient *tmpClient; - WAVEFORMATEX *mixFormat; - - hr = ActivateAudioInterface(wasapiDeviceInfo, NULL, &tmpClient); - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - - // Get latency - hr = IAudioClient_GetDevicePeriod(tmpClient, &wasapiDeviceInfo->DefaultDevicePeriod, &wasapiDeviceInfo->MinimumDevicePeriod); - if (FAILED(hr)) - { - PA_DEBUG(("WASAPI:%d| failed getting min/default periods by IAudioClient::GetDevicePeriod() with error[%08X], will use 30000/100000 hns\n", index, (UINT32)hr)); - - // assign WASAPI common values - wasapiDeviceInfo->DefaultDevicePeriod = 100000; - wasapiDeviceInfo->MinimumDevicePeriod = 30000; - - // ignore error, let continue further without failing with paInternalError - hr = S_OK; - } - - // Get mix format - hr = IAudioClient_GetMixFormat(tmpClient, &mixFormat); - if (SUCCEEDED(hr)) - { - memcpy(&wasapiDeviceInfo->MixFormat, mixFormat, min(sizeof(wasapiDeviceInfo->MixFormat), (sizeof(*mixFormat) + mixFormat->cbSize))); - CoTaskMemFree(mixFormat); - } - - // Register WINRT device - #ifdef PA_WINRT - if (SUCCEEDED(hr)) - { - // Set state - wasapiDeviceInfo->state = DEVICE_STATE_ACTIVE; - - // Default format (Shared mode) is always a mix format - wasapiDeviceInfo->DefaultFormat = wasapiDeviceInfo->MixFormat; - } - #endif - - // Release tmp client - SAFE_RELEASE(tmpClient); - - if (hr != S_OK) - { - //davidv: this happened with my hardware, previously for that same device in DirectSound: - //Digital Output (Realtek AC'97 Audio)'s GUID: {0x38f2cf50,0x7b4c,0x4740,0x86,0xeb,0xd4,0x38,0x66,0xd8,0xc8, 0x9f} - //so something must be _really_ wrong with this device, TODO handle this better. We kind of need GetMixFormat - LogHostError(hr); - result = paInternalError; - goto error; - } - } - - // Fill basic device data - deviceInfo->maxInputChannels = 0; - deviceInfo->maxOutputChannels = 0; - deviceInfo->defaultSampleRate = wasapiDeviceInfo->MixFormat.Format.nSamplesPerSec; - switch (wasapiDeviceInfo->flow) - { - case eRender: { - deviceInfo->maxOutputChannels = wasapiDeviceInfo->MixFormat.Format.nChannels; - deviceInfo->defaultHighOutputLatency = nano100ToSeconds(wasapiDeviceInfo->DefaultDevicePeriod); - deviceInfo->defaultLowOutputLatency = nano100ToSeconds(wasapiDeviceInfo->MinimumDevicePeriod); - PA_DEBUG(("WASAPI:%d| def.SR[%d] max.CH[%d] latency{hi[%f] lo[%f]}\n", index, (UINT32)deviceInfo->defaultSampleRate, - deviceInfo->maxOutputChannels, (float)deviceInfo->defaultHighOutputLatency, (float)deviceInfo->defaultLowOutputLatency)); - break;} - case eCapture: { - deviceInfo->maxInputChannels = wasapiDeviceInfo->MixFormat.Format.nChannels; - deviceInfo->defaultHighInputLatency = nano100ToSeconds(wasapiDeviceInfo->DefaultDevicePeriod); - deviceInfo->defaultLowInputLatency = nano100ToSeconds(wasapiDeviceInfo->MinimumDevicePeriod); - PA_DEBUG(("WASAPI:%d| def.SR[%d] max.CH[%d] latency{hi[%f] lo[%f]}\n", index, (UINT32)deviceInfo->defaultSampleRate, - deviceInfo->maxInputChannels, (float)deviceInfo->defaultHighInputLatency, (float)deviceInfo->defaultLowInputLatency)); - break; } - default: - PRINT(("WASAPI:%d| bad Data Flow!\n", index)); - result = paInternalError; - goto error; - } - - return paNoError; - -error: - - PRINT(("WASAPI: failed filling device info for device index[%d] - error[%d|%s]\n", index, result, Pa_GetErrorText(result))); - - return result; -} - -// ------------------------------------------------------------------------------------------ -static PaDeviceInfo *AllocateDeviceListMemory(PaWasapiHostApiRepresentation *paWasapi) -{ - PaUtilHostApiRepresentation *hostApi = (PaUtilHostApiRepresentation *)paWasapi; - PaDeviceInfo *deviceInfoArray = NULL; - - if ((paWasapi->devInfo = (PaWasapiDeviceInfo *)PaUtil_GroupAllocateMemory(paWasapi->allocations, - sizeof(PaWasapiDeviceInfo) * paWasapi->deviceCount)) == NULL) - { - return NULL; - } - memset(paWasapi->devInfo, 0, sizeof(PaWasapiDeviceInfo) * paWasapi->deviceCount); - - if (paWasapi->deviceCount != 0) - { - UINT32 i; - UINT32 deviceCount = paWasapi->deviceCount; - #if defined(PA_WASAPI_MAX_CONST_DEVICE_COUNT) && (PA_WASAPI_MAX_CONST_DEVICE_COUNT > 0) - if (deviceCount < PA_WASAPI_MAX_CONST_DEVICE_COUNT) - deviceCount = PA_WASAPI_MAX_CONST_DEVICE_COUNT; - #endif - - if ((hostApi->deviceInfos = (PaDeviceInfo **)PaUtil_GroupAllocateMemory(paWasapi->allocations, - sizeof(PaDeviceInfo *) * deviceCount)) == NULL) - { - return NULL; - } - for (i = 0; i < deviceCount; ++i) - hostApi->deviceInfos[i] = NULL; - - // Allocate all device info structs in a contiguous block - if ((deviceInfoArray = (PaDeviceInfo *)PaUtil_GroupAllocateMemory(paWasapi->allocations, - sizeof(PaDeviceInfo) * deviceCount)) == NULL) - { - return NULL; - } - memset(deviceInfoArray, 0, sizeof(PaDeviceInfo) * deviceCount); - } - - return deviceInfoArray; -} - -// ------------------------------------------------------------------------------------------ -static PaError CreateDeviceList(PaWasapiHostApiRepresentation *paWasapi, PaHostApiIndex hostApiIndex) -{ - PaUtilHostApiRepresentation *hostApi = (PaUtilHostApiRepresentation *)paWasapi; - PaError result = paNoError; - PaDeviceInfo *deviceInfoArray = NULL; - UINT32 i; - WCHAR *defaultRenderId = NULL; - WCHAR *defaultCaptureId = NULL; -#ifndef PA_WINRT - HRESULT hr; - IMMDeviceCollection *pEndPoints = NULL; - IMMDeviceEnumerator *pEnumerator = NULL; -#else - void *pEndPoints = NULL; - IAudioClient *tmpClient; - PaWasapiWinrtDeviceListContext deviceListContext = { 0 }; - PaWasapiWinrtDeviceInfo defaultRender = { 0 }; - PaWasapiWinrtDeviceInfo defaultCapture = { 0 }; -#endif - - // Make sure device list empty - if ((paWasapi->deviceCount != 0) || (hostApi->info.deviceCount != 0)) - return paInternalError; - -#ifndef PA_WINRT - hr = CoCreateInstance(&pa_CLSID_IMMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER, - &pa_IID_IMMDeviceEnumerator, (void **)&pEnumerator); - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - - // Get default render and capture devices - { - IMMDevice *device; - - hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator, eRender, eMultimedia, &device); - if (hr != S_OK) - { - if (hr != E_NOTFOUND) - { - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - } - } - else - { - hr = IMMDevice_GetId(device, &defaultRenderId); - IMMDevice_Release(device); - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - } - - hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(pEnumerator, eCapture, eMultimedia, &device); - if (hr != S_OK) - { - if (hr != E_NOTFOUND) - { - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - } - } - else - { - hr = IMMDevice_GetId(device, &defaultCaptureId); - IMMDevice_Release(device); - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - } - } - - // Get all currently active devices - hr = IMMDeviceEnumerator_EnumAudioEndpoints(pEnumerator, eAll, DEVICE_STATE_ACTIVE, &pEndPoints); - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); - - // Get device count - hr = IMMDeviceCollection_GetCount(pEndPoints, &paWasapi->deviceCount); - IF_FAILED_INTERNAL_ERROR_JUMP(hr, result, error); -#else - WinRT_GetDefaultDeviceId(defaultRender.id, STATIC_ARRAY_SIZE(defaultRender.id) - 1, eRender); - defaultRenderId = defaultRender.id; - - WinRT_GetDefaultDeviceId(defaultCapture.id, STATIC_ARRAY_SIZE(defaultCapture.id) - 1, eCapture); - defaultCaptureId = defaultCapture.id; - - if (g_DeviceListInfo.render.deviceCount == 0) - { - if (SUCCEEDED(WinRT_ActivateAudioInterface(defaultRenderId, GetAudioClientIID(), &tmpClient))) - { - deviceListContext.devices[paWasapi->deviceCount].info = &defaultRender; - deviceListContext.devices[paWasapi->deviceCount].flow = eRender; - paWasapi->deviceCount++; - - SAFE_RELEASE(tmpClient); - } - } - else - { - for (i = 0; i < g_DeviceListInfo.render.deviceCount; ++i) - { - deviceListContext.devices[paWasapi->deviceCount].info = &g_DeviceListInfo.render.devices[i]; - deviceListContext.devices[paWasapi->deviceCount].flow = eRender; - paWasapi->deviceCount++; - } - } - - if (g_DeviceListInfo.capture.deviceCount == 0) - { - if (SUCCEEDED(WinRT_ActivateAudioInterface(defaultCaptureId, GetAudioClientIID(), &tmpClient))) - { - deviceListContext.devices[paWasapi->deviceCount].info = &defaultCapture; - deviceListContext.devices[paWasapi->deviceCount].flow = eCapture; - paWasapi->deviceCount++; - - SAFE_RELEASE(tmpClient); - } - } - else - { - for (i = 0; i < g_DeviceListInfo.capture.deviceCount; ++i) - { - deviceListContext.devices[paWasapi->deviceCount].info = &g_DeviceListInfo.capture.devices[i]; - deviceListContext.devices[paWasapi->deviceCount].flow = eCapture; - paWasapi->deviceCount++; - } - } -#endif - - // Allocate memory for the device list - if ((paWasapi->deviceCount != 0) && ((deviceInfoArray = AllocateDeviceListMemory(paWasapi)) == NULL)) - { - result = paInsufficientMemory; - goto error; - } - - // Fill WASAPI device info - for (i = 0; i < paWasapi->deviceCount; ++i) - { - PaDeviceInfo *deviceInfo = &deviceInfoArray[i]; - - PA_DEBUG(("WASAPI: device idx: %02d\n", i)); - PA_DEBUG(("WASAPI: ---------------\n")); - - FillBaseDeviceInfo(deviceInfo, hostApiIndex); - - if ((result = FillDeviceInfo(paWasapi, pEndPoints, i, defaultRenderId, defaultCaptureId, - deviceInfo, &paWasapi->devInfo[i] - #ifdef PA_WINRT - , &deviceListContext - #endif - )) != paNoError) - { - // Faulty device is made inactive - if ((result = FillInactiveDeviceInfo(paWasapi, deviceInfo)) != paNoError) - goto error; - } - - hostApi->deviceInfos[i] = deviceInfo; - ++hostApi->info.deviceCount; - } - - // Fill the remaining slots with inactive device info -#if defined(PA_WASAPI_MAX_CONST_DEVICE_COUNT) && (PA_WASAPI_MAX_CONST_DEVICE_COUNT > 0) - if ((hostApi->info.deviceCount != 0) && (hostApi->info.deviceCount < PA_WASAPI_MAX_CONST_DEVICE_COUNT)) - { - for (i = hostApi->info.deviceCount; i < PA_WASAPI_MAX_CONST_DEVICE_COUNT; ++i) - { - PaDeviceInfo *deviceInfo = &deviceInfoArray[i]; - - FillBaseDeviceInfo(deviceInfo, hostApiIndex); - - if ((result = FillInactiveDeviceInfo(paWasapi, deviceInfo)) != paNoError) - goto error; - - hostApi->deviceInfos[i] = deviceInfo; - ++hostApi->info.deviceCount; - } - } -#endif - - // Clear any non-fatal errors - result = paNoError; - - PRINT(("WASAPI: device list ok - found %d devices\n", paWasapi->deviceCount)); - -done: - -#ifndef PA_WINRT - CoTaskMemFree(defaultRenderId); - CoTaskMemFree(defaultCaptureId); - SAFE_RELEASE(pEndPoints); - SAFE_RELEASE(pEnumerator); -#endif - - return result; - -error: - - // Safety if error was not set so that we do not think initialize was a success - if (result == paNoError) - result = paInternalError; - - PRINT(("WASAPI: failed to create device list - error[%d|%s]\n", result, Pa_GetErrorText(result))); - - goto done; -} - -// ------------------------------------------------------------------------------------------ -PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) -{ - PaError result; - PaWasapiHostApiRepresentation *paWasapi; - -#ifndef PA_WINRT - if (!SetupAVRT()) - { - PRINT(("WASAPI: No AVRT! (not VISTA?)\n")); - return paNoError; - } -#endif - - paWasapi = (PaWasapiHostApiRepresentation *)PaUtil_AllocateMemory(sizeof(PaWasapiHostApiRepresentation)); - if (paWasapi == NULL) - { - result = paInsufficientMemory; - goto error; - } - memset(paWasapi, 0, sizeof(PaWasapiHostApiRepresentation)); /* ensure all fields are zeroed. especially paWasapi->allocations */ - - // Initialize COM subsystem - result = PaWinUtil_CoInitialize(paWASAPI, &paWasapi->comInitializationResult); - if (result != paNoError) - goto error; - - // Create memory group - paWasapi->allocations = PaUtil_CreateAllocationGroup(); - if (paWasapi->allocations == NULL) - { - result = paInsufficientMemory; - goto error; - } - - // Fill basic interface info - *hostApi = &paWasapi->inheritedHostApiRep; - (*hostApi)->info.structVersion = 1; - (*hostApi)->info.type = paWASAPI; - (*hostApi)->info.name = "Windows WASAPI"; - (*hostApi)->info.deviceCount = 0; - (*hostApi)->info.defaultInputDevice = paNoDevice; - (*hostApi)->info.defaultOutputDevice = paNoDevice; - (*hostApi)->Terminate = Terminate; - (*hostApi)->OpenStream = OpenStream; - (*hostApi)->IsFormatSupported = IsFormatSupported; - - // Fill the device list - if ((result = CreateDeviceList(paWasapi, hostApiIndex)) != paNoError) - goto error; - - // Detect if platform workaround is required - paWasapi->useWOW64Workaround = UseWOW64Workaround(); - - // Initialize time getter - SystemTimer_InitializeTimeGetter(); - - PaUtil_InitializeStreamInterface( &paWasapi->callbackStreamInterface, CloseStream, StartStream, - StopStream, AbortStream, IsStreamStopped, IsStreamActive, - GetStreamTime, GetStreamCpuLoad, - PaUtil_DummyRead, PaUtil_DummyWrite, - PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable ); - - PaUtil_InitializeStreamInterface( &paWasapi->blockingStreamInterface, CloseStream, StartStream, - StopStream, AbortStream, IsStreamStopped, IsStreamActive, - GetStreamTime, PaUtil_DummyGetCpuLoad, - ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); - - PRINT(("WASAPI: initialized ok\n")); - - return paNoError; - -error: - - PRINT(("WASAPI: failed %s error[%d|%s]\n", __FUNCTION__, result, Pa_GetErrorText(result))); - - Terminate((PaUtilHostApiRepresentation *)paWasapi); - - return result; -} - -// ------------------------------------------------------------------------------------------ -static void ReleaseWasapiDeviceInfoList( PaWasapiHostApiRepresentation *paWasapi ) -{ - UINT32 i; - - // Release device info bound objects - for (i = 0; i < paWasapi->deviceCount; ++i) - { - #ifndef PA_WINRT - SAFE_RELEASE(paWasapi->devInfo[i].device); - #endif - } - - // Free device info - if (paWasapi->allocations != NULL) - PaUtil_GroupFreeMemory(paWasapi->allocations, paWasapi->devInfo); - - // Be ready for a device list reinitialization and if its creation is failed pointers must not be dangling - paWasapi->devInfo = NULL; - paWasapi->deviceCount = 0; -} - -// ------------------------------------------------------------------------------------------ -static void Terminate( PaUtilHostApiRepresentation *hostApi ) -{ - PaWasapiHostApiRepresentation *paWasapi = (PaWasapiHostApiRepresentation*)hostApi; - if (paWasapi == NULL) - return; - - // Release device list - ReleaseWasapiDeviceInfoList(paWasapi); - - // Free allocations and memory group itself - if (paWasapi->allocations != NULL) - { - PaUtil_FreeAllAllocations(paWasapi->allocations); - PaUtil_DestroyAllocationGroup(paWasapi->allocations); - } - - // Release COM subsystem - PaWinUtil_CoUninitialize(paWASAPI, &paWasapi->comInitializationResult); - - // Free API representation - PaUtil_FreeMemory(paWasapi); - - // Close AVRT - CloseAVRT(); -} - -// ------------------------------------------------------------------------------------------ -static PaWasapiHostApiRepresentation *_GetHostApi(PaError *ret) -{ - PaError error; - PaUtilHostApiRepresentation *pApi; - - if ((error = PaUtil_GetHostApiRepresentation(&pApi, paWASAPI)) != paNoError) - { - if (ret != NULL) - (*ret) = error; - - return NULL; - } - - return (PaWasapiHostApiRepresentation *)pApi; -} - -// ------------------------------------------------------------------------------------------ -static PaError UpdateDeviceList() -{ - int i; - PaError ret; - PaWasapiHostApiRepresentation *paWasapi; - PaUtilHostApiRepresentation *hostApi; - - // Get API - hostApi = (PaUtilHostApiRepresentation *)(paWasapi = _GetHostApi(&ret)); - if (paWasapi == NULL) - return paNotInitialized; - - // Make sure initialized properly - if (paWasapi->allocations == NULL) - return paNotInitialized; - - // Release WASAPI internal device info list - ReleaseWasapiDeviceInfoList(paWasapi); - - // Release external device info list - if (hostApi->deviceInfos != NULL) - { - for (i = 0; i < hostApi->info.deviceCount; ++i) - { - PaUtil_GroupFreeMemory(paWasapi->allocations, (void *)hostApi->deviceInfos[i]->name); - } - PaUtil_GroupFreeMemory(paWasapi->allocations, hostApi->deviceInfos[0]); - PaUtil_GroupFreeMemory(paWasapi->allocations, hostApi->deviceInfos); - - // Be ready for a device list reinitialization and if its creation is failed pointers must not be dangling - hostApi->deviceInfos = NULL; - hostApi->info.deviceCount = 0; - hostApi->info.defaultInputDevice = paNoDevice; - hostApi->info.defaultOutputDevice = paNoDevice; - } - - // Fill possibly updated device list - if ((ret = CreateDeviceList(paWasapi, Pa_HostApiTypeIdToHostApiIndex(paWASAPI))) != paNoError) - return ret; - - return paNoError; -} - -// ------------------------------------------------------------------------------------------ -PaError PaWasapi_UpdateDeviceList() -{ -#if defined(PA_WASAPI_MAX_CONST_DEVICE_COUNT) && (PA_WASAPI_MAX_CONST_DEVICE_COUNT > 0) - return UpdateDeviceList(); -#else - return paInternalError; -#endif -} - -// ------------------------------------------------------------------------------------------ -int PaWasapi_GetDeviceCurrentFormat( PaStream *pStream, void *pFormat, unsigned int formatSize, int bOutput ) -{ - UINT32 size; - WAVEFORMATEXTENSIBLE *format; - - PaWasapiStream *stream = (PaWasapiStream *)pStream; - if (stream == NULL) - return paBadStreamPtr; - - format = (bOutput == TRUE ? &stream->out.wavex : &stream->in.wavex); - - size = min(formatSize, (UINT32)sizeof(*format)); - memcpy(pFormat, format, size); - - return size; -} - -// ------------------------------------------------------------------------------------------ -static PaError _GetWasapiDeviceInfoByDeviceIndex( PaWasapiDeviceInfo **info, PaDeviceIndex device ) -{ - PaError ret; - PaDeviceIndex index; - - // Get API - PaWasapiHostApiRepresentation *paWasapi = _GetHostApi(&ret); - if (paWasapi == NULL) - return paNotInitialized; - - // Get device index - if ((ret = PaUtil_DeviceIndexToHostApiDeviceIndex(&index, device, &paWasapi->inheritedHostApiRep)) != paNoError) - return ret; - - // Validate index - if ((UINT32)index >= paWasapi->deviceCount) - return paInvalidDevice; - - (*info) = &paWasapi->devInfo[ index ]; - - return paNoError; -} - -// ------------------------------------------------------------------------------------------ -int PaWasapi_GetDeviceDefaultFormat( void *pFormat, unsigned int formatSize, PaDeviceIndex device ) -{ - PaError ret; - PaWasapiDeviceInfo *deviceInfo; - UINT32 size; - - if (pFormat == NULL) - return paBadBufferPtr; - if (formatSize <= 0) - return paBufferTooSmall; - - if ((ret = _GetWasapiDeviceInfoByDeviceIndex(&deviceInfo, device)) != paNoError) - return ret; - - size = min(formatSize, (UINT32)sizeof(deviceInfo->DefaultFormat)); - memcpy(pFormat, &deviceInfo->DefaultFormat, size); - - return size; -} - -// ------------------------------------------------------------------------------------------ -int PaWasapi_GetDeviceMixFormat( void *pFormat, unsigned int formatSize, PaDeviceIndex device ) -{ - PaError ret; - PaWasapiDeviceInfo *deviceInfo; - UINT32 size; - - if (pFormat == NULL) - return paBadBufferPtr; - if (formatSize <= 0) - return paBufferTooSmall; - - if ((ret = _GetWasapiDeviceInfoByDeviceIndex(&deviceInfo, device)) != paNoError) - return ret; - - size = min(formatSize, (UINT32)sizeof(deviceInfo->MixFormat)); - memcpy(pFormat, &deviceInfo->MixFormat, size); - - return size; -} - -// ------------------------------------------------------------------------------------------ -int PaWasapi_GetDeviceRole( PaDeviceIndex device ) -{ - PaError ret; - PaWasapiDeviceInfo *deviceInfo; - - if ((ret = _GetWasapiDeviceInfoByDeviceIndex(&deviceInfo, device)) != paNoError) - return ret; - - return deviceInfo->formFactor; -} - -// ------------------------------------------------------------------------------------------ -PaError PaWasapi_GetIMMDevice( PaDeviceIndex device, void **pIMMDevice ) -{ -#ifndef PA_WINRT - PaError ret; - PaWasapiDeviceInfo *deviceInfo; - - if (pIMMDevice == NULL) - return paBadBufferPtr; - - if ((ret = _GetWasapiDeviceInfoByDeviceIndex(&deviceInfo, device)) != paNoError) - return ret; - - (*pIMMDevice) = deviceInfo->device; - - return paNoError; -#else - (void)device; - (void)pIMMDevice; - return paIncompatibleStreamHostApi; -#endif -} - -// ------------------------------------------------------------------------------------------ -PaError PaWasapi_GetFramesPerHostBuffer( PaStream *pStream, unsigned int *pInput, unsigned int *pOutput ) -{ - PaWasapiStream *stream = (PaWasapiStream *)pStream; - if (stream == NULL) - return paBadStreamPtr; - - if (pInput != NULL) - (*pInput) = stream->in.framesPerHostCallback; - - if (pOutput != NULL) - (*pOutput) = stream->out.framesPerHostCallback; - - return paNoError; -} - -// ------------------------------------------------------------------------------------------ -static void LogWAVEFORMATEXTENSIBLE(const WAVEFORMATEXTENSIBLE *in) -{ - const WAVEFORMATEX *old = (WAVEFORMATEX *)in; - switch (old->wFormatTag) - { - case WAVE_FORMAT_EXTENSIBLE: { - - PRINT(("wFormatTag =WAVE_FORMAT_EXTENSIBLE\n")); - - if (IsEqualGUID(&in->SubFormat, &pa_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) - { - PRINT(("SubFormat =KSDATAFORMAT_SUBTYPE_IEEE_FLOAT\n")); - } - else - if (IsEqualGUID(&in->SubFormat, &pa_KSDATAFORMAT_SUBTYPE_PCM)) - { - PRINT(("SubFormat =KSDATAFORMAT_SUBTYPE_PCM\n")); - } - else - { - PRINT(("SubFormat =CUSTOM GUID{%d:%d:%d:%d%d%d%d%d%d%d%d}\n", - in->SubFormat.Data1, - in->SubFormat.Data2, - in->SubFormat.Data3, - (int)in->SubFormat.Data4[0], - (int)in->SubFormat.Data4[1], - (int)in->SubFormat.Data4[2], - (int)in->SubFormat.Data4[3], - (int)in->SubFormat.Data4[4], - (int)in->SubFormat.Data4[5], - (int)in->SubFormat.Data4[6], - (int)in->SubFormat.Data4[7])); - } - PRINT(("Samples.wValidBitsPerSample =%d\n", in->Samples.wValidBitsPerSample)); - PRINT(("dwChannelMask =0x%X\n",in->dwChannelMask)); - - break; } - - case WAVE_FORMAT_PCM: PRINT(("wFormatTag =WAVE_FORMAT_PCM\n")); break; - case WAVE_FORMAT_IEEE_FLOAT: PRINT(("wFormatTag =WAVE_FORMAT_IEEE_FLOAT\n")); break; - default: - PRINT(("wFormatTag =UNKNOWN(%d)\n",old->wFormatTag)); break; - } - - PRINT(("nChannels =%d\n",old->nChannels)); - PRINT(("nSamplesPerSec =%d\n",old->nSamplesPerSec)); - PRINT(("nAvgBytesPerSec=%d\n",old->nAvgBytesPerSec)); - PRINT(("nBlockAlign =%d\n",old->nBlockAlign)); - PRINT(("wBitsPerSample =%d\n",old->wBitsPerSample)); - PRINT(("cbSize =%d\n",old->cbSize)); -} - -// ------------------------------------------------------------------------------------------ -PaSampleFormat WaveToPaFormat(const WAVEFORMATEXTENSIBLE *fmtext) -{ - const WAVEFORMATEX *fmt = (WAVEFORMATEX *)fmtext; - - switch (fmt->wFormatTag) - { - case WAVE_FORMAT_EXTENSIBLE: { - if (IsEqualGUID(&fmtext->SubFormat, &pa_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) - { - if (fmtext->Samples.wValidBitsPerSample == 32) - return paFloat32; - } - else - if (IsEqualGUID(&fmtext->SubFormat, &pa_KSDATAFORMAT_SUBTYPE_PCM)) - { - switch (fmt->wBitsPerSample) - { - case 32: return paInt32; - case 24: return paInt24; - case 16: return paInt16; - case 8: return paUInt8; - } - } - break; } - - case WAVE_FORMAT_IEEE_FLOAT: - return paFloat32; - - case WAVE_FORMAT_PCM: { - switch (fmt->wBitsPerSample) - { - case 32: return paInt32; - case 24: return paInt24; - case 16: return paInt16; - case 8: return paUInt8; - } - break; } - } - - return paCustomFormat; -} - -// ------------------------------------------------------------------------------------------ -static PaError MakeWaveFormatFromParams(WAVEFORMATEXTENSIBLE *wavex, const PaStreamParameters *params, - double sampleRate, BOOL packedOnly) -{ - WORD bitsPerSample; - WAVEFORMATEX *old; - DWORD channelMask = 0; - BOOL useExtensible = (params->channelCount > 2); // format is always forced for >2 channels format - PaWasapiStreamInfo *streamInfo = (PaWasapiStreamInfo *)params->hostApiSpecificStreamInfo; - - // Convert PaSampleFormat to valid data bits - if ((bitsPerSample = PaSampleFormatToBitsPerSample(params->sampleFormat)) == 0) - return paSampleFormatNotSupported; - - // Use user assigned channel mask - if ((streamInfo != NULL) && (streamInfo->flags & paWinWasapiUseChannelMask)) - { - channelMask = streamInfo->channelMask; - useExtensible = TRUE; - } - - memset(wavex, 0, sizeof(*wavex)); - - old = (WAVEFORMATEX *)wavex; - old->nChannels = (WORD)params->channelCount; - old->nSamplesPerSec = (DWORD)sampleRate; - old->wBitsPerSample = bitsPerSample; - - // according to MSDN for WAVEFORMATEX structure for WAVE_FORMAT_PCM: - // "If wFormatTag is WAVE_FORMAT_PCM, then wBitsPerSample should be equal to 8 or 16." - if ((bitsPerSample != 8) && (bitsPerSample != 16)) - { - // Normally 20 or 24 bits must go in 32 bit containers (ints) but in Exclusive mode some devices require - // packed version of the format, e.g. for example 24-bit in 3-bytes - old->wBitsPerSample = (packedOnly ? bitsPerSample : 32); - useExtensible = TRUE; - } - - // WAVEFORMATEX - if (!useExtensible) - { - old->wFormatTag = WAVE_FORMAT_PCM; - } - // WAVEFORMATEXTENSIBLE - else - { - old->wFormatTag = WAVE_FORMAT_EXTENSIBLE; - old->cbSize = sizeof(WAVEFORMATEXTENSIBLE) - sizeof(WAVEFORMATEX); - - if ((params->sampleFormat & ~paNonInterleaved) == paFloat32) - wavex->SubFormat = pa_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; - else - wavex->SubFormat = pa_KSDATAFORMAT_SUBTYPE_PCM; - - wavex->Samples.wValidBitsPerSample = bitsPerSample; - - // Set channel mask - if (channelMask != 0) - { - wavex->dwChannelMask = channelMask; - } - else - { - switch (params->channelCount) - { - case 1: wavex->dwChannelMask = PAWIN_SPEAKER_MONO; break; - case 2: wavex->dwChannelMask = PAWIN_SPEAKER_STEREO; break; - case 3: wavex->dwChannelMask = PAWIN_SPEAKER_STEREO|SPEAKER_LOW_FREQUENCY; break; - case 4: wavex->dwChannelMask = PAWIN_SPEAKER_QUAD; break; - case 5: wavex->dwChannelMask = PAWIN_SPEAKER_QUAD|SPEAKER_LOW_FREQUENCY; break; -#ifdef PAWIN_SPEAKER_5POINT1_SURROUND - case 6: wavex->dwChannelMask = PAWIN_SPEAKER_5POINT1_SURROUND; break; -#else - case 6: wavex->dwChannelMask = PAWIN_SPEAKER_5POINT1; break; -#endif -#ifdef PAWIN_SPEAKER_5POINT1_SURROUND - case 7: wavex->dwChannelMask = PAWIN_SPEAKER_5POINT1_SURROUND|SPEAKER_BACK_CENTER; break; -#else - case 7: wavex->dwChannelMask = PAWIN_SPEAKER_5POINT1|SPEAKER_BACK_CENTER; break; -#endif -#ifdef PAWIN_SPEAKER_7POINT1_SURROUND - case 8: wavex->dwChannelMask = PAWIN_SPEAKER_7POINT1_SURROUND; break; -#else - case 8: wavex->dwChannelMask = PAWIN_SPEAKER_7POINT1; break; -#endif - - default: wavex->dwChannelMask = 0; - } - } - } - - old->nBlockAlign = old->nChannels * (old->wBitsPerSample / 8); - old->nAvgBytesPerSec = old->nSamplesPerSec * old->nBlockAlign; - - return paNoError; -} - -// ------------------------------------------------------------------------------------------ -static HRESULT GetAlternativeSampleFormatExclusive(IAudioClient *client, double sampleRate, - const PaStreamParameters *params, WAVEFORMATEXTENSIBLE *outWavex, BOOL packedSampleFormatOnly) -{ - HRESULT hr = !S_OK; - AUDCLNT_SHAREMODE shareMode = AUDCLNT_SHAREMODE_EXCLUSIVE; - WAVEFORMATEXTENSIBLE testFormat; - PaStreamParameters testParams; - int i; - static const PaSampleFormat bestToWorst[] = { paInt32, paInt24, paFloat32, paInt16 }; - - // Try combination Stereo (2 channels) and then we will use our custom mono-stereo mixer - if (params->channelCount == 1) - { - testParams = (*params); - testParams.channelCount = 2; - - if (MakeWaveFormatFromParams(&testFormat, &testParams, sampleRate, packedSampleFormatOnly) == paNoError) - { - if ((hr = IAudioClient_IsFormatSupported(client, shareMode, &testFormat.Format, NULL)) == S_OK) - { - (*outWavex) = testFormat; - return hr; - } - } - - // Try selecting suitable sample type - for (i = 0; i < STATIC_ARRAY_SIZE(bestToWorst); ++i) - { - testParams.sampleFormat = bestToWorst[i]; - - if (MakeWaveFormatFromParams(&testFormat, &testParams, sampleRate, packedSampleFormatOnly) == paNoError) - { - if ((hr = IAudioClient_IsFormatSupported(client, shareMode, &testFormat.Format, NULL)) == S_OK) - { - (*outWavex) = testFormat; - return hr; - } - } - } - } - - // Try selecting suitable sample type - testParams = (*params); - for (i = 0; i < STATIC_ARRAY_SIZE(bestToWorst); ++i) - { - testParams.sampleFormat = bestToWorst[i]; - - if (MakeWaveFormatFromParams(&testFormat, &testParams, sampleRate, packedSampleFormatOnly) == paNoError) - { - if ((hr = IAudioClient_IsFormatSupported(client, shareMode, &testFormat.Format, NULL)) == S_OK) - { - (*outWavex) = testFormat; - return hr; - } - } - } - - return hr; -} - -// ------------------------------------------------------------------------------------------ -static PaError GetClosestFormat(IAudioClient *client, double sampleRate, const PaStreamParameters *_params, - AUDCLNT_SHAREMODE shareMode, WAVEFORMATEXTENSIBLE *outWavex, BOOL output) -{ - PaWasapiStreamInfo *streamInfo = (PaWasapiStreamInfo *)_params->hostApiSpecificStreamInfo; - WAVEFORMATEX *sharedClosestMatch = NULL; - HRESULT hr = !S_OK; - PaStreamParameters params = (*_params); - const BOOL explicitFormat = (streamInfo != NULL) && ((streamInfo->flags & paWinWasapiExplicitSampleFormat) == paWinWasapiExplicitSampleFormat); - (void)output; - - /* It was not noticed that 24-bit Input producing no output while device accepts this format. - To fix this issue let's ask for 32-bits and let PA converters convert host 32-bit data - to 24-bit for user-space. The bug concerns Vista, if Windows 7 supports 24-bits for Input - please report to PortAudio developers to exclude Windows 7. - */ - /*if ((params.sampleFormat == paInt24) && (output == FALSE)) - params.sampleFormat = paFloat32;*/ // <<< The silence was due to missing Int32_To_Int24_Dither implementation - - // Try standard approach, e.g. if data is > 16 bits it will be packed into 32-bit containers - MakeWaveFormatFromParams(outWavex, ¶ms, sampleRate, FALSE); - - // If built-in PCM converter requested then shared mode format will always succeed - if ((GetWindowsVersion() >= WINDOWS_7_SERVER2008R2) && - (shareMode == AUDCLNT_SHAREMODE_SHARED) && - ((streamInfo != NULL) && (streamInfo->flags & paWinWasapiAutoConvert))) - return paFormatIsSupported; - - hr = IAudioClient_IsFormatSupported(client, shareMode, &outWavex->Format, (shareMode == AUDCLNT_SHAREMODE_SHARED ? &sharedClosestMatch : NULL)); - - // Exclusive mode can require packed format for some devices - if ((hr != S_OK) && (shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE)) - { - // Enforce packed only format, e.g. data bits will not be packed into 32-bit containers in any case - MakeWaveFormatFromParams(outWavex, ¶ms, sampleRate, TRUE); - hr = IAudioClient_IsFormatSupported(client, shareMode, &outWavex->Format, NULL); - } - - if (hr == S_OK) - { - return paFormatIsSupported; - } - else - if (sharedClosestMatch != NULL) - { - WORD bitsPerSample; - - if (sharedClosestMatch->wFormatTag == WAVE_FORMAT_EXTENSIBLE) - memcpy(outWavex, sharedClosestMatch, sizeof(WAVEFORMATEXTENSIBLE)); - else - memcpy(outWavex, sharedClosestMatch, sizeof(WAVEFORMATEX)); - - CoTaskMemFree(sharedClosestMatch); - sharedClosestMatch = NULL; - - // Validate SampleRate - if ((DWORD)sampleRate != outWavex->Format.nSamplesPerSec) - return paInvalidSampleRate; - - // Validate Channel count - if ((WORD)params.channelCount != outWavex->Format.nChannels) - { - // If mono, then driver does not support 1 channel, we use internal workaround - // of tiny software mixing functionality, e.g. we provide to user buffer 1 channel - // but then mix into 2 for device buffer - if ((params.channelCount == 1) && (outWavex->Format.nChannels == 2)) - return paFormatIsSupported; - else - return paInvalidChannelCount; - } - - // Validate Sample format - if ((bitsPerSample = PaSampleFormatToBitsPerSample(params.sampleFormat)) == 0) - return paSampleFormatNotSupported; - - // Accepted format - return paFormatIsSupported; - } - else - if ((shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE) && !explicitFormat) - { - // Try standard approach, e.g. if data is > 16 bits it will be packed into 32-bit containers - if ((hr = GetAlternativeSampleFormatExclusive(client, sampleRate, ¶ms, outWavex, FALSE)) == S_OK) - return paFormatIsSupported; - - // Enforce packed only format, e.g. data bits will not be packed into 32-bit containers in any case - if ((hr = GetAlternativeSampleFormatExclusive(client, sampleRate, ¶ms, outWavex, TRUE)) == S_OK) - return paFormatIsSupported; - - // Log failure - LogHostError(hr); - } - else - { - // Exclusive mode and requested strict format, WASAPI did not accept this sample format - LogHostError(hr); - } - - return paInvalidSampleRate; -} - -// ------------------------------------------------------------------------------------------ -static PaError IsStreamParamsValid(struct PaUtilHostApiRepresentation *hostApi, - const PaStreamParameters *inputParameters, - const PaStreamParameters *outputParameters, - double sampleRate) -{ - if (hostApi == NULL) - return paHostApiNotFound; - if ((UINT32)sampleRate == 0) - return paInvalidSampleRate; - - if (inputParameters != NULL) - { - /* all standard sample formats are supported by the buffer adapter, - this implementation doesn't support any custom sample formats */ - // Note: paCustomFormat is now 8.24 (24-bits in 32-bit containers) - //if (inputParameters->sampleFormat & paCustomFormat) - // return paSampleFormatNotSupported; - - /* unless alternate device specification is supported, reject the use of - paUseHostApiSpecificDeviceSpecification */ - if (inputParameters->device == paUseHostApiSpecificDeviceSpecification) - return paInvalidDevice; - - /* check that input device can support inputChannelCount */ - if (inputParameters->channelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels) - return paInvalidChannelCount; - - /* validate inputStreamInfo */ - if (inputParameters->hostApiSpecificStreamInfo) - { - PaWasapiStreamInfo *inputStreamInfo = (PaWasapiStreamInfo *)inputParameters->hostApiSpecificStreamInfo; - if ((inputStreamInfo->size != sizeof(PaWasapiStreamInfo)) || - (inputStreamInfo->version != 1) || - (inputStreamInfo->hostApiType != paWASAPI)) - { - return paIncompatibleHostApiSpecificStreamInfo; - } - } - - return paNoError; - } - - if (outputParameters != NULL) - { - /* all standard sample formats are supported by the buffer adapter, - this implementation doesn't support any custom sample formats */ - // Note: paCustomFormat is now 8.24 (24-bits in 32-bit containers) - //if (outputParameters->sampleFormat & paCustomFormat) - // return paSampleFormatNotSupported; - - /* unless alternate device specification is supported, reject the use of - paUseHostApiSpecificDeviceSpecification */ - if (outputParameters->device == paUseHostApiSpecificDeviceSpecification) - return paInvalidDevice; - - /* check that output device can support outputChannelCount */ - if (outputParameters->channelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels) - return paInvalidChannelCount; - - /* validate outputStreamInfo */ - if(outputParameters->hostApiSpecificStreamInfo) - { - PaWasapiStreamInfo *outputStreamInfo = (PaWasapiStreamInfo *)outputParameters->hostApiSpecificStreamInfo; - if ((outputStreamInfo->size != sizeof(PaWasapiStreamInfo)) || - (outputStreamInfo->version != 1) || - (outputStreamInfo->hostApiType != paWASAPI)) - { - return paIncompatibleHostApiSpecificStreamInfo; - } - } - - return paNoError; - } - - return (inputParameters || outputParameters ? paNoError : paInternalError); -} - -// ------------------------------------------------------------------------------------------ -static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, - const PaStreamParameters *inputParameters, - const PaStreamParameters *outputParameters, - double sampleRate ) -{ - IAudioClient *tmpClient = NULL; - PaWasapiHostApiRepresentation *paWasapi = (PaWasapiHostApiRepresentation*)hostApi; - PaWasapiStreamInfo *inputStreamInfo = NULL, *outputStreamInfo = NULL; - - // Validate PaStreamParameters - PaError error; - if ((error = IsStreamParamsValid(hostApi, inputParameters, outputParameters, sampleRate)) != paNoError) - return error; - - if (inputParameters != NULL) - { - WAVEFORMATEXTENSIBLE wavex; - HRESULT hr; - PaError answer; - AUDCLNT_SHAREMODE shareMode = AUDCLNT_SHAREMODE_SHARED; - inputStreamInfo = (PaWasapiStreamInfo *)inputParameters->hostApiSpecificStreamInfo; - - if (inputStreamInfo && (inputStreamInfo->flags & paWinWasapiExclusive)) - shareMode = AUDCLNT_SHAREMODE_EXCLUSIVE; - - hr = ActivateAudioInterface(&paWasapi->devInfo[inputParameters->device], inputStreamInfo, &tmpClient); - if (hr != S_OK) - { - LogHostError(hr); - return paInvalidDevice; - } - - answer = GetClosestFormat(tmpClient, sampleRate, inputParameters, shareMode, &wavex, FALSE); - SAFE_RELEASE(tmpClient); - - if (answer != paFormatIsSupported) - return answer; - } - - if (outputParameters != NULL) - { - HRESULT hr; - WAVEFORMATEXTENSIBLE wavex; - PaError answer; - AUDCLNT_SHAREMODE shareMode = AUDCLNT_SHAREMODE_SHARED; - outputStreamInfo = (PaWasapiStreamInfo *)outputParameters->hostApiSpecificStreamInfo; - - if (outputStreamInfo && (outputStreamInfo->flags & paWinWasapiExclusive)) - shareMode = AUDCLNT_SHAREMODE_EXCLUSIVE; - - hr = ActivateAudioInterface(&paWasapi->devInfo[outputParameters->device], outputStreamInfo, &tmpClient); - if (hr != S_OK) - { - LogHostError(hr); - return paInvalidDevice; - } - - answer = GetClosestFormat(tmpClient, sampleRate, outputParameters, shareMode, &wavex, TRUE); - SAFE_RELEASE(tmpClient); - - if (answer != paFormatIsSupported) - return answer; - } - - return paFormatIsSupported; -} - -// ------------------------------------------------------------------------------------------ -static PaUint32 _GetFramesPerHostBuffer(PaUint32 userFramesPerBuffer, PaTime suggestedLatency, double sampleRate, PaUint32 TimerJitterMs) -{ - PaUint32 frames = userFramesPerBuffer + max( userFramesPerBuffer, (PaUint32)(suggestedLatency * sampleRate) ); - frames += (PaUint32)((sampleRate * 0.001) * TimerJitterMs); - return frames; -} - -// ------------------------------------------------------------------------------------------ -static void _RecalculateBuffersCount(PaWasapiSubStream *sub, UINT32 userFramesPerBuffer, UINT32 framesPerLatency, - BOOL fullDuplex, BOOL output) -{ - // Count buffers (must be at least 1) - sub->buffers = (userFramesPerBuffer != 0 ? framesPerLatency / userFramesPerBuffer : 1); - if (sub->buffers == 0) - sub->buffers = 1; - - // Determine number of buffers used: - // - Full-duplex mode will lead to period difference, thus only 1 - // - Input mode, only 1, as WASAPI allows extraction of only 1 packet - // - For Shared mode we use double buffering - if ((sub->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE) || fullDuplex) - { - BOOL eventMode = ((sub->streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) == AUDCLNT_STREAMFLAGS_EVENTCALLBACK); - - // Exclusive mode does not allow >1 buffers be used for Event interface, e.g. GetBuffer - // call must acquire max buffer size and it all must be processed. - if (eventMode) - sub->userBufferAndHostMatch = 1; - - // Full-duplex or Event mode: prefer paUtilBoundedHostBufferSize because exclusive mode will starve - // and produce glitchy audio - // Output Polling mode: prefer paUtilFixedHostBufferSize (buffers != 1) for polling mode is it allows - // to consume user data by fixed size data chunks and thus lowers memory movement (less CPU usage) - if (fullDuplex || eventMode || !output) - sub->buffers = 1; - } -} - -// ------------------------------------------------------------------------------------------ -static void _CalculateAlignedPeriod(PaWasapiSubStream *pSub, UINT32 *nFramesPerLatency, ALIGN_FUNC pAlignFunc) -{ - // Align frames to HD Audio packet size of 128 bytes for Exclusive mode only. - // Not aligning on Windows Vista will cause Event timeout, although Windows 7 will - // return AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED error to realign buffer. Aligning is necessary - // for Exclusive mode only! when audio data is fed directly to hardware. - if (pSub->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE) - { - (*nFramesPerLatency) = AlignFramesPerBuffer((*nFramesPerLatency), - pSub->wavex.Format.nBlockAlign, pAlignFunc); - } - - // Calculate period - pSub->period = MakeHnsPeriod((*nFramesPerLatency), pSub->wavex.Format.nSamplesPerSec); -} - -// ------------------------------------------------------------------------------------------ -static void _CalculatePeriodicity(PaWasapiSubStream *pSub, BOOL output, REFERENCE_TIME *periodicity) -{ - // Note: according to Microsoft docs for IAudioClient::Initialize we can set periodicity of the buffer - // only for Exclusive mode. By setting periodicity almost equal to the user buffer frames we can - // achieve high quality (less glitchy) low-latency audio. - if (pSub->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE) - { - const PaWasapiDeviceInfo *pInfo = pSub->params.device_info; - - // By default periodicity equals to the full buffer (legacy PA WASAPI's behavior) - (*periodicity) = pSub->period; - - // Try make buffer ready for I/O once we request the buffer readiness for it. Only Polling mode - // because for Event mode buffer size and periodicity must be equal according to Microsoft - // documentation for IAudioClient::Initialize. - // - // TO-DO: try spread to capture and full-duplex cases (not tested and therefore disabled) - // - if (((pSub->streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) == 0) && - (output && !pSub->params.full_duplex)) - { - UINT32 alignedFrames; - REFERENCE_TIME userPeriodicity; - - // Align frames backwards, so device will likely make buffer read ready when we are ready - // to read it (our scheduling will wait for amount of millisoconds of frames_per_buffer) - alignedFrames = AlignFramesPerBuffer(pSub->params.frames_per_buffer, - pSub->wavex.Format.nBlockAlign, ALIGN_BWD); - - userPeriodicity = MakeHnsPeriod(alignedFrames, pSub->wavex.Format.nSamplesPerSec); - - // Must not be larger than buffer size - if (userPeriodicity > pSub->period) - userPeriodicity = pSub->period; - - // Must not be smaller than minimum supported by the device - if (userPeriodicity < pInfo->MinimumDevicePeriod) - userPeriodicity = pInfo->MinimumDevicePeriod; - - (*periodicity) = userPeriodicity; - } - } - else - (*periodicity) = 0; -} - -// ------------------------------------------------------------------------------------------ -static HRESULT CreateAudioClient(PaWasapiStream *pStream, PaWasapiSubStream *pSub, BOOL output, PaError *pa_error) -{ - PaError error; - HRESULT hr; - const PaWasapiDeviceInfo *pInfo = pSub->params.device_info; - const PaStreamParameters *params = &pSub->params.stream_params; - const double sampleRate = pSub->params.sample_rate; - const BOOL fullDuplex = pSub->params.full_duplex; - const UINT32 userFramesPerBuffer = pSub->params.frames_per_buffer; - UINT32 framesPerLatency = userFramesPerBuffer; - IAudioClient *audioClient = NULL; - REFERENCE_TIME eventPeriodicity = 0; - - // Assume default failure due to some reason - (*pa_error) = paInvalidDevice; - - // Validate parameters - if (!pSub || !pInfo || !params) - { - (*pa_error) = paBadStreamPtr; - return E_POINTER; - } - if ((UINT32)sampleRate == 0) - { - (*pa_error) = paInvalidSampleRate; - return E_INVALIDARG; - } - - // Get the audio client - if (FAILED(hr = ActivateAudioInterface(pInfo, &pSub->params.wasapi_params, &audioClient))) - { - (*pa_error) = paInsufficientMemory; - LogHostError(hr); - goto done; - } - - // Get closest format - if ((error = GetClosestFormat(audioClient, sampleRate, params, pSub->shareMode, &pSub->wavex, output)) != paFormatIsSupported) - { - (*pa_error) = error; - LogHostError(hr = AUDCLNT_E_UNSUPPORTED_FORMAT); - goto done; // fail, format not supported - } - - // Check for Mono <<>> Stereo workaround - if ((params->channelCount == 1) && (pSub->wavex.Format.nChannels == 2)) - { - // select mixer - pSub->monoMixer = GetMonoToStereoMixer(&pSub->wavex, (pInfo->flow == eRender ? MIX_DIR__1TO2 : MIX_DIR__2TO1_L)); - if (pSub->monoMixer == NULL) - { - (*pa_error) = paInvalidChannelCount; - LogHostError(hr = AUDCLNT_E_UNSUPPORTED_FORMAT); - goto done; // fail, no mixer for format - } - } - - // Calculate host buffer size - if ((pSub->shareMode != AUDCLNT_SHAREMODE_EXCLUSIVE) && - (!pSub->streamFlags || ((pSub->streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) == 0))) - { - framesPerLatency = _GetFramesPerHostBuffer(userFramesPerBuffer, - params->suggestedLatency, pSub->wavex.Format.nSamplesPerSec, 0/*, - (pSub->streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK ? 0 : 1)*/); - } - else - { - #ifdef PA_WASAPI_FORCE_POLL_IF_LARGE_BUFFER - REFERENCE_TIME overall; - #endif - - // Work 1:1 with user buffer (only polling allows to use >1) - framesPerLatency += MakeFramesFromHns(SecondsTonano100(params->suggestedLatency), pSub->wavex.Format.nSamplesPerSec); - - // Force Polling if overall latency is >= 21.33ms as it allows to use 100% CPU in a callback, - // or user specified latency parameter. - #ifdef PA_WASAPI_FORCE_POLL_IF_LARGE_BUFFER - overall = MakeHnsPeriod(framesPerLatency, pSub->wavex.Format.nSamplesPerSec); - if (overall >= (106667 * 2)/*21.33ms*/) - { - framesPerLatency = _GetFramesPerHostBuffer(userFramesPerBuffer, - params->suggestedLatency, pSub->wavex.Format.nSamplesPerSec, 0/*, - (streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK ? 0 : 1)*/); - - // Use Polling interface - pSub->streamFlags &= ~AUDCLNT_STREAMFLAGS_EVENTCALLBACK; - PRINT(("WASAPI: CreateAudioClient: forcing POLL mode\n")); - } - #endif - } - - // For full-duplex output resize buffer to be the same as for input - if (output && fullDuplex) - framesPerLatency = pStream->in.framesPerHostCallback; - - // Avoid 0 frames - if (framesPerLatency == 0) - framesPerLatency = MakeFramesFromHns(pInfo->DefaultDevicePeriod, pSub->wavex.Format.nSamplesPerSec); - - // Exclusive Input stream renders data in 6 packets, we must set then the size of - // single packet, total buffer size, e.g. required latency will be PacketSize * 6 - if (!output && (pSub->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE)) - { - // Do it only for Polling mode - if ((pSub->streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) == 0) - framesPerLatency /= WASAPI_PACKETS_PER_INPUT_BUFFER; - } - - // Calculate aligned period - _CalculateAlignedPeriod(pSub, &framesPerLatency, ALIGN_BWD); - - /*! Enforce min/max period for device in Shared mode to avoid bad audio quality. - Avoid doing so for Exclusive mode as alignment will suffer. - */ - if (pSub->shareMode == AUDCLNT_SHAREMODE_SHARED) - { - if (pSub->period < pInfo->DefaultDevicePeriod) - { - pSub->period = pInfo->DefaultDevicePeriod; - - // Recalculate aligned period - framesPerLatency = MakeFramesFromHns(pSub->period, pSub->wavex.Format.nSamplesPerSec); - _CalculateAlignedPeriod(pSub, &framesPerLatency, ALIGN_BWD); - } - } - else - { - if (pSub->period < pInfo->MinimumDevicePeriod) - { - pSub->period = pInfo->MinimumDevicePeriod; - - // Recalculate aligned period - framesPerLatency = MakeFramesFromHns(pSub->period, pSub->wavex.Format.nSamplesPerSec); - _CalculateAlignedPeriod(pSub, &framesPerLatency, ALIGN_FWD); - } - } - - /*! Windows 7 does not allow to set latency lower than minimal device period and will - return error: AUDCLNT_E_INVALID_DEVICE_PERIOD. Under Vista we enforce the same behavior - manually for unified behavior on all platforms. - */ - { - /*! AUDCLNT_E_BUFFER_SIZE_ERROR: Applies to Windows 7 and later. - Indicates that the buffer duration value requested by an exclusive-mode client is - out of range. The requested duration value for pull mode must not be greater than - 500 milliseconds; for push mode the duration value must not be greater than 2 seconds. - */ - if (pSub->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE) - { - static const REFERENCE_TIME MAX_BUFFER_EVENT_DURATION = 500 * 10000; - static const REFERENCE_TIME MAX_BUFFER_POLL_DURATION = 2000 * 10000; - - // Pull mode, max 500ms - if (pSub->streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) - { - if (pSub->period > MAX_BUFFER_EVENT_DURATION) - { - pSub->period = MAX_BUFFER_EVENT_DURATION; - - // Recalculate aligned period - framesPerLatency = MakeFramesFromHns(pSub->period, pSub->wavex.Format.nSamplesPerSec); - _CalculateAlignedPeriod(pSub, &framesPerLatency, ALIGN_BWD); - } - } - // Push mode, max 2000ms - else - { - if (pSub->period > MAX_BUFFER_POLL_DURATION) - { - pSub->period = MAX_BUFFER_POLL_DURATION; - - // Recalculate aligned period - framesPerLatency = MakeFramesFromHns(pSub->period, pSub->wavex.Format.nSamplesPerSec); - _CalculateAlignedPeriod(pSub, &framesPerLatency, ALIGN_BWD); - } - } - } - } - - // Set device scheduling period (always 0 in Shared mode according to Microsoft docs) - _CalculatePeriodicity(pSub, output, &eventPeriodicity); - - // Open the stream and associate it with an audio session - hr = IAudioClient_Initialize(audioClient, - pSub->shareMode, - pSub->streamFlags, - pSub->period, - eventPeriodicity, - &pSub->wavex.Format, - NULL); - - // [Output only] Check if buffer size is the one we requested in Exclusive mode, for UAC1 USB DACs WASAPI - // can allocate internal buffer equal to 8 times of pSub->period that has to be corrected in order to match - // the requested latency - if (output && SUCCEEDED(hr) && (pSub->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE)) - { - UINT32 maxBufferFrames; - - if (FAILED(hr = IAudioClient_GetBufferSize(audioClient, &maxBufferFrames))) - { - (*pa_error) = paInvalidDevice; - LogHostError(hr); - goto done; - } - - // For Exclusive mode for UAC1 devices maxBufferFrames may be framesPerLatency * 8 but check any difference - // to be able to guarantee the latency user requested and also resulted framesPerLatency may be bigger than - // 2 seconds that will cause audio client not operational (GetCurrentPadding() will return always 0) - if (maxBufferFrames >= (framesPerLatency * 2)) - { - UINT32 ratio = maxBufferFrames / framesPerLatency; - - PRINT(("WASAPI: CreateAudioClient: detected %d times larger buffer than requested, correct to match user latency\n", ratio)); - - // Get new aligned frames lowered by calculated ratio - framesPerLatency = MakeFramesFromHns(pSub->period / ratio, pSub->wavex.Format.nSamplesPerSec); - _CalculateAlignedPeriod(pSub, &framesPerLatency, ALIGN_BWD); - - // Make sure we are not below the minimum period - if (pSub->period < pInfo->MinimumDevicePeriod) - pSub->period = pInfo->MinimumDevicePeriod; - - // Release previous client - SAFE_RELEASE(audioClient); - - // Create a new audio client - if (FAILED(hr = ActivateAudioInterface(pInfo, &pSub->params.wasapi_params, &audioClient))) - { - (*pa_error) = paInsufficientMemory; - LogHostError(hr); - goto done; - } - - // Set device scheduling period (always 0 in Shared mode according to Microsoft docs) - _CalculatePeriodicity(pSub, output, &eventPeriodicity); - - // Open the stream and associate it with an audio session - hr = IAudioClient_Initialize(audioClient, - pSub->shareMode, - pSub->streamFlags, - pSub->period, - eventPeriodicity, - &pSub->wavex.Format, - NULL); - } - } - - /*! WASAPI is tricky on large device buffer, sometimes 2000ms can be allocated sometimes - less. There is no known guaranteed level thus we make subsequent tries by decreasing - buffer by 100ms per try. - */ - while ((hr == E_OUTOFMEMORY) && (pSub->period > (100 * 10000))) - { - PRINT(("WASAPI: CreateAudioClient: decreasing buffer size to %d milliseconds\n", (pSub->period / 10000))); - - // Decrease by 100ms and try again - pSub->period -= (100 * 10000); - - // Recalculate aligned period - framesPerLatency = MakeFramesFromHns(pSub->period, pSub->wavex.Format.nSamplesPerSec); - _CalculateAlignedPeriod(pSub, &framesPerLatency, ALIGN_BWD); - - // Release the previous allocations - SAFE_RELEASE(audioClient); - - // Create a new audio client - if (FAILED(hr = ActivateAudioInterface(pInfo, &pSub->params.wasapi_params, &audioClient))) - { - (*pa_error) = paInsufficientMemory; - LogHostError(hr); - goto done; - } - - // Set device scheduling period (always 0 in Shared mode according to Microsoft docs) - _CalculatePeriodicity(pSub, output, &eventPeriodicity); - - // Open the stream and associate it with an audio session - hr = IAudioClient_Initialize(audioClient, - pSub->shareMode, - pSub->streamFlags, - pSub->period, - eventPeriodicity, - &pSub->wavex.Format, - NULL); - } - - /*! WASAPI buffer size or alignment failure. Fallback to using default size and alignment. - */ - if ((hr == AUDCLNT_E_BUFFER_SIZE_ERROR) || (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED)) - { - // Use default - pSub->period = pInfo->DefaultDevicePeriod; - - PRINT(("WASAPI: CreateAudioClient: correcting buffer size/alignment to device default\n")); - - // Release the previous allocations - SAFE_RELEASE(audioClient); - - // Create a new audio client - if (FAILED(hr = ActivateAudioInterface(pInfo, &pSub->params.wasapi_params, &audioClient))) - { - (*pa_error) = paInsufficientMemory; - LogHostError(hr); - goto done; - } - - // Set device scheduling period (always 0 in Shared mode according to Microsoft docs) - _CalculatePeriodicity(pSub, output, &eventPeriodicity); - - // Open the stream and associate it with an audio session - hr = IAudioClient_Initialize(audioClient, - pSub->shareMode, - pSub->streamFlags, - pSub->period, - eventPeriodicity, - &pSub->wavex.Format, - NULL); - } - - // Error has no workaround, fail completely - if (FAILED(hr)) - { - (*pa_error) = paInvalidDevice; - LogHostError(hr); - goto done; - } - - // Set client - pSub->clientParent = audioClient; - IAudioClient_AddRef(pSub->clientParent); - - // Recalculate buffers count - _RecalculateBuffersCount(pSub, userFramesPerBuffer, MakeFramesFromHns(pSub->period, pSub->wavex.Format.nSamplesPerSec), - fullDuplex, output); - - // No error, client is successfully created - (*pa_error) = paNoError; - -done: - - // Clean up - SAFE_RELEASE(audioClient); - return hr; -} - -// ------------------------------------------------------------------------------------------ -static PaError ActivateAudioClientOutput(PaWasapiStream *stream) -{ - HRESULT hr; - PaError result; - UINT32 maxBufferSize; - PaTime bufferLatency; - const UINT32 framesPerBuffer = stream->out.params.frames_per_buffer; - - // Create Audio client - if (FAILED(hr = CreateAudioClient(stream, &stream->out, TRUE, &result))) - { - LogPaError(result); - goto error; - } - LogWAVEFORMATEXTENSIBLE(&stream->out.wavex); - - // Activate volume - stream->outVol = NULL; - /*hr = info->device->Activate( - __uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, - (void**)&stream->outVol); - if (hr != S_OK) - return paInvalidDevice;*/ - - // Get max possible buffer size to check if it is not less than that we request - if (FAILED(hr = IAudioClient_GetBufferSize(stream->out.clientParent, &maxBufferSize))) - { - LogHostError(hr); - LogPaError(result = paInvalidDevice); - goto error; - } - - // Correct buffer to max size if it maxed out result of GetBufferSize - stream->out.bufferSize = maxBufferSize; - - // Number of frames that are required at each period - stream->out.framesPerHostCallback = maxBufferSize; - - // Calculate frames per single buffer, if buffers > 1 then always framesPerBuffer - stream->out.framesPerBuffer = - (stream->out.userBufferAndHostMatch ? stream->out.framesPerHostCallback : framesPerBuffer); - - // Calculate buffer latency - bufferLatency = (PaTime)maxBufferSize / stream->out.wavex.Format.nSamplesPerSec; - - // Append buffer latency to interface latency in shared mode (see GetStreamLatency notes) - stream->out.latencySeconds = bufferLatency; - - PRINT(("WASAPI::OpenStream(output): framesPerUser[ %d ] framesPerHost[ %d ] latency[ %.02fms ] exclusive[ %s ] wow64_fix[ %s ] mode[ %s ]\n", (UINT32)framesPerBuffer, (UINT32)stream->out.framesPerHostCallback, (float)(stream->out.latencySeconds*1000.0f), (stream->out.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? "YES" : "NO"), (stream->out.params.wow64_workaround ? "YES" : "NO"), (stream->out.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK ? "EVENT" : "POLL"))); - - return paNoError; - -error: - - return result; -} - -// ------------------------------------------------------------------------------------------ -static PaError ActivateAudioClientInput(PaWasapiStream *stream) -{ - HRESULT hr; - PaError result; - UINT32 maxBufferSize; - PaTime bufferLatency; - const UINT32 framesPerBuffer = stream->in.params.frames_per_buffer; - - // Create Audio client - if (FAILED(hr = CreateAudioClient(stream, &stream->in, FALSE, &result))) - { - LogPaError(result); - goto error; - } - LogWAVEFORMATEXTENSIBLE(&stream->in.wavex); - - // Create volume mgr - stream->inVol = NULL; - /*hr = info->device->Activate( - __uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL, - (void**)&stream->inVol); - if (hr != S_OK) - return paInvalidDevice;*/ - - // Get max possible buffer size to check if it is not less than that we request - if (FAILED(hr = IAudioClient_GetBufferSize(stream->in.clientParent, &maxBufferSize))) - { - LogHostError(hr); - LogPaError(result = paInvalidDevice); - goto error; - } - - // Correct buffer to max size if it maxed out result of GetBufferSize - stream->in.bufferSize = maxBufferSize; - - // Get interface latency (actually unneeded as we calculate latency from the size - // of maxBufferSize). - if (FAILED(hr = IAudioClient_GetStreamLatency(stream->in.clientParent, &stream->in.deviceLatency))) - { - LogHostError(hr); - LogPaError(result = paInvalidDevice); - goto error; - } - //stream->in.latencySeconds = nano100ToSeconds(stream->in.deviceLatency); - - // Number of frames that are required at each period - stream->in.framesPerHostCallback = maxBufferSize; - - // Calculate frames per single buffer, if buffers > 1 then always framesPerBuffer - stream->in.framesPerBuffer = - (stream->in.userBufferAndHostMatch ? stream->in.framesPerHostCallback : framesPerBuffer); - - // Calculate buffer latency - bufferLatency = (PaTime)maxBufferSize / stream->in.wavex.Format.nSamplesPerSec; - - // Append buffer latency to interface latency in shared mode (see GetStreamLatency notes) - stream->in.latencySeconds = bufferLatency; - - PRINT(("WASAPI::OpenStream(input): framesPerUser[ %d ] framesPerHost[ %d ] latency[ %.02fms ] exclusive[ %s ] wow64_fix[ %s ] mode[ %s ]\n", (UINT32)framesPerBuffer, (UINT32)stream->in.framesPerHostCallback, (float)(stream->in.latencySeconds*1000.0f), (stream->in.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? "YES" : "NO"), (stream->in.params.wow64_workaround ? "YES" : "NO"), (stream->in.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK ? "EVENT" : "POLL"))); - - return paNoError; - -error: - - return result; -} - -// ------------------------------------------------------------------------------------------ -static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, - PaStream** s, - const PaStreamParameters *inputParameters, - const PaStreamParameters *outputParameters, - double sampleRate, - unsigned long framesPerBuffer, - PaStreamFlags streamFlags, - PaStreamCallback *streamCallback, - void *userData ) -{ - PaError result = paNoError; - HRESULT hr; - PaWasapiHostApiRepresentation *paWasapi = (PaWasapiHostApiRepresentation*)hostApi; - PaWasapiStream *stream = NULL; - int inputChannelCount, outputChannelCount; - PaSampleFormat inputSampleFormat, outputSampleFormat; - PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat; - PaWasapiStreamInfo *inputStreamInfo = NULL, *outputStreamInfo = NULL; - PaWasapiDeviceInfo *info = NULL; - ULONG framesPerHostCallback; - PaUtilHostBufferSizeMode bufferMode; - const BOOL fullDuplex = ((inputParameters != NULL) && (outputParameters != NULL)); - BOOL useInputBufferProcessor = (inputParameters != NULL), useOutputBufferProcessor = (outputParameters != NULL); - - // validate PaStreamParameters - if ((result = IsStreamParamsValid(hostApi, inputParameters, outputParameters, sampleRate)) != paNoError) - return LogPaError(result); - - // Validate platform specific flags - if ((streamFlags & paPlatformSpecificFlags) != 0) - { - LogPaError(result = paInvalidFlag); /* unexpected platform specific flag */ - goto error; - } - - // Allocate memory for PaWasapiStream - if ((stream = (PaWasapiStream *)PaUtil_AllocateMemory(sizeof(PaWasapiStream))) == NULL) - { - LogPaError(result = paInsufficientMemory); - goto error; - } - - // Default thread priority is Audio: for exclusive mode we will use Pro Audio. - stream->nThreadPriority = eThreadPriorityAudio; - - // Set default number of frames: paFramesPerBufferUnspecified - if (framesPerBuffer == paFramesPerBufferUnspecified) - { - UINT32 framesPerBufferIn = 0, framesPerBufferOut = 0; - if (inputParameters != NULL) - { - info = &paWasapi->devInfo[inputParameters->device]; - framesPerBufferIn = MakeFramesFromHns(info->DefaultDevicePeriod, (UINT32)sampleRate); - } - if (outputParameters != NULL) - { - info = &paWasapi->devInfo[outputParameters->device]; - framesPerBufferOut = MakeFramesFromHns(info->DefaultDevicePeriod, (UINT32)sampleRate); - } - // choosing maximum default size - framesPerBuffer = max(framesPerBufferIn, framesPerBufferOut); - } - if (framesPerBuffer == 0) - framesPerBuffer = ((UINT32)sampleRate / 100) * 2; - - // Try create device: Input - if (inputParameters != NULL) - { - inputChannelCount = inputParameters->channelCount; - inputSampleFormat = GetSampleFormatForIO(inputParameters->sampleFormat); - info = &paWasapi->devInfo[inputParameters->device]; - - // default Shared Mode - stream->in.shareMode = AUDCLNT_SHAREMODE_SHARED; - - // PaWasapiStreamInfo - if (inputParameters->hostApiSpecificStreamInfo != NULL) - { - memcpy(&stream->in.params.wasapi_params, inputParameters->hostApiSpecificStreamInfo, min(sizeof(stream->in.params.wasapi_params), ((PaWasapiStreamInfo *)inputParameters->hostApiSpecificStreamInfo)->size)); - stream->in.params.wasapi_params.size = sizeof(stream->in.params.wasapi_params); - - stream->in.params.stream_params.hostApiSpecificStreamInfo = &stream->in.params.wasapi_params; - inputStreamInfo = &stream->in.params.wasapi_params; - - stream->in.flags = inputStreamInfo->flags; - - // Exclusive Mode - if (inputStreamInfo->flags & paWinWasapiExclusive) - { - // Boost thread priority - stream->nThreadPriority = eThreadPriorityProAudio; - // Make Exclusive - stream->in.shareMode = AUDCLNT_SHAREMODE_EXCLUSIVE; - } - - // explicit thread priority level - if (inputStreamInfo->flags & paWinWasapiThreadPriority) - { - if ((inputStreamInfo->threadPriority > eThreadPriorityNone) && - (inputStreamInfo->threadPriority <= eThreadPriorityWindowManager)) - stream->nThreadPriority = inputStreamInfo->threadPriority; - } - - // redirect processing to custom user callback, ignore PA buffer processor - useInputBufferProcessor = !(inputStreamInfo->flags & paWinWasapiRedirectHostProcessor); - } - - // Choose processing mode - stream->in.streamFlags = (stream->in.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? AUDCLNT_STREAMFLAGS_EVENTCALLBACK : 0); - if (paWasapi->useWOW64Workaround) - stream->in.streamFlags = 0; // polling interface - else - if (streamCallback == NULL) - stream->in.streamFlags = 0; // polling interface - else - if ((inputStreamInfo != NULL) && (inputStreamInfo->flags & paWinWasapiPolling)) - stream->in.streamFlags = 0; // polling interface - else - if (fullDuplex) - stream->in.streamFlags = 0; // polling interface is implemented for full-duplex mode also - - // Use built-in PCM converter (channel count and sample rate) if requested - if ((GetWindowsVersion() >= WINDOWS_7_SERVER2008R2) && - (stream->in.shareMode == AUDCLNT_SHAREMODE_SHARED) && - ((inputStreamInfo != NULL) && (inputStreamInfo->flags & paWinWasapiAutoConvert))) - stream->in.streamFlags |= (AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY); - - // Fill parameters for Audio Client creation - stream->in.params.device_info = info; - stream->in.params.stream_params = (*inputParameters); - stream->in.params.frames_per_buffer = framesPerBuffer; - stream->in.params.sample_rate = sampleRate; - stream->in.params.blocking = (streamCallback == NULL); - stream->in.params.full_duplex = fullDuplex; - stream->in.params.wow64_workaround = paWasapi->useWOW64Workaround; - - // Create and activate audio client - if ((result = ActivateAudioClientInput(stream)) != paNoError) - { - LogPaError(result); - goto error; - } - - // Get closest format - hostInputSampleFormat = PaUtil_SelectClosestAvailableFormat(WaveToPaFormat(&stream->in.wavex), inputSampleFormat); - - // Set user-side custom host processor - if ((inputStreamInfo != NULL) && - (inputStreamInfo->flags & paWinWasapiRedirectHostProcessor)) - { - stream->hostProcessOverrideInput.processor = inputStreamInfo->hostProcessorInput; - stream->hostProcessOverrideInput.userData = userData; - } - - // Only get IAudioCaptureClient input once here instead of getting it at multiple places based on the use - if (FAILED(hr = IAudioClient_GetService(stream->in.clientParent, &pa_IID_IAudioCaptureClient, (void **)&stream->captureClientParent))) - { - LogHostError(hr); - LogPaError(result = paUnanticipatedHostError); - goto error; - } - - // Create ring buffer for blocking mode (It is needed because we fetch Input packets, not frames, - // and thus we have to save partial packet if such remains unread) - if (stream->in.params.blocking == TRUE) - { - UINT32 bufferFrames = ALIGN_NEXT_POW2((stream->in.framesPerHostCallback / WASAPI_PACKETS_PER_INPUT_BUFFER) * 2); - UINT32 frameSize = stream->in.wavex.Format.nBlockAlign; - - // buffer - if ((stream->in.tailBuffer = PaUtil_AllocateMemory(sizeof(PaUtilRingBuffer))) == NULL) - { - LogPaError(result = paInsufficientMemory); - goto error; - } - memset(stream->in.tailBuffer, 0, sizeof(PaUtilRingBuffer)); - - // buffer memory region - stream->in.tailBufferMemory = PaUtil_AllocateMemory(frameSize * bufferFrames); - if (stream->in.tailBufferMemory == NULL) - { - LogPaError(result = paInsufficientMemory); - goto error; - } - - // initialize - if (PaUtil_InitializeRingBuffer(stream->in.tailBuffer, frameSize, bufferFrames, stream->in.tailBufferMemory) != 0) - { - LogPaError(result = paInternalError); - goto error; - } - } - } - else - { - inputChannelCount = 0; - inputSampleFormat = hostInputSampleFormat = paInt16; /* Suppress 'uninitialised var' warnings. */ - } - - // Try create device: Output - if (outputParameters != NULL) - { - outputChannelCount = outputParameters->channelCount; - outputSampleFormat = GetSampleFormatForIO(outputParameters->sampleFormat); - info = &paWasapi->devInfo[outputParameters->device]; - - // default Shared Mode - stream->out.shareMode = AUDCLNT_SHAREMODE_SHARED; - - // set PaWasapiStreamInfo - if (outputParameters->hostApiSpecificStreamInfo != NULL) - { - memcpy(&stream->out.params.wasapi_params, outputParameters->hostApiSpecificStreamInfo, min(sizeof(stream->out.params.wasapi_params), ((PaWasapiStreamInfo *)outputParameters->hostApiSpecificStreamInfo)->size)); - stream->out.params.wasapi_params.size = sizeof(stream->out.params.wasapi_params); - - stream->out.params.stream_params.hostApiSpecificStreamInfo = &stream->out.params.wasapi_params; - outputStreamInfo = &stream->out.params.wasapi_params; - - stream->out.flags = outputStreamInfo->flags; - - // Exclusive Mode - if (outputStreamInfo->flags & paWinWasapiExclusive) - { - // Boost thread priority - stream->nThreadPriority = eThreadPriorityProAudio; - // Make Exclusive - stream->out.shareMode = AUDCLNT_SHAREMODE_EXCLUSIVE; - } - - // explicit thread priority level - if (outputStreamInfo->flags & paWinWasapiThreadPriority) - { - if ((outputStreamInfo->threadPriority > eThreadPriorityNone) && - (outputStreamInfo->threadPriority <= eThreadPriorityWindowManager)) - stream->nThreadPriority = outputStreamInfo->threadPriority; - } - - // redirect processing to custom user callback, ignore PA buffer processor - useOutputBufferProcessor = !(outputStreamInfo->flags & paWinWasapiRedirectHostProcessor); - } - - // Choose processing mode - stream->out.streamFlags = (stream->out.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? AUDCLNT_STREAMFLAGS_EVENTCALLBACK : 0); - if (paWasapi->useWOW64Workaround) - stream->out.streamFlags = 0; // polling interface - else - if (streamCallback == NULL) - stream->out.streamFlags = 0; // polling interface - else - if ((outputStreamInfo != NULL) && (outputStreamInfo->flags & paWinWasapiPolling)) - stream->out.streamFlags = 0; // polling interface - else - if (fullDuplex) - stream->out.streamFlags = 0; // polling interface is implemented for full-duplex mode also - - // Use built-in PCM converter (channel count and sample rate) if requested - if ((GetWindowsVersion() >= WINDOWS_7_SERVER2008R2) && - (stream->out.shareMode == AUDCLNT_SHAREMODE_SHARED) && - ((outputStreamInfo != NULL) && (outputStreamInfo->flags & paWinWasapiAutoConvert))) - stream->out.streamFlags |= (AUDCLNT_STREAMFLAGS_AUTOCONVERTPCM | AUDCLNT_STREAMFLAGS_SRC_DEFAULT_QUALITY); - - // Fill parameters for Audio Client creation - stream->out.params.device_info = info; - stream->out.params.stream_params = (*outputParameters); - stream->out.params.frames_per_buffer = framesPerBuffer; - stream->out.params.sample_rate = sampleRate; - stream->out.params.blocking = (streamCallback == NULL); - stream->out.params.full_duplex = fullDuplex; - stream->out.params.wow64_workaround = paWasapi->useWOW64Workaround; - - // Create and activate audio client - if ((result = ActivateAudioClientOutput(stream)) != paNoError) - { - LogPaError(result); - goto error; - } - - // Get closest format - hostOutputSampleFormat = PaUtil_SelectClosestAvailableFormat(WaveToPaFormat(&stream->out.wavex), outputSampleFormat); - - // Set user-side custom host processor - if ((outputStreamInfo != NULL) && - (outputStreamInfo->flags & paWinWasapiRedirectHostProcessor)) - { - stream->hostProcessOverrideOutput.processor = outputStreamInfo->hostProcessorOutput; - stream->hostProcessOverrideOutput.userData = userData; - } - - // Only get IAudioCaptureClient output once here instead of getting it at multiple places based on the use - if (FAILED(hr = IAudioClient_GetService(stream->out.clientParent, &pa_IID_IAudioRenderClient, (void **)&stream->renderClientParent))) - { - LogHostError(hr); - LogPaError(result = paUnanticipatedHostError); - goto error; - } - } - else - { - outputChannelCount = 0; - outputSampleFormat = hostOutputSampleFormat = paInt16; /* Suppress 'uninitialized var' warnings. */ - } - - // log full-duplex - if (fullDuplex) - PRINT(("WASAPI::OpenStream: full-duplex mode\n")); - - // paWinWasapiPolling must be on/or not on both streams - if ((inputParameters != NULL) && (outputParameters != NULL)) - { - if ((inputStreamInfo != NULL) && (outputStreamInfo != NULL)) - { - if (((inputStreamInfo->flags & paWinWasapiPolling) && - !(outputStreamInfo->flags & paWinWasapiPolling)) - || - (!(inputStreamInfo->flags & paWinWasapiPolling) && - (outputStreamInfo->flags & paWinWasapiPolling))) - { - LogPaError(result = paInvalidFlag); - goto error; - } - } - } - - // Initialize stream representation - if (streamCallback) - { - stream->bBlocking = FALSE; - PaUtil_InitializeStreamRepresentation(&stream->streamRepresentation, - &paWasapi->callbackStreamInterface, - streamCallback, userData); - } - else - { - stream->bBlocking = TRUE; - PaUtil_InitializeStreamRepresentation(&stream->streamRepresentation, - &paWasapi->blockingStreamInterface, - streamCallback, userData); - } - - // Initialize CPU measurer - PaUtil_InitializeCpuLoadMeasurer(&stream->cpuLoadMeasurer, sampleRate); - - if (outputParameters && inputParameters) - { - // serious problem #1 - No, Not a problem, especially concerning Exclusive mode. - // Input device in exclusive mode somehow is getting large buffer always, thus we - // adjust Output latency to reflect it, thus period will differ but playback will be - // normal. - /*if (stream->in.period != stream->out.period) - { - PRINT(("WASAPI: OpenStream: period discrepancy\n")); - LogPaError(result = paBadIODeviceCombination); - goto error; - }*/ - - // serious problem #2 - No, Not a problem, as framesPerHostCallback take into account - // sample size while it is not a problem for PA full-duplex, we must care of - // period only! - /*if (stream->out.framesPerHostCallback != stream->in.framesPerHostCallback) - { - PRINT(("WASAPI: OpenStream: framesPerHostCallback discrepancy\n")); - goto error; - }*/ - } - - // Calculate frames per host for processor - framesPerHostCallback = (outputParameters ? stream->out.framesPerBuffer : stream->in.framesPerBuffer); - - // Choose correct mode of buffer processing: - // Exclusive/Shared non paWinWasapiPolling mode: paUtilFixedHostBufferSize - always fixed - // Exclusive/Shared paWinWasapiPolling mode: paUtilBoundedHostBufferSize - may vary for Exclusive or Full-duplex - bufferMode = paUtilFixedHostBufferSize; - if (inputParameters) // !!! WASAPI IAudioCaptureClient::GetBuffer extracts not number of frames but 1 packet, thus we always must adapt - bufferMode = paUtilBoundedHostBufferSize; - else - if (outputParameters) - { - if ((stream->out.buffers == 1) && - (!stream->out.streamFlags || ((stream->out.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) == 0))) - bufferMode = paUtilBoundedHostBufferSize; - } - stream->bufferMode = bufferMode; - - // Initialize buffer processor - if (useInputBufferProcessor || useOutputBufferProcessor) - { - result = PaUtil_InitializeBufferProcessor( - &stream->bufferProcessor, - inputChannelCount, - inputSampleFormat, - hostInputSampleFormat, - outputChannelCount, - outputSampleFormat, - hostOutputSampleFormat, - sampleRate, - streamFlags, - framesPerBuffer, - framesPerHostCallback, - bufferMode, - streamCallback, - userData); - if (result != paNoError) - { - LogPaError(result); - goto error; - } - } - - // Set Input latency - stream->streamRepresentation.streamInfo.inputLatency = - (useInputBufferProcessor ? PaUtil_GetBufferProcessorInputLatencyFrames(&stream->bufferProcessor) / sampleRate : 0) - + (inputParameters != NULL ? stream->in.latencySeconds : 0); - - // Set Output latency - stream->streamRepresentation.streamInfo.outputLatency = - (useOutputBufferProcessor ? PaUtil_GetBufferProcessorOutputLatencyFrames(&stream->bufferProcessor) / sampleRate : 0) - + (outputParameters != NULL ? stream->out.latencySeconds : 0); - - // Set SR - stream->streamRepresentation.streamInfo.sampleRate = sampleRate; - - (*s) = (PaStream *)stream; - return result; - -error: - - if (stream != NULL) - CloseStream(stream); - - return result; -} - -// ------------------------------------------------------------------------------------------ -static PaError CloseStream( PaStream* s ) -{ - PaError result = paNoError; - PaWasapiStream *stream = (PaWasapiStream*)s; - - // abort active stream - if (IsStreamActive(s)) - { - result = AbortStream(s); - } - - SAFE_RELEASE(stream->captureClientParent); - SAFE_RELEASE(stream->renderClientParent); - SAFE_RELEASE(stream->out.clientParent); - SAFE_RELEASE(stream->in.clientParent); - SAFE_RELEASE(stream->inVol); - SAFE_RELEASE(stream->outVol); - - CloseHandle(stream->event[S_INPUT]); - CloseHandle(stream->event[S_OUTPUT]); - - _StreamCleanup(stream); - - PaWasapi_FreeMemory(stream->in.monoBuffer); - PaWasapi_FreeMemory(stream->out.monoBuffer); - - PaUtil_FreeMemory(stream->in.tailBuffer); - PaUtil_FreeMemory(stream->in.tailBufferMemory); - - PaUtil_FreeMemory(stream->out.tailBuffer); - PaUtil_FreeMemory(stream->out.tailBufferMemory); - - PaUtil_TerminateBufferProcessor(&stream->bufferProcessor); - PaUtil_TerminateStreamRepresentation(&stream->streamRepresentation); - PaUtil_FreeMemory(stream); - - return result; -} - -// ------------------------------------------------------------------------------------------ -HRESULT UnmarshalSubStreamComPointers(PaWasapiSubStream *substream) -{ -#ifndef PA_WINRT - HRESULT hResult = S_OK; - HRESULT hFirstBadResult = S_OK; - substream->clientProc = NULL; - - // IAudioClient - hResult = CoGetInterfaceAndReleaseStream(substream->clientStream, GetAudioClientIID(), (LPVOID*)&substream->clientProc); - substream->clientStream = NULL; - if (hResult != S_OK) - { - hFirstBadResult = (hFirstBadResult == S_OK) ? hResult : hFirstBadResult; - } - - return hFirstBadResult; - -#else - (void)substream; - return S_OK; -#endif -} - -// ------------------------------------------------------------------------------------------ -HRESULT UnmarshalStreamComPointers(PaWasapiStream *stream) -{ -#ifndef PA_WINRT - HRESULT hResult = S_OK; - HRESULT hFirstBadResult = S_OK; - stream->captureClient = NULL; - stream->renderClient = NULL; - stream->in.clientProc = NULL; - stream->out.clientProc = NULL; - - if (NULL != stream->in.clientParent) - { - // SubStream pointers - hResult = UnmarshalSubStreamComPointers(&stream->in); - if (hResult != S_OK) - { - hFirstBadResult = (hFirstBadResult == S_OK) ? hResult : hFirstBadResult; - } - - // IAudioCaptureClient - hResult = CoGetInterfaceAndReleaseStream(stream->captureClientStream, &pa_IID_IAudioCaptureClient, (LPVOID*)&stream->captureClient); - stream->captureClientStream = NULL; - if (hResult != S_OK) - { - hFirstBadResult = (hFirstBadResult == S_OK) ? hResult : hFirstBadResult; - } - } - - if (NULL != stream->out.clientParent) - { - // SubStream pointers - hResult = UnmarshalSubStreamComPointers(&stream->out); - if (hResult != S_OK) - { - hFirstBadResult = (hFirstBadResult == S_OK) ? hResult : hFirstBadResult; - } - - // IAudioRenderClient - hResult = CoGetInterfaceAndReleaseStream(stream->renderClientStream, &pa_IID_IAudioRenderClient, (LPVOID*)&stream->renderClient); - stream->renderClientStream = NULL; - if (hResult != S_OK) - { - hFirstBadResult = (hFirstBadResult == S_OK) ? hResult : hFirstBadResult; - } - } - - return hFirstBadResult; -#else - if (stream->in.clientParent != NULL) - { - stream->in.clientProc = stream->in.clientParent; - IAudioClient_AddRef(stream->in.clientParent); - } - - if (stream->out.clientParent != NULL) - { - stream->out.clientProc = stream->out.clientParent; - IAudioClient_AddRef(stream->out.clientParent); - } - - if (stream->renderClientParent != NULL) - { - stream->renderClient = stream->renderClientParent; - IAudioRenderClient_AddRef(stream->renderClientParent); - } - - if (stream->captureClientParent != NULL) - { - stream->captureClient = stream->captureClientParent; - IAudioCaptureClient_AddRef(stream->captureClientParent); - } - - return S_OK; -#endif -} - -// ----------------------------------------------------------------------------------------- -void ReleaseUnmarshaledSubComPointers(PaWasapiSubStream *substream) -{ - SAFE_RELEASE(substream->clientProc); -} - -// ----------------------------------------------------------------------------------------- -void ReleaseUnmarshaledComPointers(PaWasapiStream *stream) -{ - // Release AudioClient services first - SAFE_RELEASE(stream->captureClient); - SAFE_RELEASE(stream->renderClient); - - // Release AudioClients - ReleaseUnmarshaledSubComPointers(&stream->in); - ReleaseUnmarshaledSubComPointers(&stream->out); -} - -// ------------------------------------------------------------------------------------------ -HRESULT MarshalSubStreamComPointers(PaWasapiSubStream *substream) -{ -#ifndef PA_WINRT - HRESULT hResult; - substream->clientStream = NULL; - - // IAudioClient - hResult = CoMarshalInterThreadInterfaceInStream(GetAudioClientIID(), (LPUNKNOWN)substream->clientParent, &substream->clientStream); - if (hResult != S_OK) - goto marshal_sub_error; - - return hResult; - - // If marshaling error occurred, make sure to release everything. -marshal_sub_error: - - UnmarshalSubStreamComPointers(substream); - ReleaseUnmarshaledSubComPointers(substream); - return hResult; -#else - (void)substream; - return S_OK; -#endif -} - -// ------------------------------------------------------------------------------------------ -HRESULT MarshalStreamComPointers(PaWasapiStream *stream) -{ -#ifndef PA_WINRT - HRESULT hResult = S_OK; - stream->captureClientStream = NULL; - stream->in.clientStream = NULL; - stream->renderClientStream = NULL; - stream->out.clientStream = NULL; - - if (NULL != stream->in.clientParent) - { - // SubStream pointers - hResult = MarshalSubStreamComPointers(&stream->in); - if (hResult != S_OK) - goto marshal_error; - - // IAudioCaptureClient - hResult = CoMarshalInterThreadInterfaceInStream(&pa_IID_IAudioCaptureClient, (LPUNKNOWN)stream->captureClientParent, &stream->captureClientStream); - if (hResult != S_OK) - goto marshal_error; - } - - if (NULL != stream->out.clientParent) - { - // SubStream pointers - hResult = MarshalSubStreamComPointers(&stream->out); - if (hResult != S_OK) - goto marshal_error; - - // IAudioRenderClient - hResult = CoMarshalInterThreadInterfaceInStream(&pa_IID_IAudioRenderClient, (LPUNKNOWN)stream->renderClientParent, &stream->renderClientStream); - if (hResult != S_OK) - goto marshal_error; - } - - return hResult; - - // If marshaling error occurred, make sure to release everything. -marshal_error: - - UnmarshalStreamComPointers(stream); - ReleaseUnmarshaledComPointers(stream); - return hResult; -#else - (void)stream; - return S_OK; -#endif -} - -// ------------------------------------------------------------------------------------------ -static PaError StartStream( PaStream *s ) -{ - HRESULT hr; - PaWasapiStream *stream = (PaWasapiStream*)s; - PaError result = paNoError; - - // check if stream is active already - if (IsStreamActive(s)) - return paStreamIsNotStopped; - - PaUtil_ResetBufferProcessor(&stream->bufferProcessor); - - // Cleanup handles (may be necessary if stream was stopped by itself due to error) - _StreamCleanup(stream); - - // Create close event - if ((stream->hCloseRequest = CreateEvent(NULL, TRUE, FALSE, NULL)) == NULL) - { - result = paInsufficientMemory; - goto start_error; - } - - // Create thread - if (!stream->bBlocking) - { - // Create thread events - stream->hThreadStart = CreateEvent(NULL, TRUE, FALSE, NULL); - stream->hThreadExit = CreateEvent(NULL, TRUE, FALSE, NULL); - if ((stream->hThreadStart == NULL) || (stream->hThreadExit == NULL)) - { - result = paInsufficientMemory; - goto start_error; - } - - // Marshal WASAPI interface pointers for safe use in thread created below. - if ((hr = MarshalStreamComPointers(stream)) != S_OK) - { - PRINT(("Failed marshaling stream COM pointers.")); - result = paUnanticipatedHostError; - goto nonblocking_start_error; - } - - if ((stream->in.clientParent && (stream->in.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK)) || - (stream->out.clientParent && (stream->out.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK))) - { - if ((stream->hThread = CREATE_THREAD(ProcThreadEvent)) == NULL) - { - PRINT(("Failed creating thread: ProcThreadEvent.")); - result = paUnanticipatedHostError; - goto nonblocking_start_error; - } - } - else - { - if ((stream->hThread = CREATE_THREAD(ProcThreadPoll)) == NULL) - { - PRINT(("Failed creating thread: ProcThreadPoll.")); - result = paUnanticipatedHostError; - goto nonblocking_start_error; - } - } - - // Wait for thread to start - if (WaitForSingleObject(stream->hThreadStart, 60*1000) == WAIT_TIMEOUT) - { - PRINT(("Failed starting thread: timeout.")); - result = paUnanticipatedHostError; - goto nonblocking_start_error; - } - } - else - { - // Create blocking operation events (non-signaled event means - blocking operation is pending) - if (stream->out.clientParent != NULL) - { - if ((stream->hBlockingOpStreamWR = CreateEvent(NULL, TRUE, TRUE, NULL)) == NULL) - { - result = paInsufficientMemory; - goto start_error; - } - } - if (stream->in.clientParent != NULL) - { - if ((stream->hBlockingOpStreamRD = CreateEvent(NULL, TRUE, TRUE, NULL)) == NULL) - { - result = paInsufficientMemory; - goto start_error; - } - } - - // Initialize event & start INPUT stream - if (stream->in.clientParent != NULL) - { - if ((hr = IAudioClient_Start(stream->in.clientParent)) != S_OK) - { - LogHostError(hr); - result = paUnanticipatedHostError; - goto start_error; - } - } - - // Initialize event & start OUTPUT stream - if (stream->out.clientParent != NULL) - { - // Start - if ((hr = IAudioClient_Start(stream->out.clientParent)) != S_OK) - { - LogHostError(hr); - result = paUnanticipatedHostError; - goto start_error; - } - } - - // Set parent to working pointers to use shared functions. - stream->captureClient = stream->captureClientParent; - stream->renderClient = stream->renderClientParent; - stream->in.clientProc = stream->in.clientParent; - stream->out.clientProc = stream->out.clientParent; - - // Signal: stream running. - stream->running = TRUE; - } - - return result; - -nonblocking_start_error: - - // Set hThreadExit event to prevent blocking during cleanup - SetEvent(stream->hThreadExit); - UnmarshalStreamComPointers(stream); - ReleaseUnmarshaledComPointers(stream); - -start_error: - - StopStream(s); - return result; -} - -// ------------------------------------------------------------------------------------------ -void _StreamFinish(PaWasapiStream *stream) -{ - // Issue command to thread to stop processing and wait for thread exit - if (!stream->bBlocking) - { - SignalObjectAndWait(stream->hCloseRequest, stream->hThreadExit, INFINITE, FALSE); - } - else - // Blocking mode does not own thread - { - // Signal close event and wait for each of 2 blocking operations to complete - if (stream->out.clientParent) - SignalObjectAndWait(stream->hCloseRequest, stream->hBlockingOpStreamWR, INFINITE, TRUE); - if (stream->out.clientParent) - SignalObjectAndWait(stream->hCloseRequest, stream->hBlockingOpStreamRD, INFINITE, TRUE); - - // Process stop - _StreamOnStop(stream); - } - - // Cleanup handles - _StreamCleanup(stream); - - stream->running = FALSE; -} - -// ------------------------------------------------------------------------------------------ -void _StreamCleanup(PaWasapiStream *stream) -{ - // Close thread handles to allow restart - SAFE_CLOSE(stream->hThread); - SAFE_CLOSE(stream->hThreadStart); - SAFE_CLOSE(stream->hThreadExit); - SAFE_CLOSE(stream->hCloseRequest); - SAFE_CLOSE(stream->hBlockingOpStreamRD); - SAFE_CLOSE(stream->hBlockingOpStreamWR); -} - -// ------------------------------------------------------------------------------------------ -static PaError StopStream( PaStream *s ) -{ - // Finish stream - _StreamFinish((PaWasapiStream *)s); - return paNoError; -} - -// ------------------------------------------------------------------------------------------ -static PaError AbortStream( PaStream *s ) -{ - // Finish stream - _StreamFinish((PaWasapiStream *)s); - return paNoError; -} - -// ------------------------------------------------------------------------------------------ -static PaError IsStreamStopped( PaStream *s ) -{ - return !((PaWasapiStream *)s)->running; -} - -// ------------------------------------------------------------------------------------------ -static PaError IsStreamActive( PaStream *s ) -{ - return ((PaWasapiStream *)s)->running; -} - -// ------------------------------------------------------------------------------------------ -static PaTime GetStreamTime( PaStream *s ) -{ - PaWasapiStream *stream = (PaWasapiStream*)s; - - /* suppress unused variable warnings */ - (void) stream; - - return PaUtil_GetTime(); -} - -// ------------------------------------------------------------------------------------------ -static double GetStreamCpuLoad( PaStream* s ) -{ - return PaUtil_GetCpuLoad(&((PaWasapiStream *)s)->cpuLoadMeasurer); -} - -// ------------------------------------------------------------------------------------------ -static PaError ReadStream( PaStream* s, void *_buffer, unsigned long frames ) -{ - PaWasapiStream *stream = (PaWasapiStream*)s; - - HRESULT hr = S_OK; - BYTE *user_buffer = (BYTE *)_buffer; - BYTE *wasapi_buffer = NULL; - DWORD flags = 0; - UINT32 i, available, sleep = 0; - unsigned long processed; - ThreadIdleScheduler sched; - - // validate - if (!stream->running) - return paStreamIsStopped; - if (stream->captureClient == NULL) - return paBadStreamPtr; - - // Notify blocking op has begun - ResetEvent(stream->hBlockingOpStreamRD); - - // Use thread scheduling for 500 microseconds (emulated) when wait time for frames is less than - // 1 milliseconds, emulation helps to normalize CPU consumption and avoids too busy waiting - ThreadIdleScheduler_Setup(&sched, 1, 250/* microseconds */); - - // Make a local copy of the user buffer pointer(s), this is necessary - // because PaUtil_CopyOutput() advances these pointers every time it is called - if (!stream->bufferProcessor.userInputIsInterleaved) - { - user_buffer = (BYTE *)alloca(sizeof(BYTE *) * stream->bufferProcessor.inputChannelCount); - if (user_buffer == NULL) - return paInsufficientMemory; - - for (i = 0; i < stream->bufferProcessor.inputChannelCount; ++i) - ((BYTE **)user_buffer)[i] = ((BYTE **)_buffer)[i]; - } - - // Find out if there are tail frames, flush them all before reading hardware - if ((available = PaUtil_GetRingBufferReadAvailable(stream->in.tailBuffer)) != 0) - { - ring_buffer_size_t buf1_size = 0, buf2_size = 0, read, desired; - void *buf1 = NULL, *buf2 = NULL; - - // Limit desired to amount of requested frames - desired = available; - if ((UINT32)desired > frames) - desired = frames; - - // Get pointers to read regions - read = PaUtil_GetRingBufferReadRegions(stream->in.tailBuffer, desired, &buf1, &buf1_size, &buf2, &buf2_size); - - if (buf1 != NULL) - { - // Register available frames to processor - PaUtil_SetInputFrameCount(&stream->bufferProcessor, buf1_size); - - // Register host buffer pointer to processor - PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, buf1, stream->bufferProcessor.inputChannelCount); - - // Copy user data to host buffer (with conversion if applicable) - processed = PaUtil_CopyInput(&stream->bufferProcessor, (void **)&user_buffer, buf1_size); - frames -= processed; - } - - if (buf2 != NULL) - { - // Register available frames to processor - PaUtil_SetInputFrameCount(&stream->bufferProcessor, buf2_size); - - // Register host buffer pointer to processor - PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, buf2, stream->bufferProcessor.inputChannelCount); - - // Copy user data to host buffer (with conversion if applicable) - processed = PaUtil_CopyInput(&stream->bufferProcessor, (void **)&user_buffer, buf2_size); - frames -= processed; - } - - // Advance - PaUtil_AdvanceRingBufferReadIndex(stream->in.tailBuffer, read); - } - - // Read hardware - while (frames != 0) - { - // Check if blocking call must be interrupted - if (WaitForSingleObject(stream->hCloseRequest, sleep) != WAIT_TIMEOUT) - break; - - // Get available frames (must be finding out available frames before call to IAudioCaptureClient_GetBuffer - // othervise audio glitches will occur inExclusive mode as it seems that WASAPI has some scheduling/ - // processing problems when such busy polling with IAudioCaptureClient_GetBuffer occurs) - if ((hr = _PollGetInputFramesAvailable(stream, &available)) != S_OK) - { - LogHostError(hr); - return paUnanticipatedHostError; - } - - // Wait for more frames to become available - if (available == 0) - { - // Exclusive mode may require latency of 1 millisecond, thus we shall sleep - // around 500 microseconds (emulated) to collect packets in time - if (stream->in.shareMode != AUDCLNT_SHAREMODE_EXCLUSIVE) - { - UINT32 sleep_frames = (frames < stream->in.framesPerHostCallback ? frames : stream->in.framesPerHostCallback); - - sleep = GetFramesSleepTime(sleep_frames, stream->in.wavex.Format.nSamplesPerSec); - sleep /= 4; // wait only for 1/4 of the buffer - - // WASAPI input provides packets, thus expiring packet will result in bad audio - // limit waiting time to 2 seconds (will always work for smallest buffer in Shared) - if (sleep > 2) - sleep = 2; - - // Avoid busy waiting, schedule next 1 millesecond wait - if (sleep == 0) - sleep = ThreadIdleScheduler_NextSleep(&sched); - } - else - { - if ((sleep = ThreadIdleScheduler_NextSleep(&sched)) != 0) - { - Sleep(sleep); - sleep = 0; - } - } - - continue; - } - - // Get the available data in the shared buffer. - if ((hr = IAudioCaptureClient_GetBuffer(stream->captureClient, &wasapi_buffer, &available, &flags, NULL, NULL)) != S_OK) - { - // Buffer size is too small, waiting - if (hr != AUDCLNT_S_BUFFER_EMPTY) - { - LogHostError(hr); - goto end; - } - - continue; - } - - // Register available frames to processor - PaUtil_SetInputFrameCount(&stream->bufferProcessor, available); - - // Register host buffer pointer to processor - PaUtil_SetInterleavedInputChannels(&stream->bufferProcessor, 0, wasapi_buffer, stream->bufferProcessor.inputChannelCount); - - // Copy user data to host buffer (with conversion if applicable) - processed = PaUtil_CopyInput(&stream->bufferProcessor, (void **)&user_buffer, frames); - frames -= processed; - - // Save tail into buffer - if ((frames == 0) && (available > processed)) - { - UINT32 bytes_processed = processed * stream->in.wavex.Format.nBlockAlign; - UINT32 frames_to_save = available - processed; - - PaUtil_WriteRingBuffer(stream->in.tailBuffer, wasapi_buffer + bytes_processed, frames_to_save); - } - - // Release host buffer - if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->captureClient, available)) != S_OK) - { - LogHostError(hr); - goto end; - } - } - -end: - - // Notify blocking op has ended - SetEvent(stream->hBlockingOpStreamRD); - - return (hr != S_OK ? paUnanticipatedHostError : paNoError); -} - -// ------------------------------------------------------------------------------------------ -static PaError WriteStream( PaStream* s, const void *_buffer, unsigned long frames ) -{ - PaWasapiStream *stream = (PaWasapiStream*)s; - - //UINT32 frames; - const BYTE *user_buffer = (const BYTE *)_buffer; - BYTE *wasapi_buffer; - HRESULT hr = S_OK; - UINT32 i, available, sleep = 0; - unsigned long processed; - ThreadIdleScheduler sched; - - // validate - if (!stream->running) - return paStreamIsStopped; - if (stream->renderClient == NULL) - return paBadStreamPtr; - - // Notify blocking op has begun - ResetEvent(stream->hBlockingOpStreamWR); - - // Use thread scheduling for 500 microseconds (emulated) when wait time for frames is less than - // 1 milliseconds, emulation helps to normalize CPU consumption and avoids too busy waiting - ThreadIdleScheduler_Setup(&sched, 1, 500/* microseconds */); - - // Make a local copy of the user buffer pointer(s), this is necessary - // because PaUtil_CopyOutput() advances these pointers every time it is called - if (!stream->bufferProcessor.userOutputIsInterleaved) - { - user_buffer = (const BYTE *)alloca(sizeof(const BYTE *) * stream->bufferProcessor.outputChannelCount); - if (user_buffer == NULL) - return paInsufficientMemory; - - for (i = 0; i < stream->bufferProcessor.outputChannelCount; ++i) - ((const BYTE **)user_buffer)[i] = ((const BYTE **)_buffer)[i]; - } - - // Blocking (potentially, until 'frames' are consumed) loop - while (frames != 0) - { - // Check if blocking call must be interrupted - if (WaitForSingleObject(stream->hCloseRequest, sleep) != WAIT_TIMEOUT) - break; - - // Get frames available - if ((hr = _PollGetOutputFramesAvailable(stream, &available)) != S_OK) - { - LogHostError(hr); - goto end; - } - - // Wait for more frames to become available - if (available == 0) - { - UINT32 sleep_frames = (frames < stream->out.framesPerHostCallback ? frames : stream->out.framesPerHostCallback); - - sleep = GetFramesSleepTime(sleep_frames, stream->out.wavex.Format.nSamplesPerSec); - sleep /= 2; // wait only for half of the buffer - - // Avoid busy waiting, schedule next 1 millesecond wait - if (sleep == 0) - sleep = ThreadIdleScheduler_NextSleep(&sched); - - continue; - } - - // Keep in 'frames' range - if (available > frames) - available = frames; - - // Get pointer to host buffer - if ((hr = IAudioRenderClient_GetBuffer(stream->renderClient, available, &wasapi_buffer)) != S_OK) - { - // Buffer size is too big, waiting - if (hr == AUDCLNT_E_BUFFER_TOO_LARGE) - continue; - - LogHostError(hr); - goto end; - } - - // Keep waiting again (on Vista it was noticed that WASAPI could SOMETIMES return NULL pointer - // to buffer without returning AUDCLNT_E_BUFFER_TOO_LARGE instead) - if (wasapi_buffer == NULL) - continue; - - // Register available frames to processor - PaUtil_SetOutputFrameCount(&stream->bufferProcessor, available); - - // Register host buffer pointer to processor - PaUtil_SetInterleavedOutputChannels(&stream->bufferProcessor, 0, wasapi_buffer, stream->bufferProcessor.outputChannelCount); - - // Copy user data to host buffer (with conversion if applicable), this call will advance - // pointer 'user_buffer' to consumed portion of data - processed = PaUtil_CopyOutput(&stream->bufferProcessor, (const void **)&user_buffer, frames); - frames -= processed; - - // Release host buffer - if ((hr = IAudioRenderClient_ReleaseBuffer(stream->renderClient, available, 0)) != S_OK) - { - LogHostError(hr); - goto end; - } - } - -end: - - // Notify blocking op has ended - SetEvent(stream->hBlockingOpStreamWR); - - return (hr != S_OK ? paUnanticipatedHostError : paNoError); -} - -unsigned long PaUtil_GetOutputFrameCount( PaUtilBufferProcessor* bp ) -{ - return bp->hostOutputFrameCount[0]; -} - -// ------------------------------------------------------------------------------------------ -static signed long GetStreamReadAvailable( PaStream* s ) -{ - PaWasapiStream *stream = (PaWasapiStream*)s; - - HRESULT hr; - UINT32 available = 0; - - // validate - if (!stream->running) - return paStreamIsStopped; - if (stream->captureClient == NULL) - return paBadStreamPtr; - - // available in hardware buffer - if ((hr = _PollGetInputFramesAvailable(stream, &available)) != S_OK) - { - LogHostError(hr); - return paUnanticipatedHostError; - } - - // available in software tail buffer - available += PaUtil_GetRingBufferReadAvailable(stream->in.tailBuffer); - - return available; -} - -// ------------------------------------------------------------------------------------------ -static signed long GetStreamWriteAvailable( PaStream* s ) -{ - PaWasapiStream *stream = (PaWasapiStream*)s; - HRESULT hr; - UINT32 available = 0; - - // validate - if (!stream->running) - return paStreamIsStopped; - if (stream->renderClient == NULL) - return paBadStreamPtr; - - if ((hr = _PollGetOutputFramesAvailable(stream, &available)) != S_OK) - { - LogHostError(hr); - return paUnanticipatedHostError; - } - - return (signed long)available; -} - - -// ------------------------------------------------------------------------------------------ -static void WaspiHostProcessingLoop( void *inputBuffer, long inputFrames, - void *outputBuffer, long outputFrames, - void *userData ) -{ - PaWasapiStream *stream = (PaWasapiStream*)userData; - PaStreamCallbackTimeInfo timeInfo = {0,0,0}; - PaStreamCallbackFlags flags = 0; - int callbackResult; - unsigned long framesProcessed; - HRESULT hr; - UINT32 pending; - - PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer ); - - /* - Pa_GetStreamTime: - - generate timing information - - handle buffer slips - */ - timeInfo.currentTime = PaUtil_GetTime(); - // Query input latency - if (stream->in.clientProc != NULL) - { - PaTime pending_time; - if ((hr = IAudioClient_GetCurrentPadding(stream->in.clientProc, &pending)) == S_OK) - pending_time = (PaTime)pending / (PaTime)stream->in.wavex.Format.nSamplesPerSec; - else - pending_time = (PaTime)stream->in.latencySeconds; - - timeInfo.inputBufferAdcTime = timeInfo.currentTime + pending_time; - } - // Query output current latency - if (stream->out.clientProc != NULL) - { - PaTime pending_time; - if ((hr = IAudioClient_GetCurrentPadding(stream->out.clientProc, &pending)) == S_OK) - pending_time = (PaTime)pending / (PaTime)stream->out.wavex.Format.nSamplesPerSec; - else - pending_time = (PaTime)stream->out.latencySeconds; - - timeInfo.outputBufferDacTime = timeInfo.currentTime + pending_time; - } - - /* - If you need to byte swap or shift inputBuffer to convert it into a - portaudio format, do it here. - */ - - PaUtil_BeginBufferProcessing( &stream->bufferProcessor, &timeInfo, flags ); - - /* - depending on whether the host buffers are interleaved, non-interleaved - or a mixture, you will want to call PaUtil_SetInterleaved*Channels(), - PaUtil_SetNonInterleaved*Channel() or PaUtil_Set*Channel() here. - */ - - if (stream->bufferProcessor.inputChannelCount > 0) - { - PaUtil_SetInputFrameCount( &stream->bufferProcessor, inputFrames ); - PaUtil_SetInterleavedInputChannels( &stream->bufferProcessor, - 0, /* first channel of inputBuffer is channel 0 */ - inputBuffer, - 0 ); /* 0 - use inputChannelCount passed to init buffer processor */ - } - - if (stream->bufferProcessor.outputChannelCount > 0) - { - PaUtil_SetOutputFrameCount( &stream->bufferProcessor, outputFrames); - PaUtil_SetInterleavedOutputChannels( &stream->bufferProcessor, - 0, /* first channel of outputBuffer is channel 0 */ - outputBuffer, - 0 ); /* 0 - use outputChannelCount passed to init buffer processor */ - } - - /* you must pass a valid value of callback result to PaUtil_EndBufferProcessing() - in general you would pass paContinue for normal operation, and - paComplete to drain the buffer processor's internal output buffer. - You can check whether the buffer processor's output buffer is empty - using PaUtil_IsBufferProcessorOuputEmpty( bufferProcessor ) - */ - callbackResult = paContinue; - framesProcessed = PaUtil_EndBufferProcessing( &stream->bufferProcessor, &callbackResult ); - - /* - If you need to byte swap or shift outputBuffer to convert it to - host format, do it here. - */ - - PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed ); - - if (callbackResult == paContinue) - { - /* nothing special to do */ - } - else - if (callbackResult == paAbort) - { - // stop stream - SetEvent(stream->hCloseRequest); - } - else - { - // stop stream - SetEvent(stream->hCloseRequest); - } -} - -// ------------------------------------------------------------------------------------------ -#ifndef PA_WINRT -static PaError MMCSS_activate(PaWasapiThreadPriority nPriorityClass, HANDLE *ret) -{ - static const char *mmcs_name[] = - { - NULL, - "Audio", - "Capture", - "Distribution", - "Games", - "Playback", - "Pro Audio", - "Window Manager" - }; - - DWORD task_idx = 0; - HANDLE hTask; - - if ((UINT32)nPriorityClass >= STATIC_ARRAY_SIZE(mmcs_name)) - return paUnanticipatedHostError; - - if ((hTask = pAvSetMmThreadCharacteristics(mmcs_name[nPriorityClass], &task_idx)) == NULL) - { - PRINT(("WASAPI: AvSetMmThreadCharacteristics failed: error[%d]\n", GetLastError())); - return paUnanticipatedHostError; - } - - /*BOOL priority_ok = pAvSetMmThreadPriority(hTask, AVRT_PRIORITY_NORMAL); - if (priority_ok == FALSE) - { - PRINT(("WASAPI: AvSetMmThreadPriority failed!\n")); - }*/ - - // debug - { - int cur_priority = GetThreadPriority(GetCurrentThread()); - DWORD cur_priority_class = GetPriorityClass(GetCurrentProcess()); - PRINT(("WASAPI: thread[ priority-0x%X class-0x%X ]\n", cur_priority, cur_priority_class)); - } - - (*ret) = hTask; - return paNoError; -} -#endif - -// ------------------------------------------------------------------------------------------ -#ifndef PA_WINRT -static void MMCSS_deactivate(HANDLE hTask) -{ - if (pAvRevertMmThreadCharacteristics(hTask) == FALSE) - { - PRINT(("WASAPI: AvRevertMmThreadCharacteristics failed!\n")); - } -} -#endif - -// ------------------------------------------------------------------------------------------ -PaError PaWasapi_ThreadPriorityBoost(void **pTask, PaWasapiThreadPriority priorityClass) -{ - HANDLE task; - PaError ret; - - if (pTask == NULL) - return paUnanticipatedHostError; - -#ifndef PA_WINRT - if ((ret = MMCSS_activate(priorityClass, &task)) != paNoError) - return ret; -#else - switch (priorityClass) - { - case eThreadPriorityAudio: - case eThreadPriorityProAudio: { - - // Save previous thread priority - intptr_t priority_prev = GetThreadPriority(GetCurrentThread()); - - // Try set new thread priority - if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST) == FALSE) - return paUnanticipatedHostError; - - // Memorize prev priority (pretend to be non NULL pointer by adding 0x80000000 mask) - task = (HANDLE)(priority_prev | 0x80000000); - - ret = paNoError; - - break; } - - default: - return paUnanticipatedHostError; - } -#endif - - (*pTask) = task; - return ret; -} - -// ------------------------------------------------------------------------------------------ -PaError PaWasapi_ThreadPriorityRevert(void *pTask) -{ - if (pTask == NULL) - return paUnanticipatedHostError; - -#ifndef PA_WINRT - MMCSS_deactivate((HANDLE)pTask); -#else - // Revert previous priority by removing 0x80000000 mask - if (SetThreadPriority(GetCurrentThread(), (int)((intptr_t)pTask & ~0x80000000)) == FALSE) - return paUnanticipatedHostError; -#endif - - return paNoError; -} - -// ------------------------------------------------------------------------------------------ -// Described at: -// http://msdn.microsoft.com/en-us/library/dd371387(v=VS.85).aspx - -PaError PaWasapi_GetJackCount(PaDeviceIndex device, int *pJackCount) -{ -#ifndef PA_WINRT - PaError ret; - HRESULT hr = S_OK; - PaWasapiDeviceInfo *deviceInfo; - IDeviceTopology *pDeviceTopology = NULL; - IConnector *pConnFrom = NULL; - IConnector *pConnTo = NULL; - IPart *pPart = NULL; - IKsJackDescription *pJackDesc = NULL; - UINT jackCount = 0; - - if (pJackCount == NULL) - return paUnanticipatedHostError; - - if ((ret = _GetWasapiDeviceInfoByDeviceIndex(&deviceInfo, device)) != paNoError) - return ret; - - // Get the endpoint device's IDeviceTopology interface - hr = IMMDevice_Activate(deviceInfo->device, &pa_IID_IDeviceTopology, - CLSCTX_INPROC_SERVER, NULL, (void**)&pDeviceTopology); - IF_FAILED_JUMP(hr, error); - - // The device topology for an endpoint device always contains just one connector (connector number 0) - hr = IDeviceTopology_GetConnector(pDeviceTopology, 0, &pConnFrom); - IF_FAILED_JUMP(hr, error); - - // Step across the connection to the jack on the adapter - hr = IConnector_GetConnectedTo(pConnFrom, &pConnTo); - if (HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr) - { - // The adapter device is not currently active - hr = E_NOINTERFACE; - } - IF_FAILED_JUMP(hr, error); - - // Get the connector's IPart interface - hr = IConnector_QueryInterface(pConnTo, &pa_IID_IPart, (void**)&pPart); - IF_FAILED_JUMP(hr, error); - - // Activate the connector's IKsJackDescription interface - hr = IPart_Activate(pPart, CLSCTX_INPROC_SERVER, &pa_IID_IKsJackDescription, (void**)&pJackDesc); - IF_FAILED_JUMP(hr, error); - - // Return jack count for this device - hr = IKsJackDescription_GetJackCount(pJackDesc, &jackCount); - IF_FAILED_JUMP(hr, error); - - // Set. - (*pJackCount) = jackCount; - - // Ok. - ret = paNoError; - -error: - - SAFE_RELEASE(pDeviceTopology); - SAFE_RELEASE(pConnFrom); - SAFE_RELEASE(pConnTo); - SAFE_RELEASE(pPart); - SAFE_RELEASE(pJackDesc); - - LogHostError(hr); - return paNoError; -#else - (void)device; - (void)pJackCount; - return paUnanticipatedHostError; -#endif -} - -// ------------------------------------------------------------------------------------------ -#ifndef PA_WINRT -static PaWasapiJackConnectionType _ConvertJackConnectionTypeWASAPIToPA(int connType) -{ - switch (connType) - { - case eConnTypeUnknown: return eJackConnTypeUnknown; -#ifdef _KS_ - case eConnType3Point5mm: return eJackConnType3Point5mm; -#else - case eConnTypeEighth: return eJackConnType3Point5mm; -#endif - case eConnTypeQuarter: return eJackConnTypeQuarter; - case eConnTypeAtapiInternal: return eJackConnTypeAtapiInternal; - case eConnTypeRCA: return eJackConnTypeRCA; - case eConnTypeOptical: return eJackConnTypeOptical; - case eConnTypeOtherDigital: return eJackConnTypeOtherDigital; - case eConnTypeOtherAnalog: return eJackConnTypeOtherAnalog; - case eConnTypeMultichannelAnalogDIN: return eJackConnTypeMultichannelAnalogDIN; - case eConnTypeXlrProfessional: return eJackConnTypeXlrProfessional; - case eConnTypeRJ11Modem: return eJackConnTypeRJ11Modem; - case eConnTypeCombination: return eJackConnTypeCombination; - } - return eJackConnTypeUnknown; -} -#endif - -// ------------------------------------------------------------------------------------------ -#ifndef PA_WINRT -static PaWasapiJackGeoLocation _ConvertJackGeoLocationWASAPIToPA(int geoLoc) -{ - switch (geoLoc) - { - case eGeoLocRear: return eJackGeoLocRear; - case eGeoLocFront: return eJackGeoLocFront; - case eGeoLocLeft: return eJackGeoLocLeft; - case eGeoLocRight: return eJackGeoLocRight; - case eGeoLocTop: return eJackGeoLocTop; - case eGeoLocBottom: return eJackGeoLocBottom; -#ifdef _KS_ - case eGeoLocRearPanel: return eJackGeoLocRearPanel; -#else - case eGeoLocRearOPanel: return eJackGeoLocRearPanel; -#endif - case eGeoLocRiser: return eJackGeoLocRiser; - case eGeoLocInsideMobileLid: return eJackGeoLocInsideMobileLid; - case eGeoLocDrivebay: return eJackGeoLocDrivebay; - case eGeoLocHDMI: return eJackGeoLocHDMI; - case eGeoLocOutsideMobileLid: return eJackGeoLocOutsideMobileLid; - case eGeoLocATAPI: return eJackGeoLocATAPI; - } - return eJackGeoLocUnk; -} -#endif - -// ------------------------------------------------------------------------------------------ -#ifndef PA_WINRT -static PaWasapiJackGenLocation _ConvertJackGenLocationWASAPIToPA(int genLoc) -{ - switch (genLoc) - { - case eGenLocPrimaryBox: return eJackGenLocPrimaryBox; - case eGenLocInternal: return eJackGenLocInternal; -#ifdef _KS_ - case eGenLocSeparate: return eJackGenLocSeparate; -#else - case eGenLocSeperate: return eJackGenLocSeparate; -#endif - case eGenLocOther: return eJackGenLocOther; - } - return eJackGenLocPrimaryBox; -} -#endif - -// ------------------------------------------------------------------------------------------ -#ifndef PA_WINRT -static PaWasapiJackPortConnection _ConvertJackPortConnectionWASAPIToPA(int portConn) -{ - switch (portConn) - { - case ePortConnJack: return eJackPortConnJack; - case ePortConnIntegratedDevice: return eJackPortConnIntegratedDevice; - case ePortConnBothIntegratedAndJack: return eJackPortConnBothIntegratedAndJack; - case ePortConnUnknown: return eJackPortConnUnknown; - } - return eJackPortConnJack; -} -#endif - -// ------------------------------------------------------------------------------------------ -// Described at: -// http://msdn.microsoft.com/en-us/library/dd371387(v=VS.85).aspx - -PaError PaWasapi_GetJackDescription(PaDeviceIndex device, int jackIndex, PaWasapiJackDescription *pJackDescription) -{ -#ifndef PA_WINRT - PaError ret; - HRESULT hr = S_OK; - PaWasapiDeviceInfo *deviceInfo; - IDeviceTopology *pDeviceTopology = NULL; - IConnector *pConnFrom = NULL; - IConnector *pConnTo = NULL; - IPart *pPart = NULL; - IKsJackDescription *pJackDesc = NULL; - KSJACK_DESCRIPTION jack = { 0 }; - - if ((ret = _GetWasapiDeviceInfoByDeviceIndex(&deviceInfo, device)) != paNoError) - return ret; - - // Get the endpoint device's IDeviceTopology interface - hr = IMMDevice_Activate(deviceInfo->device, &pa_IID_IDeviceTopology, - CLSCTX_INPROC_SERVER, NULL, (void**)&pDeviceTopology); - IF_FAILED_JUMP(hr, error); - - // The device topology for an endpoint device always contains just one connector (connector number 0) - hr = IDeviceTopology_GetConnector(pDeviceTopology, 0, &pConnFrom); - IF_FAILED_JUMP(hr, error); - - // Step across the connection to the jack on the adapter - hr = IConnector_GetConnectedTo(pConnFrom, &pConnTo); - if (HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr) - { - // The adapter device is not currently active - hr = E_NOINTERFACE; - } - IF_FAILED_JUMP(hr, error); - - // Get the connector's IPart interface - hr = IConnector_QueryInterface(pConnTo, &pa_IID_IPart, (void**)&pPart); - IF_FAILED_JUMP(hr, error); - - // Activate the connector's IKsJackDescription interface - hr = IPart_Activate(pPart, CLSCTX_INPROC_SERVER, &pa_IID_IKsJackDescription, (void**)&pJackDesc); - IF_FAILED_JUMP(hr, error); - - // Test to return jack description struct for index 0 - hr = IKsJackDescription_GetJackDescription(pJackDesc, jackIndex, &jack); - IF_FAILED_JUMP(hr, error); - - // Convert WASAPI values to PA format - pJackDescription->channelMapping = jack.ChannelMapping; - pJackDescription->color = jack.Color; - pJackDescription->connectionType = _ConvertJackConnectionTypeWASAPIToPA(jack.ConnectionType); - pJackDescription->genLocation = _ConvertJackGenLocationWASAPIToPA(jack.GenLocation); - pJackDescription->geoLocation = _ConvertJackGeoLocationWASAPIToPA(jack.GeoLocation); - pJackDescription->isConnected = jack.IsConnected; - pJackDescription->portConnection = _ConvertJackPortConnectionWASAPIToPA(jack.PortConnection); - - // Ok - ret = paNoError; - -error: - - SAFE_RELEASE(pDeviceTopology); - SAFE_RELEASE(pConnFrom); - SAFE_RELEASE(pConnTo); - SAFE_RELEASE(pPart); - SAFE_RELEASE(pJackDesc); - - LogHostError(hr); - return ret; - -#else - (void)device; - (void)jackIndex; - (void)pJackDescription; - return paUnanticipatedHostError; -#endif -} - -// ------------------------------------------------------------------------------------------ -PaError PaWasapi_GetAudioClient(PaStream *pStream, void **pAudioClient, int bOutput) -{ - PaWasapiStream *stream = (PaWasapiStream *)pStream; - if (stream == NULL) - return paBadStreamPtr; - - if (pAudioClient == NULL) - return paUnanticipatedHostError; - - (*pAudioClient) = (bOutput == TRUE ? stream->out.clientParent : stream->in.clientParent); - - return paNoError; -} - -// ------------------------------------------------------------------------------------------ -#ifdef PA_WINRT -static void CopyNameOrIdString(WCHAR *dst, const UINT32 dstMaxCount, const WCHAR *src) -{ - UINT32 i; - - for (i = 0; i < dstMaxCount; ++i) - dst[i] = 0; - - if (src != NULL) - { - for (i = 0; (src[i] != 0) && (i < dstMaxCount); ++i) - dst[i] = src[i]; - } -} -#endif - -// ------------------------------------------------------------------------------------------ -PaError PaWasapiWinrt_SetDefaultDeviceId( const unsigned short *pId, int bOutput ) -{ -#ifdef PA_WINRT - INT32 i; - PaWasapiWinrtDeviceListRole *role = (bOutput ? &g_DeviceListInfo.render : &g_DeviceListInfo.capture); - - assert(STATIC_ARRAY_SIZE(role->defaultId) == PA_WASAPI_DEVICE_ID_LEN); - - // Validate Id length - if (pId != NULL) - { - for (i = 0; pId[i] != 0; ++i) - { - if (i >= PA_WASAPI_DEVICE_ID_LEN) - return paBufferTooBig; - } - } - - // Set Id (or reset to all 0 if NULL is provided) - CopyNameOrIdString(role->defaultId, STATIC_ARRAY_SIZE(role->defaultId), pId); - - return paNoError; -#else - return paIncompatibleStreamHostApi; -#endif -} - -// ------------------------------------------------------------------------------------------ -PaError PaWasapiWinrt_PopulateDeviceList( const unsigned short **pId, const unsigned short **pName, - const PaWasapiDeviceRole *pRole, unsigned int count, int bOutput ) -{ -#ifdef PA_WINRT - UINT32 i, j; - PaWasapiWinrtDeviceListRole *role = (bOutput ? &g_DeviceListInfo.render : &g_DeviceListInfo.capture); - - memset(&role->devices, 0, sizeof(role->devices)); - role->deviceCount = 0; - - if (count == 0) - return paNoError; - else - if (count > PA_WASAPI_DEVICE_MAX_COUNT) - return paBufferTooBig; - - // pName or pRole are optional - if (pId == NULL) - return paInsufficientMemory; - - // Validate Id and Name lengths - for (i = 0; i < count; ++i) - { - const unsigned short *id = pId[i]; - const unsigned short *name = pName[i]; - - for (j = 0; id[j] != 0; ++j) - { - if (j >= PA_WASAPI_DEVICE_ID_LEN) - return paBufferTooBig; - } - - for (j = 0; name[j] != 0; ++j) - { - if (j >= PA_WASAPI_DEVICE_NAME_LEN) - return paBufferTooBig; - } - } - - // Set Id and Name (or reset to all 0 if NULL is provided) - for (i = 0; i < count; ++i) - { - CopyNameOrIdString(role->devices[i].id, STATIC_ARRAY_SIZE(role->devices[i].id), pId[i]); - CopyNameOrIdString(role->devices[i].name, STATIC_ARRAY_SIZE(role->devices[i].name), pName[i]); - role->devices[i].formFactor = (pRole != NULL ? (EndpointFormFactor)pRole[i] : UnknownFormFactor); - - // Count device if it has at least the Id - role->deviceCount += (role->devices[i].id[0] != 0); - } - - return paNoError; -#else - return paIncompatibleStreamHostApi; -#endif -} - -// ------------------------------------------------------------------------------------------ -PaError PaWasapi_SetStreamStateHandler( PaStream *pStream, PaWasapiStreamStateCallback fnStateHandler, void *pUserData ) -{ - PaWasapiStream *stream = (PaWasapiStream *)pStream; - if (stream == NULL) - return paBadStreamPtr; - - stream->fnStateHandler = fnStateHandler; - stream->pStateHandlerUserData = pUserData; - - return paNoError; -} - -// ------------------------------------------------------------------------------------------ -HRESULT _PollGetOutputFramesAvailable(PaWasapiStream *stream, UINT32 *available) -{ - HRESULT hr; - UINT32 frames = stream->out.framesPerHostCallback, - padding = 0; - - (*available) = 0; - - // get read position - if ((hr = IAudioClient_GetCurrentPadding(stream->out.clientProc, &padding)) != S_OK) - return LogHostError(hr); - - // get available - frames -= padding; - - // set - (*available) = frames; - return hr; -} - -// ------------------------------------------------------------------------------------------ -HRESULT _PollGetInputFramesAvailable(PaWasapiStream *stream, UINT32 *available) -{ - HRESULT hr; - - (*available) = 0; - - // GetCurrentPadding() has opposite meaning to Output stream - if ((hr = IAudioClient_GetCurrentPadding(stream->in.clientProc, available)) != S_OK) - return LogHostError(hr); - - return hr; -} - -// ------------------------------------------------------------------------------------------ -static HRESULT ProcessOutputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor *processor, UINT32 frames) -{ - HRESULT hr; - BYTE *data = NULL; - - // Get buffer - if ((hr = IAudioRenderClient_GetBuffer(stream->renderClient, frames, &data)) != S_OK) - { - // Both modes, Shared and Exclusive, can fail with AUDCLNT_E_BUFFER_TOO_LARGE error - #if 0 - if (stream->out.shareMode == AUDCLNT_SHAREMODE_SHARED) - { - // Using GetCurrentPadding to overcome AUDCLNT_E_BUFFER_TOO_LARGE in - // shared mode results in no sound in Event-driven mode (MSDN does not - // document this, or is it WASAPI bug?), thus we better - // try to acquire buffer next time when GetBuffer allows to do so. - #if 0 - // Get Read position - UINT32 padding = 0; - hr = IAudioClient_GetCurrentPadding(stream->out.clientProc, &padding); - if (hr != S_OK) - return LogHostError(hr); - - // Get frames to write - frames -= padding; - if (frames == 0) - return S_OK; - - if ((hr = IAudioRenderClient_GetBuffer(stream->renderClient, frames, &data)) != S_OK) - return LogHostError(hr); - #else - if (hr == AUDCLNT_E_BUFFER_TOO_LARGE) - return S_OK; // be silent in shared mode, try again next time - #endif - } - else - return LogHostError(hr); - #else - if (hr == AUDCLNT_E_BUFFER_TOO_LARGE) - return S_OK; // try again next time - - return LogHostError(hr); - #endif - } - - // Process data - if (stream->out.monoMixer != NULL) - { - // expand buffer - UINT32 mono_frames_size = frames * (stream->out.wavex.Format.wBitsPerSample / 8); - if (mono_frames_size > stream->out.monoBufferSize) - { - stream->out.monoBuffer = PaWasapi_ReallocateMemory(stream->out.monoBuffer, (stream->out.monoBufferSize = mono_frames_size)); - if (stream->out.monoBuffer == NULL) - { - hr = E_OUTOFMEMORY; - LogHostError(hr); - return hr; - } - } - - // process - processor[S_OUTPUT].processor(NULL, 0, (BYTE *)stream->out.monoBuffer, frames, processor[S_OUTPUT].userData); - - // mix 1 to 2 channels - stream->out.monoMixer(data, stream->out.monoBuffer, frames); - } - else - { - processor[S_OUTPUT].processor(NULL, 0, data, frames, processor[S_OUTPUT].userData); - } - - // Release buffer - if ((hr = IAudioRenderClient_ReleaseBuffer(stream->renderClient, frames, 0)) != S_OK) - LogHostError(hr); - - return hr; -} - -// ------------------------------------------------------------------------------------------ -static HRESULT ProcessInputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor *processor) -{ - HRESULT hr = S_OK; - UINT32 frames; - BYTE *data = NULL; - DWORD flags = 0; - - for (;;) - { - // Check if blocking call must be interrupted - if (WaitForSingleObject(stream->hCloseRequest, 0) != WAIT_TIMEOUT) - break; - - // Find out if any frames available - frames = 0; - if ((hr = _PollGetInputFramesAvailable(stream, &frames)) != S_OK) - return hr; - - // Empty/consumed buffer - if (frames == 0) - break; - - // Get the available data in the shared buffer. - if ((hr = IAudioCaptureClient_GetBuffer(stream->captureClient, &data, &frames, &flags, NULL, NULL)) != S_OK) - { - if (hr == AUDCLNT_S_BUFFER_EMPTY) - { - hr = S_OK; - break; // Empty/consumed buffer - } - - return LogHostError(hr); - break; - } - - // Detect silence - // if (flags & AUDCLNT_BUFFERFLAGS_SILENT) - // data = NULL; - - // Process data - if (stream->in.monoMixer != NULL) - { - // expand buffer - UINT32 mono_frames_size = frames * (stream->in.wavex.Format.wBitsPerSample / 8); - if (mono_frames_size > stream->in.monoBufferSize) - { - stream->in.monoBuffer = PaWasapi_ReallocateMemory(stream->in.monoBuffer, (stream->in.monoBufferSize = mono_frames_size)); - if (stream->in.monoBuffer == NULL) - { - hr = E_OUTOFMEMORY; - LogHostError(hr); - return hr; - } - } - - // mix 1 to 2 channels - stream->in.monoMixer(stream->in.monoBuffer, data, frames); - - // process - processor[S_INPUT].processor((BYTE *)stream->in.monoBuffer, frames, NULL, 0, processor[S_INPUT].userData); - } - else - { - processor[S_INPUT].processor(data, frames, NULL, 0, processor[S_INPUT].userData); - } - - // Release buffer - if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->captureClient, frames)) != S_OK) - return LogHostError(hr); - - //break; - } - - return hr; -} - -// ------------------------------------------------------------------------------------------ -void _StreamOnStop(PaWasapiStream *stream) -{ - // Stop INPUT/OUTPUT clients - if (!stream->bBlocking) - { - if (stream->in.clientProc != NULL) - IAudioClient_Stop(stream->in.clientProc); - if (stream->out.clientProc != NULL) - IAudioClient_Stop(stream->out.clientProc); - } - else - { - if (stream->in.clientParent != NULL) - IAudioClient_Stop(stream->in.clientParent); - if (stream->out.clientParent != NULL) - IAudioClient_Stop(stream->out.clientParent); - } - - // Restore thread priority - if (stream->hAvTask != NULL) - { - PaWasapi_ThreadPriorityRevert(stream->hAvTask); - stream->hAvTask = NULL; - } - - // Notify - if (stream->streamRepresentation.streamFinishedCallback != NULL) - stream->streamRepresentation.streamFinishedCallback(stream->streamRepresentation.userData); -} - -// ------------------------------------------------------------------------------------------ -static BOOL PrepareComPointers(PaWasapiStream *stream, BOOL *threadComInitialized) -{ - HRESULT hr; - - /* - If COM is already initialized CoInitialize will either return - FALSE, or RPC_E_CHANGED_MODE if it was initialized in a different - threading mode. In either case we shouldn't consider it an error - but we need to be careful to not call CoUninitialize() if - RPC_E_CHANGED_MODE was returned. - */ - hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); - if (FAILED(hr) && (hr != RPC_E_CHANGED_MODE)) - { - PRINT(("WASAPI: failed ProcThreadEvent CoInitialize")); - return FALSE; - } - if (hr != RPC_E_CHANGED_MODE) - *threadComInitialized = TRUE; - - // Unmarshal stream pointers for safe COM operation - hr = UnmarshalStreamComPointers(stream); - if (hr != S_OK) - { - PRINT(("WASAPI: Error unmarshaling stream COM pointers. HRESULT: %i\n", hr)); - CoUninitialize(); - return FALSE; - } - - return TRUE; -} - -// ------------------------------------------------------------------------------------------ -static void FinishComPointers(PaWasapiStream *stream, BOOL threadComInitialized) -{ - // Release unmarshaled COM pointers - ReleaseUnmarshaledComPointers(stream); - - // Cleanup COM for this thread - if (threadComInitialized == TRUE) - CoUninitialize(); -} - -// ------------------------------------------------------------------------------------------ -PA_THREAD_FUNC ProcThreadEvent(void *param) -{ - PaWasapiHostProcessor processor[S_COUNT]; - HRESULT hr = S_OK; - DWORD dwResult; - PaWasapiStream *stream = (PaWasapiStream *)param; - PaWasapiHostProcessor defaultProcessor; - BOOL setEvent[S_COUNT] = { FALSE, FALSE }; - BOOL waitAllEvents = FALSE; - BOOL threadComInitialized = FALSE; - SystemTimer timer; - - // Notify: state - NotifyStateChanged(stream, paWasapiStreamStateThreadPrepare, ERROR_SUCCESS); - - // Prepare COM pointers - if (!PrepareComPointers(stream, &threadComInitialized)) - return (UINT32)paUnanticipatedHostError; - - // Request fine (1 ms) granularity of the system timer functions for precise operation of waitable timers - SystemTimer_SetGranularity(&timer, 1); - - // Waiting on all events in case of Full-Duplex/Exclusive mode. - if ((stream->in.clientProc != NULL) && (stream->out.clientProc != NULL)) - { - waitAllEvents = (stream->in.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE) && - (stream->out.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE); - } - - // Setup data processors - defaultProcessor.processor = WaspiHostProcessingLoop; - defaultProcessor.userData = stream; - processor[S_INPUT] = (stream->hostProcessOverrideInput.processor != NULL ? stream->hostProcessOverrideInput : defaultProcessor); - processor[S_OUTPUT] = (stream->hostProcessOverrideOutput.processor != NULL ? stream->hostProcessOverrideOutput : defaultProcessor); - - // Boost thread priority - PaWasapi_ThreadPriorityBoost((void **)&stream->hAvTask, stream->nThreadPriority); - - // Create events - if (stream->event[S_OUTPUT] == NULL) - { - stream->event[S_OUTPUT] = CreateEvent(NULL, FALSE, FALSE, NULL); - setEvent[S_OUTPUT] = TRUE; - } - if (stream->event[S_INPUT] == NULL) - { - stream->event[S_INPUT] = CreateEvent(NULL, FALSE, FALSE, NULL); - setEvent[S_INPUT] = TRUE; - } - if ((stream->event[S_OUTPUT] == NULL) || (stream->event[S_INPUT] == NULL)) - { - PRINT(("WASAPI Thread: failed creating Input/Output event handle\n")); - goto thread_error; - } - - // Signal: stream running - stream->running = TRUE; - - // Notify: thread started - SetEvent(stream->hThreadStart); - - // Initialize event & start INPUT stream - if (stream->in.clientProc) - { - // Create & set handle - if (setEvent[S_INPUT]) - { - if ((hr = IAudioClient_SetEventHandle(stream->in.clientProc, stream->event[S_INPUT])) != S_OK) - { - LogHostError(hr); - goto thread_error; - } - } - - // Start - if ((hr = IAudioClient_Start(stream->in.clientProc)) != S_OK) - { - LogHostError(hr); - goto thread_error; - } - } - - // Initialize event & start OUTPUT stream - if (stream->out.clientProc) - { - // Create & set handle - if (setEvent[S_OUTPUT]) - { - if ((hr = IAudioClient_SetEventHandle(stream->out.clientProc, stream->event[S_OUTPUT])) != S_OK) - { - LogHostError(hr); - goto thread_error; - } - } - - // Preload buffer before start - if ((hr = ProcessOutputBuffer(stream, processor, stream->out.framesPerBuffer)) != S_OK) - { - LogHostError(hr); - goto thread_error; - } - - // Start - if ((hr = IAudioClient_Start(stream->out.clientProc)) != S_OK) - { - LogHostError(hr); - goto thread_error; - } - - } - - // Notify: state - NotifyStateChanged(stream, paWasapiStreamStateThreadStart, ERROR_SUCCESS); - - // Processing Loop - for (;;) - { - // 10 sec timeout (on timeout stream will auto-stop when processed by WAIT_TIMEOUT case) - dwResult = WaitForMultipleObjects(S_COUNT, stream->event, waitAllEvents, 10*1000); - - // Check for close event (after wait for buffers to avoid any calls to user - // callback when hCloseRequest was set) - if (WaitForSingleObject(stream->hCloseRequest, 0) != WAIT_TIMEOUT) - break; - - // Process S_INPUT/S_OUTPUT - switch (dwResult) - { - case WAIT_TIMEOUT: { - PRINT(("WASAPI Thread: WAIT_TIMEOUT - probably bad audio driver or Vista x64 bug: use paWinWasapiPolling instead\n")); - goto thread_end; - break; } - - // Input stream - case WAIT_OBJECT_0 + S_INPUT: { - - if (stream->captureClient == NULL) - break; - - if ((hr = ProcessInputBuffer(stream, processor)) != S_OK) - { - LogHostError(hr); - goto thread_error; - } - - break; } - - // Output stream - case WAIT_OBJECT_0 + S_OUTPUT: { - - if (stream->renderClient == NULL) - break; - - if ((hr = ProcessOutputBuffer(stream, processor, stream->out.framesPerBuffer)) != S_OK) - { - LogHostError(hr); - goto thread_error; - } - - break; } - } - } - -thread_end: - - // Process stop - _StreamOnStop(stream); - - // Release unmarshaled COM pointers - FinishComPointers(stream, threadComInitialized); - - // Restore system timer granularity - SystemTimer_RestoreGranularity(&timer); - - // Notify: not running - stream->running = FALSE; - - // Notify: thread exited - SetEvent(stream->hThreadExit); - - // Notify: state - NotifyStateChanged(stream, paWasapiStreamStateThreadStop, hr); - - return 0; - -thread_error: - - // Prevent deadlocking in Pa_StreamStart - SetEvent(stream->hThreadStart); - - // Exit - goto thread_end; -} - -// ------------------------------------------------------------------------------------------ -static UINT32 GetSleepTime(PaWasapiStream *stream, UINT32 sleepTimeIn, UINT32 sleepTimeOut, UINT32 userFramesOut) -{ - UINT32 sleepTime; - - // According to the issue [https://github.com/PortAudio/portaudio/issues/303] glitches may occur when user frames - // equal to 1/2 of the host buffer frames, therefore the empirical workaround for this problem is to lower - // the sleep time by 2 - if (userFramesOut != 0) - { - UINT32 chunks = stream->out.framesPerHostCallback / userFramesOut; - if (chunks <= 2) - { - sleepTimeOut /= 2; - PRINT(("WASAPI: underrun workaround, sleep [%d] ms - 1/2 of the user buffer[%d] | host buffer[%d]\n", sleepTimeOut, userFramesOut, stream->out.framesPerHostCallback)); - } - } - - // Choose the smallest - if ((sleepTimeIn != 0) && (sleepTimeOut != 0)) - sleepTime = min(sleepTimeIn, sleepTimeOut); - else - sleepTime = (sleepTimeIn ? sleepTimeIn : sleepTimeOut); - - return sleepTime; -} - -// ------------------------------------------------------------------------------------------ -static UINT32 ConfigureLoopSleepTimeAndScheduler(PaWasapiStream *stream, ThreadIdleScheduler *scheduler) -{ - UINT32 sleepTime, sleepTimeIn, sleepTimeOut; - UINT32 userFramesIn = stream->in.framesPerHostCallback / WASAPI_PACKETS_PER_INPUT_BUFFER; - UINT32 userFramesOut = stream->out.framesPerBuffer; - - // Adjust polling time for non-paUtilFixedHostBufferSize, input stream is not adjustable as it is being - // polled according to its packet length - if (stream->bufferMode != paUtilFixedHostBufferSize) - { - userFramesOut = (stream->bufferProcessor.framesPerUserBuffer ? stream->bufferProcessor.framesPerUserBuffer : - stream->out.params.frames_per_buffer); - } - - // Calculate timeout for the next polling attempt - sleepTimeIn = GetFramesSleepTime(userFramesIn, stream->in.wavex.Format.nSamplesPerSec); - sleepTimeOut = GetFramesSleepTime(userFramesOut, stream->out.wavex.Format.nSamplesPerSec); - - // WASAPI input packets tend to expire very easily, let's limit sleep time to 2 milliseconds - // for all cases. Please propose better solution if any - if (sleepTimeIn > 2) - sleepTimeIn = 2; - - sleepTime = GetSleepTime(stream, sleepTimeIn, sleepTimeOut, userFramesOut); - - // Make sure not 0, othervise use ThreadIdleScheduler to bounce between [0, 1] ms to avoid too busy loop - if (sleepTime == 0) - { - sleepTimeIn = GetFramesSleepTimeMicroseconds(userFramesIn, stream->in.wavex.Format.nSamplesPerSec); - sleepTimeOut = GetFramesSleepTimeMicroseconds(userFramesOut, stream->out.wavex.Format.nSamplesPerSec); - - sleepTime = GetSleepTime(stream, sleepTimeIn, sleepTimeOut, userFramesOut); - - // Setup thread sleep scheduler - ThreadIdleScheduler_Setup(scheduler, 1, sleepTime/* microseconds here */); - sleepTime = 0; - } - - return sleepTime; -} - -// ------------------------------------------------------------------------------------------ -static inline INT32 GetNextSleepTime(SystemTimer *timer, ThreadIdleScheduler *scheduler, LONGLONG startTime, - UINT32 sleepTime) -{ - INT32 nextSleepTime; - INT32 procTime; - - // Get next sleep time - if (sleepTime == 0) - nextSleepTime = ThreadIdleScheduler_NextSleep(scheduler); - else - nextSleepTime = sleepTime; - - // Adjust next sleep time dynamically depending on how much time was spent in ProcessOutputBuffer/ProcessInputBuffer - // therefore periodicity will not jitter or be increased for the amount of time spent in processing; - // example when sleepTime is 10 ms where [] is polling time slot, {} processing time slot: - // - // [9],{2},[8],{1},[9],{1},[9],{3},[7],{2},[8],{3},[7],{2},[8],{2},[8],{3},[7],{2},[8],... - // - procTime = (INT32)(SystemTimer_GetTime(timer) - startTime); - nextSleepTime -= procTime; - if (nextSleepTime < timer->granularity) - nextSleepTime = 0; - else - if (timer->granularity > 1) - nextSleepTime = ALIGN_BWD(nextSleepTime, timer->granularity); - -#ifdef PA_WASAPI_LOG_TIME_SLOTS - printf("{%d},", procTime); -#endif - - return nextSleepTime; -} - -// ------------------------------------------------------------------------------------------ -PA_THREAD_FUNC ProcThreadPoll(void *param) -{ - PaWasapiHostProcessor processor[S_COUNT]; - HRESULT hr = S_OK; - PaWasapiStream *stream = (PaWasapiStream *)param; - PaWasapiHostProcessor defaultProcessor; - INT32 i; - ThreadIdleScheduler scheduler; - SystemTimer timer; - LONGLONG startTime; - UINT32 sleepTime; - INT32 nextSleepTime = 0; //! Do first loop without waiting as time could be spent when calling other APIs before ProcessXXXBuffer. - BOOL threadComInitialized = FALSE; -#ifdef PA_WASAPI_LOG_TIME_SLOTS - LONGLONG startWaitTime; -#endif - - // Notify: state - NotifyStateChanged(stream, paWasapiStreamStateThreadPrepare, ERROR_SUCCESS); - - // Prepare COM pointers - if (!PrepareComPointers(stream, &threadComInitialized)) - return (UINT32)paUnanticipatedHostError; - - // Request fine (1 ms) granularity of the system timer functions to guarantee correct logic around WaitForSingleObject - SystemTimer_SetGranularity(&timer, 1); - - // Calculate sleep time of the processing loop (inside WaitForSingleObject) - sleepTime = ConfigureLoopSleepTimeAndScheduler(stream, &scheduler); - - // Setup data processors - defaultProcessor.processor = WaspiHostProcessingLoop; - defaultProcessor.userData = stream; - processor[S_INPUT] = (stream->hostProcessOverrideInput.processor != NULL ? stream->hostProcessOverrideInput : defaultProcessor); - processor[S_OUTPUT] = (stream->hostProcessOverrideOutput.processor != NULL ? stream->hostProcessOverrideOutput : defaultProcessor); - - // Boost thread priority - PaWasapi_ThreadPriorityBoost((void **)&stream->hAvTask, stream->nThreadPriority); - - // Signal: stream running - stream->running = TRUE; - - // Notify: thread started - SetEvent(stream->hThreadStart); - - // Initialize event & start INPUT stream - if (stream->in.clientProc) - { - if ((hr = IAudioClient_Start(stream->in.clientProc)) != S_OK) - { - LogHostError(hr); - goto thread_error; - } - } - - // Initialize event & start OUTPUT stream - if (stream->out.clientProc) - { - // Preload buffer (obligatory, othervise ->Start() will fail), avoid processing - // when in full-duplex mode as it requires input processing as well - if (!PA_WASAPI__IS_FULLDUPLEX(stream)) - { - UINT32 frames = 0; - if ((hr = _PollGetOutputFramesAvailable(stream, &frames)) == S_OK) - { - if (stream->bufferMode == paUtilFixedHostBufferSize) - { - // It is important to preload whole host buffer to avoid underruns/glitches when stream is started, - // for more details see the discussion: https://github.com/PortAudio/portaudio/issues/303 - while (frames >= stream->out.framesPerBuffer) - { - if ((hr = ProcessOutputBuffer(stream, processor, stream->out.framesPerBuffer)) != S_OK) - { - LogHostError(hr); // not fatal, just log - break; - } - - frames -= stream->out.framesPerBuffer; - } - } - else - { - // Some devices may not start (will get stuck with 0 ready frames) if data not prefetched - if (frames == 0) - frames = stream->out.framesPerBuffer; - - // USB DACs report large buffer in Exclusive mode and if it is filled fully will stuck in - // non playing state, e.g. IAudioClient_GetCurrentPadding() will start reporting max buffer size - // constantly, thus preload data size equal to the user buffer to allow process going - if ((stream->out.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE) && (frames >= (stream->out.framesPerBuffer * 2))) - frames -= stream->out.framesPerBuffer; - - if ((hr = ProcessOutputBuffer(stream, processor, frames)) != S_OK) - { - LogHostError(hr); // not fatal, just log - } - } - } - else - { - LogHostError(hr); // not fatal, just log - } - } - - // Start - if ((hr = IAudioClient_Start(stream->out.clientProc)) != S_OK) - { - LogHostError(hr); - goto thread_error; - } - } - - // Notify: state - NotifyStateChanged(stream, paWasapiStreamStateThreadStart, ERROR_SUCCESS); - -#ifdef PA_WASAPI_LOG_TIME_SLOTS - startWaitTime = SystemTimer_GetTime(&timer); -#endif - - if (!PA_WASAPI__IS_FULLDUPLEX(stream)) - { - // Processing Loop - while (WaitForSingleObject(stream->hCloseRequest, nextSleepTime) == WAIT_TIMEOUT) - { - startTime = SystemTimer_GetTime(&timer); - - #ifdef PA_WASAPI_LOG_TIME_SLOTS - printf("[%d|%d],", nextSleepTime, (INT32)(startTime - startWaitTime)); - #endif - - for (i = 0; i < S_COUNT; ++i) - { - // Process S_INPUT/S_OUTPUT - switch (i) - { - // Input stream - case S_INPUT: { - - if (stream->captureClient == NULL) - break; - - if ((hr = ProcessInputBuffer(stream, processor)) != S_OK) - { - LogHostError(hr); - goto thread_error; - } - - break; } - - // Output stream - case S_OUTPUT: { - - UINT32 framesAvail; - - if (stream->renderClient == NULL) - break; - - // Get available frames - if ((hr = _PollGetOutputFramesAvailable(stream, &framesAvail)) != S_OK) - { - LogHostError(hr); - goto thread_error; - } - - // Output data to the user callback - if (stream->bufferMode == paUtilFixedHostBufferSize) - { - UINT32 framesProc = stream->out.framesPerBuffer; - - // If we got less frames avoid sleeping again as it might be the corner case and buffer - // has sufficient number of frames now, in case 'out.framesPerBuffer' is 1/2 of the host - // buffer sleeping again may cause underruns. Do short busy waiting (normally might take - // 1-2 iterations) - if (framesAvail < framesProc) - { - nextSleepTime = 0; - continue; - } - - while (framesAvail >= framesProc) - { - if ((hr = ProcessOutputBuffer(stream, processor, framesProc)) != S_OK) - { - LogHostError(hr); - goto thread_error; - } - - framesAvail -= framesProc; - } - } - else - if (framesAvail != 0) - { - if ((hr = ProcessOutputBuffer(stream, processor, framesAvail)) != S_OK) - { - LogHostError(hr); - goto thread_error; - } - } - - break; } - } - } - - // Get next sleep time - nextSleepTime = GetNextSleepTime(&timer, &scheduler, startTime, sleepTime); - - #ifdef PA_WASAPI_LOG_TIME_SLOTS - startWaitTime = SystemTimer_GetTime(&timer); - #endif - } - } - else - { - // Processing Loop (full-duplex) - while (WaitForSingleObject(stream->hCloseRequest, nextSleepTime) == WAIT_TIMEOUT) - { - UINT32 i_frames = 0, i_processed = 0, o_frames = 0; - BYTE *i_data = NULL, *o_data = NULL, *o_data_host = NULL; - DWORD i_flags = 0; - - startTime = SystemTimer_GetTime(&timer); - - #ifdef PA_WASAPI_LOG_TIME_SLOTS - printf("[%d|%d],", nextSleepTime, (INT32)(startTime - startWaitTime)); - #endif - - // get available frames - if ((hr = _PollGetOutputFramesAvailable(stream, &o_frames)) != S_OK) - { - LogHostError(hr); - break; - } - - while (o_frames != 0) - { - // get host input buffer - if ((hr = IAudioCaptureClient_GetBuffer(stream->captureClient, &i_data, &i_frames, &i_flags, NULL, NULL)) != S_OK) - { - if (hr == AUDCLNT_S_BUFFER_EMPTY) - break; // no data in capture buffer - - LogHostError(hr); - break; - } - - // process equal amount of frames - if (o_frames >= i_frames) - { - // process input amount of frames - UINT32 o_processed = i_frames; - - // get host output buffer - if ((hr = IAudioRenderClient_GetBuffer(stream->renderClient, o_processed, &o_data)) == S_OK) - { - // processed amount of i_frames - i_processed = i_frames; - o_data_host = o_data; - - // convert output mono - if (stream->out.monoMixer) - { - UINT32 mono_frames_size = o_processed * (stream->out.wavex.Format.wBitsPerSample / 8); - // expand buffer - if (mono_frames_size > stream->out.monoBufferSize) - { - stream->out.monoBuffer = PaWasapi_ReallocateMemory(stream->out.monoBuffer, (stream->out.monoBufferSize = mono_frames_size)); - if (stream->out.monoBuffer == NULL) - { - // release input buffer - IAudioCaptureClient_ReleaseBuffer(stream->captureClient, 0); - // release output buffer - IAudioRenderClient_ReleaseBuffer(stream->renderClient, 0, 0); - - LogPaError(paInsufficientMemory); - goto thread_error; - } - } - - // replace buffer pointer - o_data = (BYTE *)stream->out.monoBuffer; - } - - // convert input mono - if (stream->in.monoMixer) - { - UINT32 mono_frames_size = i_processed * (stream->in.wavex.Format.wBitsPerSample / 8); - // expand buffer - if (mono_frames_size > stream->in.monoBufferSize) - { - stream->in.monoBuffer = PaWasapi_ReallocateMemory(stream->in.monoBuffer, (stream->in.monoBufferSize = mono_frames_size)); - if (stream->in.monoBuffer == NULL) - { - // release input buffer - IAudioCaptureClient_ReleaseBuffer(stream->captureClient, 0); - // release output buffer - IAudioRenderClient_ReleaseBuffer(stream->renderClient, 0, 0); - - LogPaError(paInsufficientMemory); - goto thread_error; - } - } - - // mix 2 to 1 input channels - stream->in.monoMixer(stream->in.monoBuffer, i_data, i_processed); - - // replace buffer pointer - i_data = (BYTE *)stream->in.monoBuffer; - } - - // process - processor[S_FULLDUPLEX].processor(i_data, i_processed, o_data, o_processed, processor[S_FULLDUPLEX].userData); - - // mix 1 to 2 output channels - if (stream->out.monoBuffer) - stream->out.monoMixer(o_data_host, stream->out.monoBuffer, o_processed); - - // release host output buffer - if ((hr = IAudioRenderClient_ReleaseBuffer(stream->renderClient, o_processed, 0)) != S_OK) - LogHostError(hr); - - o_frames -= o_processed; - } - else - { - if (stream->out.shareMode != AUDCLNT_SHAREMODE_SHARED) - LogHostError(hr); // be silent in shared mode, try again next time - } - } - else - { - i_processed = 0; - goto fd_release_buffer_in; - } - -fd_release_buffer_in: - - // release host input buffer - if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->captureClient, i_processed)) != S_OK) - { - LogHostError(hr); - break; - } - - // break processing, input hasn't been accumulated yet - if (i_processed == 0) - break; - } - - // Get next sleep time - nextSleepTime = GetNextSleepTime(&timer, &scheduler, startTime, sleepTime); - - #ifdef PA_WASAPI_LOG_TIME_SLOTS - startWaitTime = SystemTimer_GetTime(&timer); - #endif - } - } - -thread_end: - - // Process stop - _StreamOnStop(stream); - - // Release unmarshaled COM pointers - FinishComPointers(stream, threadComInitialized); - - // Restore system timer granularity - SystemTimer_RestoreGranularity(&timer); - - // Notify: not running - stream->running = FALSE; - - // Notify: thread exited - SetEvent(stream->hThreadExit); - - // Notify: state - NotifyStateChanged(stream, paWasapiStreamStateThreadStop, hr); - - return 0; - -thread_error: - - // Prevent deadlocking in Pa_StreamStart - SetEvent(stream->hThreadStart); - - // Exit - goto thread_end; -} - -// ------------------------------------------------------------------------------------------ -void *PaWasapi_ReallocateMemory(void *prev, size_t size) -{ - void *ret = realloc(prev, size); - if (ret == NULL) - { - PaWasapi_FreeMemory(prev); - return NULL; - } - return ret; -} - -// ------------------------------------------------------------------------------------------ -void PaWasapi_FreeMemory(void *ptr) -{ - free(ptr); -} |