portaudio/src/hostapi/wasapi/pa_win_wasapi.c
dmitrykos d714962621 wasapi:
- fixed int24 Mono to Stereo mixer (affects Mono streams starting in Exclusive mode)
pa_converters: 
 - avoid using 64-bit integer math for Int24_To_Float32 and Int24_To_Int32 under x64 Posix platforms (long is 64-bit there), now using explicit PaInt32 type
2010-03-25 23:27:27 +00:00

3418 lines
117 KiB
C

/*
* Portable Audio I/O Library WASAPI implementation
* Copyright (c) 2006-2010 David Viens, Dmitry Kostjuchenko
*
* Based on the Open Source API proposed by Ross Bencina
* Copyright (c) 1999-2002 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
*/
#define WIN32_LEAN_AND_MEAN // exclude rare headers
#include <windows.h>
#include <stdio.h>
#include <process.h>
#include <mmsystem.h>
#include <mmreg.h> // must be before other Wasapi headers
#if defined(_MSC_VER) && (_MSC_VER >= 1400)
#include <Avrt.h>
#define COBJMACROS
#include <Audioclient.h>
#include <endpointvolume.h>
#define INITGUID //<< avoid additional linkage of static libs, uneeded code will be optimized out
#include <mmdeviceapi.h>
#include <functiondiscoverykeys.h>
#undef INITGUID
#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_debugprint.h"
#include "pa_win_wasapi.h"
#ifndef NTDDI_VERSION
#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
#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
#ifndef WAVE_FORMAT_IEEE_FLOAT
#define WAVE_FORMAT_IEEE_FLOAT 0x0003 // 32-bit floating-point
#endif
// Speaker Positions:
#define SPEAKER_FRONT_LEFT 0x1
#define SPEAKER_FRONT_RIGHT 0x2
#define SPEAKER_FRONT_CENTER 0x4
#define SPEAKER_LOW_FREQUENCY 0x8
#define SPEAKER_BACK_LEFT 0x10
#define SPEAKER_BACK_RIGHT 0x20
#define SPEAKER_FRONT_LEFT_OF_CENTER 0x40
#define SPEAKER_FRONT_RIGHT_OF_CENTER 0x80
#define SPEAKER_BACK_CENTER 0x100
#define SPEAKER_SIDE_LEFT 0x200
#define SPEAKER_SIDE_RIGHT 0x400
#define SPEAKER_TOP_CENTER 0x800
#define SPEAKER_TOP_FRONT_LEFT 0x1000
#define SPEAKER_TOP_FRONT_CENTER 0x2000
#define SPEAKER_TOP_FRONT_RIGHT 0x4000
#define SPEAKER_TOP_BACK_LEFT 0x8000
#define SPEAKER_TOP_BACK_CENTER 0x10000
#define SPEAKER_TOP_BACK_RIGHT 0x20000
// Bit mask locations reserved for future use
#define SPEAKER_RESERVED 0x7FFC0000
// Used to specify that any possible permutation of speaker configurations
#define SPEAKER_ALL 0x80000000
// DirectSound Speaker Config
#if (NTDDI_VERSION >= NTDDI_WINXP)
#define KSAUDIO_SPEAKER_DIRECTOUT 0
#endif
#define KSAUDIO_SPEAKER_MONO (SPEAKER_FRONT_CENTER)
#define KSAUDIO_SPEAKER_STEREO (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT)
#define KSAUDIO_SPEAKER_QUAD (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | \
SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT)
#define KSAUDIO_SPEAKER_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | \
SPEAKER_FRONT_CENTER | SPEAKER_BACK_CENTER)
#define KSAUDIO_SPEAKER_5POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | \
SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | \
SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT)
#define KSAUDIO_SPEAKER_7POINT1 (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | \
SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | \
SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | \
SPEAKER_FRONT_LEFT_OF_CENTER | SPEAKER_FRONT_RIGHT_OF_CENTER)
#if ( (NTDDI_VERSION >= NTDDI_WINXPSP2) && (NTDDI_VERSION < NTDDI_WS03) ) || (NTDDI_VERSION >= NTDDI_WS03SP1)
#define KSAUDIO_SPEAKER_5POINT1_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | \
SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | \
SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT)
#define KSAUDIO_SPEAKER_7POINT1_SURROUND (SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | \
SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | \
SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | \
SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT)
// The following are obsolete 5.1 and 7.1 settings (they lack side speakers). Note this means
// that the default 5.1 and 7.1 settings (KSAUDIO_SPEAKER_5POINT1 and KSAUDIO_SPEAKER_7POINT1 are
// similarly obsolete but are unchanged for compatibility reasons).
#define KSAUDIO_SPEAKER_5POINT1_BACK KSAUDIO_SPEAKER_5POINT1
#define KSAUDIO_SPEAKER_7POINT1_WIDE KSAUDIO_SPEAKER_7POINT1
#endif // XP SP2 and later (chronologically)
// DVD Speaker Positions
#define KSAUDIO_SPEAKER_GROUND_FRONT_LEFT SPEAKER_FRONT_LEFT
#define KSAUDIO_SPEAKER_GROUND_FRONT_CENTER SPEAKER_FRONT_CENTER
#define KSAUDIO_SPEAKER_GROUND_FRONT_RIGHT SPEAKER_FRONT_RIGHT
#define KSAUDIO_SPEAKER_GROUND_REAR_LEFT SPEAKER_BACK_LEFT
#define KSAUDIO_SPEAKER_GROUND_REAR_RIGHT SPEAKER_BACK_RIGHT
#define KSAUDIO_SPEAKER_TOP_MIDDLE SPEAKER_TOP_CENTER
#define KSAUDIO_SPEAKER_SUPER_WOOFER SPEAKER_LOW_FREQUENCY
#ifdef WIN64
#include <wtypes.h>
typedef LONG NTSTATUS;
typedef void *PKSEVENT_ENTRY;
typedef void *PKSDEVICE;
typedef void *PKSFILTERFACTORY;
typedef void *PKSFILTER;
typedef void *PIRP;
typedef void *PCM_RESOURCE_LIST;
typedef void *PDEVICE_CAPABILITIES;
typedef void *PKSPIN;
typedef void *PKSPROCESSPIN_INDEXENTRY;
typedef void KSATTRIBUTE_LIST;
typedef void *PKSHANDSHAKE;
typedef void *PKTIMER;
typedef void *PKDPC;
typedef void *PKSSTREAM_POINTER;
typedef void KSPROPERTY_SET;
typedef void KSCLOCK_DISPATCH;
typedef void KSMETHOD_SET;
typedef void KSEVENT_SET;
typedef void KSALLOCATOR_DISPATCH;
typedef void KSDEVICE_DISPATCH;
typedef void KSFILTER_DISPATCH;
typedef void KSFILTER_DESCRIPTOR;
typedef void KSPIN_DESCRIPTOR_EX;
typedef void KSPIN_DISPATCH;
typedef void KSDEVICE_DESCRIPTOR;
typedef void *PFNKSDELETEALLOCATOR;
typedef void *PFNKSDEFAULTALLOCATE;
typedef void *PFNKSDEFAULTFREE;
typedef void KSNODE_DESCRIPTOR;
typedef char KSPIN_DESCRIPTOR;
typedef void *PFNKSINTERSECTHANDLEREX;
typedef void *PDEVICE_OBJECT;
typedef void *PHYSICAL_ADDRESS;
typedef void *PKSSTREAM_POINTER_OFFSET;
typedef void *PKSPROCESSPIN;
typedef void *PMDL;
typedef ULONG KSSTREAM_POINTER_OFFSET;
typedef void KSJACK_DESCRIPTION;
#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
#undef WINVER
#undef _WIN32_WINNT
#include <sdkddkver.h>
#include <propkeydef.h>
#define COBJMACROS
#define INITGUID
#include <audioclient.h>
#include <mmdeviceapi.h>
#include <endpointvolume.h>
#include <functiondiscoverykeys.h>
#undef INITGUID
#endif // NTDDI_VERSION
#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);
// "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);
// 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 );
/* 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);
// Availabe from 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
#define MAX_STR_LEN 512
enum { S_INPUT = 0, S_OUTPUT, S_COUNT };
#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 )
#ifndef IF_FAILED_JUMP
#define IF_FAILED_JUMP(hr, label) if(FAILED(hr)) 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, void *__from, UINT32 count);
// AVRT is the new "multimedia schedulling stuff"
typedef BOOL (WINAPI *FAvRtCreateThreadOrderingGroup) (PHANDLE,PLARGE_INTEGER,GUID*,PLARGE_INTEGER);
typedef BOOL (WINAPI *FAvRtDeleteThreadOrderingGroup) (HANDLE);
typedef BOOL (WINAPI *FAvRtWaitOnThreadOrderingGroup) (HANDLE);
typedef HANDLE (WINAPI *FAvSetMmThreadCharacteristics) (LPCTSTR,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;
#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
IMMDevice *device;
// from GetId
WCHAR szDeviceID[MAX_STR_LEN];
// from GetState
DWORD state;
// Fields filled from IMMEndpoint'sGetDataFlow
EDataFlow flow;
// Fields filled from IAudioDevice (_prior_ to Initialize)
// from GetDevicePeriod(
REFERENCE_TIME DefaultDevicePeriod;
REFERENCE_TIME MinimumDevicePeriod;
// from GetMixFormat
// WAVEFORMATEX *MixFormat;//needs to be CoTaskMemFree'd after use!
// Default format (setup through Control Panel by user)
WAVEFORMATEXTENSIBLE DefaultFormat;
// Formfactor
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 */
//in case we later need the synch
IMMDeviceEnumerator *enumerator;
//this is the REAL number of devices, whether they are usefull to PA or not!
UINT32 deviceCount;
WCHAR defaultRenderer [MAX_STR_LEN];
WCHAR defaultCapturer [MAX_STR_LEN];
PaWasapiDeviceInfo *devInfo;
// Is true when WOW64 Vista Workaround is needed
BOOL useVistaWOW64Workaround;
}
PaWasapiHostApiRepresentation;
// ------------------------------------------------------------------------------------------
/* PaWasapiStream - a stream data structure specifically for this implementation */
typedef struct PaWasapiSubStream
{
IAudioClient *client;
WAVEFORMATEXTENSIBLE wavex;
UINT32 bufferSize;
REFERENCE_TIME device_latency;
REFERENCE_TIME period;
double latency_seconds;
UINT32 framesPerHostCallback;
AUDCLNT_SHAREMODE shareMode;
UINT32 streamFlags; // AUDCLNT_STREAMFLAGS_EVENTCALLBACK, ...
UINT32 flags;
// Used by blocking interface:
UINT32 prevTime; // time ms between calls of WriteStream
UINT32 prevSleep; // time ms to sleep from frames written in previous call
// 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
}
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 *cclient;
IAudioEndpointVolume *inVol;
// output
PaWasapiSubStream out;
IAudioRenderClient *rclient;
IAudioEndpointVolume *outVol;
// 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 hThreadDone;
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;
}
PaWasapiStream;
// Local stream methods
static void _OnStreamStop(PaWasapiStream *stream);
static void _FinishStream(PaWasapiStream *stream);
// Local statics
static volatile BOOL g_bCOMInitialized = FALSE;
// ------------------------------------------------------------------------------------------
#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;
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;
default:
text = "UNKNOWN ERROR";
}
PRINT(("WASAPI ERROR HRESULT: 0x%X : %s\n [FUNCTION: %s FILE: %s {LINE: %d}]\n", res, text, func, file, line));
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));
return err;
}
// ------------------------------------------------------------------------------------------
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
static WORD PaSampleFormatToBitsPerSample(PaSampleFormat format_id)
{
switch (format_id & ~paNonInterleaved)
{
case paFloat32:
case paInt32: return 32;
case paInt24: return 24;
case paInt16: return 16;
case paInt8:
case paUInt8: return 8;
}
return 0;
}
// ------------------------------------------------------------------------------------------
// Converts PaSampleFormat to bits per sample value
static WORD PaSampleFormatToBytesPerSample(PaSampleFormat format_id)
{
return PaSampleFormatToBitsPerSample(format_id) >> 3; // 'bits/8'
}
// ------------------------------------------------------------------------------------------
// 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;
}
// ------------------------------------------------------------------------------------------
// Aligns v backwards
static UINT32 ALIGN_BWD(UINT32 v, UINT32 align)
{
return ((v - (align ? v % align : 0)));
}
// ------------------------------------------------------------------------------------------
// 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 nSamplesPerSec, UINT32 nBlockAlign)
{
#define HDA_PACKET_SIZE 128
long packets_total = 10000 * (nSamplesPerSec * nBlockAlign / HDA_PACKET_SIZE);
long frame_bytes = nFrames * nBlockAlign;
long packets;
// align to packet size
frame_bytes = ALIGN_BWD(frame_bytes, HDA_PACKET_SIZE);
nFrames = frame_bytes / nBlockAlign;
packets = frame_bytes / HDA_PACKET_SIZE;
// align to packets count
while (packets && ((packets_total % packets) != 0))
{
--packets;
}
frame_bytes = packets * HDA_PACKET_SIZE;
nFrames = frame_bytes / nBlockAlign;
return nFrames;
}
// ------------------------------------------------------------------------------------------
static UINT32 GetFramesSleepTime(UINT32 nFrames, UINT32 nSamplesPerSec)
{
REFERENCE_TIME nDuration;
if (nSamplesPerSec == 0)
return 0;
#define REFTIMES_PER_SEC 10000000
#define REFTIMES_PER_MILLISEC 10000
// Calculate the actual duration of the allocated buffer.
nDuration = (REFERENCE_TIME)((double)REFTIMES_PER_SEC * nFrames / nSamplesPerSec);
return (UINT32)(nDuration/REFTIMES_PER_MILLISEC/2);
}
// ------------------------------------------------------------------------------------------
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;
}
// ------------------------------------------------------------------------------------------
static void CloseAVRT()
{
if (hDInputDLL != NULL)
FreeLibrary(hDInputDLL);
hDInputDLL = NULL;
}
// ------------------------------------------------------------------------------------------
static BOOL IsWow64()
{
// 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(
GetModuleHandle(TEXT("kernel32")), TEXT("IsWow64Process"));
if (fnIsWow64Process == NULL)
return FALSE;
if (!fnIsWow64Process(GetCurrentProcess(), &bIsWow64))
return FALSE;
return bIsWow64;
}
// ------------------------------------------------------------------------------------------
typedef enum EWindowsVersion
{
WINDOWS_UNKNOWN,
WINDOWS_VISTA_SERVER2008,
WINDOWS_7_SERVER2008R2
}
EWindowsVersion;
// The function is limited to Vista/7 mostly as we need just to find out Vista/WOW64 combination
// in order to use WASAPI WOW64 workarounds.
static EWindowsVersion GetWindowsVersion()
{
DWORD dwVersion = 0;
DWORD dwMajorVersion = 0;
DWORD dwMinorVersion = 0;
DWORD dwBuild = 0;
typedef DWORD (WINAPI *LPFN_GETVERSION)(VOID);
LPFN_GETVERSION fnGetVersion;
fnGetVersion = (LPFN_GETVERSION) GetProcAddress(
GetModuleHandle(TEXT("kernel32")), TEXT("GetVersion"));
if (fnGetVersion == NULL)
return WINDOWS_UNKNOWN;
dwVersion = fnGetVersion();
// Get the Windows version
dwMajorVersion = (DWORD)(LOBYTE(LOWORD(dwVersion)));
dwMinorVersion = (DWORD)(HIBYTE(LOWORD(dwVersion)));
// Get the build number
if (dwVersion < 0x80000000)
dwBuild = (DWORD)(HIWORD(dwVersion));
switch (dwMajorVersion)
{
case 6:
switch (dwMinorVersion)
{
case 0:
return WINDOWS_VISTA_SERVER2008;
case 1:
return WINDOWS_7_SERVER2008R2;
}
}
return WINDOWS_UNKNOWN;
}
// ------------------------------------------------------------------------------------------
static BOOL UseWOW64VistaWorkaround()
{
return (IsWow64() && (GetWindowsVersion() == WINDOWS_VISTA_SERVER2008));
}
// ------------------------------------------------------------------------------------------
#define _WASAPI_MONO_TO_STEREO_MIXER(TYPE)\
TYPE * __restrict to = (TYPE *)__to;\
TYPE * __restrict from = (TYPE *)__from;\
TYPE * __restrict end = from + count;\
while (from != end)\
{\
*to ++ = *from;\
*to ++ = *from;\
++ from;\
}
// ------------------------------------------------------------------------------------------
static void _MixMonoToStereo_8(void *__to, void *__from, UINT32 count)
{
_WASAPI_MONO_TO_STEREO_MIXER(BYTE);
}
// ------------------------------------------------------------------------------------------
static void _MixMonoToStereo_16(void *__to, void *__from, UINT32 count)
{
_WASAPI_MONO_TO_STEREO_MIXER(short);
}
// ------------------------------------------------------------------------------------------
static void _MixMonoToStereo_24(void *__to, void *__from, UINT32 count)
{
_WASAPI_MONO_TO_STEREO_MIXER(int); // !!! int24 data is contained in 32-bit containers
}
// ------------------------------------------------------------------------------------------
static void _MixMonoToStereo_32(void *__to, void *__from, UINT32 count)
{
_WASAPI_MONO_TO_STEREO_MIXER(int);
}
// ------------------------------------------------------------------------------------------
static void _MixMonoToStereo_32f(void *__to, void *__from, UINT32 count)
{
_WASAPI_MONO_TO_STEREO_MIXER(float);
}
// ------------------------------------------------------------------------------------------
static MixMonoToStereoF _GetMonoToStereoMixer(PaSampleFormat format)
{
switch (format)
{
case paUInt8: return _MixMonoToStereo_8;
case paInt16: return _MixMonoToStereo_16;
case paInt24: return _MixMonoToStereo_24;
case paInt32: return _MixMonoToStereo_32;
case paFloat32: return _MixMonoToStereo_32f;
}
return NULL;
}
// ------------------------------------------------------------------------------------------
PaError PaWasapi_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex )
{
PaError result = paNoError;
PaWasapiHostApiRepresentation *paWasapi;
PaDeviceInfo *deviceInfoArray;
HRESULT hr = S_OK;
IMMDeviceCollection* pEndPoints = NULL;
UINT i;
if (!SetupAVRT())
{
PRINT(("WASAPI: No AVRT! (not VISTA?)"));
return paNoError;
}
/*
If COM is already initialized CoInitialize will either return
FALSE, or RPC_E_CHANGED_MODE if it was initialised 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 CoInitialize"));
return paUnanticipatedHostError;
}
if (hr != RPC_E_CHANGED_MODE)
g_bCOMInitialized = TRUE;
paWasapi = (PaWasapiHostApiRepresentation *)PaUtil_AllocateMemory( sizeof(PaWasapiHostApiRepresentation) );
if (paWasapi == NULL)
{
result = paInsufficientMemory;
goto error;
}
paWasapi->allocations = PaUtil_CreateAllocationGroup();
if (paWasapi->allocations == NULL)
{
result = paInsufficientMemory;
goto error;
}
*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;
paWasapi->enumerator = NULL;
hr = CoCreateInstance(&pa_CLSID_IMMDeviceEnumerator, NULL, CLSCTX_INPROC_SERVER,
&pa_IID_IMMDeviceEnumerator, (void **)&paWasapi->enumerator);
IF_FAILED_JUMP(hr, error);
// getting default device ids in the eMultimedia "role"
{
{
IMMDevice *defaultRenderer = NULL;
hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(paWasapi->enumerator, eRender, eMultimedia, &defaultRenderer);
if (hr != S_OK)
{
if (hr != E_NOTFOUND)
IF_FAILED_JUMP(hr, error);
}
else
{
WCHAR *pszDeviceId = NULL;
hr = IMMDevice_GetId(defaultRenderer, &pszDeviceId);
IF_FAILED_JUMP(hr, error);
wcsncpy(paWasapi->defaultRenderer, pszDeviceId, MAX_STR_LEN-1);
CoTaskMemFree(pszDeviceId);
IMMDevice_Release(defaultRenderer);
}
}
{
IMMDevice *defaultCapturer = NULL;
hr = IMMDeviceEnumerator_GetDefaultAudioEndpoint(paWasapi->enumerator, eCapture, eMultimedia, &defaultCapturer);
if (hr != S_OK)
{
if (hr != E_NOTFOUND)
IF_FAILED_JUMP(hr, error);
}
else
{
WCHAR *pszDeviceId = NULL;
hr = IMMDevice_GetId(defaultCapturer, &pszDeviceId);
IF_FAILED_JUMP(hr, error);
wcsncpy(paWasapi->defaultCapturer, pszDeviceId, MAX_STR_LEN-1);
CoTaskMemFree(pszDeviceId);
IMMDevice_Release(defaultCapturer);
}
}
}
hr = IMMDeviceEnumerator_EnumAudioEndpoints(paWasapi->enumerator, eAll, DEVICE_STATE_ACTIVE, &pEndPoints);
IF_FAILED_JUMP(hr, error);
hr = IMMDeviceCollection_GetCount(pEndPoints, &paWasapi->deviceCount);
IF_FAILED_JUMP(hr, error);
paWasapi->devInfo = (PaWasapiDeviceInfo *)malloc(sizeof(PaWasapiDeviceInfo) * paWasapi->deviceCount);
for (i = 0; i < paWasapi->deviceCount; ++i)
memset(&paWasapi->devInfo[i], 0, sizeof(PaWasapiDeviceInfo));
if (paWasapi->deviceCount > 0)
{
(*hostApi)->deviceInfos = (PaDeviceInfo **)PaUtil_GroupAllocateMemory(
paWasapi->allocations, sizeof(PaDeviceInfo *) * paWasapi->deviceCount);
if ((*hostApi)->deviceInfos == NULL)
{
result = paInsufficientMemory;
goto error;
}
/* allocate all device info structs in a contiguous block */
deviceInfoArray = (PaDeviceInfo *)PaUtil_GroupAllocateMemory(
paWasapi->allocations, sizeof(PaDeviceInfo) * paWasapi->deviceCount);
if (deviceInfoArray == NULL)
{
result = paInsufficientMemory;
goto error;
}
for (i = 0; i < paWasapi->deviceCount; ++i)
{
DWORD state = 0;
PaDeviceInfo *deviceInfo = &deviceInfoArray[i];
deviceInfo->structVersion = 2;
deviceInfo->hostApi = hostApiIndex;
PA_DEBUG(("WASAPI: device i: %d\n", i));
hr = IMMDeviceCollection_Item(pEndPoints, i, &paWasapi->devInfo[i].device);
IF_FAILED_JUMP(hr, error);
// getting ID
{
WCHAR *pszDeviceId = NULL;
hr = IMMDevice_GetId(paWasapi->devInfo[i].device, &pszDeviceId);
IF_FAILED_JUMP(hr, error);
wcsncpy(paWasapi->devInfo[i].szDeviceID, pszDeviceId, MAX_STR_LEN-1);
CoTaskMemFree(pszDeviceId);
if (lstrcmpW(paWasapi->devInfo[i].szDeviceID, paWasapi->defaultCapturer) == 0)
{// we found the default input!
(*hostApi)->info.defaultInputDevice = (*hostApi)->info.deviceCount;
}
if (lstrcmpW(paWasapi->devInfo[i].szDeviceID, paWasapi->defaultRenderer) == 0)
{// we found the default output!
(*hostApi)->info.defaultOutputDevice = (*hostApi)->info.deviceCount;
}
}
hr = IMMDevice_GetState(paWasapi->devInfo[i].device, &paWasapi->devInfo[i].state);
IF_FAILED_JUMP(hr, error);
if (paWasapi->devInfo[i].state != DEVICE_STATE_ACTIVE)
{
PRINT(("WASAPI device: %d is not currently available (state:%d)\n",i,state));
}
{
IPropertyStore *pProperty;
hr = IMMDevice_OpenPropertyStore(paWasapi->devInfo[i].device, STGM_READ, &pProperty);
IF_FAILED_JUMP(hr, error);
// "Friendly" Name
{
char *deviceName;
PROPVARIANT value;
PropVariantInit(&value);
hr = IPropertyStore_GetValue(pProperty, &PKEY_Device_FriendlyName, &value);
IF_FAILED_JUMP(hr, error);
deviceInfo->name = NULL;
deviceName = (char *)PaUtil_GroupAllocateMemory(paWasapi->allocations, MAX_STR_LEN + 1);
if (deviceName == NULL)
{
result = paInsufficientMemory;
goto error;
}
if (value.pwszVal)
wcstombs(deviceName, value.pwszVal, MAX_STR_LEN-1);
else
_snprintf(deviceName, MAX_STR_LEN-1, "baddev%d", i);
deviceInfo->name = deviceName;
PropVariantClear(&value);
}
// Default format
{
PROPVARIANT value;
PropVariantInit(&value);
hr = IPropertyStore_GetValue(pProperty, &PKEY_AudioEngine_DeviceFormat, &value);
IF_FAILED_JUMP(hr, error);
memcpy(&paWasapi->devInfo[i].DefaultFormat, value.blob.pBlobData, min(sizeof(paWasapi->devInfo[i].DefaultFormat), value.blob.cbSize));
// cleanup
PropVariantClear(&value);
}
// Formfactor
{
PROPVARIANT value;
PropVariantInit(&value);
hr = IPropertyStore_GetValue(pProperty, &PKEY_AudioEndpoint_FormFactor, &value);
IF_FAILED_JUMP(hr, 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));
paWasapi->devInfo[i].formFactor = (EndpointFormFactor)v;
#else
paWasapi->devInfo[i].formFactor = (EndpointFormFactor)value.uintVal;
#endif
PA_DEBUG(("WASAPI: device[%s] form-factor: %d\n", deviceInfo->name, paWasapi->devInfo[i].formFactor));
// cleanup
PropVariantClear(&value);
}
SAFE_RELEASE(pProperty);
}
// Endpoint data
{
IMMEndpoint *endpoint = NULL;
hr = IMMDevice_QueryInterface(paWasapi->devInfo[i].device, &pa_IID_IMMEndpoint, (void **)&endpoint);
if (SUCCEEDED(hr))
{
hr = IMMEndpoint_GetDataFlow(endpoint, &paWasapi->devInfo[i].flow);
SAFE_RELEASE(endpoint);
}
}
// Getting a temporary IAudioClient for more fields
// we make sure NOT to call Initialize yet!
{
IAudioClient *tmpClient = NULL;
hr = IMMDevice_Activate(paWasapi->devInfo[i].device, &pa_IID_IAudioClient,
CLSCTX_INPROC_SERVER, NULL, (void **)&tmpClient);
IF_FAILED_JUMP(hr, error);
hr = IAudioClient_GetDevicePeriod(tmpClient,
&paWasapi->devInfo[i].DefaultDevicePeriod,
&paWasapi->devInfo[i].MinimumDevicePeriod);
IF_FAILED_JUMP(hr, error);
//hr = tmpClient->GetMixFormat(&paWasapi->devInfo[i].MixFormat);
// Release 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);
goto error;
}
}
// we can now fill in portaudio device data
deviceInfo->maxInputChannels = 0; //for now
deviceInfo->maxOutputChannels = 0; //for now
switch (paWasapi->devInfo[i].flow)
{
case eRender:
// WASAPI accepts exact channels count
deviceInfo->maxOutputChannels = paWasapi->devInfo[i].DefaultFormat.Format.nChannels;
deviceInfo->defaultHighOutputLatency = nano100ToSeconds(paWasapi->devInfo[i].DefaultDevicePeriod);
deviceInfo->defaultLowOutputLatency = nano100ToSeconds(paWasapi->devInfo[i].MinimumDevicePeriod);
break;
case eCapture:
// WASAPI accepts exact channels count
deviceInfo->maxInputChannels = paWasapi->devInfo[i].DefaultFormat.Format.nChannels;
deviceInfo->defaultHighInputLatency = nano100ToSeconds(paWasapi->devInfo[i].DefaultDevicePeriod);
deviceInfo->defaultLowInputLatency = nano100ToSeconds(paWasapi->devInfo[i].MinimumDevicePeriod);
break;
default:
PRINT(("WASAPI: device %d bad Data FLow! \n",i));
goto error;
break;
}
deviceInfo->defaultSampleRate = paWasapi->devInfo[i].DefaultFormat.Format.nSamplesPerSec;
(*hostApi)->deviceInfos[i] = deviceInfo;
++(*hostApi)->info.deviceCount;
}
}
(*hostApi)->Terminate = Terminate;
(*hostApi)->OpenStream = OpenStream;
(*hostApi)->IsFormatSupported = IsFormatSupported;
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 );
// findout if platform workaround is required
paWasapi->useVistaWOW64Workaround = UseWOW64VistaWorkaround();
SAFE_RELEASE(pEndPoints);
PRINT(("WASAPI: initialized ok\n"));
return paNoError;
error:
PRINT(("WASAPI: failed %s error[%d|%s]\n", __FUNCTION__, result, Pa_GetErrorText(result)));
SAFE_RELEASE(pEndPoints);
Terminate((PaUtilHostApiRepresentation *)paWasapi);
return result;
}
// ------------------------------------------------------------------------------------------
static void Terminate( PaUtilHostApiRepresentation *hostApi )
{
UINT i;
PaWasapiHostApiRepresentation *paWasapi = (PaWasapiHostApiRepresentation*)hostApi;
if (paWasapi == NULL)
return;
// Release IMMDeviceEnumerator
SAFE_RELEASE(paWasapi->enumerator);
for (i = 0; i < paWasapi->deviceCount; ++i)
{
PaWasapiDeviceInfo *info = &paWasapi->devInfo[i];
SAFE_RELEASE(info->device);
//if (info->MixFormat)
// CoTaskMemFree(info->MixFormat);
}
free(paWasapi->devInfo);
if (paWasapi->allocations)
{
PaUtil_FreeAllAllocations(paWasapi->allocations);
PaUtil_DestroyAllocationGroup(paWasapi->allocations);
}
PaUtil_FreeMemory(paWasapi);
// Close AVRT
CloseAVRT();
// Uninit COM
if (g_bCOMInitialized)
CoUninitialize();
}
// ------------------------------------------------------------------------------------------
static PaWasapiHostApiRepresentation *_GetHostApi(PaError *_error)
{
PaError error;
PaUtilHostApiRepresentation *pApi;
if ((error = PaUtil_GetHostApiRepresentation(&pApi, paWASAPI)) != paNoError)
{
if (_error != NULL)
(*_error) = error;
return NULL;
}
return (PaWasapiHostApiRepresentation *)pApi;
}
// ------------------------------------------------------------------------------------------
int PaWasapi_GetDeviceDefaultFormat( void *pFormat, unsigned int nFormatSize, PaDeviceIndex nDevice )
{
PaError ret;
PaWasapiHostApiRepresentation *paWasapi;
UINT32 size;
PaDeviceIndex index;
if (pFormat == NULL)
return paBadBufferPtr;
if (nFormatSize <= 0)
return paBufferTooSmall;
// Get API
paWasapi = _GetHostApi(&ret);
if (paWasapi == NULL)
return ret;
// Get device index
ret = PaUtil_DeviceIndexToHostApiDeviceIndex(&index, nDevice, &paWasapi->inheritedHostApiRep);
if (ret != paNoError)
return ret;
// Validate index
if ((UINT32)index >= paWasapi->deviceCount)
return paInvalidDevice;
size = min(nFormatSize, (UINT32)sizeof(paWasapi->devInfo[ index ].DefaultFormat));
memcpy(pFormat, &paWasapi->devInfo[ index ].DefaultFormat, size);
return size;
}
// ------------------------------------------------------------------------------------------
int PaWasapi_GetDeviceRole( PaDeviceIndex nDevice )
{
PaError ret;
PaDeviceIndex index;
// Get API
PaWasapiHostApiRepresentation *paWasapi = _GetHostApi(&ret);
if (paWasapi == NULL)
return ret;
// Get device index
ret = PaUtil_DeviceIndexToHostApiDeviceIndex(&index, nDevice, &paWasapi->inheritedHostApiRep);
if (ret != paNoError)
return ret;
// Validate index
if ((UINT32)index >= paWasapi->deviceCount)
return paInvalidDevice;
return paWasapi->devInfo[ index ].formFactor;
}
// ------------------------------------------------------------------------------------------
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));
}
// ------------------------------------------------------------------------------------------
static PaSampleFormat waveformatToPaFormat(const WAVEFORMATEXTENSIBLE *in)
{
const WAVEFORMATEX *old = (WAVEFORMATEX *)in;
switch (old->wFormatTag)
{
case WAVE_FORMAT_EXTENSIBLE: {
if (IsEqualGUID(&in->SubFormat, &pa_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
{
if (in->Samples.wValidBitsPerSample == 32)
return paFloat32;
}
else
if (IsEqualGUID(&in->SubFormat, &pa_KSDATAFORMAT_SUBTYPE_PCM))
{
switch (old->wBitsPerSample)
{
case 32: return paInt32;
case 24: return paInt24;
case 8: return paUInt8;
case 16: return paInt16;
}
}
break; }
case WAVE_FORMAT_IEEE_FLOAT:
return paFloat32;
case WAVE_FORMAT_PCM: {
switch (old->wBitsPerSample)
{
case 32: return paInt32;
case 24: return paInt24;
case 8: return paUInt8;
case 16: return paInt16;
}
break; }
}
return paCustomFormat;
}
// ------------------------------------------------------------------------------------------
static PaError MakeWaveFormatFromParams(WAVEFORMATEXTENSIBLE *wavex, const PaStreamParameters *params,
double sampleRate)
{
WORD bitsPerSample;
WAVEFORMATEX *old;
DWORD channelMask = 0;
PaWasapiStreamInfo *streamInfo = (PaWasapiStreamInfo *)params->hostApiSpecificStreamInfo;
// Get user assigned channel mask
if ((streamInfo != NULL) && (streamInfo->flags & paWinWasapiUseChannelMask))
channelMask = streamInfo->channelMask;
// Convert PaSampleFormat to bits per sample
if ((bitsPerSample = PaSampleFormatToBitsPerSample(params->sampleFormat)) == 0)
return paSampleFormatNotSupported;
memset(wavex, 0, sizeof(*wavex));
old = (WAVEFORMATEX *)wavex;
old->nChannels = (WORD)params->channelCount;
old->nSamplesPerSec = (DWORD)sampleRate;
if ((old->wBitsPerSample = bitsPerSample) > 16)
{
old->wBitsPerSample = 32; // 20 or 24 bits must go in 32 bit containers (ints)
}
old->nBlockAlign = (old->nChannels * (old->wBitsPerSample/8));
old->nAvgBytesPerSec = (old->nSamplesPerSec * old->nBlockAlign);
//WAVEFORMATEX
/*if ((params->channelCount <= 2) && ((bitsPerSample == 16) || (bitsPerSample == 8)))
{
old->cbSize = 0;
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; //no extra padding!
// Set channel mask
if (channelMask != 0)
{
wavex->dwChannelMask = channelMask;
}
else
{
switch (params->channelCount)
{
case 1: wavex->dwChannelMask = KSAUDIO_SPEAKER_MONO; break;
case 2: wavex->dwChannelMask = KSAUDIO_SPEAKER_STEREO; break;
case 4: wavex->dwChannelMask = KSAUDIO_SPEAKER_QUAD; break;
case 6: wavex->dwChannelMask = KSAUDIO_SPEAKER_5POINT1; break;
case 8: wavex->dwChannelMask = KSAUDIO_SPEAKER_7POINT1; break;
default: wavex->dwChannelMask = 0; break;
}
}
}
return paNoError;
}
// ------------------------------------------------------------------------------------------
static void wasapiFillWFEXT( WAVEFORMATEXTENSIBLE* pwfext, PaSampleFormat sampleFormat, double sampleRate, int channelCount)
{
PA_DEBUG(( "sampleFormat = %lx\n" , sampleFormat ));
PA_DEBUG(( "sampleRate = %f\n" , sampleRate ));
PA_DEBUG(( "chanelCount = %d\n", channelCount ));
pwfext->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
pwfext->Format.nChannels = (WORD)channelCount;
pwfext->Format.nSamplesPerSec = (DWORD)sampleRate;
if(channelCount == 1)
pwfext->dwChannelMask = KSAUDIO_SPEAKER_DIRECTOUT;
else
pwfext->dwChannelMask = KSAUDIO_SPEAKER_STEREO;
if(sampleFormat == paFloat32)
{
pwfext->Format.nBlockAlign = (WORD)(channelCount * 4);
pwfext->Format.wBitsPerSample = 32;
pwfext->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
pwfext->Samples.wValidBitsPerSample = 32;
pwfext->SubFormat = pa_KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
}
else if(sampleFormat == paInt32)
{
pwfext->Format.nBlockAlign = (WORD)(channelCount * 4);
pwfext->Format.wBitsPerSample = 32;
pwfext->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
pwfext->Samples.wValidBitsPerSample = 32;
pwfext->SubFormat = pa_KSDATAFORMAT_SUBTYPE_PCM;
}
else if(sampleFormat == paInt24)
{
pwfext->Format.nBlockAlign = (WORD)(channelCount * 4);
pwfext->Format.wBitsPerSample = 32; // 24-bit in 32-bit int container
pwfext->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
pwfext->Samples.wValidBitsPerSample = 24;
pwfext->SubFormat = pa_KSDATAFORMAT_SUBTYPE_PCM;
}
else if(sampleFormat == paInt16)
{
pwfext->Format.nBlockAlign = (WORD)(channelCount * 2);
pwfext->Format.wBitsPerSample = 16;
pwfext->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
pwfext->Samples.wValidBitsPerSample = 16;
pwfext->SubFormat = pa_KSDATAFORMAT_SUBTYPE_PCM;
}
pwfext->Format.nAvgBytesPerSec = pwfext->Format.nSamplesPerSec * pwfext->Format.nBlockAlign;
}
// ------------------------------------------------------------------------------------------
static PaError GetClosestFormat(IAudioClient *myClient, double sampleRate,
const PaStreamParameters *params, AUDCLNT_SHAREMODE shareMode, WAVEFORMATEXTENSIBLE *outWavex)
{
PaError answer = paInvalidSampleRate;
WAVEFORMATEX *sharedClosestMatch = NULL;
HRESULT hr = !S_OK;
MakeWaveFormatFromParams(outWavex, params, sampleRate);
hr = IAudioClient_IsFormatSupported(myClient, shareMode, &outWavex->Format, (shareMode == AUDCLNT_SHAREMODE_SHARED ? &sharedClosestMatch : NULL));
if (hr == S_OK)
answer = paFormatIsSupported;
else
if (sharedClosestMatch)
{
WORD bitsPerSample;
WAVEFORMATEXTENSIBLE *ext = (WAVEFORMATEXTENSIBLE*)sharedClosestMatch;
GUID subf_guid = GUID_NULL;
if (sharedClosestMatch->wFormatTag == WAVE_FORMAT_EXTENSIBLE)
{
memcpy(outWavex, sharedClosestMatch, sizeof(WAVEFORMATEXTENSIBLE));
subf_guid = ext->SubFormat;
}
else
memcpy(outWavex, sharedClosestMatch, sizeof(WAVEFORMATEX));
CoTaskMemFree(sharedClosestMatch);
// Make supported by default
answer = paFormatIsSupported;
// Validate SampleRate
if ((DWORD)sampleRate != outWavex->Format.nSamplesPerSec)
return paInvalidSampleRate;
// Validate Channel count
if ((WORD)params->channelCount != outWavex->Format.nChannels)
return paInvalidChannelCount;
// Validate Sample format
if ((bitsPerSample = PaSampleFormatToBitsPerSample(params->sampleFormat)) == 0)
return paSampleFormatNotSupported;
// Validate Sample format: bit size (WASAPI does not limit 'bit size')
//if (bitsPerSample != outWavex->Format.wBitsPerSample)
// return paSampleFormatNotSupported;
// Validate Sample format: paFloat32 (WASAPI does not limit 'bit type')
//if ((params->sampleFormat == paFloat32) && (subf_guid != KSDATAFORMAT_SUBTYPE_IEEE_FLOAT))
// return paSampleFormatNotSupported;
// Validate Sample format: paInt32 (WASAPI does not limit 'bit type')
//if ((params->sampleFormat == paInt32) && (subf_guid != KSDATAFORMAT_SUBTYPE_PCM))
// return paSampleFormatNotSupported;
}
else
{
#define FORMATTESTS 3
static const int BestToWorst[FORMATTESTS] = { paFloat32, paInt24, paInt16 };
// try selecting suitable sample type
int i;
for (i = 0; i < FORMATTESTS; ++i)
{
WAVEFORMATEXTENSIBLE ext = { 0 };
wasapiFillWFEXT(&ext, BestToWorst[i], sampleRate, params->channelCount);
hr = IAudioClient_IsFormatSupported(myClient, shareMode, &ext.Format, (shareMode == AUDCLNT_SHAREMODE_SHARED ? &sharedClosestMatch : NULL));
if (hr == S_OK)
{
memcpy(outWavex, &ext, sizeof(WAVEFORMATEXTENSIBLE));
answer = paFormatIsSupported;
break;
}
}
if (answer != paFormatIsSupported)
{
// 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 for 1 channel
// but then mix into 2 of device buffer
if (params->channelCount == 1)
{
WAVEFORMATEXTENSIBLE stereo = { 0 };
wasapiFillWFEXT(&stereo, params->sampleFormat, sampleRate, 2);
hr = IAudioClient_IsFormatSupported(myClient, shareMode, &stereo.Format, (shareMode == AUDCLNT_SHAREMODE_SHARED ? &sharedClosestMatch : NULL));
if (hr == S_OK)
{
memcpy(outWavex, &stereo, sizeof(WAVEFORMATEXTENSIBLE));
answer = paFormatIsSupported;
}
}
}
LogHostError(hr);
}
return answer;
}
// ------------------------------------------------------------------------------------------
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 */
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 */
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 = IMMDevice_Activate(paWasapi->devInfo[inputParameters->device].device,
&pa_IID_IAudioClient, CLSCTX_INPROC_SERVER, NULL, (void **)&tmpClient);
if (hr != S_OK)
{
LogHostError(hr);
return paInvalidDevice;
}
answer = GetClosestFormat(tmpClient, sampleRate, inputParameters, shareMode, &wavex);
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 = IMMDevice_Activate(paWasapi->devInfo[outputParameters->device].device,
&pa_IID_IAudioClient, CLSCTX_INPROC_SERVER, NULL, (void **)&tmpClient);
if (hr != S_OK)
{
LogHostError(hr);
return paInvalidDevice;
}
answer = GetClosestFormat(tmpClient, sampleRate, outputParameters, shareMode, &wavex);
SAFE_RELEASE(tmpClient);
if (answer != paFormatIsSupported)
return answer;
}
return paFormatIsSupported;
}
// ------------------------------------------------------------------------------------------
static HRESULT CreateAudioClient(PaWasapiSubStream *pSubStream, PaWasapiDeviceInfo *info,
const PaStreamParameters *params, UINT32 framesPerLatency, double sampleRate, UINT32 streamFlags,
BOOL blocking, PaError *pa_error)
{
PaError error;
HRESULT hr = S_OK;
UINT32 nFrames = 0;
IAudioClient *pAudioClient = NULL;
if (!pSubStream || !info || !params)
return E_POINTER;
if ((UINT32)sampleRate == 0)
return E_INVALIDARG;
// Get the audio client
hr = IMMDevice_Activate(info->device, &pa_IID_IAudioClient, CLSCTX_ALL, NULL, (void **)&pAudioClient);
if (hr != S_OK)
{
LogHostError(hr);
goto done;
}
// Get closest format
if ((error = GetClosestFormat(pAudioClient, sampleRate, params, pSubStream->shareMode, &pSubStream->wavex)) != paFormatIsSupported)
{
if (pa_error)
(*pa_error) = error;
return AUDCLNT_E_UNSUPPORTED_FORMAT;
}
// Check for Mono >> Stereo workaround
if ((params->channelCount == 1) && (pSubStream->wavex.Format.nChannels == 2))
{
if (blocking)
return AUDCLNT_E_UNSUPPORTED_FORMAT; // fail, blocking mode not supported
// select mixer
pSubStream->monoMixer = _GetMonoToStereoMixer(params->sampleFormat);
if (pSubStream->monoMixer == NULL)
return AUDCLNT_E_UNSUPPORTED_FORMAT; // fail, no mixer for format
}
// Add latency frames
framesPerLatency += MakeFramesFromHns(SecondsTonano100(params->suggestedLatency), pSubStream->wavex.Format.nSamplesPerSec);
// 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 feeded directly to hardware.
if (pSubStream->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE)
{
framesPerLatency = AlignFramesPerBuffer(framesPerLatency,
pSubStream->wavex.Format.nSamplesPerSec, pSubStream->wavex.Format.nBlockAlign);
}
// Calculate period
pSubStream->period = MakeHnsPeriod(framesPerLatency, pSubStream->wavex.Format.nSamplesPerSec);
// Enforce min/max period for device in Shared mode to avoid distorted sound.
// Avoid doing so for Exclusive mode as alignment will suffer. Exclusive mode processes
// big buffers without problem. Push Exclusive beyond limits if possible.
if (pSubStream->shareMode == AUDCLNT_SHAREMODE_SHARED)
{
if (pSubStream->period < info->DefaultDevicePeriod)
pSubStream->period = info->DefaultDevicePeriod;
}
// Open the stream and associate it with an audio session
hr = IAudioClient_Initialize(pAudioClient,
pSubStream->shareMode,
streamFlags/*AUDCLNT_STREAMFLAGS_EVENTCALLBACK*/,
pSubStream->period,
(pSubStream->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? pSubStream->period : 0),
&pSubStream->wavex.Format,
NULL);
// If the requested buffer size is not aligned...
if (hr == AUDCLNT_E_BUFFER_SIZE_NOT_ALIGNED)
{
PRINT(("WASAPI: CreateAudioClient: aligning buffer size"));
// Get the next aligned frame
hr = IAudioClient_GetBufferSize(pAudioClient, &nFrames);
if (hr != S_OK)
{
LogHostError(hr);
goto done;
}
// Release the previous allocations
SAFE_RELEASE(pAudioClient);
// Create a new audio client
hr = IMMDevice_Activate(info->device, &pa_IID_IAudioClient, CLSCTX_ALL, NULL, (void**)&pAudioClient);
if (hr != S_OK)
{
LogHostError(hr);
goto done;
}
// Get closest format
if (GetClosestFormat(pAudioClient, sampleRate, params, pSubStream->shareMode, &pSubStream->wavex) != paFormatIsSupported)
return AUDCLNT_E_UNSUPPORTED_FORMAT;
// Check for Mono >> Stereo workaround
if ((params->channelCount == 1) && (pSubStream->wavex.Format.nChannels == 2))
{
if (blocking)
return AUDCLNT_E_UNSUPPORTED_FORMAT; // fail, blocking mode not supported
// select mixer
pSubStream->monoMixer = _GetMonoToStereoMixer(params->sampleFormat);
if (pSubStream->monoMixer == NULL)
return AUDCLNT_E_UNSUPPORTED_FORMAT; // fail, no mixer for format
}
// Calculate period
pSubStream->period = MakeHnsPeriod(nFrames, pSubStream->wavex.Format.nSamplesPerSec);
// Open the stream and associate it with an audio session
hr = IAudioClient_Initialize(pAudioClient,
pSubStream->shareMode,
streamFlags/*AUDCLNT_STREAMFLAGS_EVENTCALLBACK*/,
pSubStream->period,
(pSubStream->shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? pSubStream->period : 0),
&pSubStream->wavex.Format,
NULL);
if (hr != S_OK)
{
LogHostError(hr);
goto done;
}
}
else
if (hr != S_OK)
{
LogHostError(hr);
goto done;
}
// Set result
pSubStream->client = pAudioClient;
IAudioClient_AddRef(pSubStream->client);
done:
// Clean up
SAFE_RELEASE(pAudioClient);
return hr;
}
// ------------------------------------------------------------------------------------------
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;
UINT32 maxBufferSize;
PaTime buffer_latency;
ULONG framesPerHostCallback;
PaUtilHostBufferSizeMode bufferMode;
// 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 = inputParameters->sampleFormat;
inputStreamInfo = (PaWasapiStreamInfo *)inputParameters->hostApiSpecificStreamInfo;
info = &paWasapi->devInfo[inputParameters->device];
stream->in.flags = (inputStreamInfo ? inputStreamInfo->flags : 0);
// Select Exclusive/Shared mode
stream->in.shareMode = AUDCLNT_SHAREMODE_SHARED;
if ((inputStreamInfo != NULL) && (inputStreamInfo->flags & paWinWasapiExclusive))
{
// Boost thread priority
stream->nThreadPriority = eThreadPriorityProAudio;
// Make Exclusive
stream->in.shareMode = AUDCLNT_SHAREMODE_EXCLUSIVE;
}
// If user provided explicit thread priority level, use it
if ((inputStreamInfo != NULL) && (inputStreamInfo->flags & paWinWasapiThreadPriority))
{
if ((inputStreamInfo->threadPriority > eThreadPriorityNone) &&
(inputStreamInfo->threadPriority <= eThreadPriorityWindowManager))
stream->nThreadPriority = inputStreamInfo->threadPriority;
}
// Choose processing mode
stream->in.streamFlags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
if (paWasapi->useVistaWOW64Workaround)
stream->in.streamFlags = 0; // polling interface
if (streamCallback == NULL)
stream->in.streamFlags = 0; // polling interface
if ((inputStreamInfo != NULL) && (inputStreamInfo->flags & paWinWasapiPolling))
stream->in.streamFlags = 0; // polling interface
// Create Audio client
hr = CreateAudioClient(&stream->in, info, inputParameters, 0/*framesPerLatency*/,
sampleRate, stream->in.streamFlags, (streamCallback == NULL), &result);
if (hr != S_OK)
{
LogHostError(hr);
if (hr != AUDCLNT_E_UNSUPPORTED_FORMAT)
result = paInvalidDevice;
LogPaError(result);
goto error;
}
LogWAVEFORMATEXTENSIBLE(&stream->in.wavex);
// Get closest format
hostInputSampleFormat = PaUtil_SelectClosestAvailableFormat( waveformatToPaFormat(&stream->in.wavex), inputSampleFormat );
// 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;*/
// Set user-side custom host processor
if ((inputStreamInfo != NULL) &&
(inputStreamInfo->flags & paWinWasapiRedirectHostProcessor))
{
stream->hostProcessOverrideInput.processor = inputStreamInfo->hostProcessorInput;
stream->hostProcessOverrideInput.userData = userData;
}
// Get max possible buffer size to check if it is not less than that we request
hr = IAudioClient_GetBufferSize(stream->in.client, &maxBufferSize);
if (hr != S_OK)
{
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 uneeded as we calculate latency from the size
// of maxBufferSize).
hr = IAudioClient_GetStreamLatency(stream->in.client, &stream->in.device_latency);
if (hr != S_OK)
{
LogHostError(hr);
LogPaError(result = paInvalidDevice);
goto error;
}
//stream->in.latency_seconds = nano100ToSeconds(stream->in.device_latency);
// Number of frames that are required at each period
stream->in.framesPerHostCallback = maxBufferSize;
// Calculate buffer latency
buffer_latency = (PaTime)maxBufferSize / stream->in.wavex.Format.nSamplesPerSec;
// Append buffer latency to interface latency in shared mode (see GetStreamLatency notes)
stream->in.latency_seconds += buffer_latency;
PRINT(("PaWASAPIOpenStream(input): framesPerHostCallback[ %d ] latency[ %.02fms ] exclusive[ %s ] wow64_fix[ %s ]\n", (UINT32)stream->in.framesPerHostCallback, (float)(stream->in.latency_seconds*1000.0f), (stream->in.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? "YES" : "NO"), (paWasapi->useVistaWOW64Workaround ? "YES" : "NO")));
}
else
{
inputChannelCount = 0;
inputSampleFormat = hostInputSampleFormat = paInt16; /* Surpress 'uninitialised var' warnings. */
}
// Try create device: Output
if (outputParameters != NULL)
{
outputChannelCount = outputParameters->channelCount;
outputSampleFormat = outputParameters->sampleFormat;
outputStreamInfo = (PaWasapiStreamInfo *)outputParameters->hostApiSpecificStreamInfo;
info = &paWasapi->devInfo[outputParameters->device];
stream->out.flags = (outputStreamInfo ? outputStreamInfo->flags : 0);
// Select Exclusive/Shared mode
stream->out.shareMode = AUDCLNT_SHAREMODE_SHARED;
if ((outputStreamInfo != NULL) && (outputStreamInfo->flags & paWinWasapiExclusive))
{
// Boost thread priority
stream->nThreadPriority = eThreadPriorityProAudio;
// Make Exclusive
stream->out.shareMode = AUDCLNT_SHAREMODE_EXCLUSIVE;
}
// If user provided explicit thread priority level, use it
if ((outputStreamInfo != NULL) && (outputStreamInfo->flags & paWinWasapiThreadPriority))
{
if ((outputStreamInfo->threadPriority > eThreadPriorityNone) &&
(outputStreamInfo->threadPriority <= eThreadPriorityWindowManager))
stream->nThreadPriority = outputStreamInfo->threadPriority;
}
// Choose processing mode
stream->out.streamFlags = AUDCLNT_STREAMFLAGS_EVENTCALLBACK;
if (paWasapi->useVistaWOW64Workaround)
stream->out.streamFlags = 0; // polling interface
if (streamCallback == NULL)
stream->out.streamFlags = 0; // polling interface
if ((outputStreamInfo != NULL) && (outputStreamInfo->flags & paWinWasapiPolling))
stream->out.streamFlags = 0; // polling interface
// Create Audio client
hr = CreateAudioClient(&stream->out, info, outputParameters, 0/*framesPerLatency*/,
sampleRate, stream->out.streamFlags, (streamCallback == NULL), &result);
if (hr != S_OK)
{
LogHostError(hr);
if (hr != AUDCLNT_E_UNSUPPORTED_FORMAT)
result = paInvalidDevice;
LogPaError(result);
goto error;
}
LogWAVEFORMATEXTENSIBLE(&stream->out.wavex);
// Get closest format
hostOutputSampleFormat = PaUtil_SelectClosestAvailableFormat( waveformatToPaFormat(&stream->out.wavex), outputSampleFormat );
// Activate volume
stream->outVol = NULL;
/*hr = info->device->Activate(
__uuidof(IAudioEndpointVolume), CLSCTX_INPROC_SERVER, NULL,
(void**)&stream->outVol);
if (hr != S_OK)
return paInvalidDevice;*/
// Set user-side custom host processor
if ((outputStreamInfo != NULL) &&
(outputStreamInfo->flags & paWinWasapiRedirectHostProcessor))
{
stream->hostProcessOverrideOutput.processor = outputStreamInfo->hostProcessorOutput;
stream->hostProcessOverrideOutput.userData = userData;
}
// Get max possible buffer size to check if it is not less than that we request
hr = IAudioClient_GetBufferSize(stream->out.client, &maxBufferSize);
if (hr != S_OK)
{
LogHostError(hr);
LogPaError(result = paInvalidDevice);
goto error;
}
// Correct buffer to max size if it maxed out result of GetBufferSize
stream->out.bufferSize = maxBufferSize;
// Get interface latency (actually uneeded as we calculate latency from the size
// of maxBufferSize).
hr = IAudioClient_GetStreamLatency(stream->out.client, &stream->out.device_latency);
if (hr != S_OK)
{
LogHostError(hr);
LogPaError(result = paInvalidDevice);
goto error;
}
//stream->out.latency_seconds = nano100ToSeconds(stream->out.device_latency);
// Number of frames that are required at each period
stream->out.framesPerHostCallback = maxBufferSize;
// Calculate buffer latency
buffer_latency = (PaTime)maxBufferSize / stream->out.wavex.Format.nSamplesPerSec;
// Append buffer latency to interface latency in shared mode (see GetStreamLatency notes)
stream->out.latency_seconds += buffer_latency;
PRINT(("PaWASAPIOpenStream(output): framesPerHostCallback[ %d ] latency[ %.02fms ] exclusive[ %s ] wow64_fix[ %s ]\n", (UINT32)stream->out.framesPerHostCallback, (float)(stream->out.latency_seconds*1000.0f), (stream->out.shareMode == AUDCLNT_SHAREMODE_EXCLUSIVE ? "YES" : "NO"), (paWasapi->useVistaWOW64Workaround ? "YES" : "NO")));
}
else
{
outputChannelCount = 0;
outputSampleFormat = hostOutputSampleFormat = paInt16; /* Surpress 'uninitialized var' warnings. */
}
// 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
if (stream->in.period != stream->out.period)
{
PRINT(("WASAPI: OpenStream: period discrepancy\n"));
goto error;
}
// serious problem #2
if (stream->out.framesPerHostCallback != stream->in.framesPerHostCallback)
{
PRINT(("WASAPI: OpenStream: framesPerHostCallback discrepancy\n"));
goto error;
}
}
framesPerHostCallback = (outputParameters) ? stream->out.framesPerHostCallback:
stream->in.framesPerHostCallback;
// Choose correct mode of buffer processing:
// Exclusive/Shared non paWinWasapiPolling mode: paUtilFixedHostBufferSize - always fixed
// Exclusive/Shared paWinWasapiPolling mode: paUtilBoundedHostBufferSize - may vary
bufferMode = paUtilFixedHostBufferSize;
if (inputParameters &&
(!stream->in.streamFlags || ((stream->in.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) == 0)))
bufferMode = paUtilBoundedHostBufferSize;
else
if (outputParameters &&
(!stream->out.streamFlags || ((stream->out.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) == 0)))
bufferMode = paUtilBoundedHostBufferSize;
// Initialize buffer processor
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 =
PaUtil_GetBufferProcessorInputLatency(&stream->bufferProcessor)
+ ((inputParameters)?stream->in.latency_seconds :0);
// Set Output latency
stream->streamRepresentation.streamInfo.outputLatency =
PaUtil_GetBufferProcessorOutputLatency(&stream->bufferProcessor)
+ ((outputParameters)?stream->out.latency_seconds :0);
// Set SR
stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
(*s) = (PaStream *)stream;
return result;
error:
if (stream != NULL)
{
CloseStream(stream);
PaUtil_FreeMemory(stream);
}
return result;
}
// ------------------------------------------------------------------------------------------
static PaError CloseStream( PaStream* s )
{
PaError result = paNoError;
PaWasapiStream *stream = (PaWasapiStream*)s;
// abort active stream
if (IsStreamActive(s))
{
if ((result = AbortStream(s)) != paNoError)
return result;
}
SAFE_RELEASE(stream->out.client);
SAFE_RELEASE(stream->in.client);
SAFE_RELEASE(stream->cclient);
SAFE_RELEASE(stream->rclient);
SAFE_RELEASE(stream->inVol);
SAFE_RELEASE(stream->outVol);
SAFE_CLOSE(stream->hThread);
SAFE_CLOSE(stream->hThreadDone);
SAFE_CLOSE(stream->hCloseRequest);
SAFE_CLOSE(stream->hBlockingOpStreamRD);
SAFE_CLOSE(stream->hBlockingOpStreamWR);
free(stream->in.monoBuffer);
free(stream->out.monoBuffer);
PaUtil_TerminateBufferProcessor(&stream->bufferProcessor);
PaUtil_TerminateStreamRepresentation(&stream->streamRepresentation);
PaUtil_FreeMemory(stream);
return result;
}
// ------------------------------------------------------------------------------------------
static PaError StartStream( PaStream *s )
{
HRESULT hr;
PaWasapiStream *stream = (PaWasapiStream*)s;
// check if stream is active already
if (IsStreamActive(s))
return paStreamIsNotStopped;
PaUtil_ResetBufferProcessor(&stream->bufferProcessor);
// Create close event
stream->hCloseRequest = CreateEvent(NULL, TRUE, FALSE, NULL);
// Create thread
if (!stream->bBlocking)
{
// Create thread done event
stream->hThreadDone = CreateEvent(NULL, TRUE, FALSE, NULL);
if ((stream->in.client && stream->in.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK) ||
(stream->out.client && stream->out.streamFlags & AUDCLNT_STREAMFLAGS_EVENTCALLBACK))
{
if ((stream->hThread = CREATE_THREAD(ProcThreadEvent)) == NULL)
return paUnanticipatedHostError;
}
else
{
if ((stream->hThread = CREATE_THREAD(ProcThreadPoll)) == NULL)
return paUnanticipatedHostError;
}
}
else
{
// Create blocking operation events (non-signaled event means - blocking operation is pending)
if (stream->out.client)
stream->hBlockingOpStreamWR = CreateEvent(NULL, TRUE, TRUE, NULL);
if (stream->in.client)
stream->hBlockingOpStreamRD = CreateEvent(NULL, TRUE, TRUE, NULL);
// Initialize event & start INPUT stream
if (stream->in.client)
{
if ((hr = IAudioClient_GetService(stream->in.client, &pa_IID_IAudioCaptureClient, (void **)&stream->cclient)) != S_OK)
{
LogHostError(hr);
return paUnanticipatedHostError;
}
if ((hr = IAudioClient_Start(stream->in.client)) != S_OK)
{
LogHostError(hr);
return paUnanticipatedHostError;
}
}
// Initialize event & start OUTPUT stream
if (stream->out.client)
{
if ((hr = IAudioClient_GetService(stream->out.client, &pa_IID_IAudioRenderClient, (void **)&stream->rclient)) != S_OK)
{
LogHostError(hr);
return paUnanticipatedHostError;
}
// Start
if ((hr = IAudioClient_Start(stream->out.client)) != S_OK)
{
LogHostError(hr);
return paUnanticipatedHostError;
}
}
// Signal: stream running
stream->running = TRUE;
// Set current time
stream->out.prevTime = timeGetTime();
stream->out.prevSleep = 0;
}
return paNoError;
}
// ------------------------------------------------------------------------------------------
static void _FinishStream(PaWasapiStream *stream)
{
// Issue command to thread to stop processing and wait for thread exit
if (!stream->bBlocking)
{
SignalObjectAndWait(stream->hCloseRequest, stream->hThreadDone, 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.client)
SignalObjectAndWait(stream->hCloseRequest, stream->hBlockingOpStreamWR, INFINITE, TRUE);
if (stream->out.client)
SignalObjectAndWait(stream->hCloseRequest, stream->hBlockingOpStreamRD, INFINITE, TRUE);
// Process stop
_OnStreamStop(stream);
}
// Close thread handles to allow restart
SAFE_CLOSE(stream->hThread);
SAFE_CLOSE(stream->hThreadDone);
SAFE_CLOSE(stream->hCloseRequest);
SAFE_CLOSE(stream->hBlockingOpStreamRD);
SAFE_CLOSE(stream->hBlockingOpStreamWR);
stream->running = FALSE;
}
// ------------------------------------------------------------------------------------------
static PaError StopStream( PaStream *s )
{
// Finish stream
_FinishStream((PaWasapiStream *)s);
return paNoError;
}
// ------------------------------------------------------------------------------------------
static PaError AbortStream( PaStream *s )
{
// Finish stream
_FinishStream((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;
/* IMPLEMENT ME, see portaudio.h for required behavior*/
//this is lame ds and mme does the same thing, quite useless method imho
//why dont we fetch the time in the pa callbacks?
//at least its doing to be clocked to something
return PaUtil_GetTime();
}
// ------------------------------------------------------------------------------------------
static double GetStreamCpuLoad( PaStream* s )
{
return PaUtil_GetCpuLoad(&((PaWasapiStream *)s)->cpuLoadMeasurer);
}
// ------------------------------------------------------------------------------------------
/* NOT TESTED */
static PaError ReadStream( PaStream* s, void *_buffer, unsigned long _frames )
{
PaWasapiStream *stream = (PaWasapiStream*)s;
HRESULT hr = S_OK;
UINT32 frames;
BYTE *buffer = (BYTE *)_buffer;
BYTE *data = NULL;
DWORD flags = 0;
UINT32 buffer_size;
// validate
if (!stream->running)
return paStreamIsStopped;
if (stream->cclient == NULL)
return paBadStreamPtr;
// Notify blocking op has begun
ResetEvent(stream->hBlockingOpStreamRD);
while (_frames != 0)
{
// Get the available data in the shared buffer.
if ((hr = IAudioCaptureClient_GetBuffer(stream->cclient, &data, &frames, &flags, NULL, NULL)) != S_OK)
{
if (hr == AUDCLNT_S_BUFFER_EMPTY)
{
// Check if blocking call must be interrupted
if (WaitForSingleObject(stream->hCloseRequest, 1) != WAIT_TIMEOUT)
break;
}
return LogHostError(hr);
goto stream_rd_end;
}
// Detect silence
// if (flags & AUDCLNT_BUFFERFLAGS_SILENT)
// data = NULL;
// Check if frames <= _frames
if (frames > _frames)
frames = _frames;
// Copy
buffer_size = frames * stream->in.wavex.Format.nBlockAlign;
memcpy(buffer, data, buffer_size);
buffer += buffer_size;
// Release buffer
if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->cclient, frames)) != S_OK)
{
LogHostError(hr);
goto stream_rd_end;
}
_frames -= frames;
}
stream_rd_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 *buffer = (BYTE *)_buffer;
BYTE *data;
HRESULT hr = S_OK;
UINT32 next_rev_sleep, blocks, block_sleep_ms;
UINT32 i;
// validate
if (!stream->running)
return paStreamIsStopped;
if (stream->rclient == NULL)
return paBadStreamPtr;
// Notify blocking op has begun
ResetEvent(stream->hBlockingOpStreamWR);
// Calculate sleep time for next call
{
UINT32 remainder = 0;
UINT32 sleep_ms = 0;
DWORD elapsed_ms;
blocks = _frames / stream->out.framesPerHostCallback;
block_sleep_ms = GetFramesSleepTime(stream->out.framesPerHostCallback, stream->out.wavex.Format.nSamplesPerSec);
if (blocks == 0)
{
blocks = 1;
sleep_ms = GetFramesSleepTime(_frames, stream->out.wavex.Format.nSamplesPerSec); // partial
}
else
{
remainder = _frames - blocks * stream->out.framesPerHostCallback;
sleep_ms = block_sleep_ms; // full
}
// Sleep for remainder
elapsed_ms = timeGetTime() - stream->out.prevTime;
if (sleep_ms >= elapsed_ms)
sleep_ms -= elapsed_ms;
next_rev_sleep = sleep_ms;
}
// Sleep diff from last call
if (stream->out.prevSleep)
Sleep(stream->out.prevSleep);
stream->out.prevSleep = next_rev_sleep;
// Feed engine
for (i = 0; i < blocks; ++i)
{
UINT32 available;
UINT32 buffer_size;
// Get block frames
frames = stream->out.framesPerHostCallback;
if (frames > _frames)
frames = _frames;
if (i)
Sleep(block_sleep_ms);
while (frames != 0)
{
UINT32 padding = 0;
// Check if blocking call must be interrupted
if (WaitForSingleObject(stream->hCloseRequest, 0) != WAIT_TIMEOUT)
break;
// Get Read position
hr = IAudioClient_GetCurrentPadding(stream->out.client, &padding);
if (hr != S_OK)
{
LogHostError(hr);
goto stream_wr_end;
}
// Get frames available
if (frames >= padding)
available = frames - padding;
else
available = frames;
// Get buffer
if ((hr = IAudioRenderClient_GetBuffer(stream->rclient, available, &data)) != S_OK)
{
// Buffer size is too big, waiting
if (hr == AUDCLNT_E_BUFFER_TOO_LARGE)
continue;
LogHostError(hr);
goto stream_wr_end;
}
// Copy
buffer_size = available * stream->out.wavex.Format.nBlockAlign;
memcpy(data, buffer, buffer_size);
buffer += buffer_size;
// Release buffer
if ((hr = IAudioRenderClient_ReleaseBuffer(stream->rclient, available, 0)) != S_OK)
{
LogHostError(hr);
goto stream_wr_end;
}
frames -= available;
}
_frames -= frames;
}
stream_wr_end:
// Set prev time
stream->out.prevTime = timeGetTime();
// Notify blocking op has ended
SetEvent(stream->hBlockingOpStreamWR);
return (hr != S_OK ? paUnanticipatedHostError : paNoError);
}
// ------------------------------------------------------------------------------------------
/* NOT TESTED */
static signed long GetStreamReadAvailable( PaStream* s )
{
PaWasapiStream *stream = (PaWasapiStream*)s;
HRESULT hr;
UINT32 pending = 0;
// validate
if (!stream->running)
return paStreamIsStopped;
if (stream->cclient == NULL)
return paBadStreamPtr;
hr = IAudioClient_GetCurrentPadding(stream->in.client, &pending);
if (hr != S_OK)
{
LogHostError(hr);
return paUnanticipatedHostError;
}
return (long)pending;
}
// ------------------------------------------------------------------------------------------
static signed long GetStreamWriteAvailable( PaStream* s )
{
PaWasapiStream *stream = (PaWasapiStream*)s;
UINT32 frames = stream->out.framesPerHostCallback;
HRESULT hr;
UINT32 padding = 0;
// validate
if (!stream->running)
return paStreamIsStopped;
if (stream->rclient == NULL)
return paBadStreamPtr;
hr = IAudioClient_GetCurrentPadding(stream->out.client, &padding);
if (hr != S_OK)
{
LogHostError(hr);
return paUnanticipatedHostError;
}
// Calculate
frames -= padding;
return frames;
}
// ------------------------------------------------------------------------------------------
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.client != NULL)
{
PaTime pending_time;
if ((hr = IAudioClient_GetCurrentPadding(stream->in.client, &pending)) == S_OK)
pending_time = (PaTime)pending / (PaTime)stream->in.wavex.Format.nSamplesPerSec;
else
pending_time = (PaTime)stream->in.latency_seconds;
timeInfo.inputBufferAdcTime = timeInfo.currentTime + pending_time;
}
// Query output current latency
if (stream->out.client != NULL)
{
PaTime pending_time;
if ((hr = IAudioClient_GetCurrentPadding(stream->out.client, &pending)) == S_OK)
pending_time = (PaTime)pending / (PaTime)stream->out.wavex.Format.nSamplesPerSec;
else
pending_time = (PaTime)stream->out.latency_seconds;
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);
}
}
// ------------------------------------------------------------------------------------------
HANDLE MMCSS_activate(const char *name)
{
DWORD task_idx = 0;
HANDLE hTask = pAvSetMmThreadCharacteristics(name, &task_idx);
if (hTask == NULL)
{
PRINT(("WASAPI: AvSetMmThreadCharacteristics failed!\n"));
}
/*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));
}
return hTask;
}
// ------------------------------------------------------------------------------------------
void MMCSS_deactivate(HANDLE hTask)
{
if (!hTask)
return;
if (pAvRevertMmThreadCharacteristics(hTask) == FALSE)
{
PRINT(("WASAPI: AvRevertMmThreadCharacteristics failed!\n"));
}
}
// ------------------------------------------------------------------------------------------
PaError PaWasapi_ThreadPriorityBoost(void **hTask, PaWasapiThreadPriority nPriorityClass)
{
static const char *mmcs_name[] =
{
NULL,
"Audio",
"Capture",
"Distribution",
"Games",
"Playback",
"Pro Audio",
"Window Manager"
};
HANDLE task;
if (hTask == NULL)
return paUnanticipatedHostError;
if ((UINT32)nPriorityClass >= STATIC_ARRAY_SIZE(mmcs_name))
return paUnanticipatedHostError;
task = MMCSS_activate(mmcs_name[nPriorityClass]);
if (task == NULL)
return paUnanticipatedHostError;
(*hTask) = task;
return paNoError;
}
// ------------------------------------------------------------------------------------------
PaError PaWasapi_ThreadPriorityRevert(void *hTask)
{
if (hTask == NULL)
return paUnanticipatedHostError;
MMCSS_deactivate((HANDLE)hTask);
return paNoError;
}
// ------------------------------------------------------------------------------------------
static HRESULT FillOutputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor *processor, UINT32 frames)
{
HRESULT hr;
BYTE *data = NULL;
// Get buffer
if ((hr = IAudioRenderClient_GetBuffer(stream->rclient, frames, &data)) != S_OK)
{
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 = stream->out.client->GetCurrentPadding(&padding);
if (hr != S_OK)
return LogHostError(hr);
// Get frames to write
frames -= padding;
if (frames == 0)
return S_OK;
if ((hr = stream->rclient->GetBuffer(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);
}
// Process data
if (stream->out.monoMixer != NULL)
{
#define __DIV_8(v) ((v) >> 3) //!< (v / 8)
// expand buffer (one way only for better performancedue to no calls to realloc)
UINT32 mono_frames_size = frames * __DIV_8(stream->out.wavex.Format.wBitsPerSample);
if (mono_frames_size > stream->out.monoBufferSize)
{
stream->out.monoBuffer = realloc(stream->out.monoBuffer, mono_frames_size);
}
// feed to user
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);
#undef __DIV_8
}
else
{
processor[S_OUTPUT].processor(NULL, 0, data, frames, processor[S_OUTPUT].userData);
}
// Release buffer
if ((hr = IAudioRenderClient_ReleaseBuffer(stream->rclient, frames, 0)) != S_OK)
LogHostError(hr);
return hr;
}
// ------------------------------------------------------------------------------------------
static HRESULT PollInputBuffer(PaWasapiStream *stream, PaWasapiHostProcessor *processor)
{
HRESULT hr;
UINT32 frames;
BYTE *data = NULL;
DWORD flags = 0;
for (;;)
{
// Get the available data in the shared buffer.
if ((hr = IAudioCaptureClient_GetBuffer(stream->cclient, &data, &frames, &flags, NULL, NULL)) != S_OK)
{
if (hr == AUDCLNT_S_BUFFER_EMPTY)
break; // capture buffer exhausted
return LogHostError(hr);
break;
}
// Detect silence
// if (flags & AUDCLNT_BUFFERFLAGS_SILENT)
// data = NULL;
// Process data
if (stream->in.monoMixer != NULL)
{
#define __DIV_8(v) ((v) >> 3) //!< (v / 8)
// expand buffer (one way only for better performancedue to no calls to realloc)
UINT32 mono_frames_size = frames * __DIV_8(stream->in.wavex.Format.wBitsPerSample);
if (mono_frames_size > stream->in.monoBufferSize)
{
stream->in.monoBuffer = realloc(stream->in.monoBuffer, mono_frames_size);
}
// feed to user
processor[S_INPUT].processor((BYTE *)stream->in.monoBuffer, frames, NULL, 0, processor[S_INPUT].userData);
// mix 1 to 2 channels
stream->in.monoMixer(data, stream->in.monoBuffer, frames);
#undef __DIV_8
}
else
{
processor[S_INPUT].processor(data, frames, NULL, 0, processor[S_INPUT].userData);
}
// Release buffer
if ((hr = IAudioCaptureClient_ReleaseBuffer(stream->cclient, frames)) != S_OK)
return LogHostError(hr);
}
return hr;
}
// ------------------------------------------------------------------------------------------
void _OnStreamStop(PaWasapiStream *stream)
{
// Stop INPUT client
if (stream->in.client != NULL)
IAudioClient_Stop(stream->in.client);
// Stop OUTPUT client
if (stream->out.client != NULL)
IAudioClient_Stop(stream->out.client);
// 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);
}
// ------------------------------------------------------------------------------------------
PA_THREAD_FUNC ProcThreadEvent(void *param)
{
HANDLE event[S_COUNT];
PaWasapiHostProcessor processor[S_COUNT];
HRESULT hr;
DWORD dwResult;
PaWasapiStream *stream = (PaWasapiStream *)param;
PaWasapiHostProcessor defaultProcessor;
// Waiting on all events in case of Full-Duplex/Exclusive mode.
BOOL bWaitAllEvents = FALSE;
if ((stream->in.client != NULL) && (stream->out.client != NULL))
{
bWaitAllEvents = (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);
// Initialize event to NULL first
event[S_INPUT] = CreateEvent(NULL, FALSE, FALSE, NULL);
event[S_OUTPUT] = CreateEvent(NULL, FALSE, FALSE, NULL);
// Check on low resources and disallow any processing further
if (!event[S_INPUT] || !event[S_OUTPUT])
{
PRINT(("WASAPI Thread: failed creating one or two event handles\n"));
goto thread_end;
}
// Boost thread priority
PaWasapi_ThreadPriorityBoost((void **)&stream->hAvTask, stream->nThreadPriority);
// Initialize event & start INPUT stream
if (stream->in.client)
{
if ((hr = IAudioClient_SetEventHandle(stream->in.client, event[S_INPUT])) != S_OK)
LogHostError(hr);
if ((hr = IAudioClient_GetService(stream->in.client, &pa_IID_IAudioCaptureClient, (void **)&stream->cclient)) != S_OK)
LogHostError(hr);
if ((hr = IAudioClient_Start(stream->in.client)) != S_OK)
LogHostError(hr);
}
// Initialize event & start OUTPUT stream
if (stream->out.client)
{
if ((hr = IAudioClient_SetEventHandle(stream->out.client, event[S_OUTPUT])) != S_OK)
LogHostError(hr);
if ((hr = IAudioClient_GetService(stream->out.client, &pa_IID_IAudioRenderClient, (void **)&stream->rclient)) != S_OK)
LogHostError(hr);
// Preload buffer before start
if ((hr = FillOutputBuffer(stream, processor, stream->out.framesPerHostCallback)) != S_OK)
LogHostError(hr);
// Start
if ((hr = IAudioClient_Start(stream->out.client)) != S_OK)
LogHostError(hr);
}
// Signal: stream running
stream->running = TRUE;
// Processing Loop
for (;;)
{
// 2 sec timeout
dwResult = WaitForMultipleObjects(S_COUNT, event, bWaitAllEvents, 10000);
// 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 processing_stop;
break; }
// Input stream
case WAIT_OBJECT_0 + S_INPUT: {
if (stream->cclient == NULL)
break;
PollInputBuffer(stream, processor);
break; }
// Output stream
case WAIT_OBJECT_0 + S_OUTPUT: {
if (stream->rclient == NULL)
break;
FillOutputBuffer(stream, processor, stream->out.framesPerHostCallback);
break; }
}
}
processing_stop:
// Process stop
_OnStreamStop(stream);
thread_end:
// Close event
CloseHandle(event[S_INPUT]);
CloseHandle(event[S_OUTPUT]);
// Notify thread is exiting
SetEvent(stream->hThreadDone);
return 0;
}
// ------------------------------------------------------------------------------------------
PA_THREAD_FUNC ProcThreadPoll(void *param)
{
PaWasapiHostProcessor processor[S_COUNT];
HRESULT hr;
PaWasapiStream *stream = (PaWasapiStream *)param;
PaWasapiHostProcessor defaultProcessor;
INT32 i;
// Calculate the actual duration of the allocated buffer.
DWORD sleep_ms = 0;
DWORD sleep_ms_in = GetFramesSleepTime(stream->in.framesPerHostCallback, stream->in.wavex.Format.nSamplesPerSec);
DWORD sleep_ms_out = GetFramesSleepTime(stream->out.framesPerHostCallback, stream->out.wavex.Format.nSamplesPerSec);
if ((sleep_ms_in != 0) && (sleep_ms_out != 0))
sleep_ms = min(sleep_ms_in, sleep_ms_out);
else
{
sleep_ms = (sleep_ms_in ? sleep_ms_in : sleep_ms_out);
}
// 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);
// Initialize event & start INPUT stream
if (stream->in.client)
{
if ((hr = IAudioClient_GetService(stream->in.client, &pa_IID_IAudioCaptureClient, (void **)&stream->cclient)) != S_OK)
LogHostError(hr);
if ((hr = IAudioClient_Start(stream->in.client)) != S_OK)
LogHostError(hr);
}
// Initialize event & start OUTPUT stream
if (stream->out.client)
{
if ((hr = IAudioClient_GetService(stream->out.client, &pa_IID_IAudioRenderClient, (void **)&stream->rclient)) != S_OK)
LogHostError(hr);
// Preload buffer (obligatory, othervise ->Start() will fail)
if ((hr = FillOutputBuffer(stream, processor, stream->out.framesPerHostCallback)) != S_OK)
LogHostError(hr);
// Start
if ((hr = IAudioClient_Start(stream->out.client)) != S_OK)
LogHostError(hr);
}
// Signal: stream running
stream->running = TRUE;
// Processing Loop
while (WaitForSingleObject(stream->hCloseRequest, sleep_ms) == WAIT_TIMEOUT)
{
for (i = 0; i < S_COUNT; ++i)
{
// Process S_INPUT/S_OUTPUT
switch (i)
{
// Input stream
case S_INPUT: {
if (stream->cclient == NULL)
break;
PollInputBuffer(stream, processor);
break; }
// Output stream
case S_OUTPUT: {
UINT32 frames, padding;
if (stream->rclient == NULL)
break;
frames = stream->out.framesPerHostCallback;
// Get Read position
padding = 0;
hr = IAudioClient_GetCurrentPadding(stream->out.client, &padding);
if (hr != S_OK)
{
LogHostError(hr);
break;
}
// Fill buffer
frames -= padding;
if (frames != 0)
FillOutputBuffer(stream, processor, frames);
break; }
}
}
}
// Process stop
_OnStreamStop(stream);
// Notify thread is exiting
SetEvent(stream->hThreadDone);
return 0;
}
//#endif //VC 2005
#if 0
if(bFirst) {
float masteur;
hr = stream->outVol->GetMasterVolumeLevelScalar(&masteur);
if (hr != S_OK)
LogHostError(hr);
float chan1, chan2;
hr = stream->outVol->GetChannelVolumeLevelScalar(0, &chan1);
if (hr != S_OK)
LogHostError(hr);
hr = stream->outVol->GetChannelVolumeLevelScalar(1, &chan2);
if (hr != S_OK)
LogHostError(hr);
BOOL bMute;
hr = stream->outVol->GetMute(&bMute);
if (hr != S_OK)
LogHostError(hr);
stream->outVol->SetMasterVolumeLevelScalar(0.5, NULL);
stream->outVol->SetChannelVolumeLevelScalar(0, 0.5, NULL);
stream->outVol->SetChannelVolumeLevelScalar(1, 0.5, NULL);
stream->outVol->SetMute(FALSE, NULL);
bFirst = FALSE;
}
#endif