From 37c97e345d12f95dde44e1d1a4c2f2aadd4615bc Mon Sep 17 00:00:00 2001 From: sanine Date: Thu, 25 Aug 2022 14:54:53 -0500 Subject: add initial structure --- portaudio/src/hostapi/wdmks/pa_win_wdmks.c | 6807 ++++++++++++++++++++++++++++ portaudio/src/hostapi/wdmks/readme.txt | 85 + 2 files changed, 6892 insertions(+) create mode 100644 portaudio/src/hostapi/wdmks/pa_win_wdmks.c create mode 100644 portaudio/src/hostapi/wdmks/readme.txt (limited to 'portaudio/src/hostapi/wdmks') diff --git a/portaudio/src/hostapi/wdmks/pa_win_wdmks.c b/portaudio/src/hostapi/wdmks/pa_win_wdmks.c new file mode 100644 index 0000000..161c264 --- /dev/null +++ b/portaudio/src/hostapi/wdmks/pa_win_wdmks.c @@ -0,0 +1,6807 @@ +/* +* $Id$ +* PortAudio Windows WDM-KS interface +* +* Author: Andrew Baldwin, Robert Bielik (WaveRT) +* Based on the Open Source API proposed by Ross Bencina +* Copyright (c) 1999-2004 Andrew Baldwin, 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 Portaudio WDM-KS host API. + +@note This is the implementation of the Portaudio host API using the +Windows WDM/Kernel Streaming API in order to enable very low latency +playback and recording on all modern Windows platforms (e.g. 2K, XP, Vista, Win7) +Note: This API accesses the device drivers below the usual KMIXER +component which is normally used to enable multi-client mixing and +format conversion. That means that it will lock out all other users +of a device for the duration of active stream using those devices +*/ + +#include + +#if (defined(_WIN32) && (defined(_MSC_VER) && (_MSC_VER >= 1200))) /* MSC version 6 and above */ +#pragma comment( lib, "setupapi.lib" ) +#endif + +/* Debugging/tracing support */ + +#define PA_LOGE_ +#define PA_LOGL_ + +#ifdef __GNUC__ +#include +#define _WIN32_WINNT 0x0501 +#define WINVER 0x0501 +#endif + +#include /* strlen() */ +#include +#include /* iswspace() */ + +#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 "portaudio.h" +#include "pa_debugprint.h" +#include "pa_memorybarrier.h" +#include "pa_ringbuffer.h" +#include "pa_trace.h" +#include "pa_win_waveformat.h" + +#include "pa_win_wdmks.h" + +#ifndef DRV_QUERYDEVICEINTERFACE +#define DRV_QUERYDEVICEINTERFACE (DRV_RESERVED + 12) +#endif +#ifndef DRV_QUERYDEVICEINTERFACESIZE +#define DRV_QUERYDEVICEINTERFACESIZE (DRV_RESERVED + 13) +#endif + +#include +#ifndef __GNUC__ /* Fix for ticket #257: MinGW-w64: Inclusion of triggers multiple redefinition errors. */ +#include +#endif +#include + +#include + +#ifdef _MSC_VER +#define snprintf _snprintf +#define vsnprintf _vsnprintf +#endif + +/* The PA_HP_TRACE macro is used in RT parts, so it can be switched off without affecting +the rest of the debug tracing */ +#if 1 +#define PA_HP_TRACE(x) PaUtil_AddHighSpeedLogMessage x ; +#else +#define PA_HP_TRACE(x) +#endif + +/* A define that selects whether the resulting pin names are chosen from pin category +instead of the available pin names, who sometimes can be quite cheesy, like "Volume control". +Default is to use the pin category. +*/ +#ifndef PA_WDMKS_USE_CATEGORY_FOR_PIN_NAMES +#define PA_WDMKS_USE_CATEGORY_FOR_PIN_NAMES 1 +#endif + +#ifdef __GNUC__ +#undef PA_LOGE_ +#define PA_LOGE_ PA_DEBUG(("%s {\n",__FUNCTION__)) +#undef PA_LOGL_ +#define PA_LOGL_ PA_DEBUG(("} %s\n",__FUNCTION__)) +/* These defines are set in order to allow the WIndows DirectX +* headers to compile with a GCC compiler such as MinGW +* NOTE: The headers may generate a few warning in GCC, but +* they should compile */ +#define _INC_MMSYSTEM +#define _INC_MMREG +#define _NTRTL_ /* Turn off default definition of DEFINE_GUIDEX */ +#define DEFINE_GUID_THUNK(name,guid) DEFINE_GUID(name,guid) +#define DEFINE_GUIDEX(n) DEFINE_GUID_THUNK( n, STATIC_##n ) +#if !defined( DEFINE_WAVEFORMATEX_GUID ) +#define DEFINE_WAVEFORMATEX_GUID(x) (USHORT)(x), 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71 +#endif +#define WAVE_FORMAT_ADPCM 0x0002 +#define WAVE_FORMAT_IEEE_FLOAT 0x0003 +#define WAVE_FORMAT_ALAW 0x0006 +#define WAVE_FORMAT_MULAW 0x0007 +#define WAVE_FORMAT_MPEG 0x0050 +#define WAVE_FORMAT_DRM 0x0009 +#define DYNAMIC_GUID_THUNK(l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}} +#define DYNAMIC_GUID(data) DYNAMIC_GUID_THUNK(data) +#endif + +/* use CreateThread for CYGWIN/Windows Mobile, _beginthreadex for all others */ +#if !defined(__CYGWIN__) && !defined(_WIN32_WCE) +#define CREATE_THREAD_FUNCTION (HANDLE)_beginthreadex +#define PA_THREAD_FUNC static unsigned WINAPI +#else +#define CREATE_THREAD_FUNCTION CreateThread +#define PA_THREAD_FUNC static DWORD WINAPI +#endif + +#ifdef _MSC_VER +#define NOMMIDS +#define DYNAMIC_GUID(data) {data} +#define _NTRTL_ /* Turn off default definition of DEFINE_GUIDEX */ +#undef DEFINE_GUID +#if defined(__clang__) || (defined(_MSVC_TRADITIONAL) && !_MSVC_TRADITIONAL) /* clang-cl and new msvc preprocessor: avoid too many arguments error */ + #define DEFINE_GUID(n, ...) EXTERN_C const GUID n = {__VA_ARGS__} + #define DEFINE_GUID_THUNK(n, ...) DEFINE_GUID(n, __VA_ARGS__) + #define DEFINE_GUIDEX(n) DEFINE_GUID_THUNK(n, STATIC_##n) +#else + #define DEFINE_GUID(n, data) EXTERN_C const GUID n = {data} + #define DEFINE_GUID_THUNK(n, data) DEFINE_GUID(n, data) + #define DEFINE_GUIDEX(n) DEFINE_GUID_THUNK(n, STATIC_##n) +#endif /* __clang__, !_MSVC_TRADITIONAL */ +#endif + +#include + +#ifndef EXTERN_C +#define EXTERN_C extern +#endif + +#if defined(__GNUC__) + +/* For MinGW we reference mingw-include files supplied with WASAPI */ +#define WINBOOL BOOL + +#include "../wasapi/mingw-include/ks.h" +#include "../wasapi/mingw-include/ksmedia.h" + +#else + +#include +#include + +/* Note that Windows SDK V6.0A or later is needed for WaveRT specific structs to be present in + ksmedia.h. Also make sure that the SDK include path is before other include paths (that may contain + an "old" ksmedia.h), so the proper ksmedia.h is used */ +#include + +#endif + +#include +#include + +/* These next definitions allow the use of the KSUSER DLL */ +typedef /*KSDDKAPI*/ DWORD WINAPI KSCREATEPIN(HANDLE, PKSPIN_CONNECT, ACCESS_MASK, PHANDLE); +extern HMODULE DllKsUser; +extern KSCREATEPIN* FunctionKsCreatePin; + +/* These definitions allows the use of AVRT.DLL on Vista and later OSs */ +typedef enum _PA_AVRT_PRIORITY +{ + PA_AVRT_PRIORITY_LOW = -1, + PA_AVRT_PRIORITY_NORMAL, + PA_AVRT_PRIORITY_HIGH, + PA_AVRT_PRIORITY_CRITICAL +} PA_AVRT_PRIORITY, *PPA_AVRT_PRIORITY; + +typedef struct +{ + HINSTANCE hInstance; + + HANDLE (WINAPI *AvSetMmThreadCharacteristics) (LPCSTR, LPDWORD); + BOOL (WINAPI *AvRevertMmThreadCharacteristics) (HANDLE); + BOOL (WINAPI *AvSetMmThreadPriority) (HANDLE, PA_AVRT_PRIORITY); +} PaWinWDMKSAvRtEntryPoints; + +static PaWinWDMKSAvRtEntryPoints paWinWDMKSAvRtEntryPoints = {0}; + +/* An unspecified channel count (-1) is not treated correctly, so we replace it with +* an arbitrarily large number */ +#define MAXIMUM_NUMBER_OF_CHANNELS 256 + +/* Forward definition to break circular type reference between pin and filter */ +struct __PaWinWdmFilter; +typedef struct __PaWinWdmFilter PaWinWdmFilter; + +struct __PaWinWdmPin; +typedef struct __PaWinWdmPin PaWinWdmPin; + +struct __PaWinWdmStream; +typedef struct __PaWinWdmStream PaWinWdmStream; + +/* Function prototype for getting audio position */ +typedef PaError (*FunctionGetPinAudioPosition)(PaWinWdmPin*, unsigned long*); + +/* Function prototype for memory barrier */ +typedef void (*FunctionMemoryBarrier)(void); + +struct __PaProcessThreadInfo; +typedef struct __PaProcessThreadInfo PaProcessThreadInfo; + +typedef PaError (*FunctionPinHandler)(PaProcessThreadInfo* pInfo, unsigned eventIndex); + +typedef enum __PaStreamStartEnum +{ + StreamStart_kOk, + StreamStart_kFailed, + StreamStart_kCnt +} PaStreamStartEnum; + +/* Multiplexed input structure. +* Very often several physical inputs are multiplexed through a MUX node (represented in the topology filter) */ +typedef struct __PaWinWdmMuxedInput +{ + wchar_t friendlyName[MAX_PATH]; + ULONG muxPinId; + ULONG muxNodeId; + ULONG endpointPinId; +} PaWinWdmMuxedInput; + +/* The Pin structure +* A pin is an input or output node, e.g. for audio flow */ +struct __PaWinWdmPin +{ + HANDLE handle; + PaWinWdmMuxedInput** inputs; + unsigned inputCount; + wchar_t friendlyName[MAX_PATH]; + + PaWinWdmFilter* parentFilter; + PaWDMKSSubType pinKsSubType; + unsigned long pinId; + unsigned long endpointPinId; /* For output pins */ + KSPIN_CONNECT* pinConnect; + unsigned long pinConnectSize; + KSDATAFORMAT_WAVEFORMATEX* ksDataFormatWfx; + KSPIN_COMMUNICATION communication; + KSDATARANGE* dataRanges; + KSMULTIPLE_ITEM* dataRangesItem; + KSPIN_DATAFLOW dataFlow; + KSPIN_CINSTANCES instances; + unsigned long frameSize; + int maxChannels; + unsigned long formats; + int defaultSampleRate; + ULONG *positionRegister; /* WaveRT */ + ULONG hwLatency; /* WaveRT */ + FunctionMemoryBarrier fnMemBarrier; /* WaveRT */ + FunctionGetPinAudioPosition fnAudioPosition; /* WaveRT */ + FunctionPinHandler fnEventHandler; + FunctionPinHandler fnSubmitHandler; +}; + +/* The Filter structure +* A filter has a number of pins and a "friendly name" */ +struct __PaWinWdmFilter +{ + HANDLE handle; + PaWinWDMKSDeviceInfo devInfo; /* This will hold information that is exposed in PaDeviceInfo */ + + DWORD deviceNode; + int pinCount; + PaWinWdmPin** pins; + PaWinWdmFilter* topologyFilter; + wchar_t friendlyName[MAX_PATH]; + int validPinCount; + int usageCount; + KSMULTIPLE_ITEM* connections; + KSMULTIPLE_ITEM* nodes; + int filterRefCount; +}; + + +typedef struct __PaWinWdmDeviceInfo +{ + PaDeviceInfo inheritedDeviceInfo; + char compositeName[MAX_PATH]; /* Composite name consists of pin name + device name in utf8 */ + PaWinWdmFilter* filter; + unsigned long pin; + int muxPosition; /* Used only for input devices */ + int endpointPinId; +} +PaWinWdmDeviceInfo; + +/* PaWinWdmHostApiRepresentation - host api datastructure specific to this implementation */ +typedef struct __PaWinWdmHostApiRepresentation +{ + PaUtilHostApiRepresentation inheritedHostApiRep; + PaUtilStreamInterface callbackStreamInterface; + PaUtilStreamInterface blockingStreamInterface; + + PaUtilAllocationGroup* allocations; + int deviceCount; +} +PaWinWdmHostApiRepresentation; + +typedef struct __DATAPACKET +{ + KSSTREAM_HEADER Header; + OVERLAPPED Signal; +} DATAPACKET; + +typedef struct __PaIOPacket +{ + DATAPACKET* packet; + unsigned startByte; + unsigned lengthBytes; +} PaIOPacket; + +typedef struct __PaWinWdmIOInfo +{ + PaWinWdmPin* pPin; + char* hostBuffer; + unsigned hostBufferSize; + unsigned framesPerBuffer; + unsigned bytesPerFrame; + unsigned bytesPerSample; + unsigned noOfPackets; /* Only used in WaveCyclic */ + HANDLE *events; /* noOfPackets handles (WaveCyclic) 1 (WaveRT) */ + DATAPACKET *packets; /* noOfPackets packets (WaveCyclic) 2 (WaveRT) */ + /* WaveRT polled mode */ + unsigned lastPosition; + unsigned pollCntr; +} PaWinWdmIOInfo; + +/* PaWinWdmStream - a stream data structure specifically for this implementation */ +struct __PaWinWdmStream +{ + PaUtilStreamRepresentation streamRepresentation; + PaWDMKSSpecificStreamInfo hostApiStreamInfo; /* This holds info that is exposed through PaStreamInfo */ + PaUtilCpuLoadMeasurer cpuLoadMeasurer; + PaUtilBufferProcessor bufferProcessor; + +#if PA_TRACE_REALTIME_EVENTS + LogHandle hLog; +#endif + + PaUtilAllocationGroup* allocGroup; + PaWinWdmIOInfo capture; + PaWinWdmIOInfo render; + int streamStarted; + int streamActive; + int streamStop; + int streamAbort; + int oldProcessPriority; + HANDLE streamThread; + HANDLE eventAbort; + HANDLE eventStreamStart[StreamStart_kCnt]; /* 0 = OK, 1 = Failed */ + PaError threadResult; + PaStreamFlags streamFlags; + + /* Capture ring buffer */ + PaUtilRingBuffer ringBuffer; + char* ringBufferData; + + /* These values handle the case where the user wants to use fewer + * channels than the device has */ + int userInputChannels; + int deviceInputChannels; + int userOutputChannels; + int deviceOutputChannels; +}; + +/* Gather all processing variables in a struct */ +struct __PaProcessThreadInfo +{ + PaWinWdmStream *stream; + PaStreamCallbackTimeInfo ti; + PaStreamCallbackFlags underover; + int cbResult; + volatile int pending; + volatile int priming; + volatile int pinsStarted; + unsigned long timeout; + unsigned captureHead; + unsigned captureTail; + unsigned renderHead; + unsigned renderTail; + PaIOPacket capturePackets[4]; + PaIOPacket renderPackets[4]; +}; + +/* Used for transferring device infos during scanning / rescanning */ +typedef struct __PaWinWDMScanDeviceInfosResults +{ + PaDeviceInfo **deviceInfos; + PaDeviceIndex defaultInputDevice; + PaDeviceIndex defaultOutputDevice; +} PaWinWDMScanDeviceInfosResults; + +static const unsigned cPacketsArrayMask = 3; + +HMODULE DllKsUser = NULL; +KSCREATEPIN* FunctionKsCreatePin = NULL; + +/* prototypes for functions declared in this file */ + +#ifdef __cplusplus +extern "C" +{ +#endif /* __cplusplus */ + + PaError PaWinWdm_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex index ); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +/* Low level I/O functions */ +static PaError WdmSyncIoctl(HANDLE handle, + unsigned long ioctlNumber, + void* inBuffer, + unsigned long inBufferCount, + void* outBuffer, + unsigned long outBufferCount, + unsigned long* bytesReturned); + +static PaError WdmGetPropertySimple(HANDLE handle, + const GUID* const guidPropertySet, + unsigned long property, + void* value, + unsigned long valueCount); + +static PaError WdmSetPropertySimple(HANDLE handle, + const GUID* const guidPropertySet, + unsigned long property, + void* value, + unsigned long valueCount, + void* instance, + unsigned long instanceCount); + +static PaError WdmGetPinPropertySimple(HANDLE handle, + unsigned long pinId, + const GUID* const guidPropertySet, + unsigned long property, + void* value, + unsigned long valueCount, + unsigned long* byteCount); + +static PaError WdmGetPinPropertyMulti(HANDLE handle, + unsigned long pinId, + const GUID* const guidPropertySet, + unsigned long property, + KSMULTIPLE_ITEM** ksMultipleItem); + +static PaError WdmGetPropertyMulti(HANDLE handle, + const GUID* const guidPropertySet, + unsigned long property, + KSMULTIPLE_ITEM** ksMultipleItem); + +static PaError WdmSetMuxNodeProperty(HANDLE handle, + ULONG nodeId, + ULONG pinId); + + +/** Pin management functions */ +static PaWinWdmPin* PinNew(PaWinWdmFilter* parentFilter, unsigned long pinId, PaError* error); +static void PinFree(PaWinWdmPin* pin); +static void PinClose(PaWinWdmPin* pin); +static PaError PinInstantiate(PaWinWdmPin* pin); +/*static PaError PinGetState(PaWinWdmPin* pin, KSSTATE* state); NOT USED */ +static PaError PinSetState(PaWinWdmPin* pin, KSSTATE state); +static PaError PinSetFormat(PaWinWdmPin* pin, const WAVEFORMATEX* format); +static PaError PinIsFormatSupported(PaWinWdmPin* pin, const WAVEFORMATEX* format); +/* WaveRT support */ +static PaError PinQueryNotificationSupport(PaWinWdmPin* pPin, BOOL* pbResult); +static PaError PinGetBuffer(PaWinWdmPin* pPin, void** pBuffer, DWORD* pRequestedBufSize, BOOL* pbCallMemBarrier); +static PaError PinRegisterPositionRegister(PaWinWdmPin* pPin); +static PaError PinRegisterNotificationHandle(PaWinWdmPin* pPin, HANDLE handle); +static PaError PinUnregisterNotificationHandle(PaWinWdmPin* pPin, HANDLE handle); +static PaError PinGetHwLatency(PaWinWdmPin* pPin, ULONG* pFifoSize, ULONG* pChipsetDelay, ULONG* pCodecDelay); +static PaError PinGetAudioPositionMemoryMapped(PaWinWdmPin* pPin, ULONG* pPosition); +static PaError PinGetAudioPositionViaIOCTLRead(PaWinWdmPin* pPin, ULONG* pPosition); +static PaError PinGetAudioPositionViaIOCTLWrite(PaWinWdmPin* pPin, ULONG* pPosition); + +/* Filter management functions */ +static PaWinWdmFilter* FilterNew(PaWDMKSType type, DWORD devNode, const wchar_t* filterName, const wchar_t* friendlyName, PaError* error); +static PaError FilterInitializePins(PaWinWdmFilter* filter); +static void FilterFree(PaWinWdmFilter* filter); +static void FilterAddRef(PaWinWdmFilter* filter); +static PaWinWdmPin* FilterCreatePin( + PaWinWdmFilter* filter, + int pinId, + const WAVEFORMATEX* wfex, + PaError* error); +static PaError FilterUse(PaWinWdmFilter* filter); +static void FilterRelease(PaWinWdmFilter* filter); + +/* Hot plug functions */ +static BOOL IsDeviceTheSame(const PaWinWdmDeviceInfo* pDev1, + const PaWinWdmDeviceInfo* pDev2); + +/* Interface functions */ +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ); +static PaError IsFormatSupported( +struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ); + +static PaError ScanDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, void **newDeviceInfos, int *newDeviceCount ); +static PaError CommitDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, void *deviceInfos, int deviceCount ); +static PaError DisposeDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, void *deviceInfos, int deviceCount ); + +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 ); + +/* Utility functions */ +static unsigned long GetWfexSize(const WAVEFORMATEX* wfex); +static PaWinWdmFilter** BuildFilterList(int* filterCount, int* noOfPaDevices, PaError* result); +static BOOL PinWrite(HANDLE h, DATAPACKET* p); +static BOOL PinRead(HANDLE h, DATAPACKET* p); +static void DuplicateFirstChannelInt16(void* buffer, int channels, int samples); +static void DuplicateFirstChannelInt24(void* buffer, int channels, int samples); +PA_THREAD_FUNC ProcessingThread(void*); + +/* Pin handler functions */ +static PaError PaPinCaptureEventHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex); +static PaError PaPinCaptureSubmitHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex); + +static PaError PaPinRenderEventHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex); +static PaError PaPinRenderSubmitHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex); + +static PaError PaPinCaptureEventHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex); +static PaError PaPinCaptureEventHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex); +static PaError PaPinCaptureSubmitHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex); +static PaError PaPinCaptureSubmitHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex); + +static PaError PaPinRenderEventHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex); +static PaError PaPinRenderEventHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex); +static PaError PaPinRenderSubmitHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex); +static PaError PaPinRenderSubmitHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex); + +/* Function bodies */ + +#if defined(_DEBUG) && defined(PA_ENABLE_DEBUG_OUTPUT) +#define PA_WDMKS_SET_TREF +static PaTime tRef = 0; + +static void PaWinWdmDebugPrintf(const char* fmt, ...) +{ + va_list list; + char buffer[1024]; + PaTime t = PaUtil_GetTime() - tRef; + va_start(list, fmt); + _vsnprintf(buffer, 1023, fmt, list); + va_end(list); + PaUtil_DebugPrint("%6.3lf: %s", t, buffer); +} + +#ifdef PA_DEBUG +#undef PA_DEBUG +#define PA_DEBUG(x) PaWinWdmDebugPrintf x ; +#endif +#endif + +static BOOL IsDeviceTheSame(const PaWinWdmDeviceInfo* pDev1, + const PaWinWdmDeviceInfo* pDev2) +{ + if (pDev1 == NULL || pDev2 == NULL) + return FALSE; + + if (pDev1 == pDev2) + return TRUE; + + if (strcmp(pDev1->compositeName, pDev2->compositeName) == 0) + return TRUE; + + return FALSE; +} + +static BOOL IsEarlierThanVista() +{ +/* +NOTE: GetVersionEx() is deprecated as of Windows 8.1 and can not be used to reliably detect +versions of Windows higher than Windows 8 (due to manifest requirements for reporting higher versions). +Microsoft recommends switching to VerifyVersionInfo (available on Win 2k and later), however GetVersionEx +is faster, for now we just disable the deprecation warning. +See: https://msdn.microsoft.com/en-us/library/windows/desktop/ms724451(v=vs.85).aspx +See: http://www.codeproject.com/Articles/678606/Part-Overcoming-Windows-s-deprecation-of-GetVe +*/ +#pragma warning (disable : 4996) /* use of GetVersionEx */ + + OSVERSIONINFO osvi; + osvi.dwOSVersionInfoSize = sizeof(osvi); + if (GetVersionEx(&osvi) && osvi.dwMajorVersion<6) + { + return TRUE; + } + return FALSE; + +#pragma warning (default : 4996) +} + + + +static void MemoryBarrierDummy(void) +{ + /* Do nothing */ +} + +static void MemoryBarrierRead(void) +{ + PaUtil_ReadMemoryBarrier(); +} + +static void MemoryBarrierWrite(void) +{ + PaUtil_WriteMemoryBarrier(); +} + +static unsigned long GetWfexSize(const WAVEFORMATEX* wfex) +{ + if( wfex->wFormatTag == WAVE_FORMAT_PCM ) + { + return sizeof( WAVEFORMATEX ); + } + else + { + return (sizeof( WAVEFORMATEX ) + wfex->cbSize); + } +} + +static void PaWinWDM_SetLastErrorInfo(long errCode, const char* fmt, ...) +{ + va_list list; + char buffer[1024]; + va_start(list, fmt); + _vsnprintf(buffer, 1023, fmt, list); + va_end(list); + PaUtil_SetLastHostErrorInfo(paWDMKS, errCode, buffer); +} + +/* +Low level pin/filter access functions +*/ +static PaError WdmSyncIoctl( + HANDLE handle, + unsigned long ioctlNumber, + void* inBuffer, + unsigned long inBufferCount, + void* outBuffer, + unsigned long outBufferCount, + unsigned long* bytesReturned) +{ + PaError result = paNoError; + unsigned long dummyBytesReturned = 0; + BOOL bRes; + + if( !bytesReturned ) + { + /* Use a dummy as the caller hasn't supplied one */ + bytesReturned = &dummyBytesReturned; + } + + bRes = DeviceIoControl(handle, ioctlNumber, inBuffer, inBufferCount, outBuffer, outBufferCount, bytesReturned, NULL); + if (!bRes) + { + unsigned long error = GetLastError(); + if ( !(((error == ERROR_INSUFFICIENT_BUFFER ) || ( error == ERROR_MORE_DATA )) && + ( ioctlNumber == IOCTL_KS_PROPERTY ) && + ( outBufferCount == 0 ) ) ) + { + KSPROPERTY* ksProperty = (KSPROPERTY*)inBuffer; + + PaWinWDM_SetLastErrorInfo(result, "WdmSyncIoctl: DeviceIoControl GLE = 0x%08X (prop_set = {%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}, prop_id = %u)", + error, + ksProperty->Set.Data1, ksProperty->Set.Data2, ksProperty->Set.Data3, + ksProperty->Set.Data4[0], ksProperty->Set.Data4[1], + ksProperty->Set.Data4[2], ksProperty->Set.Data4[3], + ksProperty->Set.Data4[4], ksProperty->Set.Data4[5], + ksProperty->Set.Data4[6], ksProperty->Set.Data4[7], + ksProperty->Id + ); + result = paUnanticipatedHostError; + } + } + return result; +} + +static PaError WdmGetPropertySimple(HANDLE handle, + const GUID* const guidPropertySet, + unsigned long property, + void* value, + unsigned long valueCount) +{ + PaError result; + KSPROPERTY ksProperty; + + ksProperty.Set = *guidPropertySet; + ksProperty.Id = property; + ksProperty.Flags = KSPROPERTY_TYPE_GET; + + result = WdmSyncIoctl( + handle, + IOCTL_KS_PROPERTY, + &ksProperty, + sizeof(KSPROPERTY), + value, + valueCount, + NULL); + + return result; +} + +static PaError WdmSetPropertySimple( + HANDLE handle, + const GUID* const guidPropertySet, + unsigned long property, + void* value, + unsigned long valueCount, + void* instance, + unsigned long instanceCount) +{ + PaError result; + KSPROPERTY* ksProperty; + unsigned long propertyCount = 0; + + propertyCount = sizeof(KSPROPERTY) + instanceCount; + ksProperty = (KSPROPERTY*)_alloca( propertyCount ); + if( !ksProperty ) + { + return paInsufficientMemory; + } + + ksProperty->Set = *guidPropertySet; + ksProperty->Id = property; + ksProperty->Flags = KSPROPERTY_TYPE_SET; + + if( instance ) + { + memcpy((void*)((char*)ksProperty + sizeof(KSPROPERTY)), instance, instanceCount); + } + + result = WdmSyncIoctl( + handle, + IOCTL_KS_PROPERTY, + ksProperty, + propertyCount, + value, + valueCount, + NULL); + + return result; +} + +static PaError WdmGetPinPropertySimple( + HANDLE handle, + unsigned long pinId, + const GUID* const guidPropertySet, + unsigned long property, + void* value, + unsigned long valueCount, + unsigned long *byteCount) +{ + PaError result; + + KSP_PIN ksPProp; + ksPProp.Property.Set = *guidPropertySet; + ksPProp.Property.Id = property; + ksPProp.Property.Flags = KSPROPERTY_TYPE_GET; + ksPProp.PinId = pinId; + ksPProp.Reserved = 0; + + result = WdmSyncIoctl( + handle, + IOCTL_KS_PROPERTY, + &ksPProp, + sizeof(KSP_PIN), + value, + valueCount, + byteCount); + + return result; +} + +static PaError WdmGetPinPropertyMulti( + HANDLE handle, + unsigned long pinId, + const GUID* const guidPropertySet, + unsigned long property, + KSMULTIPLE_ITEM** ksMultipleItem) +{ + PaError result; + unsigned long multipleItemSize = 0; + KSP_PIN ksPProp; + + ksPProp.Property.Set = *guidPropertySet; + ksPProp.Property.Id = property; + ksPProp.Property.Flags = KSPROPERTY_TYPE_GET; + ksPProp.PinId = pinId; + ksPProp.Reserved = 0; + + result = WdmSyncIoctl( + handle, + IOCTL_KS_PROPERTY, + &ksPProp.Property, + sizeof(KSP_PIN), + NULL, + 0, + &multipleItemSize); + if( result != paNoError ) + { + return result; + } + + *ksMultipleItem = (KSMULTIPLE_ITEM*)PaUtil_AllocateMemory( multipleItemSize ); + if( !*ksMultipleItem ) + { + return paInsufficientMemory; + } + + result = WdmSyncIoctl( + handle, + IOCTL_KS_PROPERTY, + &ksPProp, + sizeof(KSP_PIN), + (void*)*ksMultipleItem, + multipleItemSize, + NULL); + + if( result != paNoError ) + { + PaUtil_FreeMemory( ksMultipleItem ); + } + + return result; +} + +static PaError WdmGetPropertyMulti(HANDLE handle, + const GUID* const guidPropertySet, + unsigned long property, + KSMULTIPLE_ITEM** ksMultipleItem) +{ + PaError result; + unsigned long multipleItemSize = 0; + KSPROPERTY ksProp; + + ksProp.Set = *guidPropertySet; + ksProp.Id = property; + ksProp.Flags = KSPROPERTY_TYPE_GET; + + result = WdmSyncIoctl( + handle, + IOCTL_KS_PROPERTY, + &ksProp, + sizeof(KSPROPERTY), + NULL, + 0, + &multipleItemSize); + if( result != paNoError ) + { + return result; + } + + *ksMultipleItem = (KSMULTIPLE_ITEM*)PaUtil_AllocateMemory( multipleItemSize ); + if( !*ksMultipleItem ) + { + return paInsufficientMemory; + } + + result = WdmSyncIoctl( + handle, + IOCTL_KS_PROPERTY, + &ksProp, + sizeof(KSPROPERTY), + (void*)*ksMultipleItem, + multipleItemSize, + NULL); + + if( result != paNoError ) + { + PaUtil_FreeMemory( ksMultipleItem ); + } + + return result; +} + +static PaError WdmSetMuxNodeProperty(HANDLE handle, + ULONG nodeId, + ULONG pinId) +{ + PaError result = paNoError; + KSNODEPROPERTY prop; + prop.Property.Set = KSPROPSETID_Audio; + prop.Property.Id = KSPROPERTY_AUDIO_MUX_SOURCE; + prop.Property.Flags = KSPROPERTY_TYPE_SET | KSPROPERTY_TYPE_TOPOLOGY; + prop.NodeId = nodeId; + prop.Reserved = 0; + + result = WdmSyncIoctl(handle, IOCTL_KS_PROPERTY, &prop, sizeof(KSNODEPROPERTY), &pinId, sizeof(ULONG), NULL); + + return result; +} + +/* Used when traversing topology for outputs */ +static const KSTOPOLOGY_CONNECTION* GetConnectionTo(const KSTOPOLOGY_CONNECTION* pFrom, PaWinWdmFilter* filter, int muxIdx) +{ + unsigned i; + const KSTOPOLOGY_CONNECTION* retval = NULL; + const KSTOPOLOGY_CONNECTION* connections = (const KSTOPOLOGY_CONNECTION*)(filter->connections + 1); + (void)muxIdx; + PA_DEBUG(("GetConnectionTo: Checking %u connections... (pFrom = %p)", filter->connections->Count, pFrom)); + for (i = 0; i < filter->connections->Count; ++i) + { + const KSTOPOLOGY_CONNECTION* pConn = connections + i; + if (pConn == pFrom) + continue; + + if (pConn->FromNode == pFrom->ToNode) + { + retval = pConn; + break; + } + } + PA_DEBUG(("GetConnectionTo: Returning %p\n", retval)); + return retval; +} + +/* Used when traversing topology for inputs */ +static const KSTOPOLOGY_CONNECTION* GetConnectionFrom(const KSTOPOLOGY_CONNECTION* pTo, PaWinWdmFilter* filter, int muxIdx) +{ + unsigned i; + const KSTOPOLOGY_CONNECTION* retval = NULL; + const KSTOPOLOGY_CONNECTION* connections = (const KSTOPOLOGY_CONNECTION*)(filter->connections + 1); + int muxCntr = 0; + PA_DEBUG(("GetConnectionFrom: Checking %u connections... (pTo = %p)\n", filter->connections->Count, pTo)); + for (i = 0; i < filter->connections->Count; ++i) + { + const KSTOPOLOGY_CONNECTION* pConn = connections + i; + if (pConn == pTo) + continue; + + if (pConn->ToNode == pTo->FromNode) + { + if (muxIdx >= 0) + { + if (muxCntr < muxIdx) + { + ++muxCntr; + continue; + } + } + retval = pConn; + break; + } + } + PA_DEBUG(("GetConnectionFrom: Returning %p\n", retval)); + return retval; +} + +static ULONG GetNumberOfConnectionsTo(const KSTOPOLOGY_CONNECTION* pTo, PaWinWdmFilter* filter) +{ + ULONG retval = 0; + unsigned i; + const KSTOPOLOGY_CONNECTION* connections = (const KSTOPOLOGY_CONNECTION*)(filter->connections + 1); + PA_DEBUG(("GetNumberOfConnectionsTo: Checking %u connections...\n", filter->connections->Count)); + for (i = 0; i < filter->connections->Count; ++i) + { + const KSTOPOLOGY_CONNECTION* pConn = connections + i; + if (pConn->ToNode == pTo->FromNode && + (pTo->FromNode != KSFILTER_NODE || pConn->ToNodePin == pTo->FromNodePin)) + { + ++retval; + } + } + PA_DEBUG(("GetNumberOfConnectionsTo: Returning %d\n", retval)); + return retval; +} + +typedef const KSTOPOLOGY_CONNECTION *(*TFnGetConnection)(const KSTOPOLOGY_CONNECTION*, PaWinWdmFilter*, int); + +static const KSTOPOLOGY_CONNECTION* FindStartConnectionFrom(ULONG startPin, PaWinWdmFilter* filter) +{ + unsigned i; + const KSTOPOLOGY_CONNECTION* connections = (const KSTOPOLOGY_CONNECTION*)(filter->connections + 1); + PA_DEBUG(("FindStartConnectionFrom: Startpin %u, Checking %u connections...\n", startPin, filter->connections->Count)); + for (i = 0; i < filter->connections->Count; ++i) + { + const KSTOPOLOGY_CONNECTION* pConn = connections + i; + if (pConn->ToNode == KSFILTER_NODE && pConn->ToNodePin == startPin) + { + PA_DEBUG(("FindStartConnectionFrom: returning %p\n", pConn)); + return pConn; + } + } + + /* Some devices may report topologies that leave pins unconnected. This may be by design or driver installation + issues. Pass the error condition back to caller. */ + PA_DEBUG(("FindStartConnectionFrom: returning NULL\n")); + return 0; +} + +static const KSTOPOLOGY_CONNECTION* FindStartConnectionTo(ULONG startPin, PaWinWdmFilter* filter) +{ + unsigned i; + const KSTOPOLOGY_CONNECTION* connections = (const KSTOPOLOGY_CONNECTION*)(filter->connections + 1); + PA_DEBUG(("FindStartConnectionTo: Startpin %u, Checking %u connections...\n", startPin, filter->connections->Count)); + for (i = 0; i < filter->connections->Count; ++i) + { + const KSTOPOLOGY_CONNECTION* pConn = connections + i; + if (pConn->FromNode == KSFILTER_NODE && pConn->FromNodePin == startPin) + { + PA_DEBUG(("FindStartConnectionTo: returning %p\n", pConn)); + return pConn; + } + } + + /* Unconnected pin. Inform caller. */ + PA_DEBUG(("FindStartConnectionTo: returning NULL\n")); + return 0; +} + +static ULONG GetConnectedPin(ULONG startPin, BOOL forward, PaWinWdmFilter* filter, int muxPosition, ULONG *muxInputPinId, ULONG *muxNodeId) +{ + int limit=1000; + const KSTOPOLOGY_CONNECTION *conn = NULL; + TFnGetConnection fnGetConnection = forward ? GetConnectionTo : GetConnectionFrom ; + PA_LOGE_; + while (1) + { + limit--; + if (limit == 0) { + PA_DEBUG(("GetConnectedPin: LOOP LIMIT REACHED\n")); + break; + } + + if (conn == NULL) + { + conn = forward ? FindStartConnectionTo(startPin, filter) : FindStartConnectionFrom(startPin, filter); + } + else + { + conn = fnGetConnection(conn, filter, -1); + } + + /* Handling case of erroneous connection list */ + if (conn == NULL) + { + break; + } + + if (forward ? conn->ToNode == KSFILTER_NODE : conn->FromNode == KSFILTER_NODE) + { + return forward ? conn->ToNodePin : conn->FromNodePin; + } + else + { + PA_DEBUG(("GetConnectedPin: count=%d, forward=%d, muxPosition=%d\n", filter->nodes->Count, forward, muxPosition)); + if (filter->nodes->Count > 0 && !forward && muxPosition >= 0) + { + const GUID* nodes = (const GUID*)(filter->nodes + 1); + if (IsEqualGUID(&nodes[conn->FromNode], &KSNODETYPE_MUX)) + { + ULONG nConn = GetNumberOfConnectionsTo(conn, filter); + conn = fnGetConnection(conn, filter, muxPosition); + if (conn == NULL) + { + break; + } + if (muxInputPinId != 0) + { + *muxInputPinId = conn->ToNodePin; + } + if (muxNodeId != 0) + { + *muxNodeId = conn->ToNode; + } + } + } + } + } + PA_LOGL_; + return KSFILTER_NODE; +} + +static void DumpConnectionsAndNodes(PaWinWdmFilter* filter) +{ + unsigned i; + const KSTOPOLOGY_CONNECTION* connections = (const KSTOPOLOGY_CONNECTION*)(filter->connections + 1); + const GUID* nodes = (const GUID*)(filter->nodes + 1); + + PA_LOGE_; + PA_DEBUG(("DumpConnectionsAndNodes: connections=%d, nodes=%d\n", filter->connections->Count, filter->nodes->Count)); + + for (i=0; i < filter->connections->Count; ++i) + { + const KSTOPOLOGY_CONNECTION* pConn = connections + i; + PA_DEBUG((" Connection: %u - FromNode=%u,FromPin=%u -> ToNode=%u,ToPin=%u\n", + i, + pConn->FromNode, pConn->FromNodePin, + pConn->ToNode, pConn->ToNodePin + )); + } + + for (i=0; i < filter->nodes->Count; ++i) + { + const GUID* pConn = nodes + i; + PA_DEBUG((" Node: %d - {%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}\n", + i, + pConn->Data1, pConn->Data2, pConn->Data3, + pConn->Data4[0], pConn->Data4[1], + pConn->Data4[2], pConn->Data4[3], + pConn->Data4[4], pConn->Data4[5], + pConn->Data4[6], pConn->Data4[7] + )); + } + PA_LOGL_; + +} + +typedef struct __PaUsbTerminalGUIDToName +{ + USHORT usbGUID; + wchar_t name[64]; +} PaUsbTerminalGUIDToName; + +static const PaUsbTerminalGUIDToName kNames[] = +{ + /* Types copied from: http://msdn.microsoft.com/en-us/library/ff537742(v=vs.85).aspx */ + /* Input terminal types */ + { 0x0201, L"Microphone" }, + { 0x0202, L"Desktop Microphone" }, + { 0x0203, L"Personal Microphone" }, + { 0x0204, L"Omni Directional Microphone" }, + { 0x0205, L"Microphone Array" }, + { 0x0206, L"Processing Microphone Array" }, + /* Output terminal types */ + { 0x0301, L"Speakers" }, + { 0x0302, L"Headphones" }, + { 0x0303, L"Head Mounted Display Audio" }, + { 0x0304, L"Desktop Speaker" }, + { 0x0305, L"Room Speaker" }, + { 0x0306, L"Communication Speaker" }, + { 0x0307, L"LFE Speakers" }, + /* External terminal types */ + { 0x0601, L"Analog" }, + { 0x0602, L"Digital" }, + { 0x0603, L"Line" }, + { 0x0604, L"Audio" }, + { 0x0605, L"SPDIF" }, +}; + +static const unsigned kNamesCnt = sizeof(kNames)/sizeof(PaUsbTerminalGUIDToName); + +static int PaUsbTerminalGUIDToNameCmp(const void* lhs, const void* rhs) +{ + const PaUsbTerminalGUIDToName* pL = (const PaUsbTerminalGUIDToName*)lhs; + const PaUsbTerminalGUIDToName* pR = (const PaUsbTerminalGUIDToName*)rhs; + return ((int)(pL->usbGUID) - (int)(pR->usbGUID)); +} + +static PaError GetNameFromCategory(const GUID* pGUID, BOOL input, wchar_t* name, unsigned length) +{ + PaError result = paUnanticipatedHostError; + USHORT usbTerminalGUID = (USHORT)(pGUID->Data1 - 0xDFF219E0); + + PA_LOGE_; + if (input && usbTerminalGUID >= 0x301 && usbTerminalGUID < 0x400) + { + /* Output terminal name for an input !? Set it to Line! */ + usbTerminalGUID = 0x603; + } + if (!input && usbTerminalGUID >= 0x201 && usbTerminalGUID < 0x300) + { + /* Input terminal name for an output !? Set it to Line! */ + usbTerminalGUID = 0x603; + } + if (usbTerminalGUID >= 0x201 && usbTerminalGUID < 0x713) + { + PaUsbTerminalGUIDToName s = { usbTerminalGUID }; + const PaUsbTerminalGUIDToName* ptr = bsearch( + &s, + kNames, + kNamesCnt, + sizeof(PaUsbTerminalGUIDToName), + PaUsbTerminalGUIDToNameCmp + ); + if (ptr != 0) + { + PA_DEBUG(("GetNameFromCategory: USB GUID %04X -> '%S'\n", usbTerminalGUID, ptr->name)); + + if (name != NULL && length > 0) + { + int n = _snwprintf(name, length, L"%s", ptr->name); + if (usbTerminalGUID >= 0x601 && usbTerminalGUID < 0x700) + { + _snwprintf(name + n, length - n, L" %s", (input ? L"In":L"Out")); + } + } + result = paNoError; + } + } + else + { + PaWinWDM_SetLastErrorInfo(result, "GetNameFromCategory: usbTerminalGUID = %04X ", usbTerminalGUID); + } + PA_LOGL_; + return result; +} + +static BOOL IsFrequencyWithinRange(const KSDATARANGE_AUDIO* range, int frequency) +{ + if (frequency < (int)range->MinimumSampleFrequency) + return FALSE; + if (frequency > (int)range->MaximumSampleFrequency) + return FALSE; + return TRUE; +} + +static BOOL IsBitsWithinRange(const KSDATARANGE_AUDIO* range, int noOfBits) +{ + if (noOfBits < (int)range->MinimumBitsPerSample) + return FALSE; + if (noOfBits > (int)range->MaximumBitsPerSample) + return FALSE; + return TRUE; +} + +/* Note: Somewhat different order compared to WMME implementation, as we want to focus on fidelity first */ +static const int defaultSampleRateSearchOrder[] = +{ 44100, 48000, 88200, 96000, 192000, 32000, 24000, 22050, 16000, 12000, 11025, 9600, 8000 }; +static const int defaultSampleRateSearchOrderCount = sizeof(defaultSampleRateSearchOrder)/sizeof(defaultSampleRateSearchOrder[0]); + +static int DefaultSampleFrequencyIndex(const KSDATARANGE_AUDIO* range) +{ + int i; + + for(i=0; i < defaultSampleRateSearchOrderCount; ++i) + { + int currentFrequency = defaultSampleRateSearchOrder[i]; + + if (IsFrequencyWithinRange(range, currentFrequency)) + { + return i; + } + } + + return -1; +} + +/* +Create a new pin object belonging to a filter +The pin object holds all the configuration information about the pin +before it is opened, and then the handle of the pin after is opened +*/ +static PaWinWdmPin* PinNew(PaWinWdmFilter* parentFilter, unsigned long pinId, PaError* error) +{ + PaWinWdmPin* pin; + PaError result; + unsigned long i; + KSMULTIPLE_ITEM* item = NULL; + KSIDENTIFIER* identifier; + KSDATARANGE* dataRange; + const ULONG streamingId = (parentFilter->devInfo.streamingType == Type_kWaveRT) ? KSINTERFACE_STANDARD_LOOPED_STREAMING : KSINTERFACE_STANDARD_STREAMING; + int defaultSampleRateIndex = defaultSampleRateSearchOrderCount; + + PA_LOGE_; + PA_DEBUG(("PinNew: Creating pin %d:\n",pinId)); + + /* Allocate the new PIN object */ + pin = (PaWinWdmPin*)PaUtil_AllocateMemory( sizeof(PaWinWdmPin) ); + if( !pin ) + { + result = paInsufficientMemory; + goto error; + } + + /* Zero the pin object */ + /* memset( (void*)pin, 0, sizeof(PaWinWdmPin) ); */ + + pin->parentFilter = parentFilter; + pin->pinId = pinId; + + /* Allocate a connect structure */ + pin->pinConnectSize = sizeof(KSPIN_CONNECT) + sizeof(KSDATAFORMAT_WAVEFORMATEX); + pin->pinConnect = (KSPIN_CONNECT*)PaUtil_AllocateMemory( pin->pinConnectSize ); + if( !pin->pinConnect ) + { + result = paInsufficientMemory; + goto error; + } + + /* Configure the connect structure with default values */ + pin->pinConnect->Interface.Set = KSINTERFACESETID_Standard; + pin->pinConnect->Interface.Id = streamingId; + pin->pinConnect->Interface.Flags = 0; + pin->pinConnect->Medium.Set = KSMEDIUMSETID_Standard; + pin->pinConnect->Medium.Id = KSMEDIUM_TYPE_ANYINSTANCE; + pin->pinConnect->Medium.Flags = 0; + pin->pinConnect->PinId = pinId; + pin->pinConnect->PinToHandle = NULL; + pin->pinConnect->Priority.PriorityClass = KSPRIORITY_NORMAL; + pin->pinConnect->Priority.PrioritySubClass = 1; + pin->ksDataFormatWfx = (KSDATAFORMAT_WAVEFORMATEX*)(pin->pinConnect + 1); + pin->ksDataFormatWfx->DataFormat.FormatSize = sizeof(KSDATAFORMAT_WAVEFORMATEX); + pin->ksDataFormatWfx->DataFormat.Flags = 0; + pin->ksDataFormatWfx->DataFormat.Reserved = 0; + pin->ksDataFormatWfx->DataFormat.MajorFormat = KSDATAFORMAT_TYPE_AUDIO; + pin->ksDataFormatWfx->DataFormat.SubFormat = KSDATAFORMAT_SUBTYPE_PCM; + pin->ksDataFormatWfx->DataFormat.Specifier = KSDATAFORMAT_SPECIFIER_WAVEFORMATEX; + + pin->frameSize = 0; /* Unknown until we instantiate pin */ + + /* Get the COMMUNICATION property */ + result = WdmGetPinPropertySimple( + parentFilter->handle, + pinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_COMMUNICATION, + &pin->communication, + sizeof(KSPIN_COMMUNICATION), + NULL); + if( result != paNoError ) + goto error; + + if( /*(pin->communication != KSPIN_COMMUNICATION_SOURCE) &&*/ + (pin->communication != KSPIN_COMMUNICATION_SINK) && + (pin->communication != KSPIN_COMMUNICATION_BOTH) ) + { + PA_DEBUG(("PinNew: Not source/sink\n")); + result = paInvalidDevice; + goto error; + } + + /* Get dataflow information */ + result = WdmGetPinPropertySimple( + parentFilter->handle, + pinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_DATAFLOW, + &pin->dataFlow, + sizeof(KSPIN_DATAFLOW), + NULL); + + if( result != paNoError ) + goto error; + + /* Get the INTERFACE property list */ + result = WdmGetPinPropertyMulti( + parentFilter->handle, + pinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_INTERFACES, + &item); + + if( result != paNoError ) + goto error; + + identifier = (KSIDENTIFIER*)(item+1); + + /* Check that at least one interface is STANDARD_STREAMING */ + result = paUnanticipatedHostError; + for( i = 0; i < item->Count; i++ ) + { + if( IsEqualGUID(&identifier[i].Set, &KSINTERFACESETID_Standard) && ( identifier[i].Id == streamingId ) ) + { + result = paNoError; + break; + } + } + + if( result != paNoError ) + { + PA_DEBUG(("PinNew: No %s streaming\n", streamingId==KSINTERFACE_STANDARD_LOOPED_STREAMING?"looped":"standard")); + goto error; + } + + /* Don't need interfaces any more */ + PaUtil_FreeMemory( item ); + item = NULL; + + /* Get the MEDIUM properties list */ + result = WdmGetPinPropertyMulti( + parentFilter->handle, + pinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_MEDIUMS, + &item); + + if( result != paNoError ) + goto error; + + identifier = (KSIDENTIFIER*)(item+1); /* Not actually necessary... */ + + /* Check that at least one medium is STANDARD_DEVIO */ + result = paUnanticipatedHostError; + for( i = 0; i < item->Count; i++ ) + { + if( IsEqualGUID(&identifier[i].Set, &KSMEDIUMSETID_Standard) && ( identifier[i].Id == KSMEDIUM_STANDARD_DEVIO ) ) + { + result = paNoError; + break; + } + } + + if( result != paNoError ) + { + PA_DEBUG(("No standard devio\n")); + goto error; + } + /* Don't need mediums any more */ + PaUtil_FreeMemory( item ); + item = NULL; + + /* Get DATARANGES */ + result = WdmGetPinPropertyMulti( + parentFilter->handle, + pinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_DATARANGES, + &pin->dataRangesItem); + + if( result != paNoError ) + goto error; + + pin->dataRanges = (KSDATARANGE*)(pin->dataRangesItem +1); + + /* Check that at least one datarange supports audio */ + result = paUnanticipatedHostError; + dataRange = pin->dataRanges; + pin->maxChannels = 0; + pin->defaultSampleRate = 0; + pin->formats = 0; + PA_DEBUG(("PinNew: Checking %u no of dataranges...\n", pin->dataRangesItem->Count)); + for( i = 0; i < pin->dataRangesItem->Count; i++) + { + PA_DEBUG(("PinNew: DR major format %x\n",*(unsigned long*)(&(dataRange->MajorFormat)))); + /* Check that subformat is WAVEFORMATEX, PCM or WILDCARD */ + if( IS_VALID_WAVEFORMATEX_GUID(&dataRange->SubFormat) || + IsEqualGUID(&dataRange->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM) || + IsEqualGUID(&dataRange->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) || + IsEqualGUID(&dataRange->SubFormat, &KSDATAFORMAT_SUBTYPE_WILDCARD) || + IsEqualGUID(&dataRange->MajorFormat, &KSDATAFORMAT_TYPE_AUDIO) ) + { + int defaultIndex; + result = paNoError; + /* Record the maximum possible channels with this pin */ + if( ((KSDATARANGE_AUDIO*)dataRange)->MaximumChannels == (ULONG) -1 ) + { + pin->maxChannels = MAXIMUM_NUMBER_OF_CHANNELS; + } + else if( (int) ((KSDATARANGE_AUDIO*)dataRange)->MaximumChannels > pin->maxChannels ) + { + pin->maxChannels = (int) ((KSDATARANGE_AUDIO*)dataRange)->MaximumChannels; + } + PA_DEBUG(("PinNew: MaxChannel: %d\n",pin->maxChannels)); + + /* Record the formats (bit depths) that are supported */ + if( IsBitsWithinRange((KSDATARANGE_AUDIO*)dataRange, 8) ) + { + pin->formats |= paInt8; + PA_DEBUG(("PinNew: Format PCM 8 bit supported\n")); + } + if( IsBitsWithinRange((KSDATARANGE_AUDIO*)dataRange, 16) ) + { + pin->formats |= paInt16; + PA_DEBUG(("PinNew: Format PCM 16 bit supported\n")); + } + if( IsBitsWithinRange((KSDATARANGE_AUDIO*)dataRange, 24) ) + { + pin->formats |= paInt24; + PA_DEBUG(("PinNew: Format PCM 24 bit supported\n")); + } + if( IsBitsWithinRange((KSDATARANGE_AUDIO*)dataRange, 32) ) + { + if (IsEqualGUID(&dataRange->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT)) + { + pin->formats |= paFloat32; + PA_DEBUG(("PinNew: Format IEEE float 32 bit supported\n")); + } + else + { + pin->formats |= paInt32; + PA_DEBUG(("PinNew: Format PCM 32 bit supported\n")); + } + } + + defaultIndex = DefaultSampleFrequencyIndex((KSDATARANGE_AUDIO*)dataRange); + if (defaultIndex >= 0 && defaultIndex < defaultSampleRateIndex) + { + defaultSampleRateIndex = defaultIndex; + } + } + dataRange = (KSDATARANGE*)( ((char*)dataRange) + dataRange->FormatSize); + } + + if( result != paNoError ) + goto error; + + /* If none of the frequencies searched for are present, there's something seriously wrong */ + if (defaultSampleRateIndex == defaultSampleRateSearchOrderCount) + { + PA_DEBUG(("PinNew: No default sample rate found, skipping pin!\n")); + PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "PinNew: No default sample rate found"); + result = paUnanticipatedHostError; + goto error; + } + + /* Set the default sample rate */ + pin->defaultSampleRate = defaultSampleRateSearchOrder[defaultSampleRateIndex]; + PA_DEBUG(("PinNew: Default sample rate = %d Hz\n", pin->defaultSampleRate)); + + /* Get instance information */ + result = WdmGetPinPropertySimple( + parentFilter->handle, + pinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_CINSTANCES, + &pin->instances, + sizeof(KSPIN_CINSTANCES), + NULL); + + if( result != paNoError ) + goto error; + + /* If WaveRT, check if pin supports notification mode */ + if (parentFilter->devInfo.streamingType == Type_kWaveRT) + { + BOOL bSupportsNotification = FALSE; + if (PinQueryNotificationSupport(pin, &bSupportsNotification) == paNoError) + { + pin->pinKsSubType = bSupportsNotification ? SubType_kNotification : SubType_kPolled; + } + } + + /* Query pin name (which means we need to traverse to non IRP pin, via physical connection to topology filter pin, through + its nodes to the endpoint pin, and get that ones name... phew...) */ + PA_DEBUG(("PinNew: Finding topology pin...\n")); + + { + ULONG topoPinId = GetConnectedPin(pinId, (pin->dataFlow == KSPIN_DATAFLOW_IN), parentFilter, -1, NULL, NULL); + const wchar_t kInputName[] = L"Input"; + const wchar_t kOutputName[] = L"Output"; + + if (topoPinId != KSFILTER_NODE) + { + /* Get physical connection for topo pin */ + unsigned long cbBytes = 0; + PA_DEBUG(("PinNew: Getting physical connection...\n")); + result = WdmGetPinPropertySimple(parentFilter->handle, + topoPinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_PHYSICALCONNECTION, + 0, + 0, + &cbBytes + ); + + if (result != paNoError) + { + /* No physical connection -> there is no topology filter! So we get the name of the pin! */ + PA_DEBUG(("PinNew: No physical connection! Getting the pin name\n")); + result = WdmGetPinPropertySimple(parentFilter->handle, + topoPinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_NAME, + pin->friendlyName, + MAX_PATH, + NULL); + if (result != paNoError) + { + GUID category = {0}; + + /* Get pin category information */ + result = WdmGetPinPropertySimple(parentFilter->handle, + topoPinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_CATEGORY, + &category, + sizeof(GUID), + NULL); + + if (result == paNoError) + { + result = GetNameFromCategory(&category, (pin->dataFlow == KSPIN_DATAFLOW_OUT), pin->friendlyName, MAX_PATH); + } + } + + /* Make sure pin gets a name here... */ + if (wcslen(pin->friendlyName) == 0) + { + wcscpy(pin->friendlyName, (pin->dataFlow == KSPIN_DATAFLOW_IN) ? kOutputName : kInputName); +#ifdef UNICODE + PA_DEBUG(("PinNew: Setting pin friendly name to '%s'\n", pin->friendlyName)); +#else + PA_DEBUG(("PinNew: Setting pin friendly name to '%S'\n", pin->friendlyName)); +#endif + } + + /* This is then == the endpoint pin */ + pin->endpointPinId = (pin->dataFlow == KSPIN_DATAFLOW_IN) ? pinId : topoPinId; + } + else + { + KSPIN_PHYSICALCONNECTION* pc = (KSPIN_PHYSICALCONNECTION*)PaUtil_AllocateMemory(cbBytes + 2); + ULONG pcPin; + wchar_t symbLinkName[MAX_PATH]; + PA_DEBUG(("PinNew: Physical connection found!\n")); + if (pc == NULL) + { + result = paInsufficientMemory; + goto error; + } + result = WdmGetPinPropertySimple(parentFilter->handle, + topoPinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_PHYSICALCONNECTION, + pc, + cbBytes, + NULL + ); + + pcPin = pc->Pin; + wcsncpy(symbLinkName, pc->SymbolicLinkName, MAX_PATH); + PaUtil_FreeMemory( pc ); + + if (result != paNoError) + { + /* Shouldn't happen, but fail if it does */ + PA_DEBUG(("PinNew: failed to retrieve physical connection!\n")); + goto error; + } + + if (symbLinkName[1] == TEXT('?')) + { + symbLinkName[1] = TEXT('\\'); + } + + if (pin->parentFilter->topologyFilter == NULL) + { + PA_DEBUG(("PinNew: Creating topology filter '%S'\n", symbLinkName)); + + pin->parentFilter->topologyFilter = FilterNew(Type_kNotUsed, 0, symbLinkName, L"", &result); + if (pin->parentFilter->topologyFilter == NULL) + { + PA_DEBUG(("PinNew: Failed creating topology filter\n")); + result = paUnanticipatedHostError; + PaWinWDM_SetLastErrorInfo(result, "Failed to create topology filter '%S'", symbLinkName); + goto error; + } + + /* Copy info so we have it in device info */ + wcsncpy(pin->parentFilter->devInfo.topologyPath, symbLinkName, MAX_PATH); + } + else + { + /* Must be the same */ + assert(wcscmp(symbLinkName, pin->parentFilter->topologyFilter->devInfo.filterPath) == 0); + } + + PA_DEBUG(("PinNew: Opening topology filter...")); + + result = FilterUse(pin->parentFilter->topologyFilter); + if (result == paNoError) + { + unsigned long endpointPinId; + + if (pin->dataFlow == KSPIN_DATAFLOW_IN) + { + /* The "endpointPinId" is what WASAPI looks at for pin names */ + GUID category = {0}; + + PA_DEBUG(("PinNew: Checking for output endpoint pin id...\n")); + + endpointPinId = GetConnectedPin(pcPin, TRUE, pin->parentFilter->topologyFilter, -1, NULL, NULL); + + if (endpointPinId == KSFILTER_NODE) + { + result = paUnanticipatedHostError; + PaWinWDM_SetLastErrorInfo(result, "Failed to get endpoint pin ID on topology filter!"); + goto error; + } + + PA_DEBUG(("PinNew: Found endpoint pin id %u\n", endpointPinId)); + + /* Get pin category information */ + result = WdmGetPinPropertySimple(pin->parentFilter->topologyFilter->handle, + endpointPinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_CATEGORY, + &category, + sizeof(GUID), + NULL); + + if (result == paNoError) + { +#if !PA_WDMKS_USE_CATEGORY_FOR_PIN_NAMES + wchar_t pinName[MAX_PATH]; + + PA_DEBUG(("PinNew: Getting pin name property...")); + + /* Ok, try pin name also, and favor that if available */ + result = WdmGetPinPropertySimple(pin->parentFilter->topologyFilter->handle, + endpointPinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_NAME, + pinName, + MAX_PATH, + NULL); + + if (result == paNoError && wcslen(pinName)>0) + { + wcsncpy(pin->friendlyName, pinName, MAX_PATH); + } + else +#endif + { + result = GetNameFromCategory(&category, (pin->dataFlow == KSPIN_DATAFLOW_OUT), pin->friendlyName, MAX_PATH); + } + } + + /* Make sure we get a name for the pin */ + if (wcslen(pin->friendlyName) == 0) + { + wcscpy(pin->friendlyName, kOutputName); + } +#ifdef UNICODE + PA_DEBUG(("PinNew: Pin name '%s'\n", pin->friendlyName)); +#else + PA_DEBUG(("PinNew: Pin name '%S'\n", pin->friendlyName)); +#endif + + /* Set endpoint pin ID (this is the topology INPUT pin, since portmixer will always traverse the + filter in audio streaming direction, see http://msdn.microsoft.com/en-us/library/windows/hardware/ff536331(v=vs.85).aspx + for more information) + */ + pin->endpointPinId = pcPin; + } + else + { + unsigned muxCount = 0; + int muxPos = 0; + /* Max 64 multiplexer inputs... sanity check :) */ + for (i = 0; i < 64; ++i) + { + ULONG muxNodeIdTest = (unsigned)-1; + PA_DEBUG(("PinNew: Checking for input endpoint pin id (%d)...\n", i)); + + endpointPinId = GetConnectedPin(pcPin, + FALSE, + pin->parentFilter->topologyFilter, + (int)i, + NULL, + &muxNodeIdTest); + + if (endpointPinId == KSFILTER_NODE) + { + /* We're done */ + PA_DEBUG(("PinNew: Done with inputs.\n", endpointPinId)); + break; + } + else + { + /* The "endpointPinId" is what WASAPI looks at for pin names */ + GUID category = {0}; + + PA_DEBUG(("PinNew: Found endpoint pin id %u\n", endpointPinId)); + + /* Get pin category information */ + result = WdmGetPinPropertySimple(pin->parentFilter->topologyFilter->handle, + endpointPinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_CATEGORY, + &category, + sizeof(GUID), + NULL); + + if (result == paNoError) + { + if (muxNodeIdTest == (unsigned)-1) + { + /* Ok, try pin name, and favor that if available */ + result = WdmGetPinPropertySimple(pin->parentFilter->topologyFilter->handle, + endpointPinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_NAME, + pin->friendlyName, + MAX_PATH, + NULL); + + if (result != paNoError) + { + result = GetNameFromCategory(&category, TRUE, pin->friendlyName, MAX_PATH); + } + break; + } + else + { + result = GetNameFromCategory(&category, TRUE, NULL, 0); + + if (result == paNoError) + { + ++muxCount; + } + } + } + else + { + PA_DEBUG(("PinNew: Failed to get pin category")); + } + } + } + + if (muxCount == 0) + { + pin->endpointPinId = endpointPinId; + /* Make sure we get a name for the pin */ + if (wcslen(pin->friendlyName) == 0) + { + wcscpy(pin->friendlyName, kInputName); + } +#ifdef UNICODE + PA_DEBUG(("PinNew: Input friendly name '%s'\n", pin->friendlyName)); +#else + PA_DEBUG(("PinNew: Input friendly name '%S'\n", pin->friendlyName)); +#endif + } + else // muxCount > 0 + { + PA_DEBUG(("PinNew: Setting up %u inputs\n", muxCount)); + + /* Now we redo the operation once known how many multiplexer positions there are */ + pin->inputs = (PaWinWdmMuxedInput**)PaUtil_AllocateMemory(muxCount * sizeof(PaWinWdmMuxedInput*)); + if (pin->inputs == NULL) + { + FilterRelease(pin->parentFilter->topologyFilter); + result = paInsufficientMemory; + goto error; + } + pin->inputCount = muxCount; + + for (i = 0; i < muxCount; ++muxPos) + { + PA_DEBUG(("PinNew: Setting up input %u...\n", i)); + + if (pin->inputs[i] == NULL) + { + pin->inputs[i] = (PaWinWdmMuxedInput*)PaUtil_AllocateMemory(sizeof(PaWinWdmMuxedInput)); + if (pin->inputs[i] == NULL) + { + FilterRelease(pin->parentFilter->topologyFilter); + result = paInsufficientMemory; + goto error; + } + } + + endpointPinId = GetConnectedPin(pcPin, + FALSE, + pin->parentFilter->topologyFilter, + muxPos, + &pin->inputs[i]->muxPinId, + &pin->inputs[i]->muxNodeId); + + if (endpointPinId != KSFILTER_NODE) + { + /* The "endpointPinId" is what WASAPI looks at for pin names */ + GUID category = {0}; + + /* Set input endpoint ID */ + pin->inputs[i]->endpointPinId = endpointPinId; + + /* Get pin category information */ + result = WdmGetPinPropertySimple(pin->parentFilter->topologyFilter->handle, + endpointPinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_CATEGORY, + &category, + sizeof(GUID), + NULL); + + if (result == paNoError) + { + /* Try pin name first, and if that is not defined, use category instead */ + result = WdmGetPinPropertySimple(pin->parentFilter->topologyFilter->handle, + endpointPinId, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_NAME, + pin->inputs[i]->friendlyName, + MAX_PATH, + NULL); + + if (result != paNoError) + { + result = GetNameFromCategory(&category, TRUE, pin->inputs[i]->friendlyName, MAX_PATH); + if (result != paNoError) + { + /* Only specify name, let name hash in ScanDeviceInfos fix postfix enumerators */ + wcscpy(pin->inputs[i]->friendlyName, kInputName); + } + } +#ifdef UNICODE + PA_DEBUG(("PinNew: Input (%u) friendly name '%s'\n", i, pin->inputs[i]->friendlyName)); +#else + PA_DEBUG(("PinNew: Input (%u) friendly name '%S'\n", i, pin->inputs[i]->friendlyName)); +#endif + ++i; + } + } + else + { + /* Unconnected pin */ + goto error; + } + } + } + } + } + } + } + else + { + PA_DEBUG(("PinNew: No topology pin id found. Bad...\n")); + /* No TOPO pin id ??? This is bad. Ok, so we just say it is an input or output... */ + wcscpy(pin->friendlyName, (pin->dataFlow == KSPIN_DATAFLOW_IN) ? kOutputName : kInputName); + } + } + + /* Release topology filter if it has been used */ + if (pin->parentFilter->topologyFilter && pin->parentFilter->topologyFilter->handle != NULL) + { + PA_DEBUG(("PinNew: Releasing topology filter...\n")); + FilterRelease(pin->parentFilter->topologyFilter); + } + + /* Success */ + *error = paNoError; + PA_DEBUG(("Pin created successfully\n")); + PA_LOGL_; + return pin; + +error: + PA_DEBUG(("PinNew: Error %d\n", result)); + /* + Error cleanup + */ + + if (pin->parentFilter->topologyFilter && pin->parentFilter->topologyFilter->handle != NULL) + { + FilterRelease(pin->parentFilter->topologyFilter); + } + + PaUtil_FreeMemory( item ); + PinFree(pin); + + *error = result; + PA_LOGL_; + return NULL; +} + +/* +Safely free all resources associated with the pin +*/ +static void PinFree(PaWinWdmPin* pin) +{ + unsigned i; + PA_LOGE_; + if( pin ) + { + PinClose(pin); + if( pin->pinConnect ) + { + PaUtil_FreeMemory( pin->pinConnect ); + } + if( pin->dataRangesItem ) + { + PaUtil_FreeMemory( pin->dataRangesItem ); + } + if( pin->inputs ) + { + for (i = 0; i < pin->inputCount; ++i) + { + PaUtil_FreeMemory( pin->inputs[i] ); + } + PaUtil_FreeMemory( pin->inputs ); + } + PaUtil_FreeMemory( pin ); + } + PA_LOGL_; +} + +/* +If the pin handle is open, close it +*/ +static void PinClose(PaWinWdmPin* pin) +{ + PA_LOGE_; + if( pin == NULL ) + { + PA_DEBUG(("Closing NULL pin!")); + PA_LOGL_; + return; + } + if( pin->handle != NULL ) + { + PinSetState( pin, KSSTATE_PAUSE ); + PinSetState( pin, KSSTATE_STOP ); + CloseHandle( pin->handle ); + pin->handle = NULL; + FilterRelease(pin->parentFilter); + } + PA_LOGL_; +} + +/* +Set the state of this (instantiated) pin +*/ +static PaError PinSetState(PaWinWdmPin* pin, KSSTATE state) +{ + PaError result = paNoError; + KSPROPERTY prop; + + PA_LOGE_; + prop.Set = KSPROPSETID_Connection; + prop.Id = KSPROPERTY_CONNECTION_STATE; + prop.Flags = KSPROPERTY_TYPE_SET; + + if( pin == NULL ) + return paInternalError; + if( pin->handle == NULL ) + return paInternalError; + + result = WdmSyncIoctl(pin->handle, IOCTL_KS_PROPERTY, &prop, sizeof(KSPROPERTY), &state, sizeof(KSSTATE), NULL); + + PA_LOGL_; + return result; +} + +static PaError PinInstantiate(PaWinWdmPin* pin) +{ + PaError result; + unsigned long createResult; + KSALLOCATOR_FRAMING ksaf; + KSALLOCATOR_FRAMING_EX ksafex; + + PA_LOGE_; + + if( pin == NULL ) + return paInternalError; + if(!pin->pinConnect) + return paInternalError; + + FilterUse(pin->parentFilter); + + createResult = FunctionKsCreatePin( + pin->parentFilter->handle, + pin->pinConnect, + GENERIC_WRITE | GENERIC_READ, + &pin->handle + ); + + PA_DEBUG(("Pin create result = 0x%08x\n",createResult)); + if( createResult != ERROR_SUCCESS ) + { + FilterRelease(pin->parentFilter); + pin->handle = NULL; + switch (createResult) + { + case ERROR_INVALID_PARAMETER: + /* First case when pin actually don't support the format */ + return paSampleFormatNotSupported; + case ERROR_BAD_COMMAND: + /* Case when pin is occupied (by another application) */ + return paDeviceUnavailable; + default: + /* All other cases */ + return paInvalidDevice; + } + } + + if (pin->parentFilter->devInfo.streamingType == Type_kWaveCyclic) + { + /* Framing size query only valid for WaveCyclic devices */ + result = WdmGetPropertySimple( + pin->handle, + &KSPROPSETID_Connection, + KSPROPERTY_CONNECTION_ALLOCATORFRAMING, + &ksaf, + sizeof(ksaf)); + + if( result != paNoError ) + { + result = WdmGetPropertySimple( + pin->handle, + &KSPROPSETID_Connection, + KSPROPERTY_CONNECTION_ALLOCATORFRAMING_EX, + &ksafex, + sizeof(ksafex)); + if( result == paNoError ) + { + pin->frameSize = ksafex.FramingItem[0].FramingRange.Range.MinFrameSize; + } + } + else + { + pin->frameSize = ksaf.FrameSize; + } + } + + PA_LOGL_; + + return paNoError; +} + +static PaError PinSetFormat(PaWinWdmPin* pin, const WAVEFORMATEX* format) +{ + unsigned long size; + void* newConnect; + + PA_LOGE_; + + if( pin == NULL ) + return paInternalError; + if( format == NULL ) + return paInternalError; + + size = GetWfexSize(format) + sizeof(KSPIN_CONNECT) + sizeof(KSDATAFORMAT_WAVEFORMATEX) - sizeof(WAVEFORMATEX); + + if( pin->pinConnectSize != size ) + { + newConnect = PaUtil_AllocateMemory( size ); + if( newConnect == NULL ) + return paInsufficientMemory; + memcpy( newConnect, (void*)pin->pinConnect, min(pin->pinConnectSize,size) ); + PaUtil_FreeMemory( pin->pinConnect ); + pin->pinConnect = (KSPIN_CONNECT*)newConnect; + pin->pinConnectSize = size; + pin->ksDataFormatWfx = (KSDATAFORMAT_WAVEFORMATEX*)((KSPIN_CONNECT*)newConnect + 1); + pin->ksDataFormatWfx->DataFormat.FormatSize = size - sizeof(KSPIN_CONNECT); + } + + memcpy( (void*)&(pin->ksDataFormatWfx->WaveFormatEx), format, GetWfexSize(format) ); + pin->ksDataFormatWfx->DataFormat.SampleSize = (unsigned short)(format->nChannels * (format->wBitsPerSample / 8)); + + PA_LOGL_; + + return paNoError; +} + +static PaError PinIsFormatSupported(PaWinWdmPin* pin, const WAVEFORMATEX* format) +{ + KSDATARANGE_AUDIO* dataRange; + unsigned long count; + GUID guid = DYNAMIC_GUID( DEFINE_WAVEFORMATEX_GUID(format->wFormatTag) ); + PaError result = paInvalidDevice; + const WAVEFORMATEXTENSIBLE* pFormatExt = (format->wFormatTag == WAVE_FORMAT_EXTENSIBLE) ? (const WAVEFORMATEXTENSIBLE*)format : 0; + + PA_LOGE_; + + if( pFormatExt != 0 ) + { + guid = pFormatExt->SubFormat; + } + dataRange = (KSDATARANGE_AUDIO*)pin->dataRanges; + for(count = 0; + countdataRangesItem->Count; + count++, + dataRange = (KSDATARANGE_AUDIO*)( ((char*)dataRange) + dataRange->DataRange.FormatSize)) /* Need to update dataRange here, due to 'continue' !! */ + { + /* Check major format*/ + if (!(IsEqualGUID(&(dataRange->DataRange.MajorFormat), &KSDATAFORMAT_TYPE_AUDIO) || + IsEqualGUID(&(dataRange->DataRange.MajorFormat), &KSDATAFORMAT_TYPE_WILDCARD))) + { + continue; + } + + /* This is an audio or wildcard datarange... */ + if (! (IsEqualGUID(&(dataRange->DataRange.SubFormat), &KSDATAFORMAT_SUBTYPE_WILDCARD) || + IsEqualGUID(&(dataRange->DataRange.SubFormat), &KSDATAFORMAT_SUBTYPE_PCM) || + IsEqualGUID(&(dataRange->DataRange.SubFormat), &guid) )) + { + continue; + } + + /* Check specifier... */ + if (! (IsEqualGUID(&(dataRange->DataRange.Specifier), &KSDATAFORMAT_SPECIFIER_WILDCARD) || + IsEqualGUID(&(dataRange->DataRange.Specifier), &KSDATAFORMAT_SPECIFIER_WAVEFORMATEX)) ) + { + continue; + } + + PA_DEBUG(("Pin:%x, DataRange:%d\n",(void*)pin,count)); + PA_DEBUG(("\tFormatSize:%d, SampleSize:%d\n",dataRange->DataRange.FormatSize,dataRange->DataRange.SampleSize)); + PA_DEBUG(("\tMaxChannels:%d\n",dataRange->MaximumChannels)); + PA_DEBUG(("\tBits:%d-%d\n",dataRange->MinimumBitsPerSample,dataRange->MaximumBitsPerSample)); + PA_DEBUG(("\tSampleRate:%d-%d\n",dataRange->MinimumSampleFrequency,dataRange->MaximumSampleFrequency)); + + if( dataRange->MaximumChannels != (ULONG)-1 && + dataRange->MaximumChannels < format->nChannels ) + { + result = paInvalidChannelCount; + continue; + } + + if (pFormatExt != 0) + { + if (!IsBitsWithinRange(dataRange, pFormatExt->Samples.wValidBitsPerSample)) + { + result = paSampleFormatNotSupported; + continue; + } + } + else + { + if (!IsBitsWithinRange(dataRange, format->wBitsPerSample)) + { + result = paSampleFormatNotSupported; + continue; + } + } + + if (!IsFrequencyWithinRange(dataRange, format->nSamplesPerSec)) + { + result = paInvalidSampleRate; + continue; + } + + /* Success! */ + result = paNoError; + break; + } + + PA_LOGL_; + return result; +} + +static PaError PinQueryNotificationSupport(PaWinWdmPin* pPin, BOOL* pbResult) +{ + PaError result = paNoError; + KSPROPERTY propIn; + + PA_LOGE_; + + propIn.Set = KSPROPSETID_RtAudio; + propIn.Id = 8; /* = KSPROPERTY_RTAUDIO_QUERY_NOTIFICATION_SUPPORT */ + propIn.Flags = KSPROPERTY_TYPE_GET; + + result = WdmSyncIoctl(pPin->handle, IOCTL_KS_PROPERTY, + &propIn, + sizeof(KSPROPERTY), + pbResult, + sizeof(BOOL), + NULL); + + if (result != paNoError) + { + PA_DEBUG(("Failed PinQueryNotificationSupport\n")); + } + + PA_LOGL_; + return result; +} + +static PaError PinGetBufferWithNotification(PaWinWdmPin* pPin, void** pBuffer, DWORD* pRequestedBufSize, BOOL* pbCallMemBarrier) +{ + PaError result = paNoError; + KSRTAUDIO_BUFFER_PROPERTY_WITH_NOTIFICATION propIn; + KSRTAUDIO_BUFFER propOut; + + PA_LOGE_; + + propIn.BaseAddress = 0; + propIn.NotificationCount = 2; + propIn.RequestedBufferSize = *pRequestedBufSize; + propIn.Property.Set = KSPROPSETID_RtAudio; + propIn.Property.Id = KSPROPERTY_RTAUDIO_BUFFER_WITH_NOTIFICATION; + propIn.Property.Flags = KSPROPERTY_TYPE_GET; + + result = WdmSyncIoctl(pPin->handle, IOCTL_KS_PROPERTY, + &propIn, + sizeof(KSRTAUDIO_BUFFER_PROPERTY_WITH_NOTIFICATION), + &propOut, + sizeof(KSRTAUDIO_BUFFER), + NULL); + + if (result == paNoError) + { + *pBuffer = propOut.BufferAddress; + *pRequestedBufSize = propOut.ActualBufferSize; + *pbCallMemBarrier = propOut.CallMemoryBarrier; + } + else + { + PA_DEBUG(("Failed to get buffer with notification\n")); + } + + PA_LOGL_; + return result; +} + +static PaError PinGetBufferWithoutNotification(PaWinWdmPin* pPin, void** pBuffer, DWORD* pRequestedBufSize, BOOL* pbCallMemBarrier) +{ + PaError result = paNoError; + KSRTAUDIO_BUFFER_PROPERTY propIn; + KSRTAUDIO_BUFFER propOut; + + PA_LOGE_; + + propIn.BaseAddress = NULL; + propIn.RequestedBufferSize = *pRequestedBufSize; + propIn.Property.Set = KSPROPSETID_RtAudio; + propIn.Property.Id = KSPROPERTY_RTAUDIO_BUFFER; + propIn.Property.Flags = KSPROPERTY_TYPE_GET; + + result = WdmSyncIoctl(pPin->handle, IOCTL_KS_PROPERTY, + &propIn, + sizeof(KSRTAUDIO_BUFFER_PROPERTY), + &propOut, + sizeof(KSRTAUDIO_BUFFER), + NULL); + + if (result == paNoError) + { + *pBuffer = propOut.BufferAddress; + *pRequestedBufSize = propOut.ActualBufferSize; + *pbCallMemBarrier = propOut.CallMemoryBarrier; + } + else + { + PA_DEBUG(("Failed to get buffer without notification\n")); + } + + PA_LOGL_; + return result; +} + +/* greatest common divisor - PGCD in French */ +static unsigned long PaWinWDMGCD( unsigned long a, unsigned long b ) +{ + return (b==0) ? a : PaWinWDMGCD( b, a%b); +} + + +/* This function will handle getting the cyclic buffer from a WaveRT driver. Certain WaveRT drivers needs to have +requested buffer size on multiples of 128 bytes: + +*/ +static PaError PinGetBuffer(PaWinWdmPin* pPin, void** pBuffer, DWORD* pRequestedBufSize, BOOL* pbCallMemBarrier) +{ + PaError result = paNoError; + int limit = 1000; + PA_LOGE_; + + while (1) + { + limit--; + if (limit == 0) { + PA_DEBUG(("PinGetBuffer: LOOP LIMIT REACHED\n")); + break; + } + + if (pPin->pinKsSubType != SubType_kPolled) + { + /* In case of unknown (or notification), we try both modes */ + result = PinGetBufferWithNotification(pPin, pBuffer, pRequestedBufSize, pbCallMemBarrier); + if (result == paNoError) + { + PA_DEBUG(("PinGetBuffer: SubType_kNotification\n")); + pPin->pinKsSubType = SubType_kNotification; + break; + } + } + + result = PinGetBufferWithoutNotification(pPin, pBuffer, pRequestedBufSize, pbCallMemBarrier); + if (result == paNoError) + { + PA_DEBUG(("PinGetBuffer: SubType_kPolled\n")); + pPin->pinKsSubType = SubType_kPolled; + break; + } + + /* Check if requested size is on a 128 byte boundary */ + if (((*pRequestedBufSize) % 128UL) == 0) + { + PA_DEBUG(("Buffer size on 128 byte boundary, still fails :(\n")); + /* Ok, can't do much more */ + break; + } + else + { + /* Compute LCM so we know which sizes are on a 128 byte boundary */ + const unsigned gcd = PaWinWDMGCD(128UL, pPin->ksDataFormatWfx->WaveFormatEx.nBlockAlign); + const unsigned lcm = (128UL * pPin->ksDataFormatWfx->WaveFormatEx.nBlockAlign) / gcd; + DWORD dwOldSize = *pRequestedBufSize; + + /* Align size to (next larger) LCM byte boundary, and then we try again. Note that LCM is not necessarily a + power of 2. */ + *pRequestedBufSize = ((*pRequestedBufSize + lcm - 1) / lcm) * lcm; + + PA_DEBUG(("Adjusting buffer size from %u to %u bytes (128 byte boundary, LCM=%u)\n", dwOldSize, *pRequestedBufSize, lcm)); + } + } + + PA_LOGL_; + + return result; +} + +static PaError PinRegisterPositionRegister(PaWinWdmPin* pPin) +{ + PaError result = paNoError; + KSRTAUDIO_HWREGISTER_PROPERTY propIn; + KSRTAUDIO_HWREGISTER propOut; + + PA_LOGE_; + + propIn.BaseAddress = NULL; + propIn.Property.Set = KSPROPSETID_RtAudio; + propIn.Property.Id = KSPROPERTY_RTAUDIO_POSITIONREGISTER; + propIn.Property.Flags = KSPROPERTY_TYPE_SET; + + result = WdmSyncIoctl(pPin->handle, IOCTL_KS_PROPERTY, + &propIn, + sizeof(KSRTAUDIO_HWREGISTER_PROPERTY), + &propOut, + sizeof(KSRTAUDIO_HWREGISTER), + NULL); + + if (result == paNoError) + { + pPin->positionRegister = (ULONG*)propOut.Register; + } + else + { + PA_DEBUG(("Failed to register position register\n")); + } + + PA_LOGL_; + + return result; +} + +static PaError PinRegisterNotificationHandle(PaWinWdmPin* pPin, HANDLE handle) +{ + PaError result = paNoError; + KSRTAUDIO_NOTIFICATION_EVENT_PROPERTY prop; + + PA_LOGE_; + + prop.NotificationEvent = handle; + prop.Property.Set = KSPROPSETID_RtAudio; + prop.Property.Id = KSPROPERTY_RTAUDIO_REGISTER_NOTIFICATION_EVENT; + prop.Property.Flags = KSPROPERTY_TYPE_SET; + + result = WdmSyncIoctl(pPin->handle, + IOCTL_KS_PROPERTY, + &prop, + sizeof(KSRTAUDIO_NOTIFICATION_EVENT_PROPERTY), + &prop, + sizeof(KSRTAUDIO_NOTIFICATION_EVENT_PROPERTY), + NULL); + + if (result != paNoError) { + PA_DEBUG(("Failed to register notification handle 0x%08X\n", handle)); + } + + PA_LOGL_; + + return result; +} + +static PaError PinUnregisterNotificationHandle(PaWinWdmPin* pPin, HANDLE handle) +{ + PaError result = paNoError; + KSRTAUDIO_NOTIFICATION_EVENT_PROPERTY prop; + + PA_LOGE_; + + if (handle != NULL) + { + prop.NotificationEvent = handle; + prop.Property.Set = KSPROPSETID_RtAudio; + prop.Property.Id = KSPROPERTY_RTAUDIO_UNREGISTER_NOTIFICATION_EVENT; + prop.Property.Flags = KSPROPERTY_TYPE_SET; + + result = WdmSyncIoctl(pPin->handle, + IOCTL_KS_PROPERTY, + &prop, + sizeof(KSRTAUDIO_NOTIFICATION_EVENT_PROPERTY), + &prop, + sizeof(KSRTAUDIO_NOTIFICATION_EVENT_PROPERTY), + NULL); + + if (result != paNoError) { + PA_DEBUG(("Failed to unregister notification handle 0x%08X\n", handle)); + } + } + PA_LOGL_; + + return result; +} + +static PaError PinGetHwLatency(PaWinWdmPin* pPin, ULONG* pFifoSize, ULONG* pChipsetDelay, ULONG* pCodecDelay) +{ + PaError result = paNoError; + KSPROPERTY propIn; + KSRTAUDIO_HWLATENCY propOut; + + PA_LOGE_; + + propIn.Set = KSPROPSETID_RtAudio; + propIn.Id = KSPROPERTY_RTAUDIO_HWLATENCY; + propIn.Flags = KSPROPERTY_TYPE_GET; + + result = WdmSyncIoctl(pPin->handle, IOCTL_KS_PROPERTY, + &propIn, + sizeof(KSPROPERTY), + &propOut, + sizeof(KSRTAUDIO_HWLATENCY), + NULL); + + if (result == paNoError) + { + *pFifoSize = propOut.FifoSize; + *pChipsetDelay = propOut.ChipsetDelay; + *pCodecDelay = propOut.CodecDelay; + } + else + { + PA_DEBUG(("Failed to retrieve hardware FIFO size!\n")); + } + + PA_LOGL_; + + return result; +} + +/* This one is used for WaveRT */ +static PaError PinGetAudioPositionMemoryMapped(PaWinWdmPin* pPin, ULONG* pPosition) +{ + *pPosition = (*pPin->positionRegister); + return paNoError; +} + +/* This one also, but in case the driver hasn't implemented memory mapped access to the position register */ +static PaError PinGetAudioPositionViaIOCTLRead(PaWinWdmPin* pPin, ULONG* pPosition) +{ + PaError result = paNoError; + KSPROPERTY propIn; + KSAUDIO_POSITION propOut; + + PA_LOGE_; + + propIn.Set = KSPROPSETID_Audio; + propIn.Id = KSPROPERTY_AUDIO_POSITION; + propIn.Flags = KSPROPERTY_TYPE_GET; + + result = WdmSyncIoctl(pPin->handle, + IOCTL_KS_PROPERTY, + &propIn, sizeof(KSPROPERTY), + &propOut, sizeof(KSAUDIO_POSITION), + NULL); + + if (result == paNoError) + { + *pPosition = (ULONG)(propOut.PlayOffset); + } + else + { + PA_DEBUG(("Failed to get audio play position!\n")); + } + + PA_LOGL_; + + return result; + +} + +/* This one also, but in case the driver hasn't implemented memory mapped access to the position register */ +static PaError PinGetAudioPositionViaIOCTLWrite(PaWinWdmPin* pPin, ULONG* pPosition) +{ + PaError result = paNoError; + KSPROPERTY propIn; + KSAUDIO_POSITION propOut; + + PA_LOGE_; + + propIn.Set = KSPROPSETID_Audio; + propIn.Id = KSPROPERTY_AUDIO_POSITION; + propIn.Flags = KSPROPERTY_TYPE_GET; + + result = WdmSyncIoctl(pPin->handle, + IOCTL_KS_PROPERTY, + &propIn, sizeof(KSPROPERTY), + &propOut, sizeof(KSAUDIO_POSITION), + NULL); + + if (result == paNoError) + { + *pPosition = (ULONG)(propOut.WriteOffset); + } + else + { + PA_DEBUG(("Failed to get audio write position!\n")); + } + + PA_LOGL_; + + return result; + +} + +/***********************************************************************************************/ + +/** +* Create a new filter object. +*/ +static PaWinWdmFilter* FilterNew( PaWDMKSType type, DWORD devNode, const wchar_t* filterName, const wchar_t* friendlyName, PaError* error ) +{ + PaWinWdmFilter* filter = 0; + PaError result; + + /* Allocate the new filter object */ + filter = (PaWinWdmFilter*)PaUtil_AllocateMemory( sizeof(PaWinWdmFilter) ); + if( !filter ) + { + result = paInsufficientMemory; + goto error; + } + + PA_DEBUG(("FilterNew: Creating filter '%S'\n", friendlyName)); + + /* Set type flag */ + filter->devInfo.streamingType = type; + + /* Store device node */ + filter->deviceNode = devNode; + + /* Zero the filter object - done by AllocateMemory */ + /* memset( (void*)filter, 0, sizeof(PaWinWdmFilter) ); */ + + /* Copy the filter name */ + wcsncpy(filter->devInfo.filterPath, filterName, MAX_PATH); + + /* Copy the friendly name */ + wcsncpy(filter->friendlyName, friendlyName, MAX_PATH); + + PA_DEBUG(("FilterNew: Opening filter...\n", friendlyName)); + + /* Open the filter handle */ + result = FilterUse(filter); + if( result != paNoError ) + { + goto error; + } + + /* Get pin count */ + result = WdmGetPinPropertySimple + ( + filter->handle, + 0, + &KSPROPSETID_Pin, + KSPROPERTY_PIN_CTYPES, + &filter->pinCount, + sizeof(filter->pinCount), + NULL); + + if( result != paNoError) + { + goto error; + } + + /* Get connections & nodes for filter */ + result = WdmGetPropertyMulti( + filter->handle, + &KSPROPSETID_Topology, + KSPROPERTY_TOPOLOGY_CONNECTIONS, + &filter->connections); + + if( result != paNoError) + { + goto error; + } + + result = WdmGetPropertyMulti( + filter->handle, + &KSPROPSETID_Topology, + KSPROPERTY_TOPOLOGY_NODES, + &filter->nodes); + + if( result != paNoError) + { + goto error; + } + + /* For debugging purposes */ + DumpConnectionsAndNodes(filter); + + /* Get product GUID (it might not be supported) */ + { + KSCOMPONENTID compId; + if (WdmGetPropertySimple(filter->handle, &KSPROPSETID_General, KSPROPERTY_GENERAL_COMPONENTID, &compId, sizeof(KSCOMPONENTID)) == paNoError) + { + filter->devInfo.deviceProductGuid = compId.Product; + } + } + + /* This section is not executed for topology filters */ + if (type != Type_kNotUsed) + { + /* Initialize the pins */ + result = FilterInitializePins(filter); + + if( result != paNoError) + { + goto error; + } + } + + /* Close the filter handle for now + * It will be opened later when needed */ + FilterRelease(filter); + + *error = paNoError; + return filter; + +error: + PA_DEBUG(("FilterNew: Error %d\n", result)); + /* + Error cleanup + */ + FilterFree(filter); + + *error = result; + return NULL; +} + +/** +* Add reference to filter +*/ +static void FilterAddRef( PaWinWdmFilter* filter ) +{ + if (filter != 0) + { + filter->filterRefCount++; + } +} + + +/** +* Initialize the pins of the filter. This is separated from FilterNew because this might fail if there is another +* process using the pin(s). +*/ +PaError FilterInitializePins( PaWinWdmFilter* filter ) +{ + PaError result = paNoError; + int pinId; + + if (filter->devInfo.streamingType == Type_kNotUsed) + return paNoError; + + if (filter->pins != NULL) + return paNoError; + + /* Allocate pointer array to hold the pins */ + filter->pins = (PaWinWdmPin**)PaUtil_AllocateMemory( sizeof(PaWinWdmPin*) * filter->pinCount ); + if( !filter->pins ) + { + result = paInsufficientMemory; + goto error; + } + + /* Create all the pins we can */ + for(pinId = 0; pinId < filter->pinCount; pinId++) + { + /* Create the pin with this Id */ + PaWinWdmPin* newPin; + newPin = PinNew(filter, pinId, &result); + if( result == paInsufficientMemory ) + goto error; + if( newPin != NULL ) + { + filter->pins[pinId] = newPin; + ++filter->validPinCount; + } + else + { + filter->pins[pinId] = 0; + } + } + + if (filter->validPinCount == 0) + { + result = paDeviceUnavailable; + goto error; + } + + return paNoError; + +error: + + if (filter->pins) + { + for (pinId = 0; pinId < filter->pinCount; ++pinId) + { + if (filter->pins[pinId]) + { + PinFree(filter->pins[pinId]); + filter->pins[pinId] = 0; + } + } + PaUtil_FreeMemory( filter->pins ); + filter->pins = 0; + } + + return result; +} + + +/** +* Free a previously created filter +*/ +static void FilterFree(PaWinWdmFilter* filter) +{ + PA_LOGL_; + if( filter ) + { + if (--filter->filterRefCount > 0) + { + /* Ok, a stream has a ref count to this filter */ + return; + } + + if ( filter->topologyFilter ) + { + FilterFree(filter->topologyFilter); + filter->topologyFilter = 0; + } + if ( filter->pins ) + { + int pinId; + for( pinId = 0; pinId < filter->pinCount; pinId++ ) + PinFree(filter->pins[pinId]); + PaUtil_FreeMemory( filter->pins ); + filter->pins = 0; + } + if( filter->connections ) + { + PaUtil_FreeMemory(filter->connections); + filter->connections = 0; + } + if( filter->nodes ) + { + PaUtil_FreeMemory(filter->nodes); + filter->nodes = 0; + } + if( filter->handle ) + CloseHandle( filter->handle ); + PaUtil_FreeMemory( filter ); + } + PA_LOGE_; +} + +/** +* Reopen the filter handle if necessary so it can be used +**/ +static PaError FilterUse(PaWinWdmFilter* filter) +{ + assert( filter ); + + PA_LOGE_; + if( filter->handle == NULL ) + { + /* Open the filter */ + filter->handle = CreateFileW( + filter->devInfo.filterPath, + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, + NULL); + + if( filter->handle == NULL ) + { + return paDeviceUnavailable; + } + } + filter->usageCount++; + PA_LOGL_; + return paNoError; +} + +/** +* Release the filter handle if nobody is using it +**/ +static void FilterRelease(PaWinWdmFilter* filter) +{ + assert( filter ); + assert( filter->usageCount > 0 ); + + PA_LOGE_; + /* Check first topology filter, if used */ + if (filter->topologyFilter != NULL && filter->topologyFilter->handle != NULL) + { + FilterRelease(filter->topologyFilter); + } + + filter->usageCount--; + if( filter->usageCount == 0 ) + { + if( filter->handle != NULL ) + { + CloseHandle( filter->handle ); + filter->handle = NULL; + } + } + PA_LOGL_; +} + +/** +* Create a render or playback pin using the supplied format +**/ +static PaWinWdmPin* FilterCreatePin(PaWinWdmFilter* filter, + int pinId, + const WAVEFORMATEX* wfex, + PaError* error) +{ + PaError result = paNoError; + PaWinWdmPin* pin = NULL; + assert( filter ); + assert( pinId < filter->pinCount ); + pin = filter->pins[pinId]; + assert( pin ); + result = PinSetFormat(pin,wfex); + if( result == paNoError ) + { + result = PinInstantiate(pin); + } + *error = result; + return result == paNoError ? pin : 0; +} + +static const wchar_t kUsbPrefix[] = L"\\\\?\\USB"; + +static BOOL IsUSBDevice(const wchar_t* devicePath) +{ + /* Alex Lessard pointed out that different devices might present the device path with + lower case letters. */ + return (_wcsnicmp(devicePath, kUsbPrefix, sizeof(kUsbPrefix)/sizeof(kUsbPrefix[0]) ) == 0); +} + +/* This should make it more language tolerant, I hope... */ +static const wchar_t kUsbNamePrefix[] = L"USB Audio"; + +static BOOL IsNameUSBAudioDevice(const wchar_t* friendlyName) +{ + return (_wcsnicmp(friendlyName, kUsbNamePrefix, sizeof(kUsbNamePrefix)/sizeof(kUsbNamePrefix[0])) == 0); +} + +typedef enum _tag_EAlias +{ + Alias_kRender = (1<<0), + Alias_kCapture = (1<<1), + Alias_kRealtime = (1<<2), +} EAlias; + +/* Trim whitespace from string */ +static void TrimString(wchar_t* str, size_t length) +{ + wchar_t* s = str; + wchar_t* e = 0; + + /* Find start of string */ + while (iswspace(*s)) ++s; + e=s+min(length,wcslen(s))-1; + + /* Find end of string */ + while(e>s && iswspace(*e)) --e; + ++e; + + length = e - s; + memmove(str, s, length * sizeof(wchar_t)); + str[length] = 0; +} + +/** +* Build the list of available filters +* Use the SetupDi API to enumerate all devices in the KSCATEGORY_AUDIO which +* have a KSCATEGORY_RENDER or KSCATEGORY_CAPTURE alias. For each of these +* devices initialise a PaWinWdmFilter structure by calling our NewFilter() +* function. We enumerate devices twice, once to count how many there are, +* and once to initialize the PaWinWdmFilter structures. +* +* Vista and later: Also check KSCATEGORY_REALTIME for WaveRT devices. +*/ +//PaError BuildFilterList( PaWinWdmHostApiRepresentation* wdmHostApi, int* noOfPaDevices ) +PaWinWdmFilter** BuildFilterList( int* pFilterCount, int* pNoOfPaDevices, PaError* pResult ) +{ + PaWinWdmFilter** ppFilters = NULL; + HDEVINFO handle = NULL; + int device; + int invalidDevices; + int slot; + SP_DEVICE_INTERFACE_DATA interfaceData; + SP_DEVICE_INTERFACE_DATA aliasData; + SP_DEVINFO_DATA devInfoData; + int noError; + const int sizeInterface = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + (MAX_PATH * sizeof(WCHAR)); + unsigned char interfaceDetailsArray[sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + (MAX_PATH * sizeof(WCHAR))]; + SP_DEVICE_INTERFACE_DETAIL_DATA_W* devInterfaceDetails = (SP_DEVICE_INTERFACE_DETAIL_DATA_W*)interfaceDetailsArray; + const GUID* category = (const GUID*)&KSCATEGORY_AUDIO; + const GUID* alias_render = (const GUID*)&KSCATEGORY_RENDER; + const GUID* alias_capture = (const GUID*)&KSCATEGORY_CAPTURE; + const GUID* category_realtime = (const GUID*)&KSCATEGORY_REALTIME; + DWORD aliasFlags; + PaWDMKSType streamingType; + int filterCount = 0; + int noOfPaDevices = 0; + + PA_LOGE_; + + assert(pFilterCount != NULL); + assert(pNoOfPaDevices != NULL); + assert(pResult != NULL); + + devInterfaceDetails->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W); + *pFilterCount = 0; + *pNoOfPaDevices = 0; + + /* Open a handle to search for devices (filters) */ + handle = SetupDiGetClassDevs(category,NULL,NULL,DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + if( handle == INVALID_HANDLE_VALUE ) + { + *pResult = paUnanticipatedHostError; + return NULL; + } + PA_DEBUG(("Setup called\n")); + + /* First let's count the number of devices so we can allocate a list */ + invalidDevices = 0; + for( device = 0;;device++ ) + { + interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + interfaceData.Reserved = 0; + aliasData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + aliasData.Reserved = 0; + noError = SetupDiEnumDeviceInterfaces(handle,NULL,category,device,&interfaceData); + PA_DEBUG(("Enum called\n")); + if( !noError ) + break; /* No more devices */ + + /* Check this one has the render or capture alias */ + aliasFlags = 0; + noError = SetupDiGetDeviceInterfaceAlias(handle,&interfaceData,alias_render,&aliasData); + PA_DEBUG(("noError = %d\n",noError)); + if(noError) + { + if(aliasData.Flags && (!(aliasData.Flags & SPINT_REMOVED))) + { + PA_DEBUG(("Device %d has render alias\n",device)); + aliasFlags |= Alias_kRender; /* Has render alias */ + } + else + { + PA_DEBUG(("Device %d has no render alias\n",device)); + } + } + noError = SetupDiGetDeviceInterfaceAlias(handle,&interfaceData,alias_capture,&aliasData); + if(noError) + { + if(aliasData.Flags && (!(aliasData.Flags & SPINT_REMOVED))) + { + PA_DEBUG(("Device %d has capture alias\n",device)); + aliasFlags |= Alias_kCapture; /* Has capture alias */ + } + else + { + PA_DEBUG(("Device %d has no capture alias\n",device)); + } + } + if(!aliasFlags) + invalidDevices++; /* This was not a valid capture or render audio device */ + } + /* Remember how many there are */ + filterCount = device-invalidDevices; + + PA_DEBUG(("Interfaces found: %d\n",device-invalidDevices)); + + /* Now allocate the list of pointers to devices */ + ppFilters = (PaWinWdmFilter**)PaUtil_AllocateMemory( sizeof(PaWinWdmFilter*) * filterCount); + if( ppFilters == 0 ) + { + if(handle != NULL) + SetupDiDestroyDeviceInfoList(handle); + *pResult = paInsufficientMemory; + return NULL; + } + + /* Now create filter objects for each interface found */ + slot = 0; + for( device = 0;;device++ ) + { + interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + interfaceData.Reserved = 0; + aliasData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + aliasData.Reserved = 0; + devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); + devInfoData.Reserved = 0; + streamingType = Type_kWaveCyclic; + + noError = SetupDiEnumDeviceInterfaces(handle,NULL,category,device,&interfaceData); + if( !noError ) + break; /* No more devices */ + + /* Check this one has the render or capture alias */ + aliasFlags = 0; + noError = SetupDiGetDeviceInterfaceAlias(handle,&interfaceData,alias_render,&aliasData); + if(noError) + { + if(aliasData.Flags && (!(aliasData.Flags & SPINT_REMOVED))) + { + PA_DEBUG(("Device %d has render alias\n",device)); + aliasFlags |= Alias_kRender; /* Has render alias */ + } + } + noError = SetupDiGetDeviceInterfaceAlias(handle,&interfaceData,alias_capture,&aliasData); + if(noError) + { + if(aliasData.Flags && (!(aliasData.Flags & SPINT_REMOVED))) + { + PA_DEBUG(("Device %d has capture alias\n",device)); + aliasFlags |= Alias_kCapture; /* Has capture alias */ + } + } + if(!aliasFlags) + { + continue; /* This was not a valid capture or render audio device */ + } + else + { + /* Check if filter is WaveRT, if not it is a WaveCyclic */ + noError = SetupDiGetDeviceInterfaceAlias(handle,&interfaceData,category_realtime,&aliasData); + if (noError) + { + PA_DEBUG(("Device %d has realtime alias\n",device)); + aliasFlags |= Alias_kRealtime; + streamingType = Type_kWaveRT; + } + } + + noError = SetupDiGetDeviceInterfaceDetailW(handle,&interfaceData,devInterfaceDetails,sizeInterface,NULL,&devInfoData); + if( noError ) + { + DWORD type; + WCHAR friendlyName[MAX_PATH] = {0}; + DWORD sizeFriendlyName; + PaWinWdmFilter* newFilter = 0; + + PaError result = paNoError; + /* Try to get the "friendly name" for this interface */ + sizeFriendlyName = sizeof(friendlyName); + + if (IsEarlierThanVista() && IsUSBDevice(devInterfaceDetails->DevicePath)) + { + /* XP and USB audio device needs to look elsewhere, otherwise it'll only be a "USB Audio Device". Not + very literate. */ + if (!SetupDiGetDeviceRegistryPropertyW(handle, + &devInfoData, + SPDRP_LOCATION_INFORMATION, + &type, + (BYTE*)friendlyName, + sizeof(friendlyName), + NULL)) + { + friendlyName[0] = 0; + } + } + + if (friendlyName[0] == 0 || IsNameUSBAudioDevice(friendlyName)) + { + /* Fix contributed by Ben Allison + * Removed KEY_SET_VALUE from flags on following call + * as its causes failure when running without admin rights + * and it was not required */ + HKEY hkey=SetupDiOpenDeviceInterfaceRegKey(handle,&interfaceData,0,KEY_QUERY_VALUE); + if(hkey!=INVALID_HANDLE_VALUE) + { + noError = RegQueryValueExW(hkey,L"FriendlyName",0,&type,(BYTE*)friendlyName,&sizeFriendlyName); + if( noError == ERROR_SUCCESS ) + { + PA_DEBUG(("Interface %d, Name: %s\n",device,friendlyName)); + RegCloseKey(hkey); + } + else + { + friendlyName[0] = 0; + } + } + } + + TrimString(friendlyName, sizeFriendlyName); + + newFilter = FilterNew(streamingType, + devInfoData.DevInst, + devInterfaceDetails->DevicePath, + friendlyName, + &result); + + if( result == paNoError ) + { + int pin; + unsigned filterIOs = 0; + + /* Increment number of "devices" */ + for (pin = 0; pin < newFilter->pinCount; ++pin) + { + PaWinWdmPin* pPin = newFilter->pins[pin]; + if (pPin == NULL) + continue; + + filterIOs += max(1, pPin->inputCount); + } + + noOfPaDevices += filterIOs; + + PA_DEBUG(("Filter (%s) created with %d valid pins (total I/Os: %u)\n", ((newFilter->devInfo.streamingType==Type_kWaveRT)?"WaveRT":"WaveCyclic"), newFilter->validPinCount, filterIOs)); + + assert(slot < filterCount); + + ppFilters[slot] = newFilter; + + slot++; + } + else + { + PA_DEBUG(("Filter NOT created\n")); + /* As there are now less filters than we initially thought + * we must reduce the count by one */ + filterCount--; + } + } + } + + /* Clean up */ + if(handle != NULL) + SetupDiDestroyDeviceInfoList(handle); + + *pFilterCount = filterCount; + *pNoOfPaDevices = noOfPaDevices; + + return ppFilters; +} + +typedef struct PaNameHashIndex +{ + unsigned index; + unsigned count; + ULONG hash; + struct PaNameHashIndex *next; +} PaNameHashIndex; + +typedef struct PaNameHashObject +{ + PaNameHashIndex* list; + PaUtilAllocationGroup* allocGroup; +} PaNameHashObject; + +static ULONG GetNameHash(const wchar_t* str, const BOOL input) +{ + /* This is to make sure that a name that exists as both input & output won't get the same hash value */ + const ULONG fnv_prime = (input ? 0x811C9DD7 : 0x811FEB0B); + ULONG hash = 0; + for(; *str != 0; str++) + { + hash *= fnv_prime; + hash ^= (*str); + } + assert(hash != 0); + return hash; +} + +static PaError CreateHashEntry(PaNameHashObject* obj, const wchar_t* name, const BOOL input) +{ + ULONG hash = GetNameHash(name, input); + PaNameHashIndex * pLast = NULL; + PaNameHashIndex * p = obj->list; + while (p != 0) + { + if (p->hash == hash) + { + break; + } + pLast = p; + p = p->next; + } + if (p == NULL) + { + p = (PaNameHashIndex*)PaUtil_GroupAllocateMemory(obj->allocGroup, sizeof(PaNameHashIndex)); + if (p == NULL) + { + return paInsufficientMemory; + } + p->hash = hash; + p->count = 1; + if (pLast != 0) + { + assert(pLast->next == 0); + pLast->next = p; + } + if (obj->list == 0) + { + obj->list = p; + } + } + else + { + ++p->count; + } + return paNoError; +} + +static PaError InitNameHashObject(PaNameHashObject* obj, PaWinWdmFilter* pFilter) +{ + int i; + + obj->allocGroup = PaUtil_CreateAllocationGroup(); + if (obj->allocGroup == NULL) + { + return paInsufficientMemory; + } + + for (i = 0; i < pFilter->pinCount; ++i) + { + unsigned m; + PaWinWdmPin* pin = pFilter->pins[i]; + + if (pin == NULL) + continue; + + for (m = 0; m < max(1, pin->inputCount); ++m) + { + const BOOL isInput = (pin->dataFlow == KSPIN_DATAFLOW_OUT); + const wchar_t* name = (pin->inputs == NULL) ? pin->friendlyName : pin->inputs[m]->friendlyName; + + PaError result = CreateHashEntry(obj, name, isInput); + + if (result != paNoError) + { + return result; + } + } + } + return paNoError; +} + +static void DeinitNameHashObject(PaNameHashObject* obj) +{ + assert(obj != 0); + PaUtil_FreeAllAllocations(obj->allocGroup); + PaUtil_DestroyAllocationGroup(obj->allocGroup); + memset(obj, 0, sizeof(PaNameHashObject)); +} + +static unsigned GetNameIndex(PaNameHashObject* obj, const wchar_t* name, const BOOL input) +{ + ULONG hash = GetNameHash(name, input); + PaNameHashIndex* p = obj->list; + while (p != NULL) + { + if (p->hash == hash) + { + if (p->count > 1) + { + return (++p->index); + } + else + { + return 0; + } + } + + p = p->next; + } + // Should never get here!! + assert(FALSE); + return 0; +} + +static PaError ScanDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex hostApiIndex, void **scanResults, int *newDeviceCount ) +{ + PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi; + PaError result = paNoError; + PaWinWdmFilter** ppFilters = 0; + PaWinWDMScanDeviceInfosResults *outArgument = 0; + int filterCount = 0; + int totalDeviceCount = 0; + int idxDevice = 0; + DWORD defaultInDevPathSize = 0; + DWORD defaultOutDevPathSize = 0; + wchar_t* defaultInDevPath = 0; + wchar_t* defaultOutDevPath = 0; + + ppFilters = BuildFilterList( &filterCount, &totalDeviceCount, &result ); + if( result != paNoError ) + { + goto error; + } + + // Get hold of default device paths for capture & playback + if( waveInMessage(0, DRV_QUERYDEVICEINTERFACESIZE, (DWORD_PTR)&defaultInDevPathSize, 0 ) == MMSYSERR_NOERROR ) + { + defaultInDevPath = (wchar_t *)PaUtil_AllocateMemory((defaultInDevPathSize + 1) * sizeof(wchar_t)); + waveInMessage(0, DRV_QUERYDEVICEINTERFACE, (DWORD_PTR)defaultInDevPath, defaultInDevPathSize); + } + if( waveOutMessage(0, DRV_QUERYDEVICEINTERFACESIZE, (DWORD_PTR)&defaultOutDevPathSize, 0 ) == MMSYSERR_NOERROR ) + { + defaultOutDevPath = (wchar_t *)PaUtil_AllocateMemory((defaultOutDevPathSize + 1) * sizeof(wchar_t)); + waveOutMessage(0, DRV_QUERYDEVICEINTERFACE, (DWORD_PTR)defaultOutDevPath, defaultOutDevPathSize); + } + + if( totalDeviceCount > 0 ) + { + PaWinWdmDeviceInfo *deviceInfoArray = 0; + int idxFilter; + int i; + unsigned devIsDefaultIn = 0, devIsDefaultOut = 0; + + /* Allocate the out param for all the info we need */ + outArgument = (PaWinWDMScanDeviceInfosResults *) PaUtil_GroupAllocateMemory( + wdmHostApi->allocations, sizeof(PaWinWDMScanDeviceInfosResults) ); + if( !outArgument ) + { + result = paInsufficientMemory; + goto error; + } + + outArgument->defaultInputDevice = paNoDevice; + outArgument->defaultOutputDevice = paNoDevice; + + outArgument->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory( + wdmHostApi->allocations, sizeof(PaDeviceInfo*) * totalDeviceCount ); + if( !outArgument->deviceInfos ) + { + result = paInsufficientMemory; + goto error; + } + + /* allocate all device info structs in a contiguous block */ + deviceInfoArray = (PaWinWdmDeviceInfo*)PaUtil_GroupAllocateMemory( + wdmHostApi->allocations, sizeof(PaWinWdmDeviceInfo) * totalDeviceCount ); + if( !deviceInfoArray ) + { + result = paInsufficientMemory; + goto error; + } + + /* Make sure all items in array */ + for( i = 0 ; i < totalDeviceCount; ++i ) + { + PaDeviceInfo *deviceInfo = &deviceInfoArray[i].inheritedDeviceInfo; + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + deviceInfo->name = 0; + outArgument->deviceInfos[ i ] = deviceInfo; + } + + idxDevice = 0; + for (idxFilter = 0; idxFilter < filterCount; ++idxFilter) + { + PaNameHashObject nameHash = {0}; + PaWinWdmFilter* pFilter = ppFilters[idxFilter]; + if( pFilter == NULL ) + continue; + + if (InitNameHashObject(&nameHash, pFilter) != paNoError) + { + DeinitNameHashObject(&nameHash); + continue; + } + + devIsDefaultIn = (defaultInDevPath && (_wcsicmp(pFilter->devInfo.filterPath, defaultInDevPath) == 0)); + devIsDefaultOut = (defaultOutDevPath && (_wcsicmp(pFilter->devInfo.filterPath, defaultOutDevPath) == 0)); + + for (i = 0; i < pFilter->pinCount; ++i) + { + unsigned m; + ULONG nameIndex = 0; + ULONG nameIndexHash = 0; + PaWinWdmPin* pin = pFilter->pins[i]; + + if (pin == NULL) + continue; + + for (m = 0; m < max(1, pin->inputCount); ++m) + { + PaWinWdmDeviceInfo *wdmDeviceInfo = (PaWinWdmDeviceInfo *)outArgument->deviceInfos[idxDevice]; + PaDeviceInfo *deviceInfo = &wdmDeviceInfo->inheritedDeviceInfo; + wchar_t localCompositeName[MAX_PATH]; + unsigned nameIndex = 0; + const BOOL isInput = (pin->dataFlow == KSPIN_DATAFLOW_OUT); + + wdmDeviceInfo->filter = pFilter; + + deviceInfo->structVersion = 2; + deviceInfo->hostApi = hostApiIndex; + deviceInfo->name = wdmDeviceInfo->compositeName; + /* deviceInfo->hostApiSpecificDeviceInfo = &pFilter->devInfo; */ + + wdmDeviceInfo->pin = pin->pinId; + + /* Get the name of the "device" */ + if (pin->inputs == NULL) + { + wcsncpy(localCompositeName, pin->friendlyName, MAX_PATH); + wdmDeviceInfo->muxPosition = -1; + wdmDeviceInfo->endpointPinId = pin->endpointPinId; + } + else + { + PaWinWdmMuxedInput* input = pin->inputs[m]; + wcsncpy(localCompositeName, input->friendlyName, MAX_PATH); + wdmDeviceInfo->muxPosition = (int)m; + wdmDeviceInfo->endpointPinId = input->endpointPinId; + } + + { + /* Get base length */ + size_t n = wcslen(localCompositeName); + + /* Check if there are more entries with same name (which might very well be the case), if there + are, the name will be postfixed with an index. */ + nameIndex = GetNameIndex(&nameHash, localCompositeName, isInput); + if (nameIndex > 0) + { + /* This name has multiple instances, so we post fix with a number */ + n += _snwprintf(localCompositeName + n, MAX_PATH - n, L" %u", nameIndex); + } + /* Postfix with filter name */ + _snwprintf(localCompositeName + n, MAX_PATH - n, L" (%s)", pFilter->friendlyName); + } + + /* Convert wide char string to utf-8 */ + WideCharToMultiByte(CP_UTF8, 0, localCompositeName, -1, wdmDeviceInfo->compositeName, MAX_PATH, NULL, NULL); + + /* NB! WDM/KS has no concept of a full-duplex device, each pin is either an input or an output */ + if (isInput) + { + /* INPUT ! */ + deviceInfo->maxInputChannels = pin->maxChannels; + deviceInfo->maxOutputChannels = 0; + + /* RoBi NB: Due to the fact that input audio endpoints in Vista (& later OSs) can be the same device, but with + different input mux settings, there might be a discrepancy between the default input device chosen, and + that which will be used by Portaudio. Not much to do about that unfortunately. + */ + if ((defaultInDevPath == 0 || devIsDefaultIn) && + outArgument->defaultInputDevice == paNoDevice) + { + outArgument->defaultInputDevice = idxDevice; + } + } + else + { + /* OUTPUT ! */ + deviceInfo->maxInputChannels = 0; + deviceInfo->maxOutputChannels = pin->maxChannels; + + if ((defaultOutDevPath == 0 || devIsDefaultOut) && + outArgument->defaultOutputDevice == paNoDevice) + { + outArgument->defaultOutputDevice = idxDevice; + } + } + + /* These low values are not very useful because + * a) The lowest latency we end up with can depend on many factors such + * as the device buffer sizes/granularities, sample rate, channels and format + * b) We cannot know the device buffer sizes until we try to open/use it at + * a particular setting + * So: we give 512x48000Hz frames as the default low input latency + **/ + switch (pFilter->devInfo.streamingType) + { + case Type_kWaveCyclic: + if (IsEarlierThanVista()) + { + /* XP doesn't tolerate low latency, unless the Process Priority Class is set to REALTIME_PRIORITY_CLASS + through SetPriorityClass, then 10 ms is quite feasible. However, one should then bear in mind that ALL of + the process is running in REALTIME_PRIORITY_CLASS, which might not be appropriate for an application with + a GUI . In this case it is advisable to separate the audio engine in another process and use IPC to communicate + with it. */ + deviceInfo->defaultLowInputLatency = 0.02; + deviceInfo->defaultLowOutputLatency = 0.02; + } + else + { + /* This is a conservative estimate. Most WaveCyclic drivers will limit the available latency, but f.i. my Edirol + PCR-A30 can reach 3 ms latency easily... */ + deviceInfo->defaultLowInputLatency = 0.01; + deviceInfo->defaultLowOutputLatency = 0.01; + } + deviceInfo->defaultHighInputLatency = (4096.0/48000.0); + deviceInfo->defaultHighOutputLatency = (4096.0/48000.0); + deviceInfo->defaultSampleRate = (double)(pin->defaultSampleRate); + break; + case Type_kWaveRT: + /* This is also a conservative estimate, based on WaveRT polled mode. In polled mode, the latency will be dictated + by the buffer size given by the driver. */ + deviceInfo->defaultLowInputLatency = 0.01; + deviceInfo->defaultLowOutputLatency = 0.01; + deviceInfo->defaultHighInputLatency = 0.04; + deviceInfo->defaultHighOutputLatency = 0.04; + deviceInfo->defaultSampleRate = (double)(pin->defaultSampleRate); + break; + default: + assert(0); + break; + } + + /* Add reference to filter */ + FilterAddRef(wdmDeviceInfo->filter); + + assert(idxDevice < totalDeviceCount); + ++idxDevice; + } + } + + /* If no one has add ref'd the filter, drop it */ + if (pFilter->filterRefCount == 0) + { + FilterFree(pFilter); + } + + /* Deinitialize name hash object */ + DeinitNameHashObject(&nameHash); + } + } + + *scanResults = outArgument; + *newDeviceCount = idxDevice; + return result; + +error: + result = DisposeDeviceInfos(hostApi, outArgument, totalDeviceCount); + + return result; +} + +static PaError CommitDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, PaHostApiIndex index, void *scanResults, int deviceCount ) +{ + PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi; + + hostApi->info.deviceCount = 0; + hostApi->info.defaultInputDevice = paNoDevice; + hostApi->info.defaultOutputDevice = paNoDevice; + + /* Free any old memory which might be in the device info */ + if( hostApi->deviceInfos ) + { + PaWinWDMScanDeviceInfosResults* localScanResults = (PaWinWDMScanDeviceInfosResults*)PaUtil_GroupAllocateMemory( + wdmHostApi->allocations, sizeof(PaWinWDMScanDeviceInfosResults)); + localScanResults->deviceInfos = hostApi->deviceInfos; + + DisposeDeviceInfos(hostApi, &localScanResults, hostApi->info.deviceCount); + + hostApi->deviceInfos = NULL; + } + + if( scanResults != NULL ) + { + PaWinWDMScanDeviceInfosResults *scanDeviceInfosResults = ( PaWinWDMScanDeviceInfosResults * ) scanResults; + + if( deviceCount > 0 ) + { + /* use the array allocated in ScanDeviceInfos() as our deviceInfos */ + hostApi->deviceInfos = scanDeviceInfosResults->deviceInfos; + + hostApi->info.defaultInputDevice = scanDeviceInfosResults->defaultInputDevice; + hostApi->info.defaultOutputDevice = scanDeviceInfosResults->defaultOutputDevice; + + hostApi->info.deviceCount = deviceCount; + } + + PaUtil_GroupFreeMemory( wdmHostApi->allocations, scanDeviceInfosResults ); + } + + return paNoError; + +} + +static PaError DisposeDeviceInfos( struct PaUtilHostApiRepresentation *hostApi, void *scanResults, int deviceCount ) +{ + PaWinWdmHostApiRepresentation *winDsHostApi = (PaWinWdmHostApiRepresentation*)hostApi; + + if( scanResults != NULL ) + { + PaWinWDMScanDeviceInfosResults *scanDeviceInfosResults = ( PaWinWDMScanDeviceInfosResults * ) scanResults; + + if( scanDeviceInfosResults->deviceInfos ) + { + int i; + for (i = 0; i < deviceCount; ++i) + { + PaWinWdmDeviceInfo* pDevice = (PaWinWdmDeviceInfo*)scanDeviceInfosResults->deviceInfos[i]; + if (pDevice->filter != 0) + { + FilterFree(pDevice->filter); + } + } + + PaUtil_GroupFreeMemory( winDsHostApi->allocations, scanDeviceInfosResults->deviceInfos[0] ); /* all device info structs are allocated in a block so we can destroy them here */ + PaUtil_GroupFreeMemory( winDsHostApi->allocations, scanDeviceInfosResults->deviceInfos ); + } + + PaUtil_GroupFreeMemory( winDsHostApi->allocations, scanDeviceInfosResults ); + } + + return paNoError; + +} + +PaError PaWinWdm_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex ) +{ + PaError result = paNoError; + int deviceCount = 0; + void *scanResults = 0; + PaWinWdmHostApiRepresentation *wdmHostApi = NULL; + + PA_LOGE_; + +#ifdef PA_WDMKS_SET_TREF + tRef = PaUtil_GetTime(); +#endif + + /* + Attempt to load the KSUSER.DLL without which we cannot create pins + We will unload this on termination + */ + if(DllKsUser == NULL) + { + DllKsUser = LoadLibrary(TEXT("ksuser.dll")); + if(DllKsUser == NULL) + goto error; + } + FunctionKsCreatePin = (KSCREATEPIN*)GetProcAddress(DllKsUser, "KsCreatePin"); + if(FunctionKsCreatePin == NULL) + goto error; + + /* Attempt to load AVRT.DLL, if we can't, then we'll just use time critical prio instead... */ + if(paWinWDMKSAvRtEntryPoints.hInstance == NULL) + { + paWinWDMKSAvRtEntryPoints.hInstance = LoadLibrary(TEXT("avrt.dll")); + if (paWinWDMKSAvRtEntryPoints.hInstance != NULL) + { + paWinWDMKSAvRtEntryPoints.AvSetMmThreadCharacteristics = + (HANDLE(WINAPI*)(LPCSTR,LPDWORD))GetProcAddress(paWinWDMKSAvRtEntryPoints.hInstance,"AvSetMmThreadCharacteristicsA"); + paWinWDMKSAvRtEntryPoints.AvRevertMmThreadCharacteristics = + (BOOL(WINAPI*)(HANDLE))GetProcAddress(paWinWDMKSAvRtEntryPoints.hInstance, "AvRevertMmThreadCharacteristics"); + paWinWDMKSAvRtEntryPoints.AvSetMmThreadPriority = + (BOOL(WINAPI*)(HANDLE,PA_AVRT_PRIORITY))GetProcAddress(paWinWDMKSAvRtEntryPoints.hInstance, "AvSetMmThreadPriority"); + } + } + + wdmHostApi = (PaWinWdmHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinWdmHostApiRepresentation) ); + if( !wdmHostApi ) + { + result = paInsufficientMemory; + goto error; + } + + wdmHostApi->allocations = PaUtil_CreateAllocationGroup(); + if( !wdmHostApi->allocations ) + { + result = paInsufficientMemory; + goto error; + } + + *hostApi = &wdmHostApi->inheritedHostApiRep; + (*hostApi)->info.structVersion = 1; + (*hostApi)->info.type = paWDMKS; + (*hostApi)->info.name = "Windows WDM-KS"; + + /* these are all updated by CommitDeviceInfos() */ + (*hostApi)->info.deviceCount = 0; + (*hostApi)->info.defaultInputDevice = paNoDevice; + (*hostApi)->info.defaultOutputDevice = paNoDevice; + (*hostApi)->deviceInfos = 0; + + result = ScanDeviceInfos(&wdmHostApi->inheritedHostApiRep, hostApiIndex, &scanResults, &deviceCount); + if (result != paNoError) + { + goto error; + } + + CommitDeviceInfos(&wdmHostApi->inheritedHostApiRep, hostApiIndex, scanResults, deviceCount); + + (*hostApi)->Terminate = Terminate; + (*hostApi)->OpenStream = OpenStream; + (*hostApi)->IsFormatSupported = IsFormatSupported; + /* In preparation for hotplug + (*hostApi)->ScanDeviceInfos = ScanDeviceInfos; + (*hostApi)->CommitDeviceInfos = CommitDeviceInfos; + (*hostApi)->DisposeDeviceInfos = DisposeDeviceInfos; + */ + PaUtil_InitializeStreamInterface( &wdmHostApi->callbackStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, GetStreamCpuLoad, + PaUtil_DummyRead, PaUtil_DummyWrite, + PaUtil_DummyGetReadAvailable, PaUtil_DummyGetWriteAvailable ); + + PaUtil_InitializeStreamInterface( &wdmHostApi->blockingStreamInterface, CloseStream, StartStream, + StopStream, AbortStream, IsStreamStopped, IsStreamActive, + GetStreamTime, PaUtil_DummyGetCpuLoad, + ReadStream, WriteStream, GetStreamReadAvailable, GetStreamWriteAvailable ); + + PA_LOGL_; + return result; + +error: + Terminate( (PaUtilHostApiRepresentation*)wdmHostApi ); + + PA_LOGL_; + return result; +} + + +static void Terminate( struct PaUtilHostApiRepresentation *hostApi ) +{ + PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi; + PA_LOGE_; + + /* Do not unload the libraries */ + if( DllKsUser != NULL ) + { + FreeLibrary( DllKsUser ); + DllKsUser = NULL; + } + + if( paWinWDMKSAvRtEntryPoints.hInstance != NULL ) + { + FreeLibrary( paWinWDMKSAvRtEntryPoints.hInstance ); + paWinWDMKSAvRtEntryPoints.hInstance = NULL; + } + + if( wdmHostApi) + { + PaWinWDMScanDeviceInfosResults* localScanResults = (PaWinWDMScanDeviceInfosResults*)PaUtil_GroupAllocateMemory( + wdmHostApi->allocations, sizeof(PaWinWDMScanDeviceInfosResults)); + localScanResults->deviceInfos = hostApi->deviceInfos; + DisposeDeviceInfos(hostApi, localScanResults, hostApi->info.deviceCount); + + if( wdmHostApi->allocations ) + { + PaUtil_FreeAllAllocations( wdmHostApi->allocations ); + PaUtil_DestroyAllocationGroup( wdmHostApi->allocations ); + } + PaUtil_FreeMemory( wdmHostApi ); + } + PA_LOGL_; +} + +static PaError IsFormatSupported( struct PaUtilHostApiRepresentation *hostApi, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate ) +{ + int inputChannelCount, outputChannelCount; + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi; + PaWinWdmFilter* pFilter; + int result = paFormatIsSupported; + WAVEFORMATEXTENSIBLE wfx; + PaWinWaveFormatChannelMask channelMask; + + PA_LOGE_; + + if( inputParameters ) + { + PaWinWdmDeviceInfo* pDeviceInfo = (PaWinWdmDeviceInfo*)wdmHostApi->inheritedHostApiRep.deviceInfos[inputParameters->device]; + PaWinWdmPin* pin; + unsigned fmt; + unsigned long testFormat = 0; + unsigned validBits = 0; + + inputChannelCount = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( inputSampleFormat & paCustomFormat ) + { + PaWinWDM_SetLastErrorInfo(paSampleFormatNotSupported, "IsFormatSupported: Custom input format not supported"); + return paSampleFormatNotSupported; + } + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + { + PaWinWDM_SetLastErrorInfo(paInvalidDevice, "IsFormatSupported: paUseHostApiSpecificDeviceSpecification not supported"); + return paInvalidDevice; + } + + /* check that input device can support inputChannelCount */ + if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + { + PaWinWDM_SetLastErrorInfo(paInvalidChannelCount, "IsFormatSupported: Invalid input channel count"); + return paInvalidChannelCount; + } + + /* validate inputStreamInfo */ + if( inputParameters->hostApiSpecificStreamInfo ) + { + PaWinWDM_SetLastErrorInfo(paIncompatibleHostApiSpecificStreamInfo, "Host API stream info not supported"); + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + + pFilter = pDeviceInfo->filter; + pin = pFilter->pins[pDeviceInfo->pin]; + + /* Find out the testing format */ + for (fmt = paFloat32; fmt <= paUInt8; fmt <<= 1) + { + if ((fmt & pin->formats) != 0) + { + /* Found a matching format! */ + testFormat = fmt; + break; + } + } + if (testFormat == 0) + { + PaWinWDM_SetLastErrorInfo(result, "IsFormatSupported(capture) failed: no testformat found!"); + return paUnanticipatedHostError; + } + + /* Due to special considerations, WaveRT devices with paInt24 should be tested with paInt32 and + valid bits = 24 (instead of 24 bit samples) */ + if (pFilter->devInfo.streamingType == Type_kWaveRT && testFormat == paInt24) + { + PA_DEBUG(("IsFormatSupported (capture): WaveRT overriding testFormat paInt24 with paInt32 (24 valid bits)")); + testFormat = paInt32; + validBits = 24; + } + + /* Check that the input format is supported */ + channelMask = PaWin_DefaultChannelMask(inputChannelCount); + PaWin_InitializeWaveFormatExtensible((PaWinWaveFormat*)&wfx, + inputChannelCount, + testFormat, + PaWin_SampleFormatToLinearWaveFormatTag(testFormat), + sampleRate, + channelMask ); + if (validBits != 0) + { + wfx.Samples.wValidBitsPerSample = validBits; + } + + result = PinIsFormatSupported(pin, (const WAVEFORMATEX*)&wfx); + if( result != paNoError ) + { + /* Try a WAVE_FORMAT_PCM instead */ + PaWin_InitializeWaveFormatEx((PaWinWaveFormat*)&wfx, + inputChannelCount, + testFormat, + PaWin_SampleFormatToLinearWaveFormatTag(testFormat), + sampleRate); + + if (validBits != 0) + { + wfx.Samples.wValidBitsPerSample = validBits; + } + + result = PinIsFormatSupported(pin, (const WAVEFORMATEX*)&wfx); + if( result != paNoError ) + { + PaWinWDM_SetLastErrorInfo(result, "IsFormatSupported(capture) failed: sr=%u,ch=%u,bits=%u", wfx.Format.nSamplesPerSec, wfx.Format.nChannels, wfx.Format.wBitsPerSample); + return result; + } + } + } + else + { + inputChannelCount = 0; + } + + if( outputParameters ) + { + PaWinWdmDeviceInfo* pDeviceInfo = (PaWinWdmDeviceInfo*)wdmHostApi->inheritedHostApiRep.deviceInfos[outputParameters->device]; + PaWinWdmPin* pin; + unsigned fmt; + unsigned long testFormat = 0; + unsigned validBits = 0; + + outputChannelCount = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* all standard sample formats are supported by the buffer adapter, + this implementation doesn't support any custom sample formats */ + if( outputSampleFormat & paCustomFormat ) + { + PaWinWDM_SetLastErrorInfo(paSampleFormatNotSupported, "IsFormatSupported: Custom output format not supported"); + return paSampleFormatNotSupported; + } + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + { + PaWinWDM_SetLastErrorInfo(paInvalidDevice, "IsFormatSupported: paUseHostApiSpecificDeviceSpecification not supported"); + return paInvalidDevice; + } + + /* check that output device can support outputChannelCount */ + if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + { + PaWinWDM_SetLastErrorInfo(paInvalidChannelCount, "Invalid output channel count"); + return paInvalidChannelCount; + } + + /* validate outputStreamInfo */ + if( outputParameters->hostApiSpecificStreamInfo ) + { + PaWinWDM_SetLastErrorInfo(paIncompatibleHostApiSpecificStreamInfo, "Host API stream info not supported"); + return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */ + } + + pFilter = pDeviceInfo->filter; + pin = pFilter->pins[pDeviceInfo->pin]; + + /* Find out the testing format */ + for (fmt = paFloat32; fmt <= paUInt8; fmt <<= 1) + { + if ((fmt & pin->formats) != 0) + { + /* Found a matching format! */ + testFormat = fmt; + break; + } + } + if (testFormat == 0) + { + PaWinWDM_SetLastErrorInfo(result, "IsFormatSupported(render) failed: no testformat found!"); + return paUnanticipatedHostError; + } + + /* Due to special considerations, WaveRT devices with paInt24 should be tested with paInt32 and + valid bits = 24 (instead of 24 bit samples) */ + if (pFilter->devInfo.streamingType == Type_kWaveRT && testFormat == paInt24) + { + PA_DEBUG(("IsFormatSupported (render): WaveRT overriding testFormat paInt24 with paInt32 (24 valid bits)")); + testFormat = paInt32; + validBits = 24; + } + + /* Check that the output format is supported */ + channelMask = PaWin_DefaultChannelMask(outputChannelCount); + PaWin_InitializeWaveFormatExtensible((PaWinWaveFormat*)&wfx, + outputChannelCount, + testFormat, + PaWin_SampleFormatToLinearWaveFormatTag(testFormat), + sampleRate, + channelMask ); + + if (validBits != 0) + { + wfx.Samples.wValidBitsPerSample = validBits; + } + + result = PinIsFormatSupported(pin, (const WAVEFORMATEX*)&wfx); + if( result != paNoError ) + { + /* Try a WAVE_FORMAT_PCM instead */ + PaWin_InitializeWaveFormatEx((PaWinWaveFormat*)&wfx, + outputChannelCount, + testFormat, + PaWin_SampleFormatToLinearWaveFormatTag(testFormat), + sampleRate); + + if (validBits != 0) + { + wfx.Samples.wValidBitsPerSample = validBits; + } + + result = PinIsFormatSupported(pin, (const WAVEFORMATEX*)&wfx); + if( result != paNoError ) + { + PaWinWDM_SetLastErrorInfo(result, "IsFormatSupported(render) failed: %u,%u,%u", wfx.Format.nSamplesPerSec, wfx.Format.nChannels, wfx.Format.wBitsPerSample); + return result; + } + } + + } + else + { + outputChannelCount = 0; + } + + /* + IMPLEMENT ME: + + - if a full duplex stream is requested, check that the combination + of input and output parameters is supported if necessary + + - check that the device supports sampleRate + + Because the buffer adapter handles conversion between all standard + sample formats, the following checks are only required if paCustomFormat + is implemented, or under some other unusual conditions. + + - check that input device can support inputSampleFormat, or that + we have the capability to convert from inputSampleFormat to + a native format + + - check that output device can support outputSampleFormat, or that + we have the capability to convert from outputSampleFormat to + a native format + */ + if((inputChannelCount == 0)&&(outputChannelCount == 0)) + { + PaWinWDM_SetLastErrorInfo(paSampleFormatNotSupported, "No input or output channels defined"); + result = paSampleFormatNotSupported; /* Not right error */ + } + + PA_LOGL_; + return result; +} + +static void ResetStreamEvents(PaWinWdmStream* stream) +{ + unsigned i; + ResetEvent(stream->eventAbort); + ResetEvent(stream->eventStreamStart[StreamStart_kOk]); + ResetEvent(stream->eventStreamStart[StreamStart_kFailed]); + + for (i=0; icapture.noOfPackets; ++i) + { + if (stream->capture.events && stream->capture.events[i]) + { + ResetEvent(stream->capture.events[i]); + } + } + + for (i=0; irender.noOfPackets; ++i) + { + if (stream->render.events && stream->render.events[i]) + { + ResetEvent(stream->render.events[i]); + } + } +} + +static void CloseStreamEvents(PaWinWdmStream* stream) +{ + unsigned i; + PaWinWdmIOInfo* ios[2] = { &stream->capture, &stream->render }; + + if (stream->eventAbort) + { + CloseHandle(stream->eventAbort); + stream->eventAbort = 0; + } + if (stream->eventStreamStart[StreamStart_kOk]) + { + CloseHandle(stream->eventStreamStart[StreamStart_kOk]); + } + if (stream->eventStreamStart[StreamStart_kFailed]) + { + CloseHandle(stream->eventStreamStart[StreamStart_kFailed]); + } + + for (i = 0; i < 2; ++i) + { + unsigned j; + /* Unregister notification handles for WaveRT */ + if (ios[i]->pPin && ios[i]->pPin->parentFilter->devInfo.streamingType == Type_kWaveRT && + ios[i]->pPin->pinKsSubType == SubType_kNotification && + ios[i]->events != 0) + { + PinUnregisterNotificationHandle(ios[i]->pPin, ios[i]->events[0]); + } + + for (j=0; j < ios[i]->noOfPackets; ++j) + { + if (ios[i]->events && ios[i]->events[j]) + { + CloseHandle(ios[i]->events[j]); + ios[i]->events[j] = 0; + } + } + } +} + +static unsigned NextPowerOf2(unsigned val) +{ + val--; + val = (val >> 1) | val; + val = (val >> 2) | val; + val = (val >> 4) | val; + val = (val >> 8) | val; + val = (val >> 16) | val; + return ++val; +} + +static PaError ValidateSpecificStreamParameters( + const PaStreamParameters *streamParameters, + const PaWinWDMKSInfo *streamInfo, + unsigned isInput) +{ + if( streamInfo ) + { + if( streamInfo->size != sizeof( PaWinWDMKSInfo ) + || streamInfo->version != 1 ) + { + PA_DEBUG(("Stream parameters: size or version not correct")); + return paIncompatibleHostApiSpecificStreamInfo; + } + + if (!!(streamInfo->flags & ~(paWinWDMKSOverrideFramesize | paWinWDMKSUseGivenChannelMask))) + { + PA_DEBUG(("Stream parameters: non supported flags set")); + return paIncompatibleHostApiSpecificStreamInfo; + } + + if (streamInfo->noOfPackets != 0 && + (streamInfo->noOfPackets < 2 || streamInfo->noOfPackets > 8)) + { + PA_DEBUG(("Stream parameters: noOfPackets %u out of range [2,8]", streamInfo->noOfPackets)); + return paIncompatibleHostApiSpecificStreamInfo; + } + + if (streamInfo->flags & paWinWDMKSUseGivenChannelMask) + { + if (isInput) + { + PA_DEBUG(("Stream parameters: Channels mask setting not supported for input stream")); + return paIncompatibleHostApiSpecificStreamInfo; + } + + if (streamInfo->channelMask & PAWIN_SPEAKER_RESERVED) + { + PA_DEBUG(("Stream parameters: Given channels mask 0x%08X not supported", streamInfo->channelMask)); + return paIncompatibleHostApiSpecificStreamInfo; + } + } + + } + + return paNoError; +} + + + + +/* see pa_hostapi.h for a list of validity guarantees made about OpenStream parameters */ + +static PaError OpenStream( struct PaUtilHostApiRepresentation *hostApi, + PaStream** s, + const PaStreamParameters *inputParameters, + const PaStreamParameters *outputParameters, + double sampleRate, + unsigned long framesPerUserBuffer, + PaStreamFlags streamFlags, + PaStreamCallback *streamCallback, + void *userData ) +{ + PaError result = paNoError; + PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi; + PaWinWdmStream *stream = 0; + /* unsigned long framesPerHostBuffer; these may not be equivalent for all implementations */ + PaSampleFormat inputSampleFormat, outputSampleFormat; + PaSampleFormat hostInputSampleFormat, hostOutputSampleFormat; + int userInputChannels,userOutputChannels; + WAVEFORMATEXTENSIBLE wfx; + + PA_LOGE_; + PA_DEBUG(("OpenStream:sampleRate = %f\n",sampleRate)); + PA_DEBUG(("OpenStream:framesPerBuffer = %lu\n",framesPerUserBuffer)); + + if( inputParameters ) + { + userInputChannels = inputParameters->channelCount; + inputSampleFormat = inputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( inputParameters->device == paUseHostApiSpecificDeviceSpecification ) + { + PaWinWDM_SetLastErrorInfo(paInvalidDevice, "paUseHostApiSpecificDeviceSpecification(in) not supported"); + return paInvalidDevice; + } + + /* check that input device can support stream->userInputChannels */ + if( userInputChannels > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels ) + { + PaWinWDM_SetLastErrorInfo(paInvalidChannelCount, "Invalid input channel count"); + return paInvalidChannelCount; + } + + /* validate inputStreamInfo */ + result = ValidateSpecificStreamParameters(inputParameters, inputParameters->hostApiSpecificStreamInfo, 1 ); + if(result != paNoError) + { + PaWinWDM_SetLastErrorInfo(result, "Host API stream info not supported (in)"); + return result; /* this implementation doesn't use custom stream info */ + } + } + else + { + userInputChannels = 0; + inputSampleFormat = paInt16; /* Suppress 'uninitialised var' warnings. */ + } + + if( outputParameters ) + { + userOutputChannels = outputParameters->channelCount; + outputSampleFormat = outputParameters->sampleFormat; + + /* unless alternate device specification is supported, reject the use of + paUseHostApiSpecificDeviceSpecification */ + + if( outputParameters->device == paUseHostApiSpecificDeviceSpecification ) + { + PaWinWDM_SetLastErrorInfo(paInvalidDevice, "paUseHostApiSpecificDeviceSpecification(out) not supported"); + return paInvalidDevice; + } + + /* check that output device can support stream->userInputChannels */ + if( userOutputChannels > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels ) + { + PaWinWDM_SetLastErrorInfo(paInvalidChannelCount, "Invalid output channel count"); + return paInvalidChannelCount; + } + + /* validate outputStreamInfo */ + result = ValidateSpecificStreamParameters( outputParameters, outputParameters->hostApiSpecificStreamInfo, 0 ); + if (result != paNoError) + { + PaWinWDM_SetLastErrorInfo(result, "Host API stream info not supported (out)"); + return result; /* this implementation doesn't use custom stream info */ + } + } + else + { + userOutputChannels = 0; + outputSampleFormat = paInt16; /* Suppress 'uninitialized var' warnings. */ + } + + /* validate platform specific flags */ + if( (streamFlags & paPlatformSpecificFlags) != 0 ) + { + PaWinWDM_SetLastErrorInfo(paInvalidFlag, "Invalid flag supplied"); + return paInvalidFlag; /* unexpected platform specific flag */ + } + + stream = (PaWinWdmStream*)PaUtil_AllocateMemory( sizeof(PaWinWdmStream) ); + if( !stream ) + { + result = paInsufficientMemory; + goto error; + } + + /* Create allocation group */ + stream->allocGroup = PaUtil_CreateAllocationGroup(); + if( !stream->allocGroup ) + { + result = paInsufficientMemory; + goto error; + } + + /* Zero the stream object */ + /* memset((void*)stream,0,sizeof(PaWinWdmStream)); */ + + if( streamCallback ) + { + PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &wdmHostApi->callbackStreamInterface, streamCallback, userData ); + } + else + { + /* PaUtil_InitializeStreamRepresentation( &stream->streamRepresentation, + &wdmHostApi->blockingStreamInterface, streamCallback, userData ); */ + + /* We don't support the blocking API yet */ + PA_DEBUG(("Blocking API not supported yet!\n")); + PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "Blocking API not supported yet"); + result = paUnanticipatedHostError; + goto error; + } + + PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate ); + + /* Instantiate the input pin if necessary */ + if(userInputChannels > 0) + { + PaWinWdmFilter* pFilter; + PaWinWdmDeviceInfo* pDeviceInfo; + PaWinWdmPin* pPin; + unsigned validBitsPerSample = 0; + PaWinWaveFormatChannelMask channelMask = PaWin_DefaultChannelMask( userInputChannels ); + + result = paSampleFormatNotSupported; + pDeviceInfo = (PaWinWdmDeviceInfo*)wdmHostApi->inheritedHostApiRep.deviceInfos[inputParameters->device]; + pFilter = pDeviceInfo->filter; + pPin = pFilter->pins[pDeviceInfo->pin]; + + stream->userInputChannels = userInputChannels; + + hostInputSampleFormat = PaUtil_SelectClosestAvailableFormat( pPin->formats, inputSampleFormat ); + if (hostInputSampleFormat == paSampleFormatNotSupported) + { + result = paUnanticipatedHostError; + PaWinWDM_SetLastErrorInfo(result, "PU_SCAF(%X,%X) failed (input)", pPin->formats, inputSampleFormat); + goto error; + } + else if (pFilter->devInfo.streamingType == Type_kWaveRT && hostInputSampleFormat == paInt24) + { + /* For WaveRT, we choose 32 bit format instead of paInt24, since we MIGHT need to align buffer on a + 128 byte boundary (see PinGetBuffer) */ + hostInputSampleFormat = paInt32; + /* But we'll tell the driver that it's 24 bit in 32 bit container */ + validBitsPerSample = 24; + } + + while (hostInputSampleFormat <= paUInt8) + { + unsigned channelsToProbe = stream->userInputChannels; + /* Some or all KS devices can only handle the exact number of channels + * they specify. But PortAudio clients expect to be able to + * at least specify mono I/O on a multi-channel device + * If this is the case, then we will do the channel mapping internally + * The following loop tests this case + **/ + while (1) + { + PaWin_InitializeWaveFormatExtensible((PaWinWaveFormat*)&wfx, + channelsToProbe, + hostInputSampleFormat, + PaWin_SampleFormatToLinearWaveFormatTag(hostInputSampleFormat), + sampleRate, + channelMask ); + stream->capture.bytesPerFrame = wfx.Format.nBlockAlign; + if (validBitsPerSample != 0) + { + wfx.Samples.wValidBitsPerSample = validBitsPerSample; + } + stream->capture.pPin = FilterCreatePin(pFilter, pPin->pinId, (WAVEFORMATEX*)&wfx, &result); + stream->deviceInputChannels = channelsToProbe; + + if( result != paNoError && result != paDeviceUnavailable ) + { + /* Try a WAVE_FORMAT_PCM instead */ + PaWin_InitializeWaveFormatEx((PaWinWaveFormat*)&wfx, + channelsToProbe, + hostInputSampleFormat, + PaWin_SampleFormatToLinearWaveFormatTag(hostInputSampleFormat), + sampleRate); + if (validBitsPerSample != 0) + { + wfx.Samples.wValidBitsPerSample = validBitsPerSample; + } + stream->capture.pPin = FilterCreatePin(pFilter, pPin->pinId, (const WAVEFORMATEX*)&wfx, &result); + } + + if (result == paDeviceUnavailable) goto occupied; + + if (result == paNoError) + { + /* We're done */ + break; + } + + if (channelsToProbe < (unsigned)pPin->maxChannels) + { + /* Go to next multiple of 2 */ + channelsToProbe = min((((channelsToProbe>>1)+1)<<1), (unsigned)pPin->maxChannels); + continue; + } + + break; + } + + if (result == paNoError) + { + /* We're done */ + break; + } + + /* Go to next format in line with lower resolution */ + hostInputSampleFormat <<= 1; + } + + if(stream->capture.pPin == NULL) + { + PaWinWDM_SetLastErrorInfo(result, "Failed to create capture pin: sr=%u,ch=%u,bits=%u,align=%u", + wfx.Format.nSamplesPerSec, wfx.Format.nChannels, wfx.Format.wBitsPerSample, wfx.Format.nBlockAlign); + goto error; + } + + /* Select correct mux input on MUX node of topology filter */ + if (pDeviceInfo->muxPosition >= 0) + { + assert(pPin->parentFilter->topologyFilter != NULL); + + result = FilterUse(pPin->parentFilter->topologyFilter); + if (result != paNoError) + { + PaWinWDM_SetLastErrorInfo(result, "Failed to open topology filter"); + goto error; + } + + result = WdmSetMuxNodeProperty(pPin->parentFilter->topologyFilter->handle, + pPin->inputs[pDeviceInfo->muxPosition]->muxNodeId, + pPin->inputs[pDeviceInfo->muxPosition]->muxPinId); + + FilterRelease(pPin->parentFilter->topologyFilter); + + if(result != paNoError) + { + PaWinWDM_SetLastErrorInfo(result, "Failed to set topology mux node"); + goto error; + } + } + + stream->capture.bytesPerSample = stream->capture.bytesPerFrame / stream->deviceInputChannels; + stream->capture.pPin->frameSize /= stream->capture.bytesPerFrame; + PA_DEBUG(("Capture pin frames: %d\n",stream->capture.pPin->frameSize)); + } + else + { + hostInputSampleFormat = (PaSampleFormat)0; /* Avoid uninitialized variable warning */ + + stream->capture.pPin = NULL; + stream->capture.bytesPerFrame = 0; + } + + /* Instantiate the output pin if necessary */ + if(userOutputChannels > 0) + { + PaWinWdmFilter* pFilter; + PaWinWdmDeviceInfo* pDeviceInfo; + PaWinWdmPin* pPin; + PaWinWDMKSInfo* pInfo = (PaWinWDMKSInfo*)(outputParameters->hostApiSpecificStreamInfo); + unsigned validBitsPerSample = 0; + PaWinWaveFormatChannelMask channelMask = PaWin_DefaultChannelMask( userOutputChannels ); + if (pInfo && (pInfo->flags & paWinWDMKSUseGivenChannelMask)) + { + PA_DEBUG(("Using channelMask 0x%08X instead of default 0x%08X\n", + pInfo->channelMask, + channelMask)); + channelMask = pInfo->channelMask; + } + + result = paSampleFormatNotSupported; + pDeviceInfo = (PaWinWdmDeviceInfo*)wdmHostApi->inheritedHostApiRep.deviceInfos[outputParameters->device]; + pFilter = pDeviceInfo->filter; + pPin = pFilter->pins[pDeviceInfo->pin]; + + stream->userOutputChannels = userOutputChannels; + + hostOutputSampleFormat = PaUtil_SelectClosestAvailableFormat( pPin->formats, outputSampleFormat ); + if (hostOutputSampleFormat == paSampleFormatNotSupported) + { + result = paUnanticipatedHostError; + PaWinWDM_SetLastErrorInfo(result, "PU_SCAF(%X,%X) failed (output)", pPin->formats, hostOutputSampleFormat); + goto error; + } + else if (pFilter->devInfo.streamingType == Type_kWaveRT && hostOutputSampleFormat == paInt24) + { + /* For WaveRT, we choose 32 bit format instead of paInt24, since we MIGHT need to align buffer on a + 128 byte boundary (see PinGetBuffer) */ + hostOutputSampleFormat = paInt32; + /* But we'll tell the driver that it's 24 bit in 32 bit container */ + validBitsPerSample = 24; + } + + while (hostOutputSampleFormat <= paUInt8) + { + unsigned channelsToProbe = stream->userOutputChannels; + /* Some or all KS devices can only handle the exact number of channels + * they specify. But PortAudio clients expect to be able to + * at least specify mono I/O on a multi-channel device + * If this is the case, then we will do the channel mapping internally + * The following loop tests this case + **/ + while (1) + { + PaWin_InitializeWaveFormatExtensible((PaWinWaveFormat*)&wfx, + channelsToProbe, + hostOutputSampleFormat, + PaWin_SampleFormatToLinearWaveFormatTag(hostOutputSampleFormat), + sampleRate, + channelMask ); + stream->render.bytesPerFrame = wfx.Format.nBlockAlign; + if (validBitsPerSample != 0) + { + wfx.Samples.wValidBitsPerSample = validBitsPerSample; + } + stream->render.pPin = FilterCreatePin(pFilter, pPin->pinId, (WAVEFORMATEX*)&wfx, &result); + stream->deviceOutputChannels = channelsToProbe; + + if( result != paNoError && result != paDeviceUnavailable ) + { + PaWin_InitializeWaveFormatEx((PaWinWaveFormat*)&wfx, + channelsToProbe, + hostOutputSampleFormat, + PaWin_SampleFormatToLinearWaveFormatTag(hostOutputSampleFormat), + sampleRate); + if (validBitsPerSample != 0) + { + wfx.Samples.wValidBitsPerSample = validBitsPerSample; + } + stream->render.pPin = FilterCreatePin(pFilter, pPin->pinId, (const WAVEFORMATEX*)&wfx, &result); + } + + if (result == paDeviceUnavailable) goto occupied; + + if (result == paNoError) + { + /* We're done */ + break; + } + + if (channelsToProbe < (unsigned)pPin->maxChannels) + { + /* Go to next multiple of 2 */ + channelsToProbe = min((((channelsToProbe>>1)+1)<<1), (unsigned)pPin->maxChannels); + continue; + } + + break; + }; + + if (result == paNoError) + { + /* We're done */ + break; + } + + /* Go to next format in line with lower resolution */ + hostOutputSampleFormat <<= 1; + } + + if(stream->render.pPin == NULL) + { + PaWinWDM_SetLastErrorInfo(result, "Failed to create render pin: sr=%u,ch=%u,bits=%u,align=%u", + wfx.Format.nSamplesPerSec, wfx.Format.nChannels, wfx.Format.wBitsPerSample, wfx.Format.nBlockAlign); + goto error; + } + + stream->render.bytesPerSample = stream->render.bytesPerFrame / stream->deviceOutputChannels; + stream->render.pPin->frameSize /= stream->render.bytesPerFrame; + PA_DEBUG(("Render pin frames: %d\n",stream->render.pPin->frameSize)); + } + else + { + hostOutputSampleFormat = (PaSampleFormat)0; /* Avoid uninitialized variable warning */ + + stream->render.pPin = NULL; + stream->render.bytesPerFrame = 0; + } + + /* Calculate the framesPerHostXxxxBuffer size based upon the suggested latency values */ + /* Record the buffer length */ + if(inputParameters) + { + /* Calculate the frames from the user's value - add a bit to round up */ + stream->capture.framesPerBuffer = (unsigned long)((inputParameters->suggestedLatency*sampleRate)+0.0001); + if(stream->capture.framesPerBuffer > (unsigned long)sampleRate) + { /* Upper limit is 1 second */ + stream->capture.framesPerBuffer = (unsigned long)sampleRate; + } + else if(stream->capture.framesPerBuffer < stream->capture.pPin->frameSize) + { + stream->capture.framesPerBuffer = stream->capture.pPin->frameSize; + } + PA_DEBUG(("Input frames chosen:%ld\n",stream->capture.framesPerBuffer)); + + /* Setup number of packets to use */ + stream->capture.noOfPackets = 2; + + if (inputParameters->hostApiSpecificStreamInfo) + { + PaWinWDMKSInfo* pInfo = (PaWinWDMKSInfo*)inputParameters->hostApiSpecificStreamInfo; + + if (stream->capture.pPin->parentFilter->devInfo.streamingType == Type_kWaveCyclic && + pInfo->noOfPackets != 0) + { + stream->capture.noOfPackets = pInfo->noOfPackets; + } + } + } + + if(outputParameters) + { + /* Calculate the frames from the user's value - add a bit to round up */ + stream->render.framesPerBuffer = (unsigned long)((outputParameters->suggestedLatency*sampleRate)+0.0001); + if(stream->render.framesPerBuffer > (unsigned long)sampleRate) + { /* Upper limit is 1 second */ + stream->render.framesPerBuffer = (unsigned long)sampleRate; + } + else if(stream->render.framesPerBuffer < stream->render.pPin->frameSize) + { + stream->render.framesPerBuffer = stream->render.pPin->frameSize; + } + PA_DEBUG(("Output frames chosen:%ld\n",stream->render.framesPerBuffer)); + + /* Setup number of packets to use */ + stream->render.noOfPackets = 2; + + if (outputParameters->hostApiSpecificStreamInfo) + { + PaWinWDMKSInfo* pInfo = (PaWinWDMKSInfo*)outputParameters->hostApiSpecificStreamInfo; + + if (stream->render.pPin->parentFilter->devInfo.streamingType == Type_kWaveCyclic && + pInfo->noOfPackets != 0) + { + stream->render.noOfPackets = pInfo->noOfPackets; + } + } + } + + /* Host buffer size is bound to the largest of the input and output frame sizes */ + result = PaUtil_InitializeBufferProcessor( &stream->bufferProcessor, + stream->userInputChannels, inputSampleFormat, hostInputSampleFormat, + stream->userOutputChannels, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, framesPerUserBuffer, + max(stream->capture.framesPerBuffer, stream->render.framesPerBuffer), + paUtilBoundedHostBufferSize, + streamCallback, userData ); + if( result != paNoError ) + { + PaWinWDM_SetLastErrorInfo(result, "PaUtil_InitializeBufferProcessor failed: ich=%u, isf=%u, hisf=%u, och=%u, osf=%u, hosf=%u, sr=%lf, flags=0x%X, fpub=%u, fphb=%u", + stream->userInputChannels, inputSampleFormat, hostInputSampleFormat, + stream->userOutputChannels, outputSampleFormat, hostOutputSampleFormat, + sampleRate, streamFlags, framesPerUserBuffer, + max(stream->capture.framesPerBuffer, stream->render.framesPerBuffer)); + goto error; + } + + /* Allocate/get all the buffers for host I/O */ + if (stream->userInputChannels > 0) + { + stream->streamRepresentation.streamInfo.inputLatency = stream->capture.framesPerBuffer / sampleRate; + + switch (stream->capture.pPin->parentFilter->devInfo.streamingType) + { + case Type_kWaveCyclic: + { + unsigned size = stream->capture.noOfPackets * stream->capture.framesPerBuffer * stream->capture.bytesPerFrame; + /* Allocate input host buffer */ + stream->capture.hostBuffer = (char*)PaUtil_GroupAllocateMemory(stream->allocGroup, size); + PA_DEBUG(("Input buffer allocated (size = %u)\n", size)); + if( !stream->capture.hostBuffer ) + { + PA_DEBUG(("Cannot allocate host input buffer!\n")); + PaWinWDM_SetLastErrorInfo(paInsufficientMemory, "Failed to allocate input buffer"); + result = paInsufficientMemory; + goto error; + } + stream->capture.hostBufferSize = size; + PA_DEBUG(("Input buffer start = %p (size=%u)\n",stream->capture.hostBuffer, stream->capture.hostBufferSize)); + stream->capture.pPin->fnEventHandler = PaPinCaptureEventHandler_WaveCyclic; + stream->capture.pPin->fnSubmitHandler = PaPinCaptureSubmitHandler_WaveCyclic; + } + break; + case Type_kWaveRT: + { + const DWORD dwTotalSize = 2 * stream->capture.framesPerBuffer * stream->capture.bytesPerFrame; + DWORD dwRequestedSize = dwTotalSize; + BOOL bCallMemoryBarrier = FALSE; + ULONG hwFifoLatency = 0; + ULONG dummy; + result = PinGetBuffer(stream->capture.pPin, (void**)&stream->capture.hostBuffer, &dwRequestedSize, &bCallMemoryBarrier); + if (!result) + { + PA_DEBUG(("Input buffer start = %p, size = %u\n", stream->capture.hostBuffer, dwRequestedSize)); + if (dwRequestedSize != dwTotalSize) + { + PA_DEBUG(("Buffer length changed by driver from %u to %u !\n", dwTotalSize, dwRequestedSize)); + /* Recalculate to what the driver has given us */ + stream->capture.framesPerBuffer = dwRequestedSize / (2 * stream->capture.bytesPerFrame); + } + stream->capture.hostBufferSize = dwRequestedSize; + + if (stream->capture.pPin->pinKsSubType == SubType_kPolled) + { + stream->capture.pPin->fnEventHandler = PaPinCaptureEventHandler_WaveRTPolled; + stream->capture.pPin->fnSubmitHandler = PaPinCaptureSubmitHandler_WaveRTPolled; + } + else + { + stream->capture.pPin->fnEventHandler = PaPinCaptureEventHandler_WaveRTEvent; + stream->capture.pPin->fnSubmitHandler = PaPinCaptureSubmitHandler_WaveRTEvent; + } + + stream->capture.pPin->fnMemBarrier = bCallMemoryBarrier ? MemoryBarrierRead : MemoryBarrierDummy; + } + else + { + PA_DEBUG(("Failed to get input buffer (WaveRT)\n")); + PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "Failed to get input buffer (WaveRT)"); + result = paUnanticipatedHostError; + goto error; + } + + /* Get latency */ + result = PinGetHwLatency(stream->capture.pPin, &hwFifoLatency, &dummy, &dummy); + if (result == paNoError) + { + stream->capture.pPin->hwLatency = hwFifoLatency; + + /* Add HW latency into total input latency */ + stream->streamRepresentation.streamInfo.inputLatency += ((hwFifoLatency / stream->capture.bytesPerFrame) / sampleRate); + } + else + { + PA_DEBUG(("Failed to get size of FIFO hardware buffer (is set to zero)\n")); + stream->capture.pPin->hwLatency = 0; + } + } + break; + default: + /* Undefined wave type!! */ + assert(0); + result = paInternalError; + PaWinWDM_SetLastErrorInfo(result, "Wave type %u ??", stream->capture.pPin->parentFilter->devInfo.streamingType); + goto error; + } + } + else + { + stream->capture.hostBuffer = 0; + } + + if (stream->userOutputChannels > 0) + { + stream->streamRepresentation.streamInfo.outputLatency = stream->render.framesPerBuffer / sampleRate; + + switch (stream->render.pPin->parentFilter->devInfo.streamingType) + { + case Type_kWaveCyclic: + { + unsigned size = stream->render.noOfPackets * stream->render.framesPerBuffer * stream->render.bytesPerFrame; + /* Allocate output device buffer */ + stream->render.hostBuffer = (char*)PaUtil_GroupAllocateMemory(stream->allocGroup, size); + PA_DEBUG(("Output buffer allocated (size = %u)\n", size)); + if( !stream->render.hostBuffer ) + { + PA_DEBUG(("Cannot allocate host output buffer!\n")); + PaWinWDM_SetLastErrorInfo(paInsufficientMemory, "Failed to allocate output buffer"); + result = paInsufficientMemory; + goto error; + } + stream->render.hostBufferSize = size; + PA_DEBUG(("Output buffer start = %p (size=%u)\n",stream->render.hostBuffer, stream->render.hostBufferSize)); + + stream->render.pPin->fnEventHandler = PaPinRenderEventHandler_WaveCyclic; + stream->render.pPin->fnSubmitHandler = PaPinRenderSubmitHandler_WaveCyclic; + } + break; + case Type_kWaveRT: + { + const DWORD dwTotalSize = 2 * stream->render.framesPerBuffer * stream->render.bytesPerFrame; + DWORD dwRequestedSize = dwTotalSize; + BOOL bCallMemoryBarrier = FALSE; + ULONG hwFifoLatency = 0; + ULONG dummy; + result = PinGetBuffer(stream->render.pPin, (void**)&stream->render.hostBuffer, &dwRequestedSize, &bCallMemoryBarrier); + if (!result) + { + PA_DEBUG(("Output buffer start = %p, size = %u, membarrier = %u\n", stream->render.hostBuffer, dwRequestedSize, bCallMemoryBarrier)); + if (dwRequestedSize != dwTotalSize) + { + PA_DEBUG(("Buffer length changed by driver from %u to %u !\n", dwTotalSize, dwRequestedSize)); + /* Recalculate to what the driver has given us */ + stream->render.framesPerBuffer = dwRequestedSize / (2 * stream->render.bytesPerFrame); + } + stream->render.hostBufferSize = dwRequestedSize; + + if (stream->render.pPin->pinKsSubType == SubType_kPolled) + { + stream->render.pPin->fnEventHandler = PaPinRenderEventHandler_WaveRTPolled; + stream->render.pPin->fnSubmitHandler = PaPinRenderSubmitHandler_WaveRTPolled; + } + else + { + stream->render.pPin->fnEventHandler = PaPinRenderEventHandler_WaveRTEvent; + stream->render.pPin->fnSubmitHandler = PaPinRenderSubmitHandler_WaveRTEvent; + } + + stream->render.pPin->fnMemBarrier = bCallMemoryBarrier ? MemoryBarrierWrite : MemoryBarrierDummy; + } + else + { + PA_DEBUG(("Failed to get output buffer (with notification)\n")); + PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "Failed to get output buffer (with notification)"); + result = paUnanticipatedHostError; + goto error; + } + + /* Get latency */ + result = PinGetHwLatency(stream->render.pPin, &hwFifoLatency, &dummy, &dummy); + if (result == paNoError) + { + stream->render.pPin->hwLatency = hwFifoLatency; + + /* Add HW latency into total output latency */ + stream->streamRepresentation.streamInfo.outputLatency += ((hwFifoLatency / stream->render.bytesPerFrame) / sampleRate); + } + else + { + PA_DEBUG(("Failed to get size of FIFO hardware buffer (is set to zero)\n")); + stream->render.pPin->hwLatency = 0; + } + } + break; + default: + /* Undefined wave type!! */ + assert(0); + result = paInternalError; + PaWinWDM_SetLastErrorInfo(result, "Wave type %u ??", stream->capture.pPin->parentFilter->devInfo.streamingType); + goto error; + } + } + else + { + stream->render.hostBuffer = 0; + } + + stream->streamRepresentation.streamInfo.sampleRate = sampleRate; + + PA_DEBUG(("BytesPerInputFrame = %d\n",stream->capture.bytesPerFrame)); + PA_DEBUG(("BytesPerOutputFrame = %d\n",stream->render.bytesPerFrame)); + + /* memset(stream->hostBuffer,0,size); */ + + /* Abort */ + stream->eventAbort = CreateEvent(NULL, TRUE, FALSE, NULL); + if (stream->eventAbort == 0) + { + result = paInsufficientMemory; + goto error; + } + stream->eventStreamStart[0] = CreateEvent(NULL, TRUE, FALSE, NULL); + if (stream->eventStreamStart[0] == 0) + { + result = paInsufficientMemory; + goto error; + } + stream->eventStreamStart[1] = CreateEvent(NULL, TRUE, FALSE, NULL); + if (stream->eventStreamStart[1] == 0) + { + result = paInsufficientMemory; + goto error; + } + + if(stream->userInputChannels > 0) + { + const unsigned bufferSizeInBytes = stream->capture.framesPerBuffer * stream->capture.bytesPerFrame; + const unsigned ringBufferFrameSize = NextPowerOf2( 1024 + 2 * max(stream->capture.framesPerBuffer, stream->render.framesPerBuffer) ); + + stream->capture.events = (HANDLE*)PaUtil_GroupAllocateMemory(stream->allocGroup, stream->capture.noOfPackets * sizeof(HANDLE)); + if (stream->capture.events == NULL) + { + result = paInsufficientMemory; + goto error; + } + + stream->capture.packets = (DATAPACKET*)PaUtil_GroupAllocateMemory(stream->allocGroup, stream->capture.noOfPackets * sizeof(DATAPACKET)); + if (stream->capture.packets == NULL) + { + result = paInsufficientMemory; + goto error; + } + + switch(stream->capture.pPin->parentFilter->devInfo.streamingType) + { + case Type_kWaveCyclic: + { + /* WaveCyclic case */ + unsigned i; + for (i = 0; i < stream->capture.noOfPackets; ++i) + { + /* Set up the packets */ + DATAPACKET *p = stream->capture.packets + i; + + /* Record event */ + stream->capture.events[i] = CreateEvent(NULL, TRUE, FALSE, NULL); + + p->Signal.hEvent = stream->capture.events[i]; + p->Header.Data = stream->capture.hostBuffer + (i*bufferSizeInBytes); + p->Header.FrameExtent = bufferSizeInBytes; + p->Header.DataUsed = 0; + p->Header.Size = sizeof(p->Header); + p->Header.PresentationTime.Numerator = 1; + p->Header.PresentationTime.Denominator = 1; + } + } + break; + case Type_kWaveRT: + { + /* Set up the "packets" */ + DATAPACKET *p = stream->capture.packets + 0; + + /* Record event: WaveRT has a single event for 2 notification per buffer */ + stream->capture.events[0] = CreateEvent(NULL, FALSE, FALSE, NULL); + + p->Header.Data = stream->capture.hostBuffer; + p->Header.FrameExtent = bufferSizeInBytes; + p->Header.DataUsed = 0; + p->Header.Size = sizeof(p->Header); + p->Header.PresentationTime.Numerator = 1; + p->Header.PresentationTime.Denominator = 1; + + ++p; + p->Header.Data = stream->capture.hostBuffer + bufferSizeInBytes; + p->Header.FrameExtent = bufferSizeInBytes; + p->Header.DataUsed = 0; + p->Header.Size = sizeof(p->Header); + p->Header.PresentationTime.Numerator = 1; + p->Header.PresentationTime.Denominator = 1; + + if (stream->capture.pPin->pinKsSubType == SubType_kNotification) + { + result = PinRegisterNotificationHandle(stream->capture.pPin, stream->capture.events[0]); + + if (result != paNoError) + { + PA_DEBUG(("Failed to register capture notification handle\n")); + PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "Failed to register capture notification handle"); + result = paUnanticipatedHostError; + goto error; + } + } + + result = PinRegisterPositionRegister(stream->capture.pPin); + + if (result != paNoError) + { + unsigned long pos = 0xdeadc0de; + PA_DEBUG(("Failed to register capture position register, using PinGetAudioPositionViaIOCTLWrite\n")); + stream->capture.pPin->fnAudioPosition = PinGetAudioPositionViaIOCTLWrite; + /* Test position function */ + result = (stream->capture.pPin->fnAudioPosition)(stream->capture.pPin, &pos); + if (result != paNoError || pos != 0x0) + { + PA_DEBUG(("Failed to read capture position register (IOCTL)\n")); + PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "Failed to read capture position register (IOCTL)"); + result = paUnanticipatedHostError; + goto error; + } + } + else + { + stream->capture.pPin->fnAudioPosition = PinGetAudioPositionMemoryMapped; + } + } + break; + default: + /* Undefined wave type!! */ + assert(0); + result = paInternalError; + PaWinWDM_SetLastErrorInfo(result, "Wave type %u ??", stream->capture.pPin->parentFilter->devInfo.streamingType); + goto error; + } + + /* Setup the input ring buffer here */ + stream->ringBufferData = (char*)PaUtil_GroupAllocateMemory(stream->allocGroup, ringBufferFrameSize * stream->capture.bytesPerFrame); + if (stream->ringBufferData == NULL) + { + result = paInsufficientMemory; + goto error; + } + PaUtil_InitializeRingBuffer(&stream->ringBuffer, stream->capture.bytesPerFrame, ringBufferFrameSize, stream->ringBufferData); + } + if(stream->userOutputChannels > 0) + { + const unsigned bufferSizeInBytes = stream->render.framesPerBuffer * stream->render.bytesPerFrame; + + stream->render.events = (HANDLE*)PaUtil_GroupAllocateMemory(stream->allocGroup, stream->render.noOfPackets * sizeof(HANDLE)); + if (stream->render.events == NULL) + { + result = paInsufficientMemory; + goto error; + } + + stream->render.packets = (DATAPACKET*)PaUtil_GroupAllocateMemory(stream->allocGroup, stream->render.noOfPackets * sizeof(DATAPACKET)); + if (stream->render.packets == NULL) + { + result = paInsufficientMemory; + goto error; + } + + switch(stream->render.pPin->parentFilter->devInfo.streamingType) + { + case Type_kWaveCyclic: + { + /* WaveCyclic case */ + unsigned i; + for (i = 0; i < stream->render.noOfPackets; ++i) + { + /* Set up the packets */ + DATAPACKET *p = stream->render.packets + i; + + /* Playback event */ + stream->render.events[i] = CreateEvent(NULL, TRUE, FALSE, NULL); + + /* In this case, we just use the packets as ptr to the device buffer */ + p->Signal.hEvent = stream->render.events[i]; + p->Header.Data = stream->render.hostBuffer + (i*bufferSizeInBytes); + p->Header.FrameExtent = bufferSizeInBytes; + p->Header.DataUsed = bufferSizeInBytes; + p->Header.Size = sizeof(p->Header); + p->Header.PresentationTime.Numerator = 1; + p->Header.PresentationTime.Denominator = 1; + } + } + break; + case Type_kWaveRT: + { + /* WaveRT case */ + + /* Set up the "packets" */ + DATAPACKET *p = stream->render.packets; + + /* The only playback event */ + stream->render.events[0] = CreateEvent(NULL, FALSE, FALSE, NULL); + + /* In this case, we just use the packets as ptr to the device buffer */ + p->Header.Data = stream->render.hostBuffer; + p->Header.FrameExtent = stream->render.framesPerBuffer*stream->render.bytesPerFrame; + p->Header.DataUsed = stream->render.framesPerBuffer*stream->render.bytesPerFrame; + p->Header.Size = sizeof(p->Header); + p->Header.PresentationTime.Numerator = 1; + p->Header.PresentationTime.Denominator = 1; + + ++p; + p->Header.Data = stream->render.hostBuffer + stream->render.framesPerBuffer*stream->render.bytesPerFrame; + p->Header.FrameExtent = stream->render.framesPerBuffer*stream->render.bytesPerFrame; + p->Header.DataUsed = stream->render.framesPerBuffer*stream->render.bytesPerFrame; + p->Header.Size = sizeof(p->Header); + p->Header.PresentationTime.Numerator = 1; + p->Header.PresentationTime.Denominator = 1; + + if (stream->render.pPin->pinKsSubType == SubType_kNotification) + { + result = PinRegisterNotificationHandle(stream->render.pPin, stream->render.events[0]); + + if (result != paNoError) + { + PA_DEBUG(("Failed to register rendering notification handle\n")); + PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "Failed to register rendering notification handle"); + result = paUnanticipatedHostError; + goto error; + } + } + + result = PinRegisterPositionRegister(stream->render.pPin); + + if (result != paNoError) + { + unsigned long pos = 0xdeadc0de; + PA_DEBUG(("Failed to register rendering position register, using PinGetAudioPositionViaIOCTLRead\n")); + stream->render.pPin->fnAudioPosition = PinGetAudioPositionViaIOCTLRead; + /* Test position function */ + result = (stream->render.pPin->fnAudioPosition)(stream->render.pPin, &pos); + if (result != paNoError || pos != 0x0) + { + PA_DEBUG(("Failed to read render position register (IOCTL)\n")); + PaWinWDM_SetLastErrorInfo(paUnanticipatedHostError, "Failed to read render position register (IOCTL)"); + result = paUnanticipatedHostError; + goto error; + } + } + else + { + stream->render.pPin->fnAudioPosition = PinGetAudioPositionMemoryMapped; + } + } + break; + default: + /* Undefined wave type!! */ + assert(0); + result = paInternalError; + PaWinWDM_SetLastErrorInfo(result, "Wave type %u ??", stream->capture.pPin->parentFilter->devInfo.streamingType); + goto error; + } + } + + stream->streamStarted = 0; + stream->streamActive = 0; + stream->streamStop = 0; + stream->streamAbort = 0; + stream->streamFlags = streamFlags; + stream->oldProcessPriority = REALTIME_PRIORITY_CLASS; + + /* Increase ref count on filters in use, so that a CommitDeviceInfos won't delete them */ + if (stream->capture.pPin != 0) + { + FilterAddRef(stream->capture.pPin->parentFilter); + } + if (stream->render.pPin != 0) + { + FilterAddRef(stream->render.pPin->parentFilter); + } + + /* Ok, now update our host API specific stream info */ + if (stream->userInputChannels) + { + PaWinWdmDeviceInfo *pDeviceInfo = (PaWinWdmDeviceInfo*)wdmHostApi->inheritedHostApiRep.deviceInfos[inputParameters->device]; + + stream->hostApiStreamInfo.input.device = Pa_HostApiDeviceIndexToDeviceIndex(Pa_HostApiTypeIdToHostApiIndex(paWDMKS), inputParameters->device); + stream->hostApiStreamInfo.input.channels = stream->deviceInputChannels; + stream->hostApiStreamInfo.input.muxNodeId = -1; + if (stream->capture.pPin->inputs) + { + stream->hostApiStreamInfo.input.muxNodeId = stream->capture.pPin->inputs[pDeviceInfo->muxPosition]->muxNodeId; + } + stream->hostApiStreamInfo.input.endpointPinId = pDeviceInfo->endpointPinId; + stream->hostApiStreamInfo.input.framesPerHostBuffer = stream->capture.framesPerBuffer; + stream->hostApiStreamInfo.input.streamingSubType = stream->capture.pPin->pinKsSubType; + } + else + { + stream->hostApiStreamInfo.input.device = paNoDevice; + } + if (stream->userOutputChannels) + { + stream->hostApiStreamInfo.output.device = Pa_HostApiDeviceIndexToDeviceIndex(Pa_HostApiTypeIdToHostApiIndex(paWDMKS), outputParameters->device); + stream->hostApiStreamInfo.output.channels = stream->deviceOutputChannels; + stream->hostApiStreamInfo.output.framesPerHostBuffer = stream->render.framesPerBuffer; + stream->hostApiStreamInfo.output.endpointPinId = stream->render.pPin->endpointPinId; + stream->hostApiStreamInfo.output.streamingSubType = stream->render.pPin->pinKsSubType; + } + else + { + stream->hostApiStreamInfo.output.device = paNoDevice; + } + /*stream->streamRepresentation.streamInfo.hostApiTypeId = paWDMKS; + stream->streamRepresentation.streamInfo.hostApiSpecificStreamInfo = &stream->hostApiStreamInfo;*/ + stream->streamRepresentation.streamInfo.structVersion = 2; + + *s = (PaStream*)stream; + + PA_LOGL_; + return result; + +occupied: + /* Ok, someone else is hogging the pin, bail out */ + assert (result == paDeviceUnavailable); + PaWinWDM_SetLastErrorInfo(result, "Device is occupied"); + +error: + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + + CloseStreamEvents(stream); + + if (stream->allocGroup) + { + PaUtil_FreeAllAllocations(stream->allocGroup); + PaUtil_DestroyAllocationGroup(stream->allocGroup); + stream->allocGroup = 0; + } + + if(stream->render.pPin) + PinClose(stream->render.pPin); + if(stream->capture.pPin) + PinClose(stream->capture.pPin); + + PaUtil_FreeMemory( stream ); + + PA_LOGL_; + return result; +} + +/* +When CloseStream() is called, the multi-api layer ensures that +the stream has already been stopped or aborted. +*/ +static PaError CloseStream( PaStream* s ) +{ + PaError result = paNoError; + PaWinWdmStream *stream = (PaWinWdmStream*)s; + + PA_LOGE_; + + assert(!stream->streamStarted); + assert(!stream->streamActive); + + PaUtil_TerminateBufferProcessor( &stream->bufferProcessor ); + PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation ); + + CloseStreamEvents(stream); + + if (stream->allocGroup) + { + PaUtil_FreeAllAllocations(stream->allocGroup); + PaUtil_DestroyAllocationGroup(stream->allocGroup); + stream->allocGroup = 0; + } + + if(stream->render.pPin) + { + PinClose(stream->render.pPin); + } + if(stream->capture.pPin) + { + PinClose(stream->capture.pPin); + } + + if (stream->render.pPin) + { + FilterFree(stream->render.pPin->parentFilter); + } + if (stream->capture.pPin) + { + FilterFree(stream->capture.pPin->parentFilter); + } + + PaUtil_FreeMemory( stream ); + + PA_LOGL_; + return result; +} + +/* +Write the supplied packet to the pin +Asynchronous +Should return paNoError on success +*/ +static PaError PinWrite(HANDLE h, DATAPACKET* p) +{ + PaError result = paNoError; + unsigned long cbReturned = 0; + BOOL fRes = DeviceIoControl(h, + IOCTL_KS_WRITE_STREAM, + NULL, + 0, + &p->Header, + p->Header.Size, + &cbReturned, + &p->Signal); + if (!fRes) + { + unsigned long error = GetLastError(); + if (error != ERROR_IO_PENDING) + { + result = paInternalError; + } + } + return result; +} + +/* +Read to the supplied packet from the pin +Asynchronous +Should return paNoError on success +*/ +static PaError PinRead(HANDLE h, DATAPACKET* p) +{ + PaError result = paNoError; + unsigned long cbReturned = 0; + BOOL fRes = DeviceIoControl(h, + IOCTL_KS_READ_STREAM, + NULL, + 0, + &p->Header, + p->Header.Size, + &cbReturned, + &p->Signal); + if (!fRes) + { + unsigned long error = GetLastError(); + if (error != ERROR_IO_PENDING) + { + result = paInternalError; + } + } + return result; +} + +/* +Copy the first interleaved channel of 16 bit data to the other channels +*/ +static void DuplicateFirstChannelInt16(void* buffer, int channels, int samples) +{ + unsigned short* data = (unsigned short*)buffer; + int channel; + unsigned short sourceSample; + while( samples-- ) + { + sourceSample = *data++; + channel = channels-1; + while( channel-- ) + { + *data++ = sourceSample; + } + } +} + +/* +Copy the first interleaved channel of 24 bit data to the other channels +*/ +static void DuplicateFirstChannelInt24(void* buffer, int channels, int samples) +{ + unsigned char* data = (unsigned char*)buffer; + int channel; + unsigned char sourceSample[3]; + while( samples-- ) + { + sourceSample[0] = data[0]; + sourceSample[1] = data[1]; + sourceSample[2] = data[2]; + data += 3; + channel = channels-1; + while( channel-- ) + { + data[0] = sourceSample[0]; + data[1] = sourceSample[1]; + data[2] = sourceSample[2]; + data += 3; + } + } +} + +/* +Copy the first interleaved channel of 32 bit data to the other channels +*/ +static void DuplicateFirstChannelInt32(void* buffer, int channels, int samples) +{ + unsigned long* data = (unsigned long*)buffer; + int channel; + unsigned long sourceSample; + while( samples-- ) + { + sourceSample = *data++; + channel = channels-1; + while( channel-- ) + { + *data++ = sourceSample; + } + } +} + +/* +Increase the priority of the calling thread to RT +*/ +static HANDLE BumpThreadPriority() +{ + HANDLE hThread = GetCurrentThread(); + DWORD dwTask = 0; + HANDLE hAVRT = NULL; + + /* If we have access to AVRT.DLL (Vista and later), use it */ + if (paWinWDMKSAvRtEntryPoints.AvSetMmThreadCharacteristics != NULL) + { + hAVRT = paWinWDMKSAvRtEntryPoints.AvSetMmThreadCharacteristics("Pro Audio", &dwTask); + if (hAVRT != NULL && hAVRT != INVALID_HANDLE_VALUE) + { + BOOL bret = paWinWDMKSAvRtEntryPoints.AvSetMmThreadPriority(hAVRT, PA_AVRT_PRIORITY_CRITICAL); + if (!bret) + { + PA_DEBUG(("Set mm thread prio to critical failed!\n")); + } + else + { + return hAVRT; + } + } + else + { + PA_DEBUG(("Set mm thread characteristic to 'Pro Audio' failed, reverting to SetThreadPriority\n")); + } + } + + /* For XP and earlier, or if AvSetMmThreadCharacteristics fails (MMCSS disabled ?) */ + if (timeBeginPeriod(1) != TIMERR_NOERROR) { + PA_DEBUG(("timeBeginPeriod(1) failed!\n")); + } + + if (!SetThreadPriority(hThread, THREAD_PRIORITY_TIME_CRITICAL)) { + PA_DEBUG(("SetThreadPriority failed!\n")); + } + + return hAVRT; +} + +/* +Decrease the priority of the calling thread to normal +*/ +static void DropThreadPriority(HANDLE hAVRT) +{ + HANDLE hThread = GetCurrentThread(); + + if (hAVRT != NULL) + { + paWinWDMKSAvRtEntryPoints.AvSetMmThreadPriority(hAVRT, PA_AVRT_PRIORITY_NORMAL); + paWinWDMKSAvRtEntryPoints.AvRevertMmThreadCharacteristics(hAVRT); + return; + } + + SetThreadPriority(hThread, THREAD_PRIORITY_NORMAL); + timeEndPeriod(1); +} + +static PaError PreparePinForStart(PaWinWdmPin* pin) +{ + PaError result; + result = PinSetState(pin, KSSTATE_ACQUIRE); + if (result != paNoError) + { + goto error; + } + result = PinSetState(pin, KSSTATE_PAUSE); + if (result != paNoError) + { + goto error; + } + return result; + +error: + PinSetState(pin, KSSTATE_STOP); + return result; +} + +static PaError PreparePinsForStart(PaProcessThreadInfo* pInfo) +{ + PaError result = paNoError; + /* Submit buffers */ + if (pInfo->stream->capture.pPin) + { + if ((result = PreparePinForStart(pInfo->stream->capture.pPin)) != paNoError) + { + goto error; + } + + if (pInfo->stream->capture.pPin->parentFilter->devInfo.streamingType == Type_kWaveCyclic) + { + unsigned i; + for(i=0; i < pInfo->stream->capture.noOfPackets; ++i) + { + if ((result = PinRead(pInfo->stream->capture.pPin->handle, pInfo->stream->capture.packets + i)) != paNoError) + { + goto error; + } + ++pInfo->pending; + } + } + else + { + pInfo->pending = 2; + } + } + + if(pInfo->stream->render.pPin) + { + if ((result = PreparePinForStart(pInfo->stream->render.pPin)) != paNoError) + { + goto error; + } + + pInfo->priming += pInfo->stream->render.noOfPackets; + ++pInfo->pending; + SetEvent(pInfo->stream->render.events[0]); + if (pInfo->stream->render.pPin->parentFilter->devInfo.streamingType == Type_kWaveCyclic) + { + unsigned i; + for(i=1; i < pInfo->stream->render.noOfPackets; ++i) + { + SetEvent(pInfo->stream->render.events[i]); + ++pInfo->pending; + } + } + } + +error: + PA_DEBUG(("PreparePinsForStart = %d\n", result)); + return result; +} + +static PaError StartPin(PaWinWdmPin* pin) +{ + return PinSetState(pin, KSSTATE_RUN); +} + +static PaError StartPins(PaProcessThreadInfo* pInfo) +{ + PaError result = paNoError; + /* Start the pins as synced as possible */ + if (pInfo->stream->capture.pPin) + { + result = StartPin(pInfo->stream->capture.pPin); + } + if(pInfo->stream->render.pPin) + { + result = StartPin(pInfo->stream->render.pPin); + } + PA_DEBUG(("StartPins = %d\n", result)); + return result; +} + + +static PaError StopPin(PaWinWdmPin* pin) +{ + PinSetState(pin, KSSTATE_PAUSE); + PinSetState(pin, KSSTATE_STOP); + return paNoError; +} + + +static PaError StopPins(PaProcessThreadInfo* pInfo) +{ + PaError result = paNoError; + if(pInfo->stream->render.pPin) + { + StopPin(pInfo->stream->render.pPin); + } + if(pInfo->stream->capture.pPin) + { + StopPin(pInfo->stream->capture.pPin); + } + return result; +} + +typedef void (*TSetInputFrameCount)(PaUtilBufferProcessor*, unsigned long); +typedef void (*TSetInputChannel)(PaUtilBufferProcessor*, unsigned int, void *, unsigned int); +static const TSetInputFrameCount fnSetInputFrameCount[2] = { PaUtil_SetInputFrameCount, PaUtil_Set2ndInputFrameCount }; +static const TSetInputChannel fnSetInputChannel[2] = { PaUtil_SetInputChannel, PaUtil_Set2ndInputChannel }; + +static PaError PaDoProcessing(PaProcessThreadInfo* pInfo) +{ + PaError result = paNoError; + int i, framesProcessed = 0, doChannelCopy = 0; + ring_buffer_size_t inputFramesAvailable = PaUtil_GetRingBufferReadAvailable(&pInfo->stream->ringBuffer); + + /* Do necessary buffer processing (which will invoke user callback if necessary) */ + if (pInfo->cbResult == paContinue && + (pInfo->renderHead != pInfo->renderTail || inputFramesAvailable)) + { + unsigned processFullDuplex = pInfo->stream->capture.pPin && pInfo->stream->render.pPin && (!pInfo->priming); + + PA_HP_TRACE((pInfo->stream->hLog, "DoProcessing: InputFrames=%u", inputFramesAvailable)); + + PaUtil_BeginCpuLoadMeasurement( &pInfo->stream->cpuLoadMeasurer ); + + pInfo->ti.currentTime = PaUtil_GetTime(); + + PaUtil_BeginBufferProcessing(&pInfo->stream->bufferProcessor, &pInfo->ti, pInfo->underover); + pInfo->underover = 0; /* Reset the (under|over)flow status */ + + if (pInfo->renderTail != pInfo->renderHead) + { + DATAPACKET* packet = pInfo->renderPackets[pInfo->renderTail & cPacketsArrayMask].packet; + + assert(packet != 0); + assert(packet->Header.Data != 0); + + PaUtil_SetOutputFrameCount(&pInfo->stream->bufferProcessor, pInfo->stream->render.framesPerBuffer); + + for(i=0;istream->userOutputChannels;i++) + { + /* Only write the user output channels. Leave the rest blank */ + PaUtil_SetOutputChannel(&pInfo->stream->bufferProcessor, + i, + ((unsigned char*)(packet->Header.Data))+(i*pInfo->stream->render.bytesPerSample), + pInfo->stream->deviceOutputChannels); + } + + /* We will do a copy to the other channels after the data has been written */ + doChannelCopy = ( pInfo->stream->userOutputChannels == 1 ); + } + + if (inputFramesAvailable && (!pInfo->stream->userOutputChannels || inputFramesAvailable >= (int)pInfo->stream->render.framesPerBuffer)) + { + unsigned wrapCntr = 0; + void* data[2] = {0}; + ring_buffer_size_t size[2] = {0}; + + /* If full-duplex, we just extract output buffer number of frames */ + if (pInfo->stream->userOutputChannels) + { + inputFramesAvailable = min(inputFramesAvailable, (int)pInfo->stream->render.framesPerBuffer); + } + + inputFramesAvailable = PaUtil_GetRingBufferReadRegions(&pInfo->stream->ringBuffer, + inputFramesAvailable, + &data[0], + &size[0], + &data[1], + &size[1]); + + for (wrapCntr = 0; wrapCntr < 2; ++wrapCntr) + { + if (size[wrapCntr] == 0) + break; + + fnSetInputFrameCount[wrapCntr](&pInfo->stream->bufferProcessor, size[wrapCntr]); + for(i=0;istream->userInputChannels;i++) + { + /* Only read as many channels as the user wants */ + fnSetInputChannel[wrapCntr](&pInfo->stream->bufferProcessor, + i, + ((unsigned char*)(data[wrapCntr]))+(i*pInfo->stream->capture.bytesPerSample), + pInfo->stream->deviceInputChannels); + } + } + } + else + { + /* We haven't consumed anything from the ring buffer... */ + inputFramesAvailable = 0; + /* If we have full-duplex, this is at startup, so mark no-input! */ + if (pInfo->stream->userOutputChannels>0 && pInfo->stream->userInputChannels>0) + { + PA_HP_TRACE((pInfo->stream->hLog, "Input startup, marking no input.")); + PaUtil_SetNoInput(&pInfo->stream->bufferProcessor); + } + } + + if (processFullDuplex) /* full duplex */ + { + /* Only call the EndBufferProcessing function when the total input frames == total output frames */ + const unsigned long totalInputFrameCount = pInfo->stream->bufferProcessor.hostInputFrameCount[0] + pInfo->stream->bufferProcessor.hostInputFrameCount[1]; + const unsigned long totalOutputFrameCount = pInfo->stream->bufferProcessor.hostOutputFrameCount[0] + pInfo->stream->bufferProcessor.hostOutputFrameCount[1]; + + if(totalInputFrameCount == totalOutputFrameCount && totalOutputFrameCount != 0) + { + framesProcessed = PaUtil_EndBufferProcessing(&pInfo->stream->bufferProcessor, &pInfo->cbResult); + } + else + { + framesProcessed = 0; + } + } + else + { + framesProcessed = PaUtil_EndBufferProcessing(&pInfo->stream->bufferProcessor, &pInfo->cbResult); + } + + PA_HP_TRACE((pInfo->stream->hLog, "Frames processed: %u %s", framesProcessed, (pInfo->priming ? "(priming)":""))); + + if( doChannelCopy ) + { + DATAPACKET* packet = pInfo->renderPackets[pInfo->renderTail & cPacketsArrayMask].packet; + /* Copy the first output channel to the other channels */ + switch (pInfo->stream->render.bytesPerSample) + { + case 2: + DuplicateFirstChannelInt16(packet->Header.Data, pInfo->stream->deviceOutputChannels, pInfo->stream->render.framesPerBuffer); + break; + case 3: + DuplicateFirstChannelInt24(packet->Header.Data, pInfo->stream->deviceOutputChannels, pInfo->stream->render.framesPerBuffer); + break; + case 4: + DuplicateFirstChannelInt32(packet->Header.Data, pInfo->stream->deviceOutputChannels, pInfo->stream->render.framesPerBuffer); + break; + default: + assert(0); /* Unsupported format! */ + break; + } + } + PaUtil_EndCpuLoadMeasurement( &pInfo->stream->cpuLoadMeasurer, framesProcessed ); + + if (inputFramesAvailable) + { + PaUtil_AdvanceRingBufferReadIndex(&pInfo->stream->ringBuffer, inputFramesAvailable); + } + + if (pInfo->renderTail != pInfo->renderHead) + { + if (!pInfo->stream->streamStop) + { + result = pInfo->stream->render.pPin->fnSubmitHandler(pInfo, pInfo->renderTail); + if (result != paNoError) + { + PA_HP_TRACE((pInfo->stream->hLog, "Capture submit handler failed with result %d", result)); + return result; + } + } + pInfo->renderTail++; + if (!pInfo->pinsStarted && pInfo->priming == 0) + { + /* We start the pins here to allow "prime time" */ + if ((result = StartPins(pInfo)) == paNoError) + { + PA_HP_TRACE((pInfo->stream->hLog, "Starting pins!")); + pInfo->pinsStarted = 1; + } + } + } + } + + return result; +} + +static VOID CALLBACK TimerAPCWaveRTPolledMode( + LPVOID lpArgToCompletionRoutine, + DWORD dwTimerLowValue, + DWORD dwTimerHighValue) +{ + HANDLE* pHandles = (HANDLE*)lpArgToCompletionRoutine; + if (pHandles[0]) SetEvent(pHandles[0]); + if (pHandles[1]) SetEvent(pHandles[1]); +} + +static DWORD GetCurrentTimeInMillisecs() +{ + return timeGetTime(); +} + +PA_THREAD_FUNC ProcessingThread(void* pParam) +{ + PaError result = paNoError; + HANDLE hAVRT = NULL; + HANDLE hTimer = NULL; + HANDLE *handleArray = NULL; + HANDLE timerEventHandles[2] = {0}; + unsigned noOfHandles = 0; + unsigned captureEvents = 0; + unsigned renderEvents = 0; + unsigned timerPeriod = 0; + DWORD timeStamp[2] = {0}; + + PaProcessThreadInfo info; + memset(&info, 0, sizeof(PaProcessThreadInfo)); + info.stream = (PaWinWdmStream*)pParam; + + info.stream->threadResult = paNoError; + + PA_LOGE_; + + info.ti.inputBufferAdcTime = 0.0; + info.ti.currentTime = 0.0; + info.ti.outputBufferDacTime = 0.0; + + PA_DEBUG(("In buffer len: %.3f ms\n",(2000*info.stream->capture.framesPerBuffer) / info.stream->streamRepresentation.streamInfo.sampleRate)); + PA_DEBUG(("Out buffer len: %.3f ms\n",(2000*info.stream->render.framesPerBuffer) / info.stream->streamRepresentation.streamInfo.sampleRate)); + info.timeout = (DWORD)max( + (2000*info.stream->render.framesPerBuffer/info.stream->streamRepresentation.streamInfo.sampleRate + 0.5), + (2000*info.stream->capture.framesPerBuffer/info.stream->streamRepresentation.streamInfo.sampleRate + 0.5)); + info.timeout = max(info.timeout*8, 100); + timerPeriod = info.timeout; + PA_DEBUG(("Timeout = %ld ms\n",info.timeout)); + + /* Allocate handle array */ + handleArray = (HANDLE*)PaUtil_AllocateMemory((info.stream->capture.noOfPackets + info.stream->render.noOfPackets + 1) * sizeof(HANDLE)); + + /* Setup handle array for WFMO */ + if (info.stream->capture.pPin != 0) + { + handleArray[noOfHandles++] = info.stream->capture.events[0]; + if (info.stream->capture.pPin->parentFilter->devInfo.streamingType == Type_kWaveCyclic) + { + unsigned i; + for(i=1; i < info.stream->capture.noOfPackets; ++i) + { + handleArray[noOfHandles++] = info.stream->capture.events[i]; + } + } + captureEvents = noOfHandles; + renderEvents = noOfHandles; + } + + if (info.stream->render.pPin != 0) + { + handleArray[noOfHandles++] = info.stream->render.events[0]; + if (info.stream->render.pPin->parentFilter->devInfo.streamingType == Type_kWaveCyclic) + { + unsigned i; + for(i=1; i < info.stream->render.noOfPackets; ++i) + { + handleArray[noOfHandles++] = info.stream->render.events[i]; + } + } + renderEvents = noOfHandles; + } + handleArray[noOfHandles++] = info.stream->eventAbort; + assert(noOfHandles <= (info.stream->capture.noOfPackets + info.stream->render.noOfPackets + 1)); + + /* Prepare render and capture pins */ + if ((result = PreparePinsForStart(&info)) != paNoError) + { + PA_DEBUG(("Failed to prepare device(s)!\n")); + goto error; + } + + /* Init high speed logger */ + if (PaUtil_InitializeHighSpeedLog(&info.stream->hLog, 1000000) != paNoError) + { + PA_DEBUG(("Failed to init high speed logger!\n")); + goto error; + } + + /* Heighten priority here */ + hAVRT = BumpThreadPriority(); + + /* If input only, we start the pins immediately */ + if (info.stream->render.pPin == 0) + { + if ((result = StartPins(&info)) != paNoError) + { + PA_DEBUG(("Failed to start device(s)!\n")); + goto error; + } + info.pinsStarted = 1; + } + + /* Handle WaveRT polled mode */ + { + const unsigned fs = (unsigned)info.stream->streamRepresentation.streamInfo.sampleRate; + if (info.stream->capture.pPin != 0 && info.stream->capture.pPin->pinKsSubType == SubType_kPolled) + { + timerEventHandles[0] = info.stream->capture.events[0]; + timerPeriod = min(timerPeriod, (1000*info.stream->capture.framesPerBuffer)/fs); + } + + if (info.stream->render.pPin != 0 && info.stream->render.pPin->pinKsSubType == SubType_kPolled) + { + timerEventHandles[1] = info.stream->render.events[0]; + timerPeriod = min(timerPeriod, (1000*info.stream->render.framesPerBuffer)/fs); + } + + if (timerEventHandles[0] || timerEventHandles[1]) + { + LARGE_INTEGER dueTime = {0}; + + timerPeriod=max(timerPeriod/5,1); + PA_DEBUG(("Timer event handles=0x%04X,0x%04X period=%u ms", timerEventHandles[0], timerEventHandles[1], timerPeriod)); + hTimer = CreateWaitableTimer(0, FALSE, NULL); + if (hTimer == NULL) + { + result = paUnanticipatedHostError; + goto error; + } + /* invoke first timeout immediately */ + if (!SetWaitableTimer(hTimer, &dueTime, timerPeriod, TimerAPCWaveRTPolledMode, timerEventHandles, FALSE)) + { + result = paUnanticipatedHostError; + goto error; + } + PA_DEBUG(("Waitable timer started, period = %u ms\n", timerPeriod)); + } + } + + /* Mark stream as active */ + info.stream->streamActive = 1; + info.stream->threadResult = paNoError; + + /* Up and running... */ + SetEvent(info.stream->eventStreamStart[StreamStart_kOk]); + + /* Take timestamp here */ + timeStamp[0] = timeStamp[1] = GetCurrentTimeInMillisecs(); + + while(!info.stream->streamAbort) + { + unsigned doProcessing = 1; + unsigned wait = WaitForMultipleObjects(noOfHandles, handleArray, FALSE, 0); + unsigned eventSignalled = wait - WAIT_OBJECT_0; + DWORD dwCurrentTime = 0; + + if (wait == WAIT_FAILED) + { + PA_DEBUG(("Wait failed = %ld! \n",wait)); + break; + } + if (wait == WAIT_TIMEOUT) + { + wait = WaitForMultipleObjectsEx(noOfHandles, handleArray, FALSE, 50, TRUE); + eventSignalled = wait - WAIT_OBJECT_0; + } + else + { + if (eventSignalled < captureEvents) + { + if (PaUtil_GetRingBufferWriteAvailable(&info.stream->ringBuffer) == 0) + { + PA_HP_TRACE((info.stream->hLog, "!!!!! Input overflow !!!!!")); + info.underover |= paInputOverflow; + } + } + else if (eventSignalled < renderEvents) + { + if (!info.priming && info.renderHead - info.renderTail > 1) + { + PA_HP_TRACE((info.stream->hLog, "!!!!! Output underflow !!!!!")); + info.underover |= paOutputUnderflow; + } + } + } + + /* Get event time */ + dwCurrentTime = GetCurrentTimeInMillisecs(); + + /* Since we can mix capture/render devices between WaveCyclic, WaveRT polled and WaveRT notification (3x3 combinations), + we can't rely on the timeout of WFMO to check for device timeouts, we need to keep tally. */ + if (info.stream->capture.pPin && (dwCurrentTime - timeStamp[0]) >= info.timeout) + { + PA_DEBUG(("Timeout for capture device (%u ms)!", info.timeout, (dwCurrentTime - timeStamp[0]))); + result = paTimedOut; + break; + } + if (info.stream->render.pPin && (dwCurrentTime - timeStamp[1]) >= info.timeout) + { + PA_DEBUG(("Timeout for render device (%u ms)!", info.timeout, (dwCurrentTime - timeStamp[1]))); + result = paTimedOut; + break; + } + + if (wait == WAIT_IO_COMPLETION) + { + /* Waitable timer has fired! */ + PA_HP_TRACE((info.stream->hLog, "WAIT_IO_COMPLETION")); + continue; + } + + if (wait == WAIT_TIMEOUT) + { + continue; + } + else + { + if (eventSignalled < captureEvents) + { + if (info.stream->capture.pPin->fnEventHandler(&info, eventSignalled) == paNoError) + { + timeStamp[0] = dwCurrentTime; + + /* Since we use the ring buffer, we can submit the buffers directly */ + if (!info.stream->streamStop) + { + result = info.stream->capture.pPin->fnSubmitHandler(&info, info.captureTail); + if (result != paNoError) + { + PA_HP_TRACE((info.stream->hLog, "Capture submit handler failed with result %d", result)); + break; + } + } + ++info.captureTail; + /* If full-duplex, let _only_ render event trigger processing. We still need the stream stop + handling working, so let that be processed anyways... */ + if (info.stream->userOutputChannels > 0) + { + doProcessing = 0; + } + } + } + else if (eventSignalled < renderEvents) + { + timeStamp[1] = dwCurrentTime; + eventSignalled -= captureEvents; + info.stream->render.pPin->fnEventHandler(&info, eventSignalled); + } + else + { + assert(info.stream->streamAbort); + PA_HP_TRACE((info.stream->hLog, "Stream abort!")); + continue; + } + } + + /* Handle processing */ + if (doProcessing) + { + result = PaDoProcessing(&info); + if (result != paNoError) + { + PA_HP_TRACE((info.stream->hLog, "PaDoProcessing failed!")); + break; + } + } + + if(info.stream->streamStop && info.cbResult != paComplete) + { + PA_HP_TRACE((info.stream->hLog, "Stream stop! pending=%d",info.pending)); + info.cbResult = paComplete; /* Stop, but play remaining buffers */ + } + + if(info.pending<=0) + { + PA_HP_TRACE((info.stream->hLog, "pending==0 finished...")); + break; + } + if((!info.stream->render.pPin)&&(info.cbResult!=paContinue)) + { + PA_HP_TRACE((info.stream->hLog, "record only cbResult=%d...",info.cbResult)); + break; + } + } + + PA_DEBUG(("Finished processing loop\n")); + + info.stream->threadResult = result; + goto bailout; + +error: + PA_DEBUG(("Error starting processing thread\n")); + /* Set the "error" event together with result */ + info.stream->threadResult = result; + SetEvent(info.stream->eventStreamStart[StreamStart_kFailed]); + +bailout: + if (hTimer) + { + PA_DEBUG(("Waitable timer stopped\n", timerPeriod)); + CancelWaitableTimer(hTimer); + CloseHandle(hTimer); + hTimer = 0; + } + + if (info.pinsStarted) + { + StopPins(&info); + } + + /* Lower prio here */ + DropThreadPriority(hAVRT); + + if (handleArray != NULL) + { + PaUtil_FreeMemory(handleArray); + } + +#if PA_TRACE_REALTIME_EVENTS + if (info.stream->hLog) + { + PA_DEBUG(("Dumping highspeed trace...\n")); + PaUtil_DumpHighSpeedLog(info.stream->hLog, "hp_trace.log"); + PaUtil_DiscardHighSpeedLog(info.stream->hLog); + info.stream->hLog = 0; + } +#endif + info.stream->streamActive = 0; + + if((!info.stream->streamStop)&&(!info.stream->streamAbort)) + { + /* Invoke the user stream finished callback */ + /* Only do it from here if not being stopped/aborted by user */ + if( info.stream->streamRepresentation.streamFinishedCallback != 0 ) + info.stream->streamRepresentation.streamFinishedCallback( info.stream->streamRepresentation.userData ); + } + info.stream->streamStop = 0; + info.stream->streamAbort = 0; + + PA_LOGL_; + return 0; +} + + +static PaError StartStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinWdmStream *stream = (PaWinWdmStream*)s; + + PA_LOGE_; + + if (stream->streamThread != NULL) + { + return paStreamIsNotStopped; + } + + stream->streamStop = 0; + stream->streamAbort = 0; + + ResetStreamEvents(stream); + + PaUtil_ResetBufferProcessor( &stream->bufferProcessor ); + + stream->oldProcessPriority = GetPriorityClass(GetCurrentProcess()); + /* Uncomment the following line to enable dynamic boosting of the process + * priority to real time for best low latency support + * Disabled by default because RT processes can easily block the OS */ + /*ret = SetPriorityClass(GetCurrentProcess(),REALTIME_PRIORITY_CLASS); + PA_DEBUG(("Class ret = %d;",ret));*/ + + stream->streamThread = CREATE_THREAD_FUNCTION (NULL, 0, ProcessingThread, stream, CREATE_SUSPENDED, NULL); + if(stream->streamThread == NULL) + { + result = paInsufficientMemory; + goto end; + } + ResumeThread(stream->streamThread); + + switch (WaitForMultipleObjects(2, stream->eventStreamStart, FALSE, 5000)) + { + case WAIT_OBJECT_0 + StreamStart_kOk: + PA_DEBUG(("Processing thread started!\n")); + result = paNoError; + /* streamActive is set in processing thread */ + stream->streamStarted = 1; + break; + case WAIT_OBJECT_0 + StreamStart_kFailed: + PA_DEBUG(("Processing thread start failed! (result=%d)\n", stream->threadResult)); + result = stream->threadResult; + /* Wait for the stream to really exit */ + WaitForSingleObject(stream->streamThread, 200); + CloseHandle(stream->streamThread); + stream->streamThread = 0; + break; + case WAIT_TIMEOUT: + default: + result = paTimedOut; + PaWinWDM_SetLastErrorInfo(result, "Failed to start processing thread (timeout)!"); + break; + } + +end: + PA_LOGL_; + return result; +} + + +static PaError StopStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinWdmStream *stream = (PaWinWdmStream*)s; + BOOL doCb = FALSE; + + PA_LOGE_; + + if(stream->streamActive) + { + DWORD dwExitCode; + doCb = TRUE; + stream->streamStop = 1; + if (GetExitCodeThread(stream->streamThread, &dwExitCode) && dwExitCode == STILL_ACTIVE) + { + if (WaitForSingleObject(stream->streamThread, INFINITE) != WAIT_OBJECT_0) + { + PA_DEBUG(("StopStream: stream thread terminated\n")); + TerminateThread(stream->streamThread, -1); + result = paTimedOut; + } + } + else + { + PA_DEBUG(("StopStream: GECT says not active, but streamActive is not false ??")); + result = paUnanticipatedHostError; + PaWinWDM_SetLastErrorInfo(result, "StopStream: GECT says not active, but streamActive = %d", stream->streamActive); + } + } + else + { + if (stream->threadResult != paNoError) + { + PA_DEBUG(("StopStream: Stream not active (%d)\n", stream->threadResult)); + result = stream->threadResult; + stream->threadResult = paNoError; + } + } + + if (stream->streamThread != NULL) + { + CloseHandle(stream->streamThread); + stream->streamThread = 0; + } + stream->streamStarted = 0; + stream->streamActive = 0; + + if(doCb) + { + /* Do user callback now after all state has been reset */ + /* This means it should be safe for the called function */ + /* to invoke e.g. StartStream */ + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + } + + PA_LOGL_; + return result; +} + +static PaError AbortStream( PaStream *s ) +{ + PaError result = paNoError; + PaWinWdmStream *stream = (PaWinWdmStream*)s; + int doCb = 0; + + PA_LOGE_; + + if(stream->streamActive) + { + doCb = 1; + stream->streamAbort = 1; + SetEvent(stream->eventAbort); /* Signal immediately */ + if (WaitForSingleObject(stream->streamThread, 10000) != WAIT_OBJECT_0) + { + TerminateThread(stream->streamThread, -1); + result = paTimedOut; + + PA_DEBUG(("AbortStream: stream thread terminated\n")); + } + assert(!stream->streamActive); + } + CloseHandle(stream->streamThread); + stream->streamThread = NULL; + stream->streamStarted = 0; + + if(doCb) + { + /* Do user callback now after all state has been reset */ + /* This means it should be safe for the called function */ + /* to invoke e.g. StartStream */ + if( stream->streamRepresentation.streamFinishedCallback != 0 ) + stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData ); + } + + stream->streamActive = 0; + stream->streamStarted = 0; + + PA_LOGL_; + return result; +} + + +static PaError IsStreamStopped( PaStream *s ) +{ + PaWinWdmStream *stream = (PaWinWdmStream*)s; + int result = 0; + + PA_LOGE_; + + if(!stream->streamStarted) + result = 1; + + PA_LOGL_; + return result; +} + + +static PaError IsStreamActive( PaStream *s ) +{ + PaWinWdmStream *stream = (PaWinWdmStream*)s; + int result = 0; + + PA_LOGE_; + + if(stream->streamActive) + result = 1; + + PA_LOGL_; + return result; +} + + +static PaTime GetStreamTime( PaStream* s ) +{ + PA_LOGE_; + PA_LOGL_; + (void)s; + return PaUtil_GetTime(); +} + + +static double GetStreamCpuLoad( PaStream* s ) +{ + PaWinWdmStream *stream = (PaWinWdmStream*)s; + double result; + PA_LOGE_; + result = PaUtil_GetCpuLoad( &stream->cpuLoadMeasurer ); + PA_LOGL_; + return result; +} + + +/* +As separate stream interfaces are used for blocking and callback +streams, the following functions can be guaranteed to only be called +for blocking streams. +*/ + +static PaError ReadStream( PaStream* s, + void *buffer, + unsigned long frames ) +{ + PaWinWdmStream *stream = (PaWinWdmStream*)s; + + PA_LOGE_; + + /* suppress unused variable warnings */ + (void) buffer; + (void) frames; + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + PA_LOGL_; + return paInternalError; +} + + +static PaError WriteStream( PaStream* s, + const void *buffer, + unsigned long frames ) +{ + PaWinWdmStream *stream = (PaWinWdmStream*)s; + + PA_LOGE_; + + /* suppress unused variable warnings */ + (void) buffer; + (void) frames; + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + PA_LOGL_; + return paInternalError; +} + + +static signed long GetStreamReadAvailable( PaStream* s ) +{ + PaWinWdmStream *stream = (PaWinWdmStream*)s; + + PA_LOGE_; + + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + PA_LOGL_; + return 0; +} + + +static signed long GetStreamWriteAvailable( PaStream* s ) +{ + PaWinWdmStream *stream = (PaWinWdmStream*)s; + + PA_LOGE_; + /* suppress unused variable warnings */ + (void) stream; + + /* IMPLEMENT ME, see portaudio.h for required behavior*/ + PA_LOGL_; + return 0; +} + +/***************************************************************************************/ +/* Event and submit handlers for WaveCyclic */ +/***************************************************************************************/ + +static PaError PaPinCaptureEventHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex) +{ + PaError result = paNoError; + ring_buffer_size_t frameCount; + DATAPACKET* packet = pInfo->stream->capture.packets + eventIndex; + + assert( eventIndex < pInfo->stream->capture.noOfPackets ); + + if (packet->Header.DataUsed == 0) + { + PA_HP_TRACE((pInfo->stream->hLog, ">>> Capture bogus event (no data): idx=%u", eventIndex)); + + /* Bogus event, reset! This is to handle the behavior of this USB mic: http://shop.xtz.se/measurement-system/microphone-to-dirac-live-room-correction-suite + on startup of streaming, where it erroneously sets the event without the corresponding buffer being filled (DataUsed == 0) */ + ResetEvent(packet->Signal.hEvent); + + result = -1; /* Only need this to be NOT paNoError */ + } + else + { + pInfo->capturePackets[pInfo->captureHead & cPacketsArrayMask].packet = packet; + + frameCount = PaUtil_WriteRingBuffer(&pInfo->stream->ringBuffer, packet->Header.Data, pInfo->stream->capture.framesPerBuffer); + + PA_HP_TRACE((pInfo->stream->hLog, ">>> Capture event: idx=%u (frames=%u)", eventIndex, frameCount)); + ++pInfo->captureHead; + } + + --pInfo->pending; /* This needs to be done in either case */ + return result; +} + +static PaError PaPinCaptureSubmitHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex) +{ + PaError result = paNoError; + DATAPACKET* packet = pInfo->capturePackets[pInfo->captureTail & cPacketsArrayMask].packet; + pInfo->capturePackets[pInfo->captureTail & cPacketsArrayMask].packet = 0; + assert(packet != 0); + PA_HP_TRACE((pInfo->stream->hLog, "Capture submit: %u", eventIndex)); + packet->Header.DataUsed = 0; /* Reset for reuse */ + packet->Header.OptionsFlags = 0; /* Reset for reuse. Required for e.g. Focusrite Scarlett 2i4 (1st Gen) see #310 */ + ResetEvent(packet->Signal.hEvent); + result = PinRead(pInfo->stream->capture.pPin->handle, packet); + ++pInfo->pending; + return result; +} + +static PaError PaPinRenderEventHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex) +{ + assert( eventIndex < pInfo->stream->render.noOfPackets ); + + pInfo->renderPackets[pInfo->renderHead & cPacketsArrayMask].packet = pInfo->stream->render.packets + eventIndex; + PA_HP_TRACE((pInfo->stream->hLog, "<<< Render event : idx=%u head=%u", eventIndex, pInfo->renderHead)); + ++pInfo->renderHead; + --pInfo->pending; + return paNoError; +} + +static PaError PaPinRenderSubmitHandler_WaveCyclic(PaProcessThreadInfo* pInfo, unsigned eventIndex) +{ + PaError result = paNoError; + DATAPACKET* packet = pInfo->renderPackets[pInfo->renderTail & cPacketsArrayMask].packet; + pInfo->renderPackets[pInfo->renderTail & cPacketsArrayMask].packet = 0; + assert(packet != 0); + + PA_HP_TRACE((pInfo->stream->hLog, "Render submit : %u idx=%u", pInfo->renderTail, (unsigned)(packet - pInfo->stream->render.packets))); + ResetEvent(packet->Signal.hEvent); + result = PinWrite(pInfo->stream->render.pPin->handle, packet); + /* Reset event, just in case we have an analogous situation to capture (see PaPinCaptureSubmitHandler_WaveCyclic) */ + ++pInfo->pending; + if (pInfo->priming) + { + --pInfo->priming; + } + return result; +} + +/***************************************************************************************/ +/* Event and submit handlers for WaveRT */ +/***************************************************************************************/ + +static PaError PaPinCaptureEventHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex) +{ + unsigned long pos; + unsigned realInBuf; + unsigned frameCount; + PaWinWdmIOInfo* pCapture = &pInfo->stream->capture; + const unsigned halfInputBuffer = pCapture->hostBufferSize >> 1; + PaWinWdmPin* pin = pCapture->pPin; + DATAPACKET* packet = 0; + + /* Get hold of current ADC position */ + pin->fnAudioPosition(pin, &pos); + /* Wrap it (robi: why not use hw latency compensation here ?? because pos then gets _way_ off from + where it should be, i.e. at beginning or half buffer position. Why? No idea.) */ + + pos %= pCapture->hostBufferSize; + /* Then realInBuf will point to "other" half of double buffer */ + realInBuf = pos < halfInputBuffer ? 1U : 0U; + + packet = pInfo->stream->capture.packets + realInBuf; + + /* Call barrier (or dummy) */ + pin->fnMemBarrier(); + + /* Put it in queue */ + frameCount = PaUtil_WriteRingBuffer(&pInfo->stream->ringBuffer, packet->Header.Data, pCapture->framesPerBuffer); + + pInfo->capturePackets[pInfo->captureHead & cPacketsArrayMask].packet = packet; + + PA_HP_TRACE((pInfo->stream->hLog, "Capture event (WaveRT): idx=%u head=%u (pos = %4.1lf%%, frames=%u)", realInBuf, pInfo->captureHead, (pos * 100.0 / pCapture->hostBufferSize), frameCount)); + + ++pInfo->captureHead; + --pInfo->pending; + + return paNoError; +} + +static PaError PaPinCaptureEventHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex) +{ + unsigned long pos; + unsigned bytesToRead; + PaWinWdmIOInfo* pCapture = &pInfo->stream->capture; + const unsigned halfInputBuffer = pCapture->hostBufferSize>>1; + PaWinWdmPin* pin = pInfo->stream->capture.pPin; + + /* Get hold of current ADC position */ + pin->fnAudioPosition(pin, &pos); + /* Wrap it (robi: why not use hw latency compensation here ?? because pos then gets _way_ off from + where it should be, i.e. at beginning or half buffer position. Why? No idea.) */ + /* Compensate for HW FIFO to get to last read buffer position */ + pos += pin->hwLatency; + pos %= pCapture->hostBufferSize; + /* Need to align position on frame boundary */ + pos &= ~(pCapture->bytesPerFrame - 1); + + /* Call barrier (or dummy) */ + pin->fnMemBarrier(); + + /* Put it in "queue" */ + bytesToRead = (pCapture->hostBufferSize + pos - pCapture->lastPosition) % pCapture->hostBufferSize; + if (bytesToRead > 0) + { + unsigned frameCount = PaUtil_WriteRingBuffer(&pInfo->stream->ringBuffer, + pCapture->hostBuffer + pCapture->lastPosition, + bytesToRead / pCapture->bytesPerFrame); + + pCapture->lastPosition = (pCapture->lastPosition + frameCount * pCapture->bytesPerFrame) % pCapture->hostBufferSize; + + PA_HP_TRACE((pInfo->stream->hLog, "Capture event (WaveRTPolled): pos = %4.1lf%%, framesRead=%u", (pos * 100.0 / pCapture->hostBufferSize), frameCount)); + ++pInfo->captureHead; + --pInfo->pending; + } + return paNoError; +} + +static PaError PaPinCaptureSubmitHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex) +{ + pInfo->capturePackets[pInfo->captureTail & cPacketsArrayMask].packet = 0; + ++pInfo->pending; + return paNoError; +} + +static PaError PaPinCaptureSubmitHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex) +{ + pInfo->capturePackets[pInfo->captureTail & cPacketsArrayMask].packet = 0; + ++pInfo->pending; + return paNoError; +} + +static PaError PaPinRenderEventHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex) +{ + unsigned long pos; + unsigned realOutBuf; + PaWinWdmIOInfo* pRender = &pInfo->stream->render; + const unsigned halfOutputBuffer = pRender->hostBufferSize >> 1; + PaWinWdmPin* pin = pInfo->stream->render.pPin; + PaIOPacket* ioPacket = &pInfo->renderPackets[pInfo->renderHead & cPacketsArrayMask]; + + /* Get hold of current DAC position */ + pin->fnAudioPosition(pin, &pos); + /* Compensate for HW FIFO to get to last read buffer position */ + pos += pin->hwLatency; + /* Wrap it */ + pos %= pRender->hostBufferSize; + /* And align it, not sure its really needed though */ + pos &= ~(pRender->bytesPerFrame - 1); + /* Then realOutBuf will point to "other" half of double buffer */ + realOutBuf = pos < halfOutputBuffer ? 1U : 0U; + + if (pInfo->priming) + { + realOutBuf = pInfo->renderHead & 0x1; + } + ioPacket->packet = pInfo->stream->render.packets + realOutBuf; + ioPacket->startByte = realOutBuf * halfOutputBuffer; + ioPacket->lengthBytes = halfOutputBuffer; + + PA_HP_TRACE((pInfo->stream->hLog, "Render event (WaveRT) : idx=%u head=%u (pos = %4.1lf%%)", realOutBuf, pInfo->renderHead, (pos * 100.0 / pRender->hostBufferSize) )); + + ++pInfo->renderHead; + --pInfo->pending; + return paNoError; +} + +static PaError PaPinRenderEventHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex) +{ + unsigned long pos; + unsigned realOutBuf; + unsigned bytesToWrite; + + PaWinWdmIOInfo* pRender = &pInfo->stream->render; + const unsigned halfOutputBuffer = pRender->hostBufferSize >> 1; + PaWinWdmPin* pin = pInfo->stream->render.pPin; + PaIOPacket* ioPacket = &pInfo->renderPackets[pInfo->renderHead & cPacketsArrayMask]; + + /* Get hold of current DAC position */ + pin->fnAudioPosition(pin, &pos); + /* Compensate for HW FIFO to get to last read buffer position */ + pos += pin->hwLatency; + /* Wrap it */ + pos %= pRender->hostBufferSize; + /* And align it, not sure its really needed though */ + pos &= ~(pRender->bytesPerFrame - 1); + + if (pInfo->priming) + { + realOutBuf = pInfo->renderHead & 0x1; + ioPacket->packet = pInfo->stream->render.packets + realOutBuf; + ioPacket->startByte = realOutBuf * halfOutputBuffer; + ioPacket->lengthBytes = halfOutputBuffer; + ++pInfo->renderHead; + --pInfo->pending; + } + else + { + bytesToWrite = (pRender->hostBufferSize + pos - pRender->lastPosition) % pRender->hostBufferSize; + ++pRender->pollCntr; + if (bytesToWrite >= halfOutputBuffer) + { + realOutBuf = (pos < halfOutputBuffer) ? 1U : 0U; + ioPacket->packet = pInfo->stream->render.packets + realOutBuf; + pRender->lastPosition = realOutBuf ? 0U : halfOutputBuffer; + ioPacket->startByte = realOutBuf * halfOutputBuffer; + ioPacket->lengthBytes = halfOutputBuffer; + ++pInfo->renderHead; + --pInfo->pending; + PA_HP_TRACE((pInfo->stream->hLog, "Render event (WaveRTPolled) : idx=%u head=%u (pos = %4.1lf%%, cnt=%u)", realOutBuf, pInfo->renderHead, (pos * 100.0 / pRender->hostBufferSize), pRender->pollCntr)); + pRender->pollCntr = 0; + } + } + return paNoError; +} + +static PaError PaPinRenderSubmitHandler_WaveRTEvent(PaProcessThreadInfo* pInfo, unsigned eventIndex) +{ + PaWinWdmPin* pin = pInfo->stream->render.pPin; + pInfo->renderPackets[pInfo->renderTail & cPacketsArrayMask].packet = 0; + /* Call barrier (if needed) */ + pin->fnMemBarrier(); + PA_HP_TRACE((pInfo->stream->hLog, "Render submit (WaveRT) : submit=%u", pInfo->renderTail)); + ++pInfo->pending; + if (pInfo->priming) + { + --pInfo->priming; + if (pInfo->priming) + { + PA_HP_TRACE((pInfo->stream->hLog, "Setting WaveRT event for priming (2)")); + SetEvent(pInfo->stream->render.events[0]); + } + } + return paNoError; +} + +static PaError PaPinRenderSubmitHandler_WaveRTPolled(PaProcessThreadInfo* pInfo, unsigned eventIndex) +{ + PaWinWdmPin* pin = pInfo->stream->render.pPin; + pInfo->renderPackets[pInfo->renderTail & cPacketsArrayMask].packet = 0; + /* Call barrier (if needed) */ + pin->fnMemBarrier(); + PA_HP_TRACE((pInfo->stream->hLog, "Render submit (WaveRTPolled) : submit=%u", pInfo->renderTail)); + ++pInfo->pending; + if (pInfo->priming) + { + --pInfo->priming; + if (pInfo->priming) + { + PA_HP_TRACE((pInfo->stream->hLog, "Setting WaveRT event for priming (2)")); + SetEvent(pInfo->stream->render.events[0]); + } + } + return paNoError; +} diff --git a/portaudio/src/hostapi/wdmks/readme.txt b/portaudio/src/hostapi/wdmks/readme.txt new file mode 100644 index 0000000..611acc7 --- /dev/null +++ b/portaudio/src/hostapi/wdmks/readme.txt @@ -0,0 +1,85 @@ +Notes about WDM-KS host API +--------------------------- + +Status history +-------------- +16th January 2011: +Added support for WaveRT device API (Vista and later) for even lesser +latency support. + +10th November 2005: +Made following changes: + * OpenStream: Try all PaSampleFormats internally if the the chosen + format is not supported natively. This fixed several problems + with soundcards that did not take kindly to using 24-bit 3-byte formats. + * OpenStream: Make the minimum framesPerHostIBuffer (and framesPerHostOBuffer) + the default frameSize for the playback/recording pin. + * ProcessingThread: Added a switch to only call PaUtil_EndBufferProcessing + if the total input frames equals the total output frames + +5th September 2004: +This is the first public version of the code. It should be considered +an alpha release with zero guarantee not to crash on any particular +system. So far it has only been tested in the author's development +environment, which means a Win2k/SP2 PIII laptop with integrated +SoundMAX driver and USB Tascam US-428 compiled with both MinGW +(GCC 3.3) and MSVC++6 using the MS DirectX 9 SDK. +It has been most widely tested with the MinGW build, with most of the +test programs (particularly paqa_devs and paqa_errs) passing. +There are some notable failures: patest_out_underflow and both of the +blocking I/O tests (as blocking I/O is not implemented). +At this point the code needs to be tested with a much wider variety +of configurations and feedback provided from testers regarding +both working and failing cases. + +What is the WDM-KS host API? +---------------------------- +PortAudio for Windows currently has 3 functional host implementations. +MME uses the oldest Windows audio API which does not offer good +play/record latency. +DirectX improves this, but still imposes a penalty +of 10s of milliseconds due to the system mixing of streams from +multiple applications. +ASIO offers very good latency, but requires special drivers which are +not always available for cheaper audio hardware. Also, when ASIO +drivers are available, they are not always so robust because they +bypass all of the standardised Windows device driver architecture +and hit the hardware their own way. +Alternatively there are a couple of free (but closed source) ASIO +implementations which connect to the lower level Windows +"Kernel Streaming" API, but again these require special installation +by the user, and can be limited in functionality or difficult to use. + +This is where the PortAudio "WDM-KS" host implementation comes in. +It directly connects PortAudio to the same Kernel Streaming API which +those ASIO bridges use. This avoids the mixing penalty of DirectX, +giving at least as good latency as any ASIO driver, but it has the +advantage of working with ANY Windows audio hardware which is available +through the normal MME/DirectX routes without the user requiring +any additional device drivers to be installed, and allowing all +device selection to be done through the normal PortAudio API. + +Note that in general you should only be using this host API if your +application has a real requirement for very low latency audio (<20ms), +either because you are generating sounds in real-time based upon +user input, or you a processing recorded audio in real time. + +The only thing to be aware of is that using the KS interface will +block that device from being used by the rest of system through +the higher level APIs, or conversely, if the system is using +a device, the KS API will not be able to use it. MS recommend that +you should keep the device open only when your application has focus. +In PortAudio terms, this means having a stream Open on a WDMKS device. + +Usage +----- +To add the WDMKS backend to your program which is already using +PortAudio, you must define PA_USE_WDMKS=1 in your build file, +and include the pa_win_wdmks\pa_win_wdmks.c into your build. +The file should compile in both C and C++. +You will need a DirectX SDK installed on your system for the +ks.h and ksmedia.h header files. +You will need to link to the system "setupapi" library. +Note that if you use MinGW, you will get more warnings from +the DX header files when using GCC(C), and still a few warnings +with G++(CPP). -- cgit v1.2.1