1402 lines
43 KiB
C++
1402 lines
43 KiB
C++
#include "global.h"
|
|
#include "RageSoundDriver_WDMKS.h"
|
|
|
|
#include "RageLog.h"
|
|
#include "RageUtil.h"
|
|
#include "PrefsManager.h"
|
|
#include "archutils/Win32/ErrorStrings.h"
|
|
|
|
#define _INC_MMREG
|
|
#define _NTRTL_ /* Turn off default definition of DEFINE_GUIDEX */
|
|
#if !defined(DEFINE_WAVEFORMATEX_GUID)
|
|
#define DEFINE_WAVEFORMATEX_GUID(x) (USHORT)(x), 0x0000, 0x0010, 0x80, 0x00, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71
|
|
#endif
|
|
|
|
#include <windows.h>
|
|
#include <winioctl.h>
|
|
#include <ks.h>
|
|
#include <mmsystem.h>
|
|
#include <ksmedia.h>
|
|
#include <setupapi.h>
|
|
|
|
typedef KSDDKAPI DWORD WINAPI KSCREATEPIN(HANDLE, PKSPIN_CONNECT, ACCESS_MASK, PHANDLE);
|
|
|
|
#ifndef KSAUDIO_SPEAKER_5POINT1_SURROUND
|
|
#define KSAUDIO_SPEAKER_5POINT1_SURROUND \
|
|
(SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT)
|
|
#endif
|
|
|
|
#ifndef KSAUDIO_SPEAKER_7POINT1_SURROUND
|
|
#define KSAUDIO_SPEAKER_7POINT1_SURROUND \
|
|
(SPEAKER_FRONT_LEFT | SPEAKER_FRONT_RIGHT | SPEAKER_FRONT_CENTER | SPEAKER_LOW_FREQUENCY | \
|
|
SPEAKER_BACK_LEFT | SPEAKER_BACK_RIGHT | SPEAKER_SIDE_LEFT | SPEAKER_SIDE_RIGHT)
|
|
#endif
|
|
|
|
struct WinWdmFilter;
|
|
|
|
struct WinWdmPin
|
|
{
|
|
WinWdmPin( WinWdmFilter *pParentFilter, int iPinId )
|
|
{
|
|
m_hHandle = nullptr;
|
|
m_pParentFilter = pParentFilter;
|
|
m_iPinId = iPinId;
|
|
}
|
|
|
|
~WinWdmPin()
|
|
{
|
|
Close();
|
|
}
|
|
|
|
bool Instantiate( const WAVEFORMATEX *pFormat, RString &sError );
|
|
void Close();
|
|
|
|
bool SetState( KSSTATE state, RString &sError );
|
|
KSPIN_CONNECT *MakeFormat( const WAVEFORMATEX *pFormat ) const;
|
|
bool IsFormatSupported( const WAVEFORMATEX *pFormat ) const;
|
|
|
|
HANDLE m_hHandle;
|
|
WinWdmFilter *m_pParentFilter;
|
|
int m_iPinId;
|
|
std::vector<KSDATARANGE_AUDIO> m_dataRangesItem;
|
|
};
|
|
|
|
enum DeviceSampleFormat
|
|
{
|
|
DeviceSampleFormat_Float32,
|
|
DeviceSampleFormat_Int16,
|
|
DeviceSampleFormat_Int24,
|
|
DeviceSampleFormat_Int32,
|
|
NUM_DeviceSampleFormat,
|
|
DeviceSampleFormat_Invalid
|
|
};
|
|
|
|
static int GetBytesPerSample( DeviceSampleFormat sf )
|
|
{
|
|
switch( sf )
|
|
{
|
|
case DeviceSampleFormat_Float32: return 4;
|
|
case DeviceSampleFormat_Int16: return 2;
|
|
case DeviceSampleFormat_Int24: return 3;
|
|
case DeviceSampleFormat_Int32: return 4;
|
|
DEFAULT_FAIL(sf);
|
|
}
|
|
}
|
|
|
|
/* The Filter structure
|
|
* A filter has a number of pins and a "friendly name" */
|
|
struct WinWdmFilter
|
|
{
|
|
/* Filter management functions */
|
|
static WinWdmFilter *Create( const RString &sFilterName, const RString &sFriendlyName, RString &sError );
|
|
|
|
WinWdmFilter()
|
|
{
|
|
m_hHandle = nullptr;
|
|
m_iUsageCount = 0;
|
|
}
|
|
|
|
~WinWdmFilter()
|
|
{
|
|
m_apPins.clear();
|
|
if( m_hHandle )
|
|
CloseHandle( m_hHandle );
|
|
}
|
|
|
|
std::shared_ptr<WinWdmPin> CreatePin( unsigned long iPinId, RString &sError );
|
|
WinWdmPin *InstantiateRenderPin(
|
|
DeviceSampleFormat &PreferredOutputSampleFormat,
|
|
int &iPreferredOutputChannels,
|
|
int &iPreferredSampleRate,
|
|
RString &sError );
|
|
WinWdmPin *InstantiateRenderPin( const WAVEFORMATEX *wfex, RString &sError );
|
|
bool Use( RString &sError );
|
|
void Release();
|
|
|
|
HANDLE m_hHandle;
|
|
std::vector<std::shared_ptr<WinWdmPin>> m_apPins;
|
|
RString m_sFilterName;
|
|
RString m_sFriendlyName;
|
|
int m_iUsageCount;
|
|
};
|
|
|
|
static RString GUIDToString( const GUID *pGuid )
|
|
{
|
|
return ssprintf("%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
|
|
pGuid->Data1, pGuid->Data2, pGuid->Data3,
|
|
pGuid->Data4[0], pGuid->Data4[1], pGuid->Data4[2], pGuid->Data4[3],
|
|
pGuid->Data4[4], pGuid->Data4[5], pGuid->Data4[6], pGuid->Data4[7] );
|
|
}
|
|
|
|
static HMODULE DllKsUser = nullptr;
|
|
static KSCREATEPIN *FunctionKsCreatePin = nullptr;
|
|
|
|
/* Low level pin/filter access functions */
|
|
static bool WdmSyncIoctl(
|
|
HANDLE hHandle, unsigned long ioctlNumber, void *pIn, unsigned long iInSize,
|
|
void *pOut, unsigned long iOutSize, unsigned long *pBytesReturned, RString &sError )
|
|
{
|
|
unsigned long iDummyBytesReturned;
|
|
if( pBytesReturned == nullptr )
|
|
pBytesReturned = &iDummyBytesReturned;
|
|
|
|
OVERLAPPED overlapped;
|
|
memset( &overlapped, 0, sizeof(overlapped) );
|
|
overlapped.hEvent = CreateEvent( nullptr, FALSE, FALSE, nullptr );
|
|
if( !overlapped.hEvent )
|
|
{
|
|
sError = werr_ssprintf( GetLastError(), "CreateEvent" );
|
|
return false;
|
|
}
|
|
overlapped.hEvent = (HANDLE)((DWORD_PTR)overlapped.hEvent | 0x1);
|
|
|
|
int boolResult = DeviceIoControl( hHandle, ioctlNumber, pIn, iInSize, pOut, iOutSize, pBytesReturned, &overlapped );
|
|
if( !boolResult )
|
|
{
|
|
unsigned long iError = GetLastError();
|
|
if( iError == ERROR_IO_PENDING )
|
|
{
|
|
iError = WaitForSingleObject( overlapped.hEvent, INFINITE );
|
|
if( iError != WAIT_OBJECT_0 )
|
|
{
|
|
ASSERT( iError == WAIT_FAILED );
|
|
sError = werr_ssprintf( GetLastError(), "WaitForSingleObject" );
|
|
CloseHandle( overlapped.hEvent );
|
|
return false;
|
|
}
|
|
}
|
|
else if(( iError == ERROR_INSUFFICIENT_BUFFER || iError == ERROR_MORE_DATA ) &&
|
|
ioctlNumber == IOCTL_KS_PROPERTY && iOutSize == 0 )
|
|
{
|
|
boolResult = TRUE;
|
|
}
|
|
else
|
|
{
|
|
sError = werr_ssprintf( iError, "DeviceIoControl" );
|
|
CloseHandle( overlapped.hEvent );
|
|
return false;
|
|
}
|
|
}
|
|
if( !boolResult )
|
|
*pBytesReturned = 0;
|
|
|
|
CloseHandle( overlapped.hEvent );
|
|
return true;
|
|
}
|
|
|
|
static bool WdmGetPropertySimple( HANDLE hHandle, const GUID *pGuidPropertySet, unsigned long iProperty,
|
|
void *pValue, unsigned long iValueSize, void *pInstance, unsigned long iInstanceSize, RString &sError )
|
|
{
|
|
unsigned long iPropertySize = sizeof(KSPROPERTY) + iInstanceSize;
|
|
std::vector<char> buf;
|
|
buf.resize( iPropertySize );
|
|
KSPROPERTY *ksProperty = (KSPROPERTY*) &buf[0];
|
|
|
|
memset( ksProperty, 0, sizeof(*ksProperty) );
|
|
ksProperty->Set = *pGuidPropertySet;
|
|
ksProperty->Id = iProperty;
|
|
ksProperty->Flags = KSPROPERTY_TYPE_GET;
|
|
|
|
if( pInstance )
|
|
memcpy( &ksProperty[1], pInstance, iInstanceSize );
|
|
|
|
return WdmSyncIoctl( hHandle, IOCTL_KS_PROPERTY, ksProperty, iPropertySize, pValue, iValueSize, nullptr, sError );
|
|
}
|
|
|
|
static bool WdmSetPropertySimple(
|
|
HANDLE hHandle, const GUID *pGuidPropertySet, unsigned long iProperty,
|
|
void *pValue, unsigned long iValueSize,
|
|
void *instance, unsigned long iInstanceSize, RString &sError )
|
|
{
|
|
std::vector<char> buf;
|
|
unsigned long iPropertySize = sizeof(KSPROPERTY) + iInstanceSize;
|
|
buf.resize( iPropertySize );
|
|
KSPROPERTY *ksProperty = (KSPROPERTY *) &buf[0];
|
|
|
|
memset( ksProperty, 0, sizeof(*ksProperty) );
|
|
ksProperty->Set = *pGuidPropertySet;
|
|
ksProperty->Id = iProperty;
|
|
ksProperty->Flags = KSPROPERTY_TYPE_SET;
|
|
|
|
if( instance )
|
|
memcpy( ((char*)ksProperty + sizeof(KSPROPERTY)), instance, iInstanceSize );
|
|
|
|
return WdmSyncIoctl( hHandle, IOCTL_KS_PROPERTY, ksProperty, iPropertySize, pValue, iValueSize, nullptr, sError );
|
|
}
|
|
|
|
static bool WdmGetPinPropertySimple( HANDLE hHandle, unsigned long iPinId, const GUID *pGuidPropertySet, unsigned long iProperty,
|
|
void *pValue, unsigned long iInstanceSize, RString &sError )
|
|
{
|
|
KSP_PIN ksPProp;
|
|
ksPProp.Property.Set = *pGuidPropertySet;
|
|
ksPProp.Property.Id = iProperty;
|
|
ksPProp.Property.Flags = KSPROPERTY_TYPE_GET;
|
|
ksPProp.PinId = iPinId;
|
|
ksPProp.Reserved = 0;
|
|
|
|
return WdmSyncIoctl( hHandle, IOCTL_KS_PROPERTY, &ksPProp, sizeof(KSP_PIN), pValue, iInstanceSize, nullptr, sError );
|
|
}
|
|
|
|
static bool WdmGetPinPropertyMulti(
|
|
HANDLE hHandle,
|
|
unsigned long iPinId,
|
|
const GUID *pGuidPropertySet,
|
|
unsigned long iProperty,
|
|
KSMULTIPLE_ITEM **ksMultipleItem,
|
|
RString &sError )
|
|
{
|
|
KSP_PIN ksPProp;
|
|
|
|
ksPProp.Property.Set = *pGuidPropertySet;
|
|
ksPProp.Property.Id = iProperty;
|
|
ksPProp.Property.Flags = KSPROPERTY_TYPE_GET;
|
|
ksPProp.PinId = iPinId;
|
|
ksPProp.Reserved = 0;
|
|
|
|
unsigned long multipleItemSize = 0;
|
|
if( !WdmSyncIoctl(hHandle, IOCTL_KS_PROPERTY, &ksPProp.Property, sizeof(KSP_PIN), nullptr, 0, &multipleItemSize, sError) )
|
|
return false;
|
|
|
|
*ksMultipleItem = (KSMULTIPLE_ITEM*) malloc( multipleItemSize );
|
|
ASSERT( *ksMultipleItem != nullptr );
|
|
|
|
if( !WdmSyncIoctl( hHandle, IOCTL_KS_PROPERTY, &ksPProp, sizeof(KSP_PIN), (void*)*ksMultipleItem, multipleItemSize, nullptr, sError) )
|
|
{
|
|
free( ksMultipleItem );
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* 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
|
|
*/
|
|
std::shared_ptr<WinWdmPin> WinWdmFilter::CreatePin( unsigned long iPinId, RString &sError )
|
|
{
|
|
{
|
|
/* Get the COMMUNICATION property */
|
|
KSPIN_COMMUNICATION communication;
|
|
if( !WdmGetPinPropertySimple(m_hHandle, iPinId, &KSPROPSETID_Pin, KSPROPERTY_PIN_COMMUNICATION,
|
|
&communication, sizeof(KSPIN_COMMUNICATION), sError) )
|
|
{
|
|
sError = "KSPROPERTY_PIN_COMMUNICATION: " + sError;
|
|
return nullptr;
|
|
}
|
|
|
|
if( communication != KSPIN_COMMUNICATION_SINK && communication != KSPIN_COMMUNICATION_BOTH )
|
|
{
|
|
sError = "Not an audio output device";
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
/* Get dataflow information */
|
|
{
|
|
KSPIN_DATAFLOW dataFlow;
|
|
if( !WdmGetPinPropertySimple(m_hHandle, iPinId, &KSPROPSETID_Pin, KSPROPERTY_PIN_DATAFLOW,
|
|
&dataFlow, sizeof(KSPIN_DATAFLOW), sError) )
|
|
{
|
|
sError = "KSPROPERTY_PIN_DATAFLOW: " + sError;
|
|
return nullptr;
|
|
}
|
|
|
|
if( dataFlow != KSPIN_DATAFLOW_IN )
|
|
{
|
|
sError = "Not KSPIN_DATAFLOW_IN";
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
/* Get the INTERFACE property list */
|
|
{
|
|
KSMULTIPLE_ITEM *pItem = nullptr;
|
|
if( !WdmGetPinPropertyMulti(m_hHandle, iPinId, &KSPROPSETID_Pin, KSPROPERTY_PIN_INTERFACES, &pItem, sError) )
|
|
{
|
|
sError = "KSPROPERTY_PIN_INTERFACES: " + sError;
|
|
return nullptr;
|
|
}
|
|
|
|
KSIDENTIFIER *identifier = (KSIDENTIFIER *) &pItem[1];
|
|
|
|
/* Check that at least one interface is STANDARD_STREAMING */
|
|
sError = "No standard streaming";
|
|
for( unsigned i = 0; i < pItem->Count; i++ )
|
|
{
|
|
if( !memcmp( &identifier[i].Set, &KSINTERFACESETID_Standard, sizeof(GUID) ) &&
|
|
identifier[i].Id == KSINTERFACE_STANDARD_STREAMING )
|
|
{
|
|
sError = "";
|
|
break;
|
|
}
|
|
}
|
|
|
|
free( pItem );
|
|
|
|
if( sError != "" )
|
|
return nullptr;
|
|
}
|
|
|
|
/* Get the MEDIUM properties list */
|
|
{
|
|
KSMULTIPLE_ITEM *pItem = nullptr;
|
|
if( !WdmGetPinPropertyMulti( m_hHandle, iPinId, &KSPROPSETID_Pin, KSPROPERTY_PIN_MEDIUMS, &pItem, sError) )
|
|
{
|
|
sError = "KSPROPERTY_PIN_MEDIUMS: " + sError;
|
|
return nullptr;
|
|
}
|
|
|
|
const KSIDENTIFIER *identifier = (KSIDENTIFIER *) &pItem[1];
|
|
|
|
/* Check that at least one medium is STANDARD_DEVIO */
|
|
sError = "No STANDARD_DEVIO";
|
|
for( unsigned i = 0; i < pItem->Count; i++ )
|
|
{
|
|
if( !memcmp( &identifier[i].Set, &KSMEDIUMSETID_Standard, sizeof(GUID) ) &&
|
|
identifier[i].Id == KSMEDIUM_STANDARD_DEVIO )
|
|
{
|
|
sError = "";
|
|
break;
|
|
}
|
|
}
|
|
|
|
free( pItem );
|
|
|
|
if( sError != "" )
|
|
return nullptr;
|
|
}
|
|
|
|
/* Allocate the new PIN object */
|
|
auto pPin = std::make_shared<WinWdmPin>( this, iPinId );
|
|
|
|
/* Get DATARANGEs */
|
|
KSMULTIPLE_ITEM *pDataRangesItem;
|
|
if( !WdmGetPinPropertyMulti(m_hHandle, iPinId, &KSPROPSETID_Pin, KSPROPERTY_PIN_DATARANGES, &pDataRangesItem, sError) )
|
|
{
|
|
sError = "KSPROPERTY_PIN_DATARANGES: " + sError;
|
|
return nullptr;
|
|
}
|
|
|
|
KSDATARANGE* pDataRanges = (KSDATARANGE*) (pDataRangesItem + 1);
|
|
|
|
/* Find audio DATARANGEs */
|
|
{
|
|
KSDATARANGE *pDataRange = pDataRanges;
|
|
for( unsigned i = 0; i < pDataRangesItem->Count; i++, pDataRange = (KSDATARANGE*)( ((char*)pDataRange) + pDataRange->FormatSize) )
|
|
{
|
|
if( memcmp(&pDataRange->MajorFormat, &KSDATAFORMAT_TYPE_AUDIO, sizeof(GUID)) &&
|
|
memcmp(&pDataRange->MajorFormat, &KSDATAFORMAT_TYPE_WILDCARD, sizeof(GUID)) )
|
|
continue;
|
|
|
|
if( memcmp(&pDataRange->SubFormat, &KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID) ) &&
|
|
memcmp(&pDataRange->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(GUID) ) &&
|
|
memcmp(&pDataRange->SubFormat, &KSDATAFORMAT_SUBTYPE_WILDCARD, sizeof(GUID) ) )
|
|
continue;
|
|
|
|
if( memcmp(&pDataRange->Specifier, &KSDATAFORMAT_SPECIFIER_WILDCARD, sizeof(GUID)) &&
|
|
memcmp(&pDataRange->Specifier, &KSDATAFORMAT_SPECIFIER_WAVEFORMATEX, sizeof(GUID) ))
|
|
continue;
|
|
|
|
const KSDATARANGE_AUDIO *pDataRangeAudio = (KSDATARANGE_AUDIO *) pDataRange;
|
|
pPin->m_dataRangesItem.push_back( *pDataRangeAudio );
|
|
}
|
|
}
|
|
free( pDataRangesItem );
|
|
pDataRangesItem = nullptr;
|
|
|
|
if( pPin->m_dataRangesItem.size() == 0 )
|
|
{
|
|
sError = "Pin has no supported audio data ranges";
|
|
return nullptr;
|
|
}
|
|
|
|
/* Success */
|
|
sError = "";
|
|
CHECKPOINT_M( "Pin created successfully" );
|
|
return pPin;
|
|
}
|
|
|
|
/* If the pin handle is open, close it */
|
|
void WinWdmPin::Close()
|
|
{
|
|
if( m_hHandle == nullptr )
|
|
return;
|
|
RString sError;
|
|
SetState( KSSTATE_PAUSE, sError );
|
|
SetState( KSSTATE_STOP, sError );
|
|
CloseHandle( m_hHandle );
|
|
m_hHandle = nullptr;
|
|
m_pParentFilter->Release();
|
|
}
|
|
|
|
/* Set the state of this (instantiated) pin */
|
|
bool WinWdmPin::SetState( KSSTATE state, RString &sError )
|
|
{
|
|
ASSERT( m_hHandle != nullptr );
|
|
return WdmSetPropertySimple( m_hHandle, &KSPROPSETID_Connection, KSPROPERTY_CONNECTION_STATE,
|
|
&state, sizeof(state), nullptr, 0, sError );
|
|
}
|
|
|
|
bool WinWdmPin::Instantiate( const WAVEFORMATEX *pFormat, RString &sError )
|
|
{
|
|
if( !IsFormatSupported(pFormat) )
|
|
{
|
|
sError = "format not supported";
|
|
return false;
|
|
}
|
|
|
|
|
|
if( !m_pParentFilter->Use(sError) )
|
|
return false;
|
|
|
|
KSPIN_CONNECT *pPinConnect = MakeFormat( pFormat );
|
|
DWORD iRet = FunctionKsCreatePin( m_pParentFilter->m_hHandle, pPinConnect, GENERIC_WRITE | GENERIC_READ, &m_hHandle );
|
|
free( pPinConnect );
|
|
if( iRet == ERROR_SUCCESS )
|
|
return true;
|
|
|
|
sError = werr_ssprintf( iRet, "FunctionKsCreatePin" );
|
|
m_pParentFilter->Release();
|
|
m_hHandle = nullptr;
|
|
return false;
|
|
}
|
|
|
|
KSPIN_CONNECT *WinWdmPin::MakeFormat( const WAVEFORMATEX *pFormat ) const
|
|
{
|
|
ASSERT( pFormat != nullptr );
|
|
|
|
unsigned long iWfexSize = sizeof(WAVEFORMATEX) + pFormat->cbSize;
|
|
unsigned long iDataFormatSize = sizeof(KSDATAFORMAT) + iWfexSize;
|
|
unsigned long iSize = sizeof(KSPIN_CONNECT) + iDataFormatSize;
|
|
|
|
KSPIN_CONNECT *pPinConnect = (KSPIN_CONNECT *) malloc( iSize );
|
|
ASSERT( pPinConnect != nullptr );
|
|
|
|
memset( pPinConnect, 0, iSize );
|
|
pPinConnect->PinId = m_iPinId;
|
|
pPinConnect->Interface.Set = KSINTERFACESETID_Standard;
|
|
pPinConnect->Interface.Id = KSINTERFACE_STANDARD_STREAMING;
|
|
pPinConnect->Interface.Flags = 0;
|
|
pPinConnect->Medium.Set = KSMEDIUMSETID_Standard;
|
|
pPinConnect->Medium.Id = KSMEDIUM_TYPE_ANYINSTANCE;
|
|
pPinConnect->Medium.Flags = 0;
|
|
pPinConnect->PinToHandle = nullptr;
|
|
pPinConnect->Priority.PriorityClass = KSPRIORITY_NORMAL;
|
|
pPinConnect->Priority.PrioritySubClass = 1;
|
|
|
|
KSDATAFORMAT_WAVEFORMATEX *ksDataFormatWfx = (KSDATAFORMAT_WAVEFORMATEX *)(pPinConnect + 1);
|
|
ksDataFormatWfx->DataFormat.Flags = 0;
|
|
ksDataFormatWfx->DataFormat.Reserved = 0;
|
|
ksDataFormatWfx->DataFormat.MajorFormat = KSDATAFORMAT_TYPE_AUDIO;
|
|
ksDataFormatWfx->DataFormat.SubFormat = KSDATAFORMAT_SUBTYPE_PCM;
|
|
ksDataFormatWfx->DataFormat.Specifier = KSDATAFORMAT_SPECIFIER_WAVEFORMATEX;
|
|
ksDataFormatWfx->DataFormat.FormatSize = iDataFormatSize;
|
|
|
|
memcpy( &ksDataFormatWfx->WaveFormatEx, pFormat, iWfexSize );
|
|
ksDataFormatWfx->DataFormat.SampleSize = (unsigned short)(pFormat->nChannels * (pFormat->wBitsPerSample / 8));
|
|
return pPinConnect;
|
|
}
|
|
|
|
bool WinWdmPin::IsFormatSupported( const WAVEFORMATEX *pFormat ) const
|
|
{
|
|
GUID guid = { DEFINE_WAVEFORMATEX_GUID(pFormat->wFormatTag) };
|
|
|
|
if( pFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE )
|
|
guid = ((WAVEFORMATEXTENSIBLE*) pFormat)->SubFormat;
|
|
|
|
for( size_t i = 0; i < m_dataRangesItem.size(); i++ )
|
|
{
|
|
const KSDATARANGE_AUDIO *pDataRangeAudio = &m_dataRangesItem[i];
|
|
/* This is an audio or wildcard datarange... */
|
|
if( memcmp(&pDataRangeAudio->DataRange.SubFormat, &KSDATAFORMAT_SUBTYPE_WILDCARD, sizeof(GUID)) &&
|
|
memcmp(&pDataRangeAudio->DataRange.SubFormat, &guid, sizeof(GUID)) )
|
|
continue;
|
|
|
|
if( pDataRangeAudio->MaximumChannels != (ULONG) -1 && pDataRangeAudio->MaximumChannels < pFormat->nChannels )
|
|
continue;
|
|
if( pFormat->wBitsPerSample < pDataRangeAudio->MinimumBitsPerSample || pFormat->wBitsPerSample > pDataRangeAudio->MaximumBitsPerSample )
|
|
continue;
|
|
if( pFormat->nSamplesPerSec < pDataRangeAudio->MinimumSampleFrequency || pFormat->nSamplesPerSec > pDataRangeAudio->MaximumSampleFrequency )
|
|
continue;
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Create a new filter object. */
|
|
WinWdmFilter *WinWdmFilter::Create( const RString &sFilterName, const RString &sFriendlyName, RString &sError )
|
|
{
|
|
/* Allocate the new filter object */
|
|
WinWdmFilter *pFilter = new WinWdmFilter;
|
|
|
|
pFilter->m_sFilterName = sFilterName;
|
|
pFilter->m_sFriendlyName = sFriendlyName;
|
|
|
|
/* Open the filter handle */
|
|
if( !pFilter->Use(sError) )
|
|
goto error;
|
|
|
|
/* Get pin count */
|
|
int iNumPins;
|
|
if( !WdmGetPinPropertySimple(pFilter->m_hHandle, 0, &KSPROPSETID_Pin, KSPROPERTY_PIN_CTYPES, &iNumPins, sizeof(iNumPins), sError) )
|
|
goto error;
|
|
|
|
/* Create all the pins we can */
|
|
for( int iPinId = 0; iPinId < iNumPins; iPinId++ )
|
|
{
|
|
/* Create the pin with this Id */
|
|
auto pNewPin = pFilter->CreatePin( iPinId, sError );
|
|
if( pNewPin )
|
|
pFilter->m_apPins.push_back( pNewPin );
|
|
}
|
|
|
|
if( pFilter->m_apPins.empty() )
|
|
{
|
|
/* No valid pin was found on this filter so we destroy it */
|
|
sError = "filter has no supported audio pins";
|
|
goto error;
|
|
}
|
|
|
|
pFilter->Release();
|
|
|
|
sError = "";
|
|
return pFilter;
|
|
|
|
error:
|
|
/* Error cleanup */
|
|
delete pFilter;
|
|
return nullptr;
|
|
}
|
|
|
|
/*
|
|
* Reopen the filter handle if necessary so it can be used
|
|
*/
|
|
bool WinWdmFilter::Use( RString &sError )
|
|
{
|
|
if( m_hHandle == nullptr )
|
|
{
|
|
/* Open the filter */
|
|
m_hHandle = CreateFile( m_sFilterName, GENERIC_READ | GENERIC_WRITE, 0,
|
|
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, nullptr );
|
|
|
|
if( m_hHandle == nullptr )
|
|
{
|
|
sError = werr_ssprintf( GetLastError(), "CreateFile(%s)", m_sFilterName.c_str() );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
++m_iUsageCount;
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Release the filter handle if nobody is using it
|
|
*/
|
|
void WinWdmFilter::Release()
|
|
{
|
|
ASSERT( m_iUsageCount > 0 );
|
|
|
|
--m_iUsageCount;
|
|
if( m_iUsageCount == 0 )
|
|
{
|
|
if( m_hHandle != nullptr )
|
|
{
|
|
CloseHandle( m_hHandle );
|
|
m_hHandle = nullptr;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Create a render (playback) Pin using the supplied format
|
|
*/
|
|
WinWdmPin *WinWdmFilter::InstantiateRenderPin( const WAVEFORMATEX *wfex, RString &sError )
|
|
{
|
|
for( size_t i = 0; i < m_apPins.size(); ++i )
|
|
{
|
|
auto pPin = m_apPins[i];
|
|
if( pPin->Instantiate(wfex, sError) )
|
|
{
|
|
sError = "";
|
|
return pPin.get();
|
|
}
|
|
}
|
|
|
|
sError = "No pin supports format";
|
|
return nullptr;
|
|
}
|
|
|
|
template<typename T, typename U>
|
|
void MoveToBeginning( std::vector<T> &v, const U &item )
|
|
{
|
|
auto it = find( v.begin(), v.end(), item );
|
|
if( it == v.end() )
|
|
return;
|
|
auto next = it;
|
|
++next;
|
|
copy_backward( v.begin(), it, next );
|
|
*v.begin() = item;
|
|
}
|
|
|
|
static void FillWFEXT( WAVEFORMATEXTENSIBLE* pwfext, DeviceSampleFormat sampleFormat, int sampleRate, int channelCount)
|
|
{
|
|
pwfext->Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
|
|
pwfext->Format.nChannels = channelCount;
|
|
pwfext->Format.nSamplesPerSec = sampleRate;
|
|
switch( channelCount )
|
|
{
|
|
case 1: pwfext->dwChannelMask = KSAUDIO_SPEAKER_MONO; break;
|
|
case 2: pwfext->dwChannelMask = KSAUDIO_SPEAKER_STEREO; break;
|
|
case 4: pwfext->dwChannelMask = KSAUDIO_SPEAKER_QUAD; break;
|
|
case 6: pwfext->dwChannelMask = KSAUDIO_SPEAKER_5POINT1; break; // or KSAUDIO_SPEAKER_5POINT1_SURROUND
|
|
case 8: pwfext->dwChannelMask = KSAUDIO_SPEAKER_7POINT1_SURROUND; break; // or KSAUDIO_SPEAKER_7POINT1
|
|
}
|
|
|
|
switch(sampleFormat)
|
|
{
|
|
case DeviceSampleFormat_Float32: pwfext->SubFormat = KSDATAFORMAT_SUBTYPE_IEEE_FLOAT; break;
|
|
case DeviceSampleFormat_Int32: pwfext->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; break;
|
|
case DeviceSampleFormat_Int24: pwfext->SubFormat = KSDATAFORMAT_SUBTYPE_PCM; break;
|
|
case DeviceSampleFormat_Int16: pwfext->SubFormat = KSDATAFORMAT_SUBTYPE_PCM;break;
|
|
}
|
|
|
|
pwfext->Format.nBlockAlign = GetBytesPerSample( sampleFormat );
|
|
pwfext->Format.wBitsPerSample = pwfext->Format.nBlockAlign * 8;
|
|
pwfext->Format.nBlockAlign *= channelCount;
|
|
pwfext->Samples.wValidBitsPerSample = pwfext->Format.wBitsPerSample;
|
|
pwfext->Format.cbSize = sizeof(WAVEFORMATEXTENSIBLE)-sizeof(WAVEFORMATEX);
|
|
pwfext->Format.nAvgBytesPerSec = pwfext->Format.nSamplesPerSec * pwfext->Format.nBlockAlign;
|
|
}
|
|
|
|
WinWdmPin *WinWdmFilter::InstantiateRenderPin(
|
|
DeviceSampleFormat &PreferredOutputSampleFormat,
|
|
int &iPreferredOutputChannels,
|
|
int &iPreferredSampleRate,
|
|
RString &sError )
|
|
{
|
|
/*
|
|
* All Preferred settings are hints, and can be ignored if needed.
|
|
*
|
|
* Drivers can advertise DataRanges, and reject formats that fit it. This is documented
|
|
* for devices that don't support mono sound; it may happen for other cases.
|
|
*
|
|
* Supported channel configurations are 8 (7.1), 6 (5.1), 4, 2 (stereo) and 1 (mono). Prefer
|
|
* more channels, since some drivers won't send audio to rear speakers in stereo modes. Sort
|
|
* the preferred channel count first.
|
|
*/
|
|
std::vector<int> aChannels;
|
|
aChannels.push_back( 8 );
|
|
aChannels.push_back( 6 );
|
|
aChannels.push_back( 4 );
|
|
aChannels.push_back( 2 );
|
|
aChannels.push_back( 1 );
|
|
MoveToBeginning( aChannels, iPreferredOutputChannels );
|
|
|
|
/* Try all sample formats. Try PreferredOutputSampleFormat first. */
|
|
std::vector<DeviceSampleFormat> SampleFormats;
|
|
SampleFormats.push_back( DeviceSampleFormat_Int16 );
|
|
SampleFormats.push_back( DeviceSampleFormat_Int24 );
|
|
SampleFormats.push_back( DeviceSampleFormat_Int32 );
|
|
SampleFormats.push_back( DeviceSampleFormat_Float32 );
|
|
MoveToBeginning( SampleFormats, PreferredOutputSampleFormat );
|
|
|
|
/*
|
|
* Some hardware may advertise support for 44.1khz, but actually use poor resampling.
|
|
* Try to use 48khz before 44.1khz, unless iPreferredSampleRate specifically asks
|
|
* for 44.1khz.
|
|
*
|
|
* Try all samplerates listed in the device's DATARANGES. Sort iSampleRate first,
|
|
* then 48k, then 44.1k, then higher sample rates first.
|
|
*/
|
|
std::vector<int> aSampleRates;
|
|
{
|
|
for (auto pPin : m_apPins)
|
|
{
|
|
for (KSDATARANGE_AUDIO const &range : pPin->m_dataRangesItem)
|
|
{
|
|
aSampleRates.push_back( range.MinimumSampleFrequency );
|
|
aSampleRates.push_back( range.MaximumSampleFrequency );
|
|
}
|
|
}
|
|
|
|
if( iPreferredSampleRate != 0 )
|
|
aSampleRates.push_back( iPreferredSampleRate );
|
|
aSampleRates.push_back( 48000 );
|
|
aSampleRates.push_back( 44100 );
|
|
|
|
sort( aSampleRates.begin(), aSampleRates.end() );
|
|
aSampleRates.erase( unique(aSampleRates.begin(), aSampleRates.end()), aSampleRates.end() );
|
|
reverse( aSampleRates.begin(), aSampleRates.end() );
|
|
|
|
MoveToBeginning( aSampleRates, 44100 );
|
|
MoveToBeginning( aSampleRates, 48000 );
|
|
if( iPreferredSampleRate != 0 )
|
|
MoveToBeginning( aSampleRates, iPreferredSampleRate );
|
|
}
|
|
|
|
/* Try WAVE_FORMAT_EXTENSIBLE, then WAVE_FORMAT_PCM. */
|
|
std::vector<bool> aTryPCM;
|
|
aTryPCM.push_back( false );
|
|
aTryPCM.push_back( true );
|
|
|
|
for (auto bTryPCM = aTryPCM.begin(); bTryPCM != aTryPCM.end(); ++bTryPCM)
|
|
{
|
|
for (int const &iSampleRate : aSampleRates)
|
|
{
|
|
for (int const &iChannels : aChannels)
|
|
{
|
|
for (DeviceSampleFormat &fmt : SampleFormats)
|
|
{
|
|
PreferredOutputSampleFormat = fmt;
|
|
iPreferredOutputChannels = iChannels;
|
|
iPreferredSampleRate = iSampleRate;
|
|
|
|
WAVEFORMATEXTENSIBLE wfx;
|
|
FillWFEXT( &wfx, PreferredOutputSampleFormat, iPreferredSampleRate, iPreferredOutputChannels );
|
|
if( *bTryPCM )
|
|
{
|
|
/* Try WAVE_FORMAT_PCM instead of WAVE_FORMAT_EXTENSIBLE. */
|
|
wfx.Format.wFormatTag = WAVE_FORMAT_PCM;
|
|
wfx.Format.cbSize = 0;
|
|
wfx.Samples.wValidBitsPerSample = 0;
|
|
wfx.dwChannelMask = 0;
|
|
wfx.SubFormat = GUID_NULL;
|
|
}
|
|
|
|
LOG->Trace( "KS: trying format: %i channels: %i samplerate: %i format: %04x",
|
|
PreferredOutputSampleFormat, iPreferredOutputChannels,
|
|
iPreferredSampleRate, wfx.Format.wFormatTag );
|
|
WinWdmPin *pPlaybackPin = InstantiateRenderPin( (WAVEFORMATEX *) &wfx, sError );
|
|
|
|
if( pPlaybackPin != nullptr )
|
|
{
|
|
LOG->Trace( "KS: success" );
|
|
return pPlaybackPin;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sError = "No compatible format found";
|
|
return nullptr;
|
|
}
|
|
|
|
static bool GetDevicePath( HANDLE hHandle, SP_DEVICE_INTERFACE_DATA *pInterfaceData, RString &sPath )
|
|
{
|
|
unsigned char interfaceDetailsArray[sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA) + (MAX_PATH * sizeof(WCHAR))];
|
|
const int sizeInterface = sizeof(interfaceDetailsArray);
|
|
SP_DEVICE_INTERFACE_DETAIL_DATA *devInterfaceDetails = (SP_DEVICE_INTERFACE_DETAIL_DATA*) interfaceDetailsArray;
|
|
devInterfaceDetails->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
|
|
|
|
SP_DEVINFO_DATA devInfoData;
|
|
devInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
|
|
devInfoData.Reserved = 0;
|
|
|
|
if( !SetupDiGetDeviceInterfaceDetail(hHandle, pInterfaceData, devInterfaceDetails, sizeInterface, nullptr, &devInfoData) )
|
|
return false;
|
|
sPath = devInterfaceDetails->DevicePath;
|
|
return true;
|
|
}
|
|
|
|
/* Build a list of available filters. */
|
|
static bool BuildFilterList( std::vector<WinWdmFilter*> &aFilters, RString &sError )
|
|
{
|
|
const GUID *pCategoryGuid = (GUID*) &KSCATEGORY_RENDER;
|
|
|
|
/* Open a handle to search for devices (filters) */
|
|
HDEVINFO hHandle = SetupDiGetClassDevs( pCategoryGuid, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE );
|
|
if( hHandle == INVALID_HANDLE_VALUE )
|
|
{
|
|
sError = werr_ssprintf( GetLastError(), "SetupDiGetClassDevs" );
|
|
return false;
|
|
}
|
|
|
|
CHECKPOINT_M( "Setup called" );
|
|
|
|
/* Create filter objects for each interface */
|
|
for( int device = 0;;device++ )
|
|
{
|
|
SP_DEVICE_INTERFACE_DATA interfaceData;
|
|
interfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
|
|
interfaceData.Reserved = 0;
|
|
if( !SetupDiEnumDeviceInterfaces(hHandle, nullptr, pCategoryGuid, device, &interfaceData) )
|
|
break; /* No more devices */
|
|
if( !interfaceData.Flags || (interfaceData.Flags & SPINT_REMOVED) )
|
|
continue;
|
|
|
|
RString sDevicePath;
|
|
if( !GetDevicePath(hHandle, &interfaceData, sDevicePath) )
|
|
continue;
|
|
|
|
/* Try to get the friendly name for this interface */
|
|
char szFriendlyName[MAX_PATH] = "(error)";
|
|
DWORD sizeFriendlyName = sizeof(szFriendlyName);
|
|
HKEY hKey = SetupDiOpenDeviceInterfaceRegKey( hHandle, &interfaceData, 0, KEY_QUERY_VALUE );
|
|
if( hKey != INVALID_HANDLE_VALUE )
|
|
{
|
|
DWORD type;
|
|
if( RegQueryValueEx(hKey, "FriendlyName", 0, &type, (BYTE*) szFriendlyName, &sizeFriendlyName) != ERROR_SUCCESS )
|
|
strcpy( szFriendlyName, "(error)" );
|
|
RegCloseKey( hKey );
|
|
}
|
|
|
|
WinWdmFilter *pNewFilter = WinWdmFilter::Create( sDevicePath, szFriendlyName, sError );
|
|
if( pNewFilter == nullptr )
|
|
{
|
|
LOG->Trace( "Filter \"%s\" not created: %s", szFriendlyName, sError.c_str() );
|
|
continue;
|
|
}
|
|
|
|
aFilters.push_back( pNewFilter );
|
|
}
|
|
|
|
if( hHandle != nullptr )
|
|
SetupDiDestroyDeviceInfoList( hHandle );
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool PaWinWdm_Initialize( RString &sError )
|
|
{
|
|
if( DllKsUser == nullptr )
|
|
{
|
|
DllKsUser = LoadLibrary( "ksuser.dll" );
|
|
if( DllKsUser == nullptr )
|
|
{
|
|
sError = werr_ssprintf( GetLastError(), "LoadLibrary(ksuser.dll)" );
|
|
return false;
|
|
}
|
|
}
|
|
|
|
FunctionKsCreatePin = (KSCREATEPIN*) GetProcAddress( DllKsUser, "KsCreatePin" );
|
|
if( FunctionKsCreatePin == nullptr )
|
|
{
|
|
sError = "no KsCreatePin in ksuser.dll";
|
|
FreeLibrary( DllKsUser );
|
|
DllKsUser = nullptr;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#define MAX_CHUNKS 4
|
|
struct WinWdmStream
|
|
{
|
|
WinWdmStream()
|
|
{
|
|
memset( this, 0, sizeof(*this) );
|
|
for( int i = 0; i < MAX_CHUNKS; ++i )
|
|
m_Signal[i].hEvent = CreateEvent( nullptr, FALSE, FALSE, nullptr );
|
|
m_pPlaybackPin = nullptr;
|
|
}
|
|
|
|
~WinWdmStream()
|
|
{
|
|
CloseHandle( m_Signal[0].hEvent );
|
|
CloseHandle( m_Signal[1].hEvent );
|
|
Close();
|
|
}
|
|
|
|
bool Open( WinWdmFilter *pFilter,
|
|
int iWriteAheadFrames,
|
|
DeviceSampleFormat PreferredOutputSampleFormat,
|
|
int iPreferredOutputChannels,
|
|
int iSampleRate,
|
|
RString &sError );
|
|
void Close()
|
|
{
|
|
if( m_pPlaybackPin )
|
|
m_pPlaybackPin->Close();
|
|
m_pPlaybackPin = nullptr;
|
|
for( int i = 0; i < 2; ++i )
|
|
{
|
|
VirtualFree( m_Packets[i].Data, 0, MEM_RELEASE );
|
|
m_Packets[i].Data = nullptr;
|
|
}
|
|
}
|
|
|
|
bool SubmitPacket( int iPacket, RString &sError );
|
|
|
|
WinWdmPin *m_pPlaybackPin;
|
|
KSSTREAM_HEADER m_Packets[MAX_CHUNKS];
|
|
OVERLAPPED m_Signal[MAX_CHUNKS];
|
|
|
|
int m_iSampleRate;
|
|
int m_iWriteAheadChunks;
|
|
int m_iFramesPerChunk;
|
|
int m_iBytesPerOutputSample;
|
|
int m_iDeviceOutputChannels;
|
|
DeviceSampleFormat m_DeviceSampleFormat;
|
|
};
|
|
|
|
bool WinWdmStream::Open( WinWdmFilter *pFilter,
|
|
int iWriteAheadFrames,
|
|
DeviceSampleFormat PreferredOutputSampleFormat,
|
|
int iPreferredOutputChannels,
|
|
int iPreferredSampleRate,
|
|
RString &sError )
|
|
{
|
|
/* Instantiate the output pin. */
|
|
m_pPlaybackPin = pFilter->InstantiateRenderPin(
|
|
PreferredOutputSampleFormat,
|
|
iPreferredOutputChannels,
|
|
iPreferredSampleRate,
|
|
sError );
|
|
|
|
if (m_pPlaybackPin == nullptr) {
|
|
Close();
|
|
return false;
|
|
}
|
|
|
|
m_DeviceSampleFormat = PreferredOutputSampleFormat;
|
|
m_iDeviceOutputChannels = iPreferredOutputChannels;
|
|
m_iSampleRate = iPreferredSampleRate;
|
|
m_iBytesPerOutputSample = GetBytesPerSample( m_DeviceSampleFormat );
|
|
|
|
int iFrameSize = 1;
|
|
{
|
|
KSALLOCATOR_FRAMING ksaf;
|
|
KSALLOCATOR_FRAMING_EX ksafex;
|
|
if( WdmGetPropertySimple(m_pPlaybackPin->m_hHandle, &KSPROPSETID_Connection, KSPROPERTY_CONNECTION_ALLOCATORFRAMING,
|
|
&ksaf, sizeof(ksaf), nullptr, 0, sError) )
|
|
{
|
|
iFrameSize = ksaf.FrameSize;
|
|
}
|
|
else if( WdmGetPropertySimple(m_pPlaybackPin->m_hHandle, &KSPROPSETID_Connection, KSPROPERTY_CONNECTION_ALLOCATORFRAMING_EX,
|
|
&ksafex, sizeof(ksafex), nullptr, 0, sError) )
|
|
{
|
|
iFrameSize = ksafex.FramingItem[0].FramingRange.Range.MinFrameSize;
|
|
}
|
|
}
|
|
|
|
iFrameSize /= m_iBytesPerOutputSample * m_iDeviceOutputChannels;
|
|
|
|
m_iWriteAheadChunks = 2;
|
|
|
|
/* If a writeahead was specified, use it. */
|
|
m_iFramesPerChunk = iWriteAheadFrames / m_iWriteAheadChunks;
|
|
if( m_iFramesPerChunk == 0 )
|
|
{
|
|
m_iFramesPerChunk = 512 / m_iWriteAheadChunks;
|
|
m_iFramesPerChunk = std::max( m_iFramesPerChunk, iFrameSize ); // iFrameSize may be 0
|
|
}
|
|
|
|
LOG->Info( "KS: chunk size: %i; allocator framing: %i (%ims)", m_iFramesPerChunk, iFrameSize, (iFrameSize * 1000) / m_iSampleRate );
|
|
LOG->Info( "KS: %i hz", m_iSampleRate );
|
|
|
|
/* Set up chunks. */
|
|
for( int i = 0; i < MAX_CHUNKS; ++i )
|
|
{
|
|
// _aligned_malloc( size, 64 )?
|
|
KSSTREAM_HEADER *p = &m_Packets[i];
|
|
|
|
/* Avoid any FileAlignment problems by using VirtualAlloc, which is always page aligned. */
|
|
p->Data = (char *) VirtualAlloc( nullptr, m_iFramesPerChunk*m_iBytesPerOutputSample*m_iDeviceOutputChannels, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE );
|
|
ASSERT( p->Data != nullptr );
|
|
p->FrameExtent = m_iFramesPerChunk*m_iBytesPerOutputSample*m_iDeviceOutputChannels;
|
|
p->DataUsed = m_iFramesPerChunk*m_iBytesPerOutputSample*m_iDeviceOutputChannels;
|
|
p->Size = sizeof(*p);
|
|
p->PresentationTime.Numerator = 1;
|
|
p->PresentationTime.Denominator = 1;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool WinWdmStream::SubmitPacket( int iPacket, RString &sError )
|
|
{
|
|
KSSTREAM_HEADER *p = &m_Packets[iPacket];
|
|
int iRet = DeviceIoControl( m_pPlaybackPin->m_hHandle, IOCTL_KS_WRITE_STREAM, nullptr, 0,
|
|
p, p->Size, nullptr, &m_Signal[iPacket] );
|
|
ASSERT_M( iRet == 0, "DeviceIoControl" );
|
|
|
|
DWORD iError = GetLastError();
|
|
if( iError == ERROR_IO_PENDING )
|
|
return true;
|
|
|
|
sError = werr_ssprintf(iError, "DeviceIoControl");
|
|
return false;
|
|
}
|
|
|
|
|
|
#include <windows.h>
|
|
namespace
|
|
{
|
|
void MapChannels( const int16_t *pIn, int16_t *pOut, int iInChannels, int iOutChannels, int iFrames, const int *pChannelMap )
|
|
{
|
|
for( int i = 0; i < iFrames; ++i )
|
|
{
|
|
for( int j = 0; j < iOutChannels; ++j )
|
|
{
|
|
if( pChannelMap[j] == -1 )
|
|
pOut[j] = 0;
|
|
else if( pChannelMap[j] == -2 )
|
|
{
|
|
int iSum = 0;
|
|
for( int k = 0; k < iInChannels; ++k )
|
|
iSum += pIn[k];
|
|
iSum /= iInChannels;
|
|
pOut[j] = iSum;
|
|
}
|
|
else
|
|
pOut[j] = pIn[ pChannelMap[j] ];
|
|
}
|
|
pOut += iOutChannels;
|
|
pIn += iInChannels;
|
|
}
|
|
}
|
|
|
|
void MapChannels( const int16_t *pIn, int16_t *pOut, int iInChannels, int iOutChannels, int iFrames )
|
|
{
|
|
static const int i1ChannelMap[] = { -2 };
|
|
static const int i4ChannelMap[] = { 0, 1, 0, 1 };
|
|
static const int i5_1ChannelMap[] = { 0, 1, -1, -2, 0, 1 };
|
|
static const int i7_1ChannelMap[] = { 0, 1, -1, -2, 0, 1, 0, 1 };
|
|
const int *pChannelMap;
|
|
switch( iOutChannels )
|
|
{
|
|
case 1: pChannelMap = i1ChannelMap; break; // KSAUDIO_SPEAKER_MONO
|
|
case 4: pChannelMap = i4ChannelMap; break; // KSAUDIO_SPEAKER_QUAD
|
|
case 6: pChannelMap = i5_1ChannelMap; break; // KSAUDIO_SPEAKER_5POINT1_SURROUND
|
|
case 8: pChannelMap = i7_1ChannelMap; break; // KSAUDIO_SPEAKER_7POINT1_SURROUND
|
|
default: FAIL_M( ssprintf("%i", iOutChannels) );
|
|
}
|
|
MapChannels( pIn, pOut, iInChannels, iOutChannels, iFrames, pChannelMap );
|
|
}
|
|
|
|
void MapSampleFormatFromInt16( const int16_t *pIn, void *pOut, int iSamples, DeviceSampleFormat FromFormat )
|
|
{
|
|
switch( FromFormat )
|
|
{
|
|
case DeviceSampleFormat_Float32: // untested
|
|
{
|
|
float *pOutBuf = (float *) pOut;
|
|
for( int i = 0; i < iSamples; ++i )
|
|
pOutBuf[i] = SCALE( pIn[i], -32768, +32767, -1.0f, +1.0f ); // [-32768, 32767] -> [-1,+1]
|
|
break;
|
|
}
|
|
case DeviceSampleFormat_Int24:
|
|
{
|
|
const unsigned char *pInBytes = (unsigned char *) pIn;
|
|
unsigned char *pOutBuf = (unsigned char *) pOut;
|
|
for( int i = 0; i < iSamples; ++i )
|
|
{
|
|
*pOutBuf++ = 0;
|
|
*pOutBuf++ = *pInBytes++;
|
|
*pOutBuf++ = *pInBytes++;
|
|
}
|
|
break;
|
|
}
|
|
case DeviceSampleFormat_Int32:
|
|
{
|
|
int16_t *pOutBuf = (int16_t *) pOut;
|
|
for( int i = 0; i < iSamples; ++i )
|
|
{
|
|
*pOutBuf++ = 0;
|
|
*pOutBuf++ = *pIn++;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void RageSoundDriver_WDMKS::Read( void *pData, int iFrames, int iLastCursorPos, int iCurrentFrame )
|
|
{
|
|
/* If we need conversion, read into a temporary buffer. Otherwise, read directly
|
|
* into the target buffer. */
|
|
int iChannels = 2;
|
|
if( m_pStream->m_iDeviceOutputChannels == iChannels &&
|
|
m_pStream->m_DeviceSampleFormat == DeviceSampleFormat_Int16 )
|
|
{
|
|
int16_t *pBuf = (int16_t *) pData;
|
|
this->Mix( pBuf, iFrames, iLastCursorPos, iCurrentFrame );
|
|
return;
|
|
}
|
|
|
|
int16_t *pBuf = (int16_t *) alloca( iFrames * iChannels * sizeof(int16_t) );
|
|
this->Mix( (int16_t *) pBuf, iFrames, iLastCursorPos, iCurrentFrame );
|
|
|
|
/* If the device has other than 2 channels, convert. */
|
|
if( m_pStream->m_iDeviceOutputChannels != iChannels )
|
|
{
|
|
int16_t *pTempBuf = (int16_t *) alloca( iFrames * m_pStream->m_iBytesPerOutputSample * m_pStream->m_iDeviceOutputChannels );
|
|
MapChannels( (int16_t *) pBuf, pTempBuf, iChannels, m_pStream->m_iDeviceOutputChannels, iFrames );
|
|
pBuf = pTempBuf;
|
|
}
|
|
|
|
/* If the device format isn't int16_t, convert. */
|
|
if( m_pStream->m_DeviceSampleFormat != DeviceSampleFormat_Int16 )
|
|
{
|
|
int iSamples = iFrames * m_pStream->m_iDeviceOutputChannels;
|
|
void *pTempBuf = alloca( iSamples * m_pStream->m_iBytesPerOutputSample );
|
|
MapSampleFormatFromInt16( (int16_t *) pBuf, pTempBuf, iSamples, m_pStream->m_DeviceSampleFormat );
|
|
pBuf = (int16_t *) pTempBuf;
|
|
}
|
|
|
|
memcpy( pData, pBuf, iFrames * m_pStream->m_iDeviceOutputChannels * m_pStream->m_iBytesPerOutputSample );
|
|
}
|
|
|
|
bool RageSoundDriver_WDMKS::Fill( int iPacket, RString &sError )
|
|
{
|
|
uint64_t iCurrentFrame = GetPosition();
|
|
// if( iCurrentFrame == m_iLastCursorPos )
|
|
// LOG->Trace( "underrun" );
|
|
|
|
Read( m_pStream->m_Packets[iPacket].Data, m_pStream->m_iFramesPerChunk, m_iLastCursorPos, iCurrentFrame );
|
|
|
|
/* Increment m_iLastCursorPos. */
|
|
m_iLastCursorPos += m_pStream->m_iFramesPerChunk;
|
|
|
|
/* Submit the buffer. */
|
|
return m_pStream->SubmitPacket( iPacket, sError );
|
|
}
|
|
|
|
void RageSoundDriver_WDMKS::MixerThread()
|
|
{
|
|
/* I don't trust this driver with THREAD_PRIORITY_TIME_CRITICAL just yet. */
|
|
if( !SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_HIGHEST) )
|
|
// if( !SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL) )
|
|
LOG->Warn( werr_ssprintf(GetLastError(), "Failed to set sound thread priority") );
|
|
|
|
/* Enable priority boosting. */
|
|
SetThreadPriorityBoost( GetCurrentThread(), FALSE );
|
|
|
|
ASSERT( m_pStream->m_pPlaybackPin != nullptr );
|
|
|
|
/* Some drivers (stock USB audio in XP) misbehave if we go from KSSTATE_STOP to
|
|
* KSSTATE_RUN. Always transition through KSSTATE_PAUSE. */
|
|
RString sError;
|
|
if( !m_pStream->m_pPlaybackPin->SetState(KSSTATE_PAUSE, sError) ||
|
|
!m_pStream->m_pPlaybackPin->SetState(KSSTATE_RUN, sError) )
|
|
FAIL_M( sError );
|
|
|
|
/* Submit initial buffers. */
|
|
for( int i = 0; i < m_pStream->m_iWriteAheadChunks; ++i )
|
|
Fill( i, sError );
|
|
|
|
int iNextBufferToSend = m_pStream->m_iWriteAheadChunks;
|
|
iNextBufferToSend %= MAX_CHUNKS;
|
|
|
|
int iWaitFor = 0;
|
|
|
|
while( !m_bShutdown )
|
|
{
|
|
/* Wait for next output buffer to finish. */
|
|
HANDLE aEventHandles[2] = { m_hSignal, m_pStream->m_Signal[iWaitFor].hEvent };
|
|
unsigned long iWait = WaitForMultipleObjects( 2, aEventHandles, FALSE, 1000 );
|
|
|
|
if( iWait == WAIT_FAILED )
|
|
{
|
|
LOG->Warn( werr_ssprintf(GetLastError(), "WaitForMultipleObjects") );
|
|
break;
|
|
}
|
|
if( iWait == WAIT_TIMEOUT )
|
|
continue;
|
|
|
|
if( iWait == WAIT_OBJECT_0 )
|
|
{
|
|
/* Abort event */
|
|
ASSERT( m_bShutdown ); /* Should have been set */
|
|
continue;
|
|
}
|
|
++iWaitFor;
|
|
iWaitFor %= MAX_CHUNKS;
|
|
|
|
if( !Fill(iNextBufferToSend, sError) )
|
|
{
|
|
LOG->Warn( "Fill(): %s", sError.c_str() );
|
|
break;
|
|
}
|
|
|
|
++iNextBufferToSend;
|
|
iNextBufferToSend %= MAX_CHUNKS;
|
|
}
|
|
|
|
/* Finished, either normally or aborted */
|
|
m_pStream->m_pPlaybackPin->SetState( KSSTATE_PAUSE, sError );
|
|
m_pStream->m_pPlaybackPin->SetState( KSSTATE_STOP, sError );
|
|
}
|
|
|
|
|
|
REGISTER_SOUND_DRIVER_CLASS( WDMKS );
|
|
|
|
int RageSoundDriver_WDMKS::MixerThread_start( void *p )
|
|
{
|
|
((RageSoundDriver_WDMKS *) p)->MixerThread();
|
|
return 0;
|
|
}
|
|
|
|
void RageSoundDriver_WDMKS::SetupDecodingThread()
|
|
{
|
|
if( !SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL) )
|
|
LOG->Warn( werr_ssprintf(GetLastError(), "Failed to set sound thread priority") );
|
|
}
|
|
|
|
int64_t RageSoundDriver_WDMKS::GetPosition() const
|
|
{
|
|
KSAUDIO_POSITION pos;
|
|
|
|
RString sError;
|
|
WdmGetPropertySimple( m_pStream->m_pPlaybackPin->m_hHandle, &KSPROPSETID_Audio, KSPROPERTY_AUDIO_POSITION,
|
|
&pos, sizeof(pos), nullptr, 0, sError );
|
|
ASSERT_M( sError == "", sError );
|
|
|
|
pos.PlayOffset /= m_pStream->m_iBytesPerOutputSample * m_pStream->m_iDeviceOutputChannels;
|
|
return pos.PlayOffset;
|
|
}
|
|
|
|
RageSoundDriver_WDMKS::RageSoundDriver_WDMKS()
|
|
{
|
|
m_pStream = nullptr;
|
|
m_pFilter = nullptr;
|
|
m_bShutdown = false;
|
|
m_iLastCursorPos = 0;
|
|
m_hSignal = CreateEvent( nullptr, FALSE, FALSE, nullptr ); /* abort event */
|
|
}
|
|
|
|
RString RageSoundDriver_WDMKS::Init()
|
|
{
|
|
RString sError;
|
|
if( !PaWinWdm_Initialize(sError) )
|
|
return sError;
|
|
|
|
std::vector<WinWdmFilter *> apFilters;
|
|
if( !BuildFilterList(apFilters, sError) )
|
|
return "Error building filter list: " + sError;
|
|
if( apFilters.empty() )
|
|
return "No supported audio devices found";
|
|
|
|
for( size_t i = 0; i < apFilters.size(); ++i )
|
|
{
|
|
const WinWdmFilter *pFilter = apFilters[i];
|
|
LOG->Trace( "Device #%i: %s", i, pFilter->m_sFriendlyName.c_str() );
|
|
int j = 0;
|
|
for (auto pPin : pFilter->m_apPins)
|
|
{
|
|
LOG->Trace( " Pin %i", j++ );
|
|
for (KSDATARANGE_AUDIO const &range : pPin->m_dataRangesItem)
|
|
{
|
|
RString sSubFormat;
|
|
GUID const &rawSubFormat = range.DataRange.SubFormat;
|
|
if( !memcmp(&rawSubFormat, &KSDATAFORMAT_SUBTYPE_WILDCARD, sizeof(GUID)) )
|
|
sSubFormat = "WILDCARD";
|
|
else if( !memcmp(&rawSubFormat, &KSDATAFORMAT_SUBTYPE_PCM, sizeof(GUID)) )
|
|
sSubFormat = "PCM";
|
|
else if( !memcmp(&rawSubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(GUID)) )
|
|
sSubFormat = "FLOAT";
|
|
|
|
LOG->Trace( " Range: %i channels, sample %i-%i, %i-%ihz (%s)",
|
|
range.MaximumChannels,
|
|
range.MinimumBitsPerSample,
|
|
range.MaximumBitsPerSample,
|
|
range.MinimumSampleFrequency,
|
|
range.MaximumSampleFrequency,
|
|
sSubFormat.c_str()
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
m_pFilter = apFilters[0];
|
|
for( size_t i=0; i < apFilters.size(); ++i )
|
|
{
|
|
if( apFilters[i] != m_pFilter )
|
|
delete apFilters[i];
|
|
}
|
|
apFilters.clear();
|
|
|
|
m_pStream = new WinWdmStream;
|
|
if( !m_pStream->Open( m_pFilter,
|
|
PREFSMAN->m_iSoundWriteAhead,
|
|
DeviceSampleFormat_Int16,
|
|
0, // don't care
|
|
PREFSMAN->m_iSoundPreferredSampleRate,
|
|
sError ) )
|
|
{
|
|
return sError;
|
|
}
|
|
|
|
SetDecodeBufferSize( 2048 );
|
|
StartDecodeThread();
|
|
|
|
MixingThread.SetName( "Mixer thread" );
|
|
MixingThread.Create( MixerThread_start, this );
|
|
|
|
return RString();
|
|
}
|
|
|
|
RageSoundDriver_WDMKS::~RageSoundDriver_WDMKS()
|
|
{
|
|
/* Signal the mixing thread to quit. */
|
|
if( MixingThread.IsCreated() )
|
|
{
|
|
m_bShutdown = true;
|
|
SetEvent( m_hSignal ); /* Signal immediately */
|
|
LOG->Trace( "Shutting down mixer thread ..." );
|
|
MixingThread.Wait();
|
|
LOG->Trace( "Mixer thread shut down." );
|
|
|
|
delete m_pStream;
|
|
}
|
|
|
|
delete m_pFilter;
|
|
CloseHandle( m_hSignal );
|
|
}
|
|
|
|
int RageSoundDriver_WDMKS::GetSampleRate() const
|
|
{
|
|
return m_pStream->m_iSampleRate;
|
|
}
|
|
|
|
float RageSoundDriver_WDMKS::GetPlayLatency() const
|
|
{
|
|
/* If we have a 1000-byte buffer, and we fill 500 bytes at a time, we
|
|
* almost always have between 500 and 1000 bytes filled; on average, 750. */
|
|
return (m_pStream->m_iFramesPerChunk + m_pStream->m_iFramesPerChunk/2) * (1.0f / m_pStream->m_iSampleRate);
|
|
}
|
|
|
|
/*
|
|
* Based on the PortAudio Windows WDM-KS interface.
|
|
*
|
|
* Copyright (c) 1999-2004 Andrew Baldwin, Ross Bencina, Phil Burk
|
|
* Copyright (c) 2002-2006 Glenn Maynard
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a
|
|
* copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
* the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
|
* ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
|
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
/*
|
|
* The text above constitutes the entire PortAudio license; however,
|
|
* the PortAudio community also makes the following non-binding requests:
|
|
*
|
|
* Any person wishing to distribute modifications to the Software is
|
|
* requested to send the modifications to the original developer so that
|
|
* they can be incorporated into the canonical version. It is also
|
|
* requested that these non-binding requests be included along with the
|
|
* license above.
|
|
*/
|