mirror of
https://github.com/saitohirga/WSJT-X.git
synced 2024-11-19 02:22:10 -05:00
5b9645bf09
git-svn-id: svn+ssh://svn.code.sf.net/p/wsjt/wsjt/trunk@189 ab8295b8-cf94-4d9e-aec4-7959e3be5d79
3269 lines
104 KiB
C
3269 lines
104 KiB
C
/*
|
|
* $Id$
|
|
* PortAudio Windows WDM-KS interface
|
|
*
|
|
* Author: Andrew Baldwin
|
|
* 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.
|
|
*
|
|
* 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.
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
/** @file
|
|
@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)
|
|
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 <stdio.h>
|
|
|
|
/* Debugging/tracing support */
|
|
|
|
#define PA_LOGE_
|
|
#define PA_LOGL_
|
|
|
|
#ifdef __GNUC__
|
|
#include <initguid.h>
|
|
#define _WIN32_WINNT 0x0501
|
|
#define WINVER 0x0501
|
|
#endif
|
|
|
|
#include <string.h> /* strlen() */
|
|
#include <assert.h>
|
|
|
|
#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 <windows.h>
|
|
#include <winioctl.h>
|
|
|
|
|
|
#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
|
|
|
|
#ifdef _MSC_VER
|
|
#define DYNAMIC_GUID(data) {data}
|
|
#define _INC_MMREG
|
|
#define _NTRTL_ /* Turn off default definition of DEFINE_GUIDEX */
|
|
#undef DEFINE_GUID
|
|
#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)
|
|
#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
|
|
#endif
|
|
|
|
#include <ks.h>
|
|
#include <ksmedia.h>
|
|
#include <tchar.h>
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
|
|
/* 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;
|
|
|
|
/* Forward definition to break circular type reference between pin and filter */
|
|
struct __PaWinWdmFilter;
|
|
typedef struct __PaWinWdmFilter PaWinWdmFilter;
|
|
|
|
/* The Pin structure
|
|
* A pin is an input or output node, e.g. for audio flow */
|
|
typedef struct __PaWinWdmPin
|
|
{
|
|
HANDLE handle;
|
|
PaWinWdmFilter* parentFilter;
|
|
unsigned long pinId;
|
|
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 bestSampleRate;
|
|
}
|
|
PaWinWdmPin;
|
|
|
|
/* The Filter structure
|
|
* A filter has a number of pins and a "friendly name" */
|
|
struct __PaWinWdmFilter
|
|
{
|
|
HANDLE handle;
|
|
int pinCount;
|
|
PaWinWdmPin** pins;
|
|
TCHAR filterName[MAX_PATH];
|
|
TCHAR friendlyName[MAX_PATH];
|
|
int maxInputChannels;
|
|
int maxOutputChannels;
|
|
unsigned long formats;
|
|
int usageCount;
|
|
int bestSampleRate;
|
|
};
|
|
|
|
/* PaWinWdmHostApiRepresentation - host api datastructure specific to this implementation */
|
|
typedef struct __PaWinWdmHostApiRepresentation
|
|
{
|
|
PaUtilHostApiRepresentation inheritedHostApiRep;
|
|
PaUtilStreamInterface callbackStreamInterface;
|
|
PaUtilStreamInterface blockingStreamInterface;
|
|
|
|
PaUtilAllocationGroup* allocations;
|
|
PaWinWdmFilter** filters;
|
|
int filterCount;
|
|
}
|
|
PaWinWdmHostApiRepresentation;
|
|
|
|
typedef struct __PaWinWdmDeviceInfo
|
|
{
|
|
PaDeviceInfo inheritedDeviceInfo;
|
|
PaWinWdmFilter* filter;
|
|
}
|
|
PaWinWdmDeviceInfo;
|
|
|
|
typedef struct __DATAPACKET
|
|
{
|
|
KSSTREAM_HEADER Header;
|
|
OVERLAPPED Signal;
|
|
} DATAPACKET;
|
|
|
|
/* PaWinWdmStream - a stream data structure specifically for this implementation */
|
|
typedef struct __PaWinWdmStream
|
|
{
|
|
PaUtilStreamRepresentation streamRepresentation;
|
|
PaUtilCpuLoadMeasurer cpuLoadMeasurer;
|
|
PaUtilBufferProcessor bufferProcessor;
|
|
|
|
PaWinWdmPin* recordingPin;
|
|
PaWinWdmPin* playbackPin;
|
|
char* hostBuffer;
|
|
unsigned long framesPerHostIBuffer;
|
|
unsigned long framesPerHostOBuffer;
|
|
int bytesPerInputFrame;
|
|
int bytesPerOutputFrame;
|
|
int streamStarted;
|
|
int streamActive;
|
|
int streamStop;
|
|
int streamAbort;
|
|
int oldProcessPriority;
|
|
HANDLE streamThread;
|
|
HANDLE events[5]; /* 2 play + 2 record packets + abort events */
|
|
DATAPACKET packets[4]; /* 2 play + 2 record */
|
|
PaStreamFlags streamFlags;
|
|
/* 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;
|
|
int inputSampleSize;
|
|
int outputSampleSize;
|
|
}
|
|
PaWinWdmStream;
|
|
|
|
#include <setupapi.h>
|
|
|
|
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,
|
|
void* instance,
|
|
unsigned long instanceCount);
|
|
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);
|
|
static PaError WdmGetPinPropertyMulti(HANDLE handle,
|
|
unsigned long pinId,
|
|
const GUID* const guidPropertySet,
|
|
unsigned long property,
|
|
KSMULTIPLE_ITEM** ksMultipleItem);
|
|
|
|
/** 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);
|
|
|
|
/* Filter management functions */
|
|
static PaWinWdmFilter* FilterNew(
|
|
TCHAR* filterName,
|
|
TCHAR* friendlyName,
|
|
PaError* error);
|
|
static void FilterFree(PaWinWdmFilter* filter);
|
|
static PaWinWdmPin* FilterCreateRenderPin(
|
|
PaWinWdmFilter* filter,
|
|
const WAVEFORMATEX* wfex,
|
|
PaError* error);
|
|
static PaWinWdmPin* FilterFindViableRenderPin(
|
|
PaWinWdmFilter* filter,
|
|
const WAVEFORMATEX* wfex,
|
|
PaError* error);
|
|
static PaError FilterCanCreateRenderPin(
|
|
PaWinWdmFilter* filter,
|
|
const WAVEFORMATEX* wfex);
|
|
static PaWinWdmPin* FilterCreateCapturePin(
|
|
PaWinWdmFilter* filter,
|
|
const WAVEFORMATEX* wfex,
|
|
PaError* error);
|
|
static PaWinWdmPin* FilterFindViableCapturePin(
|
|
PaWinWdmFilter* filter,
|
|
const WAVEFORMATEX* wfex,
|
|
PaError* error);
|
|
static PaError FilterCanCreateCapturePin(
|
|
PaWinWdmFilter* filter,
|
|
const WAVEFORMATEX* pwfx);
|
|
static PaError FilterUse(
|
|
PaWinWdmFilter* filter);
|
|
static void FilterRelease(
|
|
PaWinWdmFilter* filter);
|
|
|
|
/* Interface functions */
|
|
static void Terminate( struct PaUtilHostApiRepresentation *hostApi );
|
|
static PaError IsFormatSupported(
|
|
struct PaUtilHostApiRepresentation *hostApi,
|
|
const PaStreamParameters *inputParameters,
|
|
const PaStreamParameters *outputParameters,
|
|
double sampleRate );
|
|
static PaError OpenStream(
|
|
struct PaUtilHostApiRepresentation *hostApi,
|
|
PaStream** s,
|
|
const PaStreamParameters *inputParameters,
|
|
const PaStreamParameters *outputParameters,
|
|
double sampleRate,
|
|
unsigned long framesPerBuffer,
|
|
PaStreamFlags streamFlags,
|
|
PaStreamCallback *streamCallback,
|
|
void *userData );
|
|
static PaError CloseStream( PaStream* stream );
|
|
static PaError StartStream( PaStream *stream );
|
|
static PaError StopStream( PaStream *stream );
|
|
static PaError AbortStream( PaStream *stream );
|
|
static PaError IsStreamStopped( PaStream *s );
|
|
static PaError IsStreamActive( PaStream *stream );
|
|
static PaTime GetStreamTime( PaStream *stream );
|
|
static double GetStreamCpuLoad( PaStream* stream );
|
|
static PaError ReadStream(
|
|
PaStream* stream,
|
|
void *buffer,
|
|
unsigned long frames );
|
|
static PaError WriteStream(
|
|
PaStream* stream,
|
|
const void *buffer,
|
|
unsigned long frames );
|
|
static signed long GetStreamReadAvailable( PaStream* stream );
|
|
static signed long GetStreamWriteAvailable( PaStream* stream );
|
|
|
|
/* Utility functions */
|
|
static unsigned long GetWfexSize(const WAVEFORMATEX* wfex);
|
|
static PaError BuildFilterList(PaWinWdmHostApiRepresentation* wdmHostApi);
|
|
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);
|
|
static DWORD WINAPI ProcessingThread(LPVOID pParam);
|
|
|
|
/* Function bodies */
|
|
|
|
static unsigned long GetWfexSize(const WAVEFORMATEX* wfex)
|
|
{
|
|
if( wfex->wFormatTag == WAVE_FORMAT_PCM )
|
|
{
|
|
return sizeof( WAVEFORMATEX );
|
|
}
|
|
else
|
|
{
|
|
return (sizeof( WAVEFORMATEX ) + wfex->cbSize);
|
|
}
|
|
}
|
|
|
|
/*
|
|
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;
|
|
OVERLAPPED overlapped;
|
|
int boolResult;
|
|
unsigned long dummyBytesReturned;
|
|
unsigned long error;
|
|
|
|
if( !bytesReturned )
|
|
{
|
|
/* User a dummy as the caller hasn't supplied one */
|
|
bytesReturned = &dummyBytesReturned;
|
|
}
|
|
|
|
FillMemory((void *)&overlapped,sizeof(overlapped),0);
|
|
overlapped.hEvent = CreateEvent(NULL,FALSE,FALSE,NULL);
|
|
if( !overlapped.hEvent )
|
|
{
|
|
result = paInsufficientMemory;
|
|
goto error;
|
|
}
|
|
overlapped.hEvent = (HANDLE)((DWORD_PTR)overlapped.hEvent | 0x1);
|
|
|
|
boolResult = DeviceIoControl(handle, ioctlNumber, inBuffer, inBufferCount,
|
|
outBuffer, outBufferCount, bytesReturned, &overlapped);
|
|
if( !boolResult )
|
|
{
|
|
error = GetLastError();
|
|
if( error == ERROR_IO_PENDING )
|
|
{
|
|
error = WaitForSingleObject(overlapped.hEvent,INFINITE);
|
|
if( error != WAIT_OBJECT_0 )
|
|
{
|
|
result = paUnanticipatedHostError;
|
|
goto error;
|
|
}
|
|
}
|
|
else if((( error == ERROR_INSUFFICIENT_BUFFER ) ||
|
|
( error == ERROR_MORE_DATA )) &&
|
|
( ioctlNumber == IOCTL_KS_PROPERTY ) &&
|
|
( outBufferCount == 0 ))
|
|
{
|
|
boolResult = TRUE;
|
|
}
|
|
else
|
|
{
|
|
result = paUnanticipatedHostError;
|
|
}
|
|
}
|
|
if( !boolResult )
|
|
*bytesReturned = 0;
|
|
|
|
error:
|
|
if( overlapped.hEvent )
|
|
{
|
|
CloseHandle( overlapped.hEvent );
|
|
}
|
|
return result;
|
|
}
|
|
|
|
static PaError WdmGetPropertySimple(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;
|
|
|
|
propertyCount = sizeof(KSPROPERTY) + instanceCount;
|
|
ksProperty = (KSPROPERTY*)PaUtil_AllocateMemory( propertyCount );
|
|
if( !ksProperty )
|
|
{
|
|
return paInsufficientMemory;
|
|
}
|
|
|
|
FillMemory((void*)ksProperty,sizeof(ksProperty),0);
|
|
ksProperty->Set = *guidPropertySet;
|
|
ksProperty->Id = property;
|
|
ksProperty->Flags = KSPROPERTY_TYPE_GET;
|
|
|
|
if( instance )
|
|
{
|
|
memcpy( (void*)(((char*)ksProperty)+sizeof(KSPROPERTY)), instance, instanceCount );
|
|
}
|
|
|
|
result = WdmSyncIoctl(
|
|
handle,
|
|
IOCTL_KS_PROPERTY,
|
|
ksProperty,
|
|
propertyCount,
|
|
value,
|
|
valueCount,
|
|
NULL);
|
|
|
|
PaUtil_FreeMemory( ksProperty );
|
|
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*)PaUtil_AllocateMemory( 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);
|
|
|
|
PaUtil_FreeMemory( ksProperty );
|
|
return result;
|
|
}
|
|
|
|
static PaError WdmGetPinPropertySimple(
|
|
HANDLE handle,
|
|
unsigned long pinId,
|
|
const GUID* const guidPropertySet,
|
|
unsigned long property,
|
|
void* value,
|
|
unsigned long valueCount)
|
|
{
|
|
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,
|
|
NULL);
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
/*
|
|
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;
|
|
|
|
PA_LOGE_;
|
|
PA_DEBUG(("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 = KSINTERFACE_STANDARD_STREAMING;
|
|
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));
|
|
if( result != paNoError )
|
|
goto error;
|
|
|
|
if( /*(pin->communication != KSPIN_COMMUNICATION_SOURCE) &&*/
|
|
(pin->communication != KSPIN_COMMUNICATION_SINK) &&
|
|
(pin->communication != KSPIN_COMMUNICATION_BOTH) )
|
|
{
|
|
PA_DEBUG(("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));
|
|
|
|
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( !memcmp( (void*)&identifier[i].Set, (void*)&KSINTERFACESETID_Standard, sizeof( GUID ) ) &&
|
|
( identifier[i].Id == KSINTERFACE_STANDARD_STREAMING ) )
|
|
{
|
|
result = paNoError;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( result != paNoError )
|
|
{
|
|
PA_DEBUG(("No standard streaming\n"));
|
|
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( !memcmp( (void*)&identifier[i].Set, (void*)&KSMEDIUMSETID_Standard, sizeof( GUID ) ) &&
|
|
( 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->bestSampleRate = 0;
|
|
pin->formats = 0;
|
|
for( i = 0; i <pin->dataRangesItem->Count; i++)
|
|
{
|
|
PA_DEBUG(("DR major format %x\n",*(unsigned long*)(&(dataRange->MajorFormat))));
|
|
/* Check that subformat is WAVEFORMATEX, PCM or WILDCARD */
|
|
if( IS_VALID_WAVEFORMATEX_GUID(&dataRange->SubFormat) ||
|
|
!memcmp((void*)&dataRange->SubFormat, (void*)&KSDATAFORMAT_SUBTYPE_PCM, sizeof ( GUID ) ) ||
|
|
( !memcmp((void*)&dataRange->SubFormat, (void*)&KSDATAFORMAT_SUBTYPE_WILDCARD, sizeof ( GUID ) ) &&
|
|
( !memcmp((void*)&dataRange->MajorFormat, (void*)&KSDATAFORMAT_TYPE_AUDIO, sizeof ( GUID ) ) ) ) )
|
|
{
|
|
result = paNoError;
|
|
/* Record the maximum possible channels with this pin */
|
|
PA_DEBUG(("MaxChannel: %d\n",pin->maxChannels));
|
|
if( (int)((KSDATARANGE_AUDIO*)dataRange)->MaximumChannels > pin->maxChannels )
|
|
{
|
|
pin->maxChannels = ((KSDATARANGE_AUDIO*)dataRange)->MaximumChannels;
|
|
/*PA_DEBUG(("MaxChannel: %d\n",pin->maxChannels));*/
|
|
}
|
|
/* Record the formats (bit depths) that are supported */
|
|
if( ((KSDATARANGE_AUDIO*)dataRange)->MinimumBitsPerSample <= 16 )
|
|
{
|
|
pin->formats |= paInt16;
|
|
PA_DEBUG(("Format 16 bit supported\n"));
|
|
}
|
|
if( ((KSDATARANGE_AUDIO*)dataRange)->MaximumBitsPerSample >= 24 )
|
|
{
|
|
pin->formats |= paInt24;
|
|
PA_DEBUG(("Format 24 bit supported\n"));
|
|
}
|
|
if( ( pin->bestSampleRate != 48000) &&
|
|
(((KSDATARANGE_AUDIO*)dataRange)->MaximumSampleFrequency >= 48000) &&
|
|
(((KSDATARANGE_AUDIO*)dataRange)->MinimumSampleFrequency <= 48000) )
|
|
{
|
|
pin->bestSampleRate = 48000;
|
|
PA_DEBUG(("48kHz supported\n"));
|
|
}
|
|
else if(( pin->bestSampleRate != 48000) && ( pin->bestSampleRate != 44100 ) &&
|
|
(((KSDATARANGE_AUDIO*)dataRange)->MaximumSampleFrequency >= 44100) &&
|
|
(((KSDATARANGE_AUDIO*)dataRange)->MinimumSampleFrequency <= 44100) )
|
|
{
|
|
pin->bestSampleRate = 44100;
|
|
PA_DEBUG(("44.1kHz supported\n"));
|
|
}
|
|
else
|
|
{
|
|
pin->bestSampleRate = ((KSDATARANGE_AUDIO*)dataRange)->MaximumSampleFrequency;
|
|
}
|
|
}
|
|
dataRange = (KSDATARANGE*)( ((char*)dataRange) + dataRange->FormatSize);
|
|
}
|
|
|
|
if( result != paNoError )
|
|
goto error;
|
|
|
|
/* Get instance information */
|
|
result = WdmGetPinPropertySimple(
|
|
parentFilter->handle,
|
|
pinId,
|
|
&KSPROPSETID_Pin,
|
|
KSPROPERTY_PIN_CINSTANCES,
|
|
&pin->instances,
|
|
sizeof(KSPIN_CINSTANCES));
|
|
|
|
if( result != paNoError )
|
|
goto error;
|
|
|
|
/* Success */
|
|
*error = paNoError;
|
|
PA_DEBUG(("Pin created successfully\n"));
|
|
PA_LOGL_;
|
|
return pin;
|
|
|
|
error:
|
|
/*
|
|
Error cleanup
|
|
*/
|
|
PaUtil_FreeMemory( item );
|
|
if( pin )
|
|
{
|
|
PaUtil_FreeMemory( pin->pinConnect );
|
|
PaUtil_FreeMemory( pin->dataRangesItem );
|
|
PaUtil_FreeMemory( pin );
|
|
}
|
|
*error = result;
|
|
PA_LOGL_;
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
Safely free all resources associated with the pin
|
|
*/
|
|
static void PinFree(PaWinWdmPin* pin)
|
|
{
|
|
PA_LOGE_;
|
|
if( pin )
|
|
{
|
|
PinClose(pin);
|
|
if( pin->pinConnect )
|
|
{
|
|
PaUtil_FreeMemory( pin->pinConnect );
|
|
}
|
|
if( pin->dataRangesItem )
|
|
{
|
|
PaUtil_FreeMemory( pin->dataRangesItem );
|
|
}
|
|
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;
|
|
|
|
PA_LOGE_;
|
|
if( pin == NULL )
|
|
return paInternalError;
|
|
if( pin->handle == NULL )
|
|
return paInternalError;
|
|
|
|
result = WdmSetPropertySimple(
|
|
pin->handle,
|
|
&KSPROPSETID_Connection,
|
|
KSPROPERTY_CONNECTION_STATE,
|
|
&state,
|
|
sizeof(state),
|
|
NULL,
|
|
0);
|
|
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 = %x\n",createResult));
|
|
if( createResult != ERROR_SUCCESS )
|
|
{
|
|
FilterRelease(pin->parentFilter);
|
|
pin->handle = NULL;
|
|
return paInvalidDevice;
|
|
}
|
|
|
|
result = WdmGetPropertySimple(
|
|
pin->handle,
|
|
&KSPROPSETID_Connection,
|
|
KSPROPERTY_CONNECTION_ALLOCATORFRAMING,
|
|
&ksaf,
|
|
sizeof(ksaf),
|
|
NULL,
|
|
0);
|
|
|
|
if( result != paNoError )
|
|
{
|
|
result = WdmGetPropertySimple(
|
|
pin->handle,
|
|
&KSPROPSETID_Connection,
|
|
KSPROPERTY_CONNECTION_ALLOCATORFRAMING_EX,
|
|
&ksafex,
|
|
sizeof(ksafex),
|
|
NULL,
|
|
0);
|
|
if( result == paNoError )
|
|
{
|
|
pin->frameSize = ksafex.FramingItem[0].FramingRange.Range.MinFrameSize;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pin->frameSize = ksaf.FrameSize;
|
|
}
|
|
|
|
PA_LOGL_;
|
|
|
|
return paNoError;
|
|
}
|
|
|
|
/* NOT USED
|
|
static PaError PinGetState(PaWinWdmPin* pin, KSSTATE* state)
|
|
{
|
|
PaError result;
|
|
|
|
if( state == NULL )
|
|
return paInternalError;
|
|
if( pin == NULL )
|
|
return paInternalError;
|
|
if( pin->handle == NULL )
|
|
return paInternalError;
|
|
|
|
result = WdmGetPropertySimple(
|
|
pin->handle,
|
|
KSPROPSETID_Connection,
|
|
KSPROPERTY_CONNECTION_STATE,
|
|
state,
|
|
sizeof(KSSTATE),
|
|
NULL,
|
|
0);
|
|
|
|
return result;
|
|
}
|
|
*/
|
|
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;
|
|
|
|
PA_LOGE_;
|
|
|
|
if( format->wFormatTag == WAVE_FORMAT_EXTENSIBLE )
|
|
{
|
|
guid = ((WAVEFORMATEXTENSIBLE*)format)->SubFormat;
|
|
}
|
|
dataRange = (KSDATARANGE_AUDIO*)pin->dataRanges;
|
|
for(count = 0; count<pin->dataRangesItem->Count; count++)
|
|
{
|
|
if(( !memcmp(&(dataRange->DataRange.MajorFormat),&KSDATAFORMAT_TYPE_AUDIO,sizeof(GUID)) ) ||
|
|
( !memcmp(&(dataRange->DataRange.MajorFormat),&KSDATAFORMAT_TYPE_WILDCARD,sizeof(GUID)) ))
|
|
{
|
|
/* This is an audio or wildcard datarange... */
|
|
if(( !memcmp(&(dataRange->DataRange.SubFormat),&KSDATAFORMAT_SUBTYPE_WILDCARD,sizeof(GUID)) ) ||
|
|
( !memcmp(&(dataRange->DataRange.SubFormat),&guid,sizeof(GUID)) ))
|
|
{
|
|
if(( !memcmp(&(dataRange->DataRange.Specifier),&KSDATAFORMAT_SPECIFIER_WILDCARD,sizeof(GUID)) ) ||
|
|
( !memcmp(&(dataRange->DataRange.Specifier),&KSDATAFORMAT_SPECIFIER_WAVEFORMATEX,sizeof(GUID) )))
|
|
{
|
|
|
|
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 < format->nChannels )
|
|
{
|
|
result = paInvalidChannelCount;
|
|
continue;
|
|
}
|
|
if( dataRange->MinimumBitsPerSample > format->wBitsPerSample )
|
|
{
|
|
result = paSampleFormatNotSupported;
|
|
continue;
|
|
}
|
|
if( dataRange->MaximumBitsPerSample < format->wBitsPerSample )
|
|
{
|
|
result = paSampleFormatNotSupported;
|
|
continue;
|
|
}
|
|
if( dataRange->MinimumSampleFrequency > format->nSamplesPerSec )
|
|
{
|
|
result = paInvalidSampleRate;
|
|
continue;
|
|
}
|
|
if( dataRange->MaximumSampleFrequency < format->nSamplesPerSec )
|
|
{
|
|
result = paInvalidSampleRate;
|
|
continue;
|
|
}
|
|
/* Success! */
|
|
PA_LOGL_;
|
|
return paNoError;
|
|
}
|
|
}
|
|
}
|
|
dataRange = (KSDATARANGE_AUDIO*)( ((char*)dataRange) + dataRange->DataRange.FormatSize);
|
|
}
|
|
|
|
PA_LOGL_;
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Create a new filter object
|
|
*/
|
|
static PaWinWdmFilter* FilterNew(TCHAR* filterName, TCHAR* friendlyName, PaError* error)
|
|
{
|
|
PaWinWdmFilter* filter;
|
|
PaError result;
|
|
int pinId;
|
|
int valid;
|
|
|
|
|
|
/* Allocate the new filter object */
|
|
filter = (PaWinWdmFilter*)PaUtil_AllocateMemory( sizeof(PaWinWdmFilter) );
|
|
if( !filter )
|
|
{
|
|
result = paInsufficientMemory;
|
|
goto error;
|
|
}
|
|
|
|
/* Zero the filter object - done by AllocateMemory */
|
|
/* memset( (void*)filter, 0, sizeof(PaWinWdmFilter) ); */
|
|
|
|
/* Copy the filter name */
|
|
_tcsncpy(filter->filterName, filterName, MAX_PATH);
|
|
|
|
/* Copy the friendly name */
|
|
_tcsncpy(filter->friendlyName, friendlyName, MAX_PATH);
|
|
|
|
/* 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)
|
|
);
|
|
|
|
if( result != paNoError)
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
/* 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 */
|
|
filter->maxInputChannels = 0;
|
|
filter->maxOutputChannels = 0;
|
|
filter->bestSampleRate = 0;
|
|
|
|
valid = 0;
|
|
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;
|
|
valid = 1;
|
|
|
|
/* Get the max output channel count */
|
|
if(( newPin->dataFlow == KSPIN_DATAFLOW_IN ) &&
|
|
(( newPin->communication == KSPIN_COMMUNICATION_SINK) ||
|
|
( newPin->communication == KSPIN_COMMUNICATION_BOTH)))
|
|
{
|
|
if(newPin->maxChannels > filter->maxOutputChannels)
|
|
filter->maxOutputChannels = newPin->maxChannels;
|
|
filter->formats |= newPin->formats;
|
|
}
|
|
/* Get the max input channel count */
|
|
if(( newPin->dataFlow == KSPIN_DATAFLOW_OUT ) &&
|
|
(( newPin->communication == KSPIN_COMMUNICATION_SINK) ||
|
|
( newPin->communication == KSPIN_COMMUNICATION_BOTH)))
|
|
{
|
|
if(newPin->maxChannels > filter->maxInputChannels)
|
|
filter->maxInputChannels = newPin->maxChannels;
|
|
filter->formats |= newPin->formats;
|
|
}
|
|
|
|
if(newPin->bestSampleRate > filter->bestSampleRate)
|
|
{
|
|
filter->bestSampleRate = newPin->bestSampleRate;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(( filter->maxInputChannels == 0) && ( filter->maxOutputChannels == 0))
|
|
{
|
|
/* No input or output... not valid */
|
|
valid = 0;
|
|
}
|
|
|
|
if( !valid )
|
|
{
|
|
/* No valid pin was found on this filter so we destroy it */
|
|
result = paDeviceUnavailable;
|
|
goto error;
|
|
}
|
|
|
|
/* Close the filter handle for now
|
|
* It will be opened later when needed */
|
|
FilterRelease(filter);
|
|
|
|
*error = paNoError;
|
|
return filter;
|
|
|
|
error:
|
|
/*
|
|
Error cleanup
|
|
*/
|
|
if( filter )
|
|
{
|
|
for( pinId = 0; pinId < filter->pinCount; pinId++ )
|
|
PinFree(filter->pins[pinId]);
|
|
PaUtil_FreeMemory( filter->pins );
|
|
if( filter->handle )
|
|
CloseHandle( filter->handle );
|
|
PaUtil_FreeMemory( filter );
|
|
}
|
|
*error = result;
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Free a previously created filter
|
|
*/
|
|
static void FilterFree(PaWinWdmFilter* filter)
|
|
{
|
|
int pinId;
|
|
PA_LOGL_;
|
|
if( filter )
|
|
{
|
|
for( pinId = 0; pinId < filter->pinCount; pinId++ )
|
|
PinFree(filter->pins[pinId]);
|
|
PaUtil_FreeMemory( filter->pins );
|
|
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 = CreateFile(
|
|
filter->filterName,
|
|
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_;
|
|
filter->usageCount--;
|
|
if( filter->usageCount == 0 )
|
|
{
|
|
if( filter->handle != NULL )
|
|
{
|
|
CloseHandle( filter->handle );
|
|
filter->handle = NULL;
|
|
}
|
|
}
|
|
PA_LOGL_;
|
|
}
|
|
|
|
/**
|
|
* Create a render (playback) Pin using the supplied format
|
|
**/
|
|
static PaWinWdmPin* FilterCreateRenderPin(PaWinWdmFilter* filter,
|
|
const WAVEFORMATEX* wfex,
|
|
PaError* error)
|
|
{
|
|
PaError result;
|
|
PaWinWdmPin* pin;
|
|
|
|
assert( filter );
|
|
|
|
pin = FilterFindViableRenderPin(filter,wfex,&result);
|
|
if(!pin)
|
|
{
|
|
goto error;
|
|
}
|
|
result = PinSetFormat(pin,wfex);
|
|
if( result != paNoError )
|
|
{
|
|
goto error;
|
|
}
|
|
result = PinInstantiate(pin);
|
|
if( result != paNoError )
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
*error = paNoError;
|
|
return pin;
|
|
|
|
error:
|
|
*error = result;
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Find a pin that supports the given format
|
|
**/
|
|
static PaWinWdmPin* FilterFindViableRenderPin(PaWinWdmFilter* filter,
|
|
const WAVEFORMATEX* wfex,
|
|
PaError* error)
|
|
{
|
|
int pinId;
|
|
PaWinWdmPin* pin;
|
|
PaError result = paDeviceUnavailable;
|
|
*error = paNoError;
|
|
|
|
assert( filter );
|
|
|
|
for( pinId = 0; pinId<filter->pinCount; pinId++ )
|
|
{
|
|
pin = filter->pins[pinId];
|
|
if( pin != NULL )
|
|
{
|
|
if(( pin->dataFlow == KSPIN_DATAFLOW_IN ) &&
|
|
(( pin->communication == KSPIN_COMMUNICATION_SINK) ||
|
|
( pin->communication == KSPIN_COMMUNICATION_BOTH)))
|
|
{
|
|
result = PinIsFormatSupported( pin, wfex );
|
|
if( result == paNoError )
|
|
{
|
|
return pin;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
*error = result;
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Check if there is a pin that should playback
|
|
* with the supplied format
|
|
**/
|
|
static PaError FilterCanCreateRenderPin(PaWinWdmFilter* filter,
|
|
const WAVEFORMATEX* wfex)
|
|
{
|
|
PaWinWdmPin* pin;
|
|
PaError result;
|
|
|
|
assert ( filter );
|
|
|
|
pin = FilterFindViableRenderPin(filter,wfex,&result);
|
|
/* result will be paNoError if pin found
|
|
* or else an error code indicating what is wrong with the format
|
|
**/
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Create a capture (record) Pin using the supplied format
|
|
**/
|
|
static PaWinWdmPin* FilterCreateCapturePin(PaWinWdmFilter* filter,
|
|
const WAVEFORMATEX* wfex,
|
|
PaError* error)
|
|
{
|
|
PaError result;
|
|
PaWinWdmPin* pin;
|
|
|
|
assert( filter );
|
|
|
|
pin = FilterFindViableCapturePin(filter,wfex,&result);
|
|
if(!pin)
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
result = PinSetFormat(pin,wfex);
|
|
if( result != paNoError )
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
result = PinInstantiate(pin);
|
|
if( result != paNoError )
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
*error = paNoError;
|
|
return pin;
|
|
|
|
error:
|
|
*error = result;
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Find a capture pin that supports the given format
|
|
**/
|
|
static PaWinWdmPin* FilterFindViableCapturePin(PaWinWdmFilter* filter,
|
|
const WAVEFORMATEX* wfex,
|
|
PaError* error)
|
|
{
|
|
int pinId;
|
|
PaWinWdmPin* pin;
|
|
PaError result = paDeviceUnavailable;
|
|
*error = paNoError;
|
|
|
|
assert( filter );
|
|
|
|
for( pinId = 0; pinId<filter->pinCount; pinId++ )
|
|
{
|
|
pin = filter->pins[pinId];
|
|
if( pin != NULL )
|
|
{
|
|
if(( pin->dataFlow == KSPIN_DATAFLOW_OUT ) &&
|
|
(( pin->communication == KSPIN_COMMUNICATION_SINK) ||
|
|
( pin->communication == KSPIN_COMMUNICATION_BOTH)))
|
|
{
|
|
result = PinIsFormatSupported( pin, wfex );
|
|
if( result == paNoError )
|
|
{
|
|
return pin;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
*error = result;
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* Check if there is a pin that should playback
|
|
* with the supplied format
|
|
**/
|
|
static PaError FilterCanCreateCapturePin(PaWinWdmFilter* filter,
|
|
const WAVEFORMATEX* wfex)
|
|
{
|
|
PaWinWdmPin* pin;
|
|
PaError result;
|
|
|
|
assert ( filter );
|
|
|
|
pin = FilterFindViableCapturePin(filter,wfex,&result);
|
|
/* result will be paNoError if pin found
|
|
* or else an error code indicating what is wrong with the format
|
|
**/
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Build the list of available filters
|
|
*/
|
|
static PaError BuildFilterList(PaWinWdmHostApiRepresentation* wdmHostApi)
|
|
{
|
|
PaError result = paNoError;
|
|
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* devInterfaceDetails = (SP_DEVICE_INTERFACE_DETAIL_DATA*)interfaceDetailsArray;
|
|
TCHAR friendlyName[MAX_PATH];
|
|
HKEY hkey;
|
|
DWORD sizeFriendlyName;
|
|
DWORD type;
|
|
PaWinWdmFilter* newFilter;
|
|
GUID* category = (GUID*)&KSCATEGORY_AUDIO;
|
|
GUID* alias_render = (GUID*)&KSCATEGORY_RENDER;
|
|
GUID* alias_capture = (GUID*)&KSCATEGORY_CAPTURE;
|
|
DWORD hasAlias;
|
|
|
|
PA_LOGE_;
|
|
|
|
devInterfaceDetails->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
|
|
|
|
/* Open a handle to search for devices (filters) */
|
|
handle = SetupDiGetClassDevs(category,NULL,NULL,DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
|
|
if( handle == NULL )
|
|
{
|
|
return paUnanticipatedHostError;
|
|
}
|
|
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 */
|
|
hasAlias = 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));
|
|
hasAlias |= 1; /* 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));
|
|
hasAlias |= 2; /* Has capture alias */
|
|
}
|
|
else
|
|
{
|
|
PA_DEBUG(("Device %d has no capture alias\n",device));
|
|
}
|
|
}
|
|
if(!hasAlias)
|
|
invalidDevices++; /* This was not a valid capture or render audio device */
|
|
|
|
}
|
|
/* Remember how many there are */
|
|
wdmHostApi->filterCount = device-invalidDevices;
|
|
|
|
PA_DEBUG(("Interfaces found: %d\n",device-invalidDevices));
|
|
|
|
/* Now allocate the list of pointers to devices */
|
|
wdmHostApi->filters = (PaWinWdmFilter**)PaUtil_AllocateMemory( sizeof(PaWinWdmFilter*) * device );
|
|
if( !wdmHostApi->filters )
|
|
{
|
|
if(handle != NULL)
|
|
SetupDiDestroyDeviceInfoList(handle);
|
|
return paInsufficientMemory;
|
|
}
|
|
|
|
/* 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;
|
|
|
|
noError = SetupDiEnumDeviceInterfaces(handle,NULL,category,device,&interfaceData);
|
|
if( !noError )
|
|
break; /* No more devices */
|
|
|
|
/* Check this one has the render or capture alias */
|
|
hasAlias = 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));
|
|
hasAlias |= 1; /* 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));
|
|
hasAlias |= 2; /* Has capture alias */
|
|
}
|
|
}
|
|
if(!hasAlias)
|
|
continue; /* This was not a valid capture or render audio device */
|
|
|
|
noError = SetupDiGetDeviceInterfaceDetail(handle,&interfaceData,devInterfaceDetails,sizeInterface,NULL,&devInfoData);
|
|
if( noError )
|
|
{
|
|
/* Try to get the "friendly name" for this interface */
|
|
sizeFriendlyName = sizeof(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=SetupDiOpenDeviceInterfaceRegKey(handle,&interfaceData,0,KEY_QUERY_VALUE);
|
|
if(hkey!=INVALID_HANDLE_VALUE)
|
|
{
|
|
noError = RegQueryValueEx(hkey,TEXT("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;
|
|
}
|
|
}
|
|
newFilter = FilterNew(devInterfaceDetails->DevicePath,friendlyName,&result);
|
|
if( result == paNoError )
|
|
{
|
|
PA_DEBUG(("Filter created\n"));
|
|
wdmHostApi->filters[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 */
|
|
wdmHostApi->filterCount--;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Clean up */
|
|
if(handle != NULL)
|
|
SetupDiDestroyDeviceInfoList(handle);
|
|
|
|
return paNoError;
|
|
}
|
|
|
|
PaError PaWinWdm_Initialize( PaUtilHostApiRepresentation **hostApi, PaHostApiIndex hostApiIndex )
|
|
{
|
|
PaError result = paNoError;
|
|
int i, deviceCount;
|
|
PaWinWdmHostApiRepresentation *wdmHostApi;
|
|
PaWinWdmDeviceInfo *deviceInfoArray;
|
|
PaWinWdmFilter* pFilter;
|
|
PaWinWdmDeviceInfo *wdmDeviceInfo;
|
|
PaDeviceInfo *deviceInfo;
|
|
|
|
PA_LOGE_;
|
|
|
|
/*
|
|
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;
|
|
|
|
wdmHostApi = (PaWinWdmHostApiRepresentation*)PaUtil_AllocateMemory( sizeof(PaWinWdmHostApiRepresentation) );
|
|
if( !wdmHostApi )
|
|
{
|
|
result = paInsufficientMemory;
|
|
goto error;
|
|
}
|
|
|
|
wdmHostApi->allocations = PaUtil_CreateAllocationGroup();
|
|
if( !wdmHostApi->allocations )
|
|
{
|
|
result = paInsufficientMemory;
|
|
goto error;
|
|
}
|
|
|
|
result = BuildFilterList( wdmHostApi );
|
|
if( result != paNoError )
|
|
{
|
|
goto error;
|
|
}
|
|
deviceCount = wdmHostApi->filterCount;
|
|
|
|
*hostApi = &wdmHostApi->inheritedHostApiRep;
|
|
(*hostApi)->info.structVersion = 1;
|
|
(*hostApi)->info.type = paWDMKS;
|
|
(*hostApi)->info.name = "Windows WDM-KS";
|
|
(*hostApi)->info.defaultInputDevice = paNoDevice;
|
|
(*hostApi)->info.defaultOutputDevice = paNoDevice;
|
|
|
|
if( deviceCount > 0 )
|
|
{
|
|
(*hostApi)->deviceInfos = (PaDeviceInfo**)PaUtil_GroupAllocateMemory(
|
|
wdmHostApi->allocations, sizeof(PaWinWdmDeviceInfo*) * deviceCount );
|
|
if( !(*hostApi)->deviceInfos )
|
|
{
|
|
result = paInsufficientMemory;
|
|
goto error;
|
|
}
|
|
|
|
/* allocate all device info structs in a contiguous block */
|
|
deviceInfoArray = (PaWinWdmDeviceInfo*)PaUtil_GroupAllocateMemory(
|
|
wdmHostApi->allocations, sizeof(PaWinWdmDeviceInfo) * deviceCount );
|
|
if( !deviceInfoArray )
|
|
{
|
|
result = paInsufficientMemory;
|
|
goto error;
|
|
}
|
|
|
|
for( i=0; i < deviceCount; ++i )
|
|
{
|
|
wdmDeviceInfo = &deviceInfoArray[i];
|
|
deviceInfo = &wdmDeviceInfo->inheritedDeviceInfo;
|
|
pFilter = wdmHostApi->filters[i];
|
|
if( pFilter == NULL )
|
|
continue;
|
|
wdmDeviceInfo->filter = pFilter;
|
|
deviceInfo->structVersion = 2;
|
|
deviceInfo->hostApi = hostApiIndex;
|
|
deviceInfo->name = (char*)pFilter->friendlyName;
|
|
PA_DEBUG(("Device found name: %s\n",(char*)pFilter->friendlyName));
|
|
deviceInfo->maxInputChannels = pFilter->maxInputChannels;
|
|
if(deviceInfo->maxInputChannels > 0)
|
|
{
|
|
/* Set the default input device to the first device we find with
|
|
* more than zero input channels
|
|
**/
|
|
if((*hostApi)->info.defaultInputDevice == paNoDevice)
|
|
{
|
|
(*hostApi)->info.defaultInputDevice = i;
|
|
}
|
|
}
|
|
|
|
deviceInfo->maxOutputChannels = pFilter->maxOutputChannels;
|
|
if(deviceInfo->maxOutputChannels > 0)
|
|
{
|
|
/* Set the default output device to the first device we find with
|
|
* more than zero output channels
|
|
**/
|
|
if((*hostApi)->info.defaultOutputDevice == paNoDevice)
|
|
{
|
|
(*hostApi)->info.defaultOutputDevice = i;
|
|
}
|
|
}
|
|
|
|
/* 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
|
|
**/
|
|
deviceInfo->defaultLowInputLatency = (512.0/48000.0);
|
|
deviceInfo->defaultLowOutputLatency = (512.0/48000.0);
|
|
deviceInfo->defaultHighInputLatency = (4096.0/48000.0);
|
|
deviceInfo->defaultHighOutputLatency = (4096.0/48000.0);
|
|
deviceInfo->defaultSampleRate = (double)(pFilter->bestSampleRate);
|
|
|
|
(*hostApi)->deviceInfos[i] = deviceInfo;
|
|
}
|
|
}
|
|
|
|
(*hostApi)->info.deviceCount = deviceCount;
|
|
|
|
(*hostApi)->Terminate = Terminate;
|
|
(*hostApi)->OpenStream = OpenStream;
|
|
(*hostApi)->IsFormatSupported = IsFormatSupported;
|
|
|
|
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:
|
|
if( DllKsUser != NULL )
|
|
{
|
|
FreeLibrary( DllKsUser );
|
|
DllKsUser = NULL;
|
|
}
|
|
|
|
if( wdmHostApi )
|
|
{
|
|
PaUtil_FreeMemory( wdmHostApi->filters );
|
|
if( wdmHostApi->allocations )
|
|
{
|
|
PaUtil_FreeAllAllocations( wdmHostApi->allocations );
|
|
PaUtil_DestroyAllocationGroup( wdmHostApi->allocations );
|
|
}
|
|
PaUtil_FreeMemory( wdmHostApi );
|
|
}
|
|
PA_LOGL_;
|
|
return result;
|
|
}
|
|
|
|
|
|
static void Terminate( struct PaUtilHostApiRepresentation *hostApi )
|
|
{
|
|
PaWinWdmHostApiRepresentation *wdmHostApi = (PaWinWdmHostApiRepresentation*)hostApi;
|
|
int i;
|
|
PA_LOGE_;
|
|
|
|
if( wdmHostApi->filters )
|
|
{
|
|
for( i=0; i<wdmHostApi->filterCount; i++)
|
|
{
|
|
if( wdmHostApi->filters[i] != NULL )
|
|
{
|
|
FilterFree( wdmHostApi->filters[i] );
|
|
wdmHostApi->filters[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
PaUtil_FreeMemory( wdmHostApi->filters );
|
|
if( wdmHostApi->allocations )
|
|
{
|
|
PaUtil_FreeAllAllocations( wdmHostApi->allocations );
|
|
PaUtil_DestroyAllocationGroup( wdmHostApi->allocations );
|
|
}
|
|
PaUtil_FreeMemory( wdmHostApi );
|
|
PA_LOGL_;
|
|
}
|
|
|
|
static void FillWFEXT( WAVEFORMATEXTENSIBLE* pwfext, PaSampleFormat sampleFormat, double sampleRate, int channelCount)
|
|
{
|
|
PA_LOGE_;
|
|
PA_DEBUG(( "sampleFormat = %lx\n" , sampleFormat ));
|
|
PA_DEBUG(( "sampleRate = %f\n" , sampleRate ));
|
|
PA_DEBUG(( "chanelCount = %d\n", channelCount ));
|
|
|
|
pwfext->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
|
|
pwfext->Format.nChannels = channelCount;
|
|
pwfext->Format.nSamplesPerSec = (int)sampleRate;
|
|
if(channelCount == 1)
|
|
pwfext->dwChannelMask = KSAUDIO_SPEAKER_DIRECTOUT;
|
|
else
|
|
pwfext->dwChannelMask = KSAUDIO_SPEAKER_STEREO;
|
|
if(sampleFormat == paFloat32)
|
|
{
|
|
pwfext->Format.nBlockAlign = channelCount * 4;
|
|
pwfext->Format.wBitsPerSample = 32;
|
|
pwfext->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
|
|
pwfext->Samples.wValidBitsPerSample = 32;
|
|
pwfext->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT;
|
|
}
|
|
else if(sampleFormat == paInt32)
|
|
{
|
|
pwfext->Format.nBlockAlign = channelCount * 4;
|
|
pwfext->Format.wBitsPerSample = 32;
|
|
pwfext->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
|
|
pwfext->Samples.wValidBitsPerSample = 32;
|
|
pwfext->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
|
}
|
|
else if(sampleFormat == paInt24)
|
|
{
|
|
pwfext->Format.nBlockAlign = channelCount * 3;
|
|
pwfext->Format.wBitsPerSample = 24;
|
|
pwfext->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
|
|
pwfext->Samples.wValidBitsPerSample = 24;
|
|
pwfext->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
|
}
|
|
else if(sampleFormat == paInt16)
|
|
{
|
|
pwfext->Format.nBlockAlign = channelCount * 2;
|
|
pwfext->Format.wBitsPerSample = 16;
|
|
pwfext->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
|
|
pwfext->Samples.wValidBitsPerSample = 16;
|
|
pwfext->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
|
}
|
|
pwfext->Format.nAvgBytesPerSec = pwfext->Format.nSamplesPerSec * pwfext->Format.nBlockAlign;
|
|
|
|
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;
|
|
|
|
PA_LOGE_;
|
|
|
|
if( inputParameters )
|
|
{
|
|
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 )
|
|
return paSampleFormatNotSupported;
|
|
|
|
/* unless alternate device specification is supported, reject the use of
|
|
paUseHostApiSpecificDeviceSpecification */
|
|
|
|
if( inputParameters->device == paUseHostApiSpecificDeviceSpecification )
|
|
return paInvalidDevice;
|
|
|
|
/* check that input device can support inputChannelCount */
|
|
if( inputChannelCount > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels )
|
|
return paInvalidChannelCount;
|
|
|
|
/* validate inputStreamInfo */
|
|
if( inputParameters->hostApiSpecificStreamInfo )
|
|
return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
|
|
|
|
/* Check that the input format is supported */
|
|
FillWFEXT(&wfx,paInt16,sampleRate,inputChannelCount);
|
|
|
|
pFilter = wdmHostApi->filters[inputParameters->device];
|
|
result = FilterCanCreateCapturePin(pFilter,(const WAVEFORMATEX*)&wfx);
|
|
if( result != paNoError )
|
|
{
|
|
/* Try a WAVE_FORMAT_PCM instead */
|
|
wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
|
|
wfx.Format.cbSize = 0;
|
|
wfx.Samples.wValidBitsPerSample = 0;
|
|
wfx.dwChannelMask = 0;
|
|
wfx.SubFormat = GUID_NULL;
|
|
result = FilterCanCreateCapturePin(pFilter,(const WAVEFORMATEX*)&wfx);
|
|
if( result != paNoError )
|
|
return result;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
inputChannelCount = 0;
|
|
}
|
|
|
|
if( outputParameters )
|
|
{
|
|
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 )
|
|
return paSampleFormatNotSupported;
|
|
|
|
/* unless alternate device specification is supported, reject the use of
|
|
paUseHostApiSpecificDeviceSpecification */
|
|
|
|
if( outputParameters->device == paUseHostApiSpecificDeviceSpecification )
|
|
return paInvalidDevice;
|
|
|
|
/* check that output device can support outputChannelCount */
|
|
if( outputChannelCount > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels )
|
|
return paInvalidChannelCount;
|
|
|
|
/* validate outputStreamInfo */
|
|
if( outputParameters->hostApiSpecificStreamInfo )
|
|
return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
|
|
|
|
/* Check that the output format is supported */
|
|
FillWFEXT(&wfx,paInt16,sampleRate,outputChannelCount);
|
|
|
|
pFilter = wdmHostApi->filters[outputParameters->device];
|
|
result = FilterCanCreateRenderPin(pFilter,(const WAVEFORMATEX*)&wfx);
|
|
if( result != paNoError )
|
|
{
|
|
/* Try a WAVE_FORMAT_PCM instead */
|
|
wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
|
|
wfx.Format.cbSize = 0;
|
|
wfx.Samples.wValidBitsPerSample = 0;
|
|
wfx.dwChannelMask = 0;
|
|
wfx.SubFormat = GUID_NULL;
|
|
result = FilterCanCreateRenderPin(pFilter,(const WAVEFORMATEX*)&wfx);
|
|
if( result != paNoError )
|
|
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))
|
|
result = paSampleFormatNotSupported; /* Not right error */
|
|
|
|
PA_LOGL_;
|
|
return result;
|
|
}
|
|
|
|
/* 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 framesPerBuffer,
|
|
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;
|
|
int size;
|
|
PaWinWdmFilter* pFilter;
|
|
WAVEFORMATEXTENSIBLE wfx;
|
|
|
|
PA_LOGE_;
|
|
PA_DEBUG(("OpenStream:sampleRate = %f\n",sampleRate));
|
|
PA_DEBUG(("OpenStream:framesPerBuffer = %lu\n",framesPerBuffer));
|
|
|
|
if( inputParameters )
|
|
{
|
|
userInputChannels = inputParameters->channelCount;
|
|
inputSampleFormat = inputParameters->sampleFormat;
|
|
|
|
/* unless alternate device specification is supported, reject the use of
|
|
paUseHostApiSpecificDeviceSpecification */
|
|
|
|
if( inputParameters->device == paUseHostApiSpecificDeviceSpecification )
|
|
return paInvalidDevice;
|
|
|
|
/* check that input device can support stream->userInputChannels */
|
|
if( userInputChannels > hostApi->deviceInfos[ inputParameters->device ]->maxInputChannels )
|
|
return paInvalidChannelCount;
|
|
|
|
/* validate inputStreamInfo */
|
|
if( inputParameters->hostApiSpecificStreamInfo )
|
|
return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
|
|
|
|
}
|
|
else
|
|
{
|
|
userInputChannels = 0;
|
|
inputSampleFormat = hostInputSampleFormat = paInt16; /* Surpress '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 )
|
|
return paInvalidDevice;
|
|
|
|
/* check that output device can support stream->userInputChannels */
|
|
if( userOutputChannels > hostApi->deviceInfos[ outputParameters->device ]->maxOutputChannels )
|
|
return paInvalidChannelCount;
|
|
|
|
/* validate outputStreamInfo */
|
|
if( outputParameters->hostApiSpecificStreamInfo )
|
|
return paIncompatibleHostApiSpecificStreamInfo; /* this implementation doesn't use custom stream info */
|
|
|
|
}
|
|
else
|
|
{
|
|
userOutputChannels = 0;
|
|
outputSampleFormat = hostOutputSampleFormat = paInt16; /* Surpress 'uninitialized var' warnings. */
|
|
}
|
|
|
|
/* validate platform specific flags */
|
|
if( (streamFlags & paPlatformSpecificFlags) != 0 )
|
|
return paInvalidFlag; /* unexpected platform specific flag */
|
|
|
|
stream = (PaWinWdmStream*)PaUtil_AllocateMemory( sizeof(PaWinWdmStream) );
|
|
if( !stream )
|
|
{
|
|
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 );
|
|
}
|
|
|
|
PaUtil_InitializeCpuLoadMeasurer( &stream->cpuLoadMeasurer, sampleRate );
|
|
|
|
/* Instantiate the input pin if necessary */
|
|
if(userInputChannels > 0)
|
|
{
|
|
result = paSampleFormatNotSupported;
|
|
pFilter = wdmHostApi->filters[inputParameters->device];
|
|
stream->userInputChannels = userInputChannels;
|
|
|
|
if(((inputSampleFormat & ~paNonInterleaved) & pFilter->formats) != 0)
|
|
{ /* inputSampleFormat is supported, so try to use it */
|
|
hostInputSampleFormat = inputSampleFormat;
|
|
FillWFEXT(&wfx, hostInputSampleFormat, sampleRate, stream->userInputChannels);
|
|
stream->bytesPerInputFrame = wfx.Format.nBlockAlign;
|
|
stream->recordingPin = FilterCreateCapturePin(pFilter, (const WAVEFORMATEX*)&wfx, &result);
|
|
stream->deviceInputChannels = stream->userInputChannels;
|
|
}
|
|
|
|
if(result != paNoError)
|
|
{ /* Search through all PaSampleFormats to find one that works */
|
|
hostInputSampleFormat = paFloat32;
|
|
|
|
do {
|
|
FillWFEXT(&wfx, hostInputSampleFormat, sampleRate, stream->userInputChannels);
|
|
stream->bytesPerInputFrame = wfx.Format.nBlockAlign;
|
|
stream->recordingPin = FilterCreateCapturePin(pFilter, (const WAVEFORMATEX*)&wfx, &result);
|
|
stream->deviceInputChannels = stream->userInputChannels;
|
|
|
|
if(stream->recordingPin == NULL) result = paSampleFormatNotSupported;
|
|
if(result != paNoError) hostInputSampleFormat <<= 1;
|
|
}
|
|
while(result != paNoError && hostInputSampleFormat <= paUInt8);
|
|
}
|
|
|
|
if(result != paNoError)
|
|
{ /* None of the PaSampleFormats worked. Set the hostInputSampleFormat to the best fit
|
|
* and try a PCM format.
|
|
**/
|
|
hostInputSampleFormat =
|
|
PaUtil_SelectClosestAvailableFormat( pFilter->formats, inputSampleFormat );
|
|
|
|
/* Try a WAVE_FORMAT_PCM instead */
|
|
wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
|
|
wfx.Format.cbSize = 0;
|
|
wfx.Samples.wValidBitsPerSample = 0;
|
|
wfx.dwChannelMask = 0;
|
|
wfx.SubFormat = GUID_NULL;
|
|
stream->recordingPin = FilterCreateCapturePin(pFilter,(const WAVEFORMATEX*)&wfx,&result);
|
|
if(stream->recordingPin == NULL) result = paSampleFormatNotSupported;
|
|
}
|
|
|
|
if( result != paNoError )
|
|
{
|
|
/* 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
|
|
**/
|
|
if( stream->userInputChannels < pFilter->maxInputChannels )
|
|
{
|
|
FillWFEXT(&wfx,hostInputSampleFormat,sampleRate,pFilter->maxInputChannels);
|
|
stream->bytesPerInputFrame = wfx.Format.nBlockAlign;
|
|
stream->recordingPin = FilterCreateCapturePin(pFilter,(const WAVEFORMATEX*)&wfx,&result);
|
|
stream->deviceInputChannels = pFilter->maxInputChannels;
|
|
|
|
if( result != paNoError )
|
|
{
|
|
/* Try a WAVE_FORMAT_PCM instead */
|
|
wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
|
|
wfx.Format.cbSize = 0;
|
|
wfx.Samples.wValidBitsPerSample = 0;
|
|
wfx.dwChannelMask = 0;
|
|
wfx.SubFormat = GUID_NULL;
|
|
stream->recordingPin = FilterCreateCapturePin(pFilter,(const WAVEFORMATEX*)&wfx,&result);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(stream->recordingPin == NULL)
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
switch(hostInputSampleFormat)
|
|
{
|
|
case paInt16: stream->inputSampleSize = 2; break;
|
|
case paInt24: stream->inputSampleSize = 3; break;
|
|
case paInt32:
|
|
case paFloat32: stream->inputSampleSize = 4; break;
|
|
}
|
|
|
|
stream->recordingPin->frameSize /= stream->bytesPerInputFrame;
|
|
PA_DEBUG(("Pin output frames: %d\n",stream->recordingPin->frameSize));
|
|
}
|
|
else
|
|
{
|
|
stream->recordingPin = NULL;
|
|
stream->bytesPerInputFrame = 0;
|
|
}
|
|
|
|
/* Instantiate the output pin if necessary */
|
|
if(userOutputChannels > 0)
|
|
{
|
|
result = paSampleFormatNotSupported;
|
|
pFilter = wdmHostApi->filters[outputParameters->device];
|
|
stream->userOutputChannels = userOutputChannels;
|
|
|
|
if(((outputSampleFormat & ~paNonInterleaved) & pFilter->formats) != 0)
|
|
{
|
|
hostOutputSampleFormat = outputSampleFormat;
|
|
FillWFEXT(&wfx,hostOutputSampleFormat,sampleRate,stream->userOutputChannels);
|
|
stream->bytesPerOutputFrame = wfx.Format.nBlockAlign;
|
|
stream->playbackPin = FilterCreateRenderPin(pFilter,(WAVEFORMATEX*)&wfx,&result);
|
|
stream->deviceOutputChannels = stream->userOutputChannels;
|
|
}
|
|
|
|
if(result != paNoError)
|
|
{
|
|
hostOutputSampleFormat = paFloat32;
|
|
|
|
do {
|
|
FillWFEXT(&wfx,hostOutputSampleFormat,sampleRate,stream->userOutputChannels);
|
|
stream->bytesPerOutputFrame = wfx.Format.nBlockAlign;
|
|
stream->playbackPin = FilterCreateRenderPin(pFilter,(WAVEFORMATEX*)&wfx,&result);
|
|
stream->deviceOutputChannels = stream->userOutputChannels;
|
|
|
|
if(stream->playbackPin == NULL) result = paSampleFormatNotSupported;
|
|
if(result != paNoError) hostOutputSampleFormat <<= 1;
|
|
}
|
|
while(result != paNoError && hostOutputSampleFormat <= paUInt8);
|
|
}
|
|
|
|
if(result != paNoError)
|
|
{
|
|
hostOutputSampleFormat =
|
|
PaUtil_SelectClosestAvailableFormat( pFilter->formats, outputSampleFormat );
|
|
|
|
/* Try a WAVE_FORMAT_PCM instead */
|
|
wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
|
|
wfx.Format.cbSize = 0;
|
|
wfx.Samples.wValidBitsPerSample = 0;
|
|
wfx.dwChannelMask = 0;
|
|
wfx.SubFormat = GUID_NULL;
|
|
stream->playbackPin = FilterCreateRenderPin(pFilter,(WAVEFORMATEX*)&wfx,&result);
|
|
if(stream->playbackPin == NULL) result = paSampleFormatNotSupported;
|
|
}
|
|
|
|
if( result != paNoError )
|
|
{
|
|
/* 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
|
|
**/
|
|
if( stream->userOutputChannels < pFilter->maxOutputChannels )
|
|
{
|
|
FillWFEXT(&wfx,hostOutputSampleFormat,sampleRate,pFilter->maxOutputChannels);
|
|
stream->bytesPerOutputFrame = wfx.Format.nBlockAlign;
|
|
stream->playbackPin = FilterCreateRenderPin(pFilter,(const WAVEFORMATEX*)&wfx,&result);
|
|
stream->deviceOutputChannels = pFilter->maxOutputChannels;
|
|
if( result != paNoError )
|
|
{
|
|
/* Try a WAVE_FORMAT_PCM instead */
|
|
wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
|
|
wfx.Format.cbSize = 0;
|
|
wfx.Samples.wValidBitsPerSample = 0;
|
|
wfx.dwChannelMask = 0;
|
|
wfx.SubFormat = GUID_NULL;
|
|
stream->playbackPin = FilterCreateRenderPin(pFilter,(const WAVEFORMATEX*)&wfx,&result);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(stream->playbackPin == NULL)
|
|
{
|
|
goto error;
|
|
}
|
|
|
|
switch(hostOutputSampleFormat)
|
|
{
|
|
case paInt16: stream->outputSampleSize = 2; break;
|
|
case paInt24: stream->outputSampleSize = 3; break;
|
|
case paInt32:
|
|
case paFloat32: stream->outputSampleSize = 4; break;
|
|
}
|
|
|
|
stream->playbackPin->frameSize /= stream->bytesPerOutputFrame;
|
|
PA_DEBUG(("Pin output frames: %d\n",stream->playbackPin->frameSize));
|
|
}
|
|
else
|
|
{
|
|
stream->playbackPin = NULL;
|
|
stream->bytesPerOutputFrame = 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->framesPerHostIBuffer = (unsigned long)((inputParameters->suggestedLatency*sampleRate)+0.0001);
|
|
if(stream->framesPerHostIBuffer > (unsigned long)sampleRate)
|
|
{ /* Upper limit is 1 second */
|
|
stream->framesPerHostIBuffer = (unsigned long)sampleRate;
|
|
}
|
|
else if(stream->framesPerHostIBuffer < stream->recordingPin->frameSize)
|
|
{
|
|
stream->framesPerHostIBuffer = stream->recordingPin->frameSize;
|
|
}
|
|
PA_DEBUG(("Input frames chosen:%ld\n",stream->framesPerHostIBuffer));
|
|
}
|
|
|
|
if(outputParameters)
|
|
{
|
|
/* Calculate the frames from the user's value - add a bit to round up */
|
|
stream->framesPerHostOBuffer = (unsigned long)((outputParameters->suggestedLatency*sampleRate)+0.0001);
|
|
if(stream->framesPerHostOBuffer > (unsigned long)sampleRate)
|
|
{ /* Upper limit is 1 second */
|
|
stream->framesPerHostOBuffer = (unsigned long)sampleRate;
|
|
}
|
|
else if(stream->framesPerHostOBuffer < stream->playbackPin->frameSize)
|
|
{
|
|
stream->framesPerHostOBuffer = stream->playbackPin->frameSize;
|
|
}
|
|
PA_DEBUG(("Output frames chosen:%ld\n",stream->framesPerHostOBuffer));
|
|
}
|
|
|
|
/* Host buffer size is bounded 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, framesPerBuffer,
|
|
max(stream->framesPerHostOBuffer,stream->framesPerHostIBuffer),
|
|
paUtilBoundedHostBufferSize,
|
|
streamCallback, userData );
|
|
if( result != paNoError )
|
|
goto error;
|
|
|
|
stream->streamRepresentation.streamInfo.inputLatency =
|
|
((double)stream->framesPerHostIBuffer) / sampleRate;
|
|
stream->streamRepresentation.streamInfo.outputLatency =
|
|
((double)stream->framesPerHostOBuffer) / sampleRate;
|
|
stream->streamRepresentation.streamInfo.sampleRate = sampleRate;
|
|
|
|
PA_DEBUG(("BytesPerInputFrame = %d\n",stream->bytesPerInputFrame));
|
|
PA_DEBUG(("BytesPerOutputFrame = %d\n",stream->bytesPerOutputFrame));
|
|
|
|
/* Allocate all the buffers for host I/O */
|
|
size = 2 * (stream->framesPerHostIBuffer*stream->bytesPerInputFrame + stream->framesPerHostOBuffer*stream->bytesPerOutputFrame);
|
|
PA_DEBUG(("Buffer size = %d\n",size));
|
|
stream->hostBuffer = (char*)PaUtil_AllocateMemory(size);
|
|
PA_DEBUG(("Buffer allocated\n"));
|
|
if( !stream->hostBuffer )
|
|
{
|
|
PA_DEBUG(("Cannot allocate host buffer!\n"));
|
|
result = paInsufficientMemory;
|
|
goto error;
|
|
}
|
|
PA_DEBUG(("Buffer start = %p\n",stream->hostBuffer));
|
|
/* memset(stream->hostBuffer,0,size); */
|
|
|
|
/* Set up the packets */
|
|
stream->events[0] = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
ResetEvent(stream->events[0]); /* Record buffer 1 */
|
|
stream->events[1] = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
ResetEvent(stream->events[1]); /* Record buffer 2 */
|
|
stream->events[2] = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
ResetEvent(stream->events[2]); /* Play buffer 1 */
|
|
stream->events[3] = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
ResetEvent(stream->events[3]); /* Play buffer 2 */
|
|
stream->events[4] = CreateEvent(NULL, FALSE, FALSE, NULL);
|
|
ResetEvent(stream->events[4]); /* Abort event */
|
|
if(stream->userInputChannels > 0)
|
|
{
|
|
DATAPACKET *p = &(stream->packets[0]);
|
|
p->Signal.hEvent = stream->events[0];
|
|
p->Header.Data = stream->hostBuffer;
|
|
p->Header.FrameExtent = stream->framesPerHostIBuffer*stream->bytesPerInputFrame;
|
|
p->Header.DataUsed = 0;
|
|
p->Header.Size = sizeof(p->Header);
|
|
p->Header.PresentationTime.Numerator = 1;
|
|
p->Header.PresentationTime.Denominator = 1;
|
|
|
|
p = &(stream->packets[1]);
|
|
p->Signal.hEvent = stream->events[1];
|
|
p->Header.Data = stream->hostBuffer + stream->framesPerHostIBuffer*stream->bytesPerInputFrame;
|
|
p->Header.FrameExtent = stream->framesPerHostIBuffer*stream->bytesPerInputFrame;
|
|
p->Header.DataUsed = 0;
|
|
p->Header.Size = sizeof(p->Header);
|
|
p->Header.PresentationTime.Numerator = 1;
|
|
p->Header.PresentationTime.Denominator = 1;
|
|
}
|
|
if(stream->userOutputChannels > 0)
|
|
{
|
|
DATAPACKET *p = &(stream->packets[2]);
|
|
p->Signal.hEvent = stream->events[2];
|
|
p->Header.Data = stream->hostBuffer + 2*stream->framesPerHostIBuffer*stream->bytesPerInputFrame;
|
|
p->Header.FrameExtent = stream->framesPerHostOBuffer*stream->bytesPerOutputFrame;
|
|
p->Header.DataUsed = stream->framesPerHostOBuffer*stream->bytesPerOutputFrame;
|
|
p->Header.Size = sizeof(p->Header);
|
|
p->Header.PresentationTime.Numerator = 1;
|
|
p->Header.PresentationTime.Denominator = 1;
|
|
|
|
p = &(stream->packets[3]);
|
|
p->Signal.hEvent = stream->events[3];
|
|
p->Header.Data = stream->hostBuffer + 2*stream->framesPerHostIBuffer*stream->bytesPerInputFrame + stream->framesPerHostOBuffer*stream->bytesPerOutputFrame;
|
|
p->Header.FrameExtent = stream->framesPerHostOBuffer*stream->bytesPerOutputFrame;
|
|
p->Header.DataUsed = stream->framesPerHostOBuffer*stream->bytesPerOutputFrame;
|
|
p->Header.Size = sizeof(p->Header);
|
|
p->Header.PresentationTime.Numerator = 1;
|
|
p->Header.PresentationTime.Denominator = 1;
|
|
}
|
|
|
|
stream->streamStarted = 0;
|
|
stream->streamActive = 0;
|
|
stream->streamStop = 0;
|
|
stream->streamAbort = 0;
|
|
stream->streamFlags = streamFlags;
|
|
stream->oldProcessPriority = REALTIME_PRIORITY_CLASS;
|
|
|
|
*s = (PaStream*)stream;
|
|
|
|
PA_LOGL_;
|
|
return result;
|
|
|
|
error:
|
|
size = 5;
|
|
while(size--)
|
|
{
|
|
if(stream->events[size] != NULL)
|
|
{
|
|
CloseHandle(stream->events[size]);
|
|
stream->events[size] = NULL;
|
|
}
|
|
}
|
|
if(stream->hostBuffer)
|
|
PaUtil_FreeMemory( stream->hostBuffer );
|
|
|
|
if(stream->playbackPin)
|
|
PinClose(stream->playbackPin);
|
|
if(stream->recordingPin)
|
|
PinClose(stream->recordingPin);
|
|
|
|
if( stream )
|
|
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;
|
|
int size;
|
|
|
|
PA_LOGE_;
|
|
|
|
assert(!stream->streamStarted);
|
|
assert(!stream->streamActive);
|
|
|
|
PaUtil_TerminateBufferProcessor( &stream->bufferProcessor );
|
|
PaUtil_TerminateStreamRepresentation( &stream->streamRepresentation );
|
|
size = 5;
|
|
while(size--)
|
|
{
|
|
if(stream->events[size] != NULL)
|
|
{
|
|
CloseHandle(stream->events[size]);
|
|
stream->events[size] = NULL;
|
|
}
|
|
}
|
|
if(stream->hostBuffer)
|
|
PaUtil_FreeMemory( stream->hostBuffer );
|
|
|
|
if(stream->playbackPin)
|
|
PinClose(stream->playbackPin);
|
|
if(stream->recordingPin)
|
|
PinClose(stream->recordingPin);
|
|
|
|
PaUtil_FreeMemory( stream );
|
|
|
|
PA_LOGL_;
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
Write the supplied packet to the pin
|
|
Asynchronous
|
|
Should return false on success
|
|
*/
|
|
static BOOL PinWrite(HANDLE h, DATAPACKET* p)
|
|
{
|
|
unsigned long cbReturned = 0;
|
|
return DeviceIoControl(h,IOCTL_KS_WRITE_STREAM,NULL,0,
|
|
&p->Header,p->Header.Size,&cbReturned,&p->Signal);
|
|
}
|
|
|
|
/*
|
|
Read to the supplied packet from the pin
|
|
Asynchronous
|
|
Should return false on success
|
|
*/
|
|
static BOOL PinRead(HANDLE h, DATAPACKET* p)
|
|
{
|
|
unsigned long cbReturned = 0;
|
|
return DeviceIoControl(h,IOCTL_KS_READ_STREAM,NULL,0,
|
|
&p->Header,p->Header.Size,&cbReturned,&p->Signal);
|
|
}
|
|
|
|
/*
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
static DWORD WINAPI ProcessingThread(LPVOID pParam)
|
|
{
|
|
PaWinWdmStream *stream = (PaWinWdmStream*)pParam;
|
|
PaStreamCallbackTimeInfo ti;
|
|
int cbResult = paContinue;
|
|
int inbuf = 0;
|
|
int outbuf = 0;
|
|
int pending = 0;
|
|
PaError result;
|
|
unsigned long wait;
|
|
unsigned long eventSignaled;
|
|
int fillPlaybuf = 0;
|
|
int emptyRecordbuf = 0;
|
|
int framesProcessed;
|
|
unsigned long timeout;
|
|
int i;
|
|
int doChannelCopy;
|
|
int priming = 0;
|
|
PaStreamCallbackFlags underover = 0;
|
|
|
|
PA_LOGE_;
|
|
|
|
ti.inputBufferAdcTime = 0.0;
|
|
ti.currentTime = 0.0;
|
|
ti.outputBufferDacTime = 0.0;
|
|
|
|
/* Get double buffering going */
|
|
|
|
/* Submit buffers */
|
|
if(stream->playbackPin)
|
|
{
|
|
result = PinSetState(stream->playbackPin, KSSTATE_RUN);
|
|
|
|
PA_DEBUG(("play state run = %d;",(int)result));
|
|
SetEvent(stream->events[outbuf+2]);
|
|
outbuf = (outbuf+1)&1;
|
|
SetEvent(stream->events[outbuf+2]);
|
|
outbuf = (outbuf+1)&1;
|
|
pending += 2;
|
|
priming += 4;
|
|
}
|
|
if(stream->recordingPin)
|
|
{
|
|
result = PinSetState(stream->recordingPin, KSSTATE_RUN);
|
|
|
|
PA_DEBUG(("recording state run = %d;",(int)result));
|
|
PinRead(stream->recordingPin->handle,&stream->packets[inbuf]);
|
|
inbuf = (inbuf+1)&1; /* Increment and wrap */
|
|
PinRead(stream->recordingPin->handle,&stream->packets[inbuf]);
|
|
inbuf = (inbuf+1)&1; /* Increment and wrap */
|
|
/* FIXME - do error checking */
|
|
pending += 2;
|
|
}
|
|
PA_DEBUG(("Out buffer len:%f\n",(2000*stream->framesPerHostOBuffer) / stream->streamRepresentation.streamInfo.sampleRate));
|
|
PA_DEBUG(("In buffer len:%f\n",(2000*stream->framesPerHostIBuffer) / stream->streamRepresentation.streamInfo.sampleRate));
|
|
timeout = max(
|
|
((2000*(DWORD)stream->framesPerHostOBuffer) / (DWORD)stream->streamRepresentation.streamInfo.sampleRate),
|
|
((2000*(DWORD)stream->framesPerHostIBuffer) / (DWORD)stream->streamRepresentation.streamInfo.sampleRate));
|
|
timeout = max(timeout,1);
|
|
PA_DEBUG(("Timeout = %ld\n",timeout));
|
|
|
|
while(!stream->streamAbort)
|
|
{
|
|
fillPlaybuf = 0;
|
|
emptyRecordbuf = 0;
|
|
|
|
/* Wait for next input or output buffer to be finished with*/
|
|
assert(pending>0);
|
|
|
|
if(stream->streamStop)
|
|
{
|
|
PA_DEBUG(("ss1:pending=%d ",pending));
|
|
}
|
|
wait = WaitForMultipleObjects(5, stream->events, FALSE, 0);
|
|
if( wait == WAIT_TIMEOUT )
|
|
{
|
|
/* No (under|over)flow has ocurred */
|
|
wait = WaitForMultipleObjects(5, stream->events, FALSE, timeout);
|
|
eventSignaled = wait - WAIT_OBJECT_0;
|
|
}
|
|
else
|
|
{
|
|
eventSignaled = wait - WAIT_OBJECT_0;
|
|
if( eventSignaled < 2 )
|
|
{
|
|
underover |= paInputOverflow;
|
|
PA_DEBUG(("Input overflow\n"));
|
|
}
|
|
else if(( eventSignaled < 4 )&&(!priming))
|
|
{
|
|
underover |= paOutputUnderflow;
|
|
PA_DEBUG(("Output underflow\n"));
|
|
}
|
|
}
|
|
|
|
if(stream->streamStop)
|
|
{
|
|
PA_DEBUG(("ss2:wait=%ld",wait));
|
|
}
|
|
if(wait == WAIT_FAILED)
|
|
{
|
|
PA_DEBUG(("Wait fail = %ld! ",wait));
|
|
break;
|
|
}
|
|
if(wait == WAIT_TIMEOUT)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if(eventSignaled < 2)
|
|
{ /* Recording input buffer has been filled */
|
|
if(stream->playbackPin)
|
|
{
|
|
/* First check if also the next playback buffer has been signaled */
|
|
wait = WaitForSingleObject(stream->events[outbuf+2],0);
|
|
if(wait == WAIT_OBJECT_0)
|
|
{
|
|
/* Yes, so do both buffers at same time */
|
|
fillPlaybuf = 1;
|
|
pending--;
|
|
/* Was this an underflow situation? */
|
|
if( underover )
|
|
underover |= paOutputUnderflow; /* Yes! */
|
|
}
|
|
}
|
|
emptyRecordbuf = 1;
|
|
pending--;
|
|
}
|
|
else if(eventSignaled < 4)
|
|
{ /* Playback output buffer has been emptied */
|
|
if(stream->recordingPin)
|
|
{
|
|
/* First check if also the next recording buffer has been signaled */
|
|
wait = WaitForSingleObject(stream->events[inbuf],0);
|
|
if(wait == WAIT_OBJECT_0)
|
|
{ /* Yes, so do both buffers at same time */
|
|
emptyRecordbuf = 1;
|
|
pending--;
|
|
/* Was this an overflow situation? */
|
|
if( underover )
|
|
underover |= paInputOverflow; /* Yes! */
|
|
}
|
|
}
|
|
fillPlaybuf = 1;
|
|
pending--;
|
|
}
|
|
else
|
|
{
|
|
/* Abort event! */
|
|
assert(stream->streamAbort); /* Should have been set */
|
|
PA_DEBUG(("ABORTING "));
|
|
break;
|
|
}
|
|
ResetEvent(stream->events[eventSignaled]);
|
|
|
|
if(stream->streamStop)
|
|
{
|
|
PA_DEBUG(("Stream stop! pending=%d",pending));
|
|
cbResult = paComplete; /* Stop, but play remaining buffers */
|
|
}
|
|
|
|
/* Do necessary buffer processing (which will invoke user callback if necessary */
|
|
doChannelCopy = 0;
|
|
if(cbResult==paContinue)
|
|
{
|
|
PaUtil_BeginCpuLoadMeasurement( &stream->cpuLoadMeasurer );
|
|
if((stream->bufferProcessor.hostInputFrameCount[0] + stream->bufferProcessor.hostInputFrameCount[1]) ==
|
|
(stream->bufferProcessor.hostOutputFrameCount[0] + stream->bufferProcessor.hostOutputFrameCount[1]) )
|
|
PaUtil_BeginBufferProcessing(&stream->bufferProcessor,&ti,underover);
|
|
underover = 0; /* Reset the (under|over)flow status */
|
|
if(fillPlaybuf)
|
|
{
|
|
PaUtil_SetOutputFrameCount(&stream->bufferProcessor,0);
|
|
if( stream->userOutputChannels == 1 )
|
|
{
|
|
/* Write the single user channel to the first interleaved block */
|
|
PaUtil_SetOutputChannel(&stream->bufferProcessor,0,stream->packets[outbuf+2].Header.Data,stream->deviceOutputChannels);
|
|
/* We will do a copy to the other channels after the data has been written */
|
|
doChannelCopy = 1;
|
|
}
|
|
else
|
|
{
|
|
for(i=0;i<stream->userOutputChannels;i++)
|
|
{
|
|
/* Only write the user output channels. Leave the rest blank */
|
|
PaUtil_SetOutputChannel(&stream->bufferProcessor,i,((unsigned char*)(stream->packets[outbuf+2].Header.Data))+(i*stream->outputSampleSize),stream->deviceOutputChannels);
|
|
}
|
|
}
|
|
}
|
|
if(emptyRecordbuf)
|
|
{
|
|
PaUtil_SetInputFrameCount(&stream->bufferProcessor,stream->packets[inbuf].Header.DataUsed/stream->bytesPerInputFrame);
|
|
for(i=0;i<stream->userInputChannels;i++)
|
|
{
|
|
/* Only read as many channels as the user wants */
|
|
PaUtil_SetInputChannel(&stream->bufferProcessor,i,((unsigned char*)(stream->packets[inbuf].Header.Data))+(i*stream->inputSampleSize),stream->deviceInputChannels);
|
|
}
|
|
}
|
|
/* Only call the EndBufferProcessing function is the total input frames == total output frames */
|
|
if((stream->bufferProcessor.hostInputFrameCount[0] + stream->bufferProcessor.hostInputFrameCount[1]) ==
|
|
(stream->bufferProcessor.hostOutputFrameCount[0] + stream->bufferProcessor.hostOutputFrameCount[1]) )
|
|
framesProcessed = PaUtil_EndBufferProcessing(&stream->bufferProcessor,&cbResult);
|
|
else framesProcessed = 0;
|
|
if( doChannelCopy )
|
|
{
|
|
/* Copy the first output channel to the other channels */
|
|
switch(stream->outputSampleSize)
|
|
{
|
|
case 2:
|
|
DuplicateFirstChannelInt16(stream->packets[outbuf+2].Header.Data,stream->deviceOutputChannels,stream->framesPerHostOBuffer);
|
|
break;
|
|
case 3:
|
|
DuplicateFirstChannelInt24(stream->packets[outbuf+2].Header.Data,stream->deviceOutputChannels,stream->framesPerHostOBuffer);
|
|
break;
|
|
case 4:
|
|
DuplicateFirstChannelInt32(stream->packets[outbuf+2].Header.Data,stream->deviceOutputChannels,stream->framesPerHostOBuffer);
|
|
break;
|
|
default:
|
|
assert(0); /* Unsupported format! */
|
|
break;
|
|
}
|
|
}
|
|
PaUtil_EndCpuLoadMeasurement( &stream->cpuLoadMeasurer, framesProcessed );
|
|
}
|
|
else
|
|
{
|
|
fillPlaybuf = 0;
|
|
emptyRecordbuf = 0;
|
|
}
|
|
|
|
/*
|
|
if(cbResult != paContinue)
|
|
{
|
|
PA_DEBUG(("cbResult=%d, pending=%d:",cbResult,pending));
|
|
}
|
|
*/
|
|
/* Submit buffers */
|
|
if((fillPlaybuf)&&(cbResult!=paAbort))
|
|
{
|
|
if(!PinWrite(stream->playbackPin->handle,&stream->packets[outbuf+2]))
|
|
outbuf = (outbuf+1)&1; /* Increment and wrap */
|
|
pending++;
|
|
if( priming )
|
|
priming--; /* Have to prime twice */
|
|
}
|
|
if((emptyRecordbuf)&&(cbResult==paContinue))
|
|
{
|
|
stream->packets[inbuf].Header.DataUsed = 0; /* Reset for reuse */
|
|
PinRead(stream->recordingPin->handle,&stream->packets[inbuf]);
|
|
inbuf = (inbuf+1)&1; /* Increment and wrap */
|
|
pending++;
|
|
}
|
|
if(pending==0)
|
|
{
|
|
PA_DEBUG(("pending==0 finished...;"));
|
|
break;
|
|
}
|
|
if((!stream->playbackPin)&&(cbResult!=paContinue))
|
|
{
|
|
PA_DEBUG(("record only cbResult=%d...;",cbResult));
|
|
break;
|
|
}
|
|
}
|
|
|
|
PA_DEBUG(("Finished thread"));
|
|
|
|
/* Finished, either normally or aborted */
|
|
if(stream->playbackPin)
|
|
{
|
|
result = PinSetState(stream->playbackPin, KSSTATE_PAUSE);
|
|
result = PinSetState(stream->playbackPin, KSSTATE_STOP);
|
|
}
|
|
if(stream->recordingPin)
|
|
{
|
|
result = PinSetState(stream->recordingPin, KSSTATE_PAUSE);
|
|
result = PinSetState(stream->recordingPin, KSSTATE_STOP);
|
|
}
|
|
|
|
stream->streamActive = 0;
|
|
|
|
if((!stream->streamStop)&&(!stream->streamAbort))
|
|
{
|
|
/* Invoke the user stream finished callback */
|
|
/* Only do it from here if not being stopped/aborted by user */
|
|
if( stream->streamRepresentation.streamFinishedCallback != 0 )
|
|
stream->streamRepresentation.streamFinishedCallback( stream->streamRepresentation.userData );
|
|
}
|
|
stream->streamStop = 0;
|
|
stream->streamAbort = 0;
|
|
|
|
/* Reset process priority if necessary */
|
|
if(stream->oldProcessPriority != REALTIME_PRIORITY_CLASS)
|
|
{
|
|
SetPriorityClass(GetCurrentProcess(),stream->oldProcessPriority);
|
|
stream->oldProcessPriority = REALTIME_PRIORITY_CLASS;
|
|
}
|
|
|
|
PA_LOGL_;
|
|
ExitThread(0);
|
|
return 0;
|
|
}
|
|
|
|
static PaError StartStream( PaStream *s )
|
|
{
|
|
PaError result = paNoError;
|
|
PaWinWdmStream *stream = (PaWinWdmStream*)s;
|
|
DWORD dwID;
|
|
BOOL ret;
|
|
int size;
|
|
|
|
PA_LOGE_;
|
|
|
|
stream->streamStop = 0;
|
|
stream->streamAbort = 0;
|
|
size = 5;
|
|
while(size--)
|
|
{
|
|
if(stream->events[size] != NULL)
|
|
{
|
|
ResetEvent(stream->events[size]);
|
|
}
|
|
}
|
|
|
|
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->streamStarted = 1;
|
|
stream->streamThread = CreateThread(NULL, 0, ProcessingThread, stream, 0, &dwID);
|
|
if(stream->streamThread == NULL)
|
|
{
|
|
stream->streamStarted = 0;
|
|
result = paInsufficientMemory;
|
|
goto end;
|
|
}
|
|
ret = SetThreadPriority(stream->streamThread,THREAD_PRIORITY_TIME_CRITICAL);
|
|
PA_DEBUG(("Priority ret = %d;",ret));
|
|
/* Make the stream active */
|
|
stream->streamActive = 1;
|
|
|
|
end:
|
|
PA_LOGL_;
|
|
return result;
|
|
}
|
|
|
|
|
|
static PaError StopStream( PaStream *s )
|
|
{
|
|
PaError result = paNoError;
|
|
PaWinWdmStream *stream = (PaWinWdmStream*)s;
|
|
int doCb = 0;
|
|
|
|
PA_LOGE_;
|
|
|
|
if(stream->streamActive)
|
|
{
|
|
doCb = 1;
|
|
stream->streamStop = 1;
|
|
while(stream->streamActive)
|
|
{
|
|
PA_DEBUG(("W."));
|
|
Sleep(10); /* Let thread sleep for 10 msec */
|
|
}
|
|
}
|
|
|
|
PA_DEBUG(("Terminating thread"));
|
|
if(stream->streamStarted && stream->streamThread)
|
|
{
|
|
TerminateThread(stream->streamThread,0);
|
|
stream->streamThread = NULL;
|
|
}
|
|
|
|
stream->streamStarted = 0;
|
|
|
|
if(stream->oldProcessPriority != REALTIME_PRIORITY_CLASS)
|
|
{
|
|
SetPriorityClass(GetCurrentProcess(),stream->oldProcessPriority);
|
|
stream->oldProcessPriority = REALTIME_PRIORITY_CLASS;
|
|
}
|
|
|
|
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->events[4]); /* Signal immediately */
|
|
while(stream->streamActive)
|
|
{
|
|
Sleep(10);
|
|
}
|
|
}
|
|
|
|
if(stream->streamStarted && stream->streamThread)
|
|
{
|
|
TerminateThread(stream->streamThread,0);
|
|
stream->streamThread = NULL;
|
|
}
|
|
|
|
stream->streamStarted = 0;
|
|
|
|
if(stream->oldProcessPriority != REALTIME_PRIORITY_CLASS)
|
|
{
|
|
SetPriorityClass(GetCurrentProcess(),stream->oldProcessPriority);
|
|
stream->oldProcessPriority = REALTIME_PRIORITY_CLASS;
|
|
}
|
|
|
|
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 paNoError;
|
|
}
|
|
|
|
|
|
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 paNoError;
|
|
}
|
|
|
|
|
|
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;
|
|
} |