X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=RtAudio.cpp;h=5bc1055c718b55b74354a698f8379b421561078e;hb=4690b26068191afa44dc8a872e250990acb1d175;hp=b091e15782690a0324a29b2f6b91e90f4e8f20a4;hpb=13d629509011f3ca87af1461d67763cd9fb28217;p=rtaudio-cdist.git diff --git a/RtAudio.cpp b/RtAudio.cpp index b091e15..5bc1055 100644 --- a/RtAudio.cpp +++ b/RtAudio.cpp @@ -1,4 +1,4 @@ -/************************************************************************/ +/************************************************************************/ /*! \class RtAudio \brief Realtime audio i/o C++ classes. @@ -98,39 +98,95 @@ std::string RtAudio :: getVersion( void ) return RTAUDIO_VERSION; } -void RtAudio :: getCompiledApi( std::vector &apis ) -{ - apis.clear(); +// Define API names and display names. +// Must be in same order as API enum. +extern "C" { +const char* rtaudio_api_names[][2] = { + { "unspecified" , "Unknown" }, + { "alsa" , "ALSA" }, + { "pulse" , "Pulse" }, + { "oss" , "OpenSoundSystem" }, + { "jack" , "Jack" }, + { "core" , "CoreAudio" }, + { "wasapi" , "WASAPI" }, + { "asio" , "ASIO" }, + { "ds" , "DirectSound" }, + { "dummy" , "Dummy" }, +}; +const unsigned int rtaudio_num_api_names = + sizeof(rtaudio_api_names)/sizeof(rtaudio_api_names[0]); - // The order here will control the order of RtAudio's API search in - // the constructor. +// The order here will control the order of RtAudio's API search in +// the constructor. +extern "C" const RtAudio::Api rtaudio_compiled_apis[] = { #if defined(__UNIX_JACK__) - apis.push_back( UNIX_JACK ); + RtAudio::UNIX_JACK, #endif #if defined(__LINUX_PULSE__) - apis.push_back( LINUX_PULSE ); + RtAudio::LINUX_PULSE, #endif #if defined(__LINUX_ALSA__) - apis.push_back( LINUX_ALSA ); + RtAudio::LINUX_ALSA, #endif #if defined(__LINUX_OSS__) - apis.push_back( LINUX_OSS ); + RtAudio::LINUX_OSS, #endif #if defined(__WINDOWS_ASIO__) - apis.push_back( WINDOWS_ASIO ); + RtAudio::WINDOWS_ASIO, #endif #if defined(__WINDOWS_WASAPI__) - apis.push_back( WINDOWS_WASAPI ); + RtAudio::WINDOWS_WASAPI, #endif #if defined(__WINDOWS_DS__) - apis.push_back( WINDOWS_DS ); + RtAudio::WINDOWS_DS, #endif #if defined(__MACOSX_CORE__) - apis.push_back( MACOSX_CORE ); + RtAudio::MACOSX_CORE, #endif #if defined(__RTAUDIO_DUMMY__) - apis.push_back( RTAUDIO_DUMMY ); + RtAudio::RTAUDIO_DUMMY, #endif + RtAudio::UNSPECIFIED, +}; +extern "C" const unsigned int rtaudio_num_compiled_apis = + sizeof(rtaudio_compiled_apis)/sizeof(rtaudio_compiled_apis[0])-1; +} + +// This is a compile-time check that rtaudio_num_api_names == RtAudio::NUM_APIS. +// If the build breaks here, check that they match. +template class StaticAssert { private: StaticAssert() {} }; +template<> class StaticAssert{ public: StaticAssert() {} }; +class StaticAssertions { StaticAssertions() { + StaticAssert(); +}}; + +void RtAudio :: getCompiledApi( std::vector &apis ) +{ + apis = std::vector(rtaudio_compiled_apis, + rtaudio_compiled_apis + rtaudio_num_compiled_apis); +} + +std::string RtAudio :: getApiName( RtAudio::Api api ) +{ + if (api < 0 || api >= RtAudio::NUM_APIS) + return ""; + return rtaudio_api_names[api][0]; +} + +std::string RtAudio :: getApiDisplayName( RtAudio::Api api ) +{ + if (api < 0 || api >= RtAudio::NUM_APIS) + return "Unknown"; + return rtaudio_api_names[api][1]; +} + +RtAudio::Api RtAudio :: getCompiledApiByName( const std::string &name ) +{ + unsigned int i=0; + for (i = 0; i < rtaudio_num_compiled_apis; ++i) + if (name == rtaudio_api_names[rtaudio_compiled_apis[i]][0]) + return rtaudio_compiled_apis[i]; + return RtAudio::UNSPECIFIED; } void RtAudio :: openRtApi( RtAudio::Api api ) @@ -1973,7 +2029,7 @@ unsigned int RtApiJack :: getDeviceCount( void ) const char **ports; std::string port, previousPort; unsigned int nChannels = 0, nDevices = 0; - ports = jack_get_ports( client, NULL, NULL, 0 ); + ports = jack_get_ports( client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0 ); if ( ports ) { // Parse the port names up to the first colon (:). size_t iColon = 0; @@ -2012,7 +2068,7 @@ RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) const char **ports; std::string port, previousPort; unsigned int nPorts = 0, nDevices = 0; - ports = jack_get_ports( client, NULL, NULL, 0 ); + ports = jack_get_ports( client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0 ); if ( ports ) { // Parse the port names up to the first colon (:). size_t iColon = 0; @@ -2047,7 +2103,7 @@ RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) // Count the available ports containing the client name as device // channels. Jack "input ports" equal RtAudio output channels. unsigned int nChannels = 0; - ports = jack_get_ports( client, info.name.c_str(), NULL, JackPortIsInput ); + ports = jack_get_ports( client, info.name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput ); if ( ports ) { while ( ports[ nChannels ] ) nChannels++; free( ports ); @@ -2056,7 +2112,7 @@ RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) // Jack "output ports" equal RtAudio input channels. nChannels = 0; - ports = jack_get_ports( client, info.name.c_str(), NULL, JackPortIsOutput ); + ports = jack_get_ports( client, info.name.c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput ); if ( ports ) { while ( ports[ nChannels ] ) nChannels++; free( ports ); @@ -2168,7 +2224,7 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne const char **ports; std::string port, previousPort, deviceName; unsigned int nPorts = 0, nDevices = 0; - ports = jack_get_ports( client, NULL, NULL, 0 ); + ports = jack_get_ports( client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0 ); if ( ports ) { // Parse the port names up to the first colon (:). size_t iColon = 0; @@ -2192,22 +2248,24 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne return FAILURE; } - // Count the available ports containing the client name as device - // channels. Jack "input ports" equal RtAudio output channels. - unsigned int nChannels = 0; unsigned long flag = JackPortIsInput; if ( mode == INPUT ) flag = JackPortIsOutput; - ports = jack_get_ports( client, deviceName.c_str(), NULL, flag ); - if ( ports ) { - while ( ports[ nChannels ] ) nChannels++; - free( ports ); - } - // Compare the jack ports for specified client to the requested number of channels. - if ( nChannels < (channels + firstChannel) ) { - errorStream_ << "RtApiJack::probeDeviceOpen: requested number of channels (" << channels << ") + offset (" << firstChannel << ") not found for specified device (" << device << ":" << deviceName << ")."; - errorText_ = errorStream_.str(); - return FAILURE; + if ( ! (options && (options->flags & RTAUDIO_JACK_DONT_CONNECT)) ) { + // Count the available ports containing the client name as device + // channels. Jack "input ports" equal RtAudio output channels. + unsigned int nChannels = 0; + ports = jack_get_ports( client, deviceName.c_str(), JACK_DEFAULT_AUDIO_TYPE, flag ); + if ( ports ) { + while ( ports[ nChannels ] ) nChannels++; + free( ports ); + } + // Compare the jack ports for specified client to the requested number of channels. + if ( nChannels < (channels + firstChannel) ) { + errorStream_ << "RtApiJack::probeDeviceOpen: requested number of channels (" << channels << ") + offset (" << firstChannel << ") not found for specified device (" << device << ":" << deviceName << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } } // Check the jack server sample rate. @@ -2221,7 +2279,7 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne stream_.sampleRate = jackRate; // Get the latency of the JACK port. - ports = jack_get_ports( client, deviceName.c_str(), NULL, flag ); + ports = jack_get_ports( client, deviceName.c_str(), JACK_DEFAULT_AUDIO_TYPE, flag ); if ( ports[ firstChannel ] ) { // Added by Ge Wang jack_latency_callback_mode_t cbmode = (mode == INPUT ? JackCaptureLatency : JackPlaybackLatency); @@ -2453,7 +2511,7 @@ void RtApiJack :: startStream( void ) // Get the list of available ports. if ( shouldAutoconnect_ && (stream_.mode == OUTPUT || stream_.mode == DUPLEX) ) { result = 1; - ports = jack_get_ports( handle->client, handle->deviceName[0].c_str(), NULL, JackPortIsInput); + ports = jack_get_ports( handle->client, handle->deviceName[0].c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput); if ( ports == NULL) { errorText_ = "RtApiJack::startStream(): error determining available JACK input ports!"; goto unlock; @@ -2477,7 +2535,7 @@ void RtApiJack :: startStream( void ) if ( shouldAutoconnect_ && (stream_.mode == INPUT || stream_.mode == DUPLEX) ) { result = 1; - ports = jack_get_ports( handle->client, handle->deviceName[1].c_str(), NULL, JackPortIsOutput ); + ports = jack_get_ports( handle->client, handle->deviceName[1].c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput ); if ( ports == NULL) { errorText_ = "RtApiJack::startStream(): error determining available JACK output ports!"; goto unlock; @@ -3687,11 +3745,32 @@ static const char* getAsioErrorString( ASIOError result ) #ifndef INITGUID #define INITGUID #endif + +#include +#include +#include +#include +#include + #include #include #include #include -#include + +#ifndef MF_E_TRANSFORM_NEED_MORE_INPUT + #define MF_E_TRANSFORM_NEED_MORE_INPUT _HRESULT_TYPEDEF_(0xc00d6d72) +#endif + +#ifndef MFSTARTUP_NOSOCKET + #define MFSTARTUP_NOSOCKET 0x1 +#endif + +#ifdef _MSC_VER + #pragma comment( lib, "ksuser" ) + #pragma comment( lib, "mfplat.lib" ) + #pragma comment( lib, "mfuuid.lib" ) + #pragma comment( lib, "wmcodecdspuuid" ) +#endif //============================================================================= @@ -3865,6 +3944,198 @@ private: //----------------------------------------------------------------------------- +// In order to satisfy WASAPI's buffer requirements, we need a means of converting sample rate +// between HW and the user. The WasapiResampler class is used to perform this conversion between +// HwIn->UserIn and UserOut->HwOut during the stream callback loop. +class WasapiResampler +{ +public: + WasapiResampler( bool isFloat, unsigned int bitsPerSample, unsigned int channelCount, + unsigned int inSampleRate, unsigned int outSampleRate ) + : _bytesPerSample( bitsPerSample / 8 ) + , _channelCount( channelCount ) + , _sampleRatio( ( float ) outSampleRate / inSampleRate ) + , _transformUnk( NULL ) + , _transform( NULL ) + , _mediaType( NULL ) + , _inputMediaType( NULL ) + , _outputMediaType( NULL ) + + #ifdef __IWMResamplerProps_FWD_DEFINED__ + , _resamplerProps( NULL ) + #endif + { + // 1. Initialization + + MFStartup( MF_VERSION, MFSTARTUP_NOSOCKET ); + + // 2. Create Resampler Transform Object + + CoCreateInstance( CLSID_CResamplerMediaObject, NULL, CLSCTX_INPROC_SERVER, + IID_IUnknown, ( void** ) &_transformUnk ); + + _transformUnk->QueryInterface( IID_PPV_ARGS( &_transform ) ); + + #ifdef __IWMResamplerProps_FWD_DEFINED__ + _transformUnk->QueryInterface( IID_PPV_ARGS( &_resamplerProps ) ); + _resamplerProps->SetHalfFilterLength( 60 ); // best conversion quality + #endif + + // 3. Specify input / output format + + MFCreateMediaType( &_mediaType ); + _mediaType->SetGUID( MF_MT_MAJOR_TYPE, MFMediaType_Audio ); + _mediaType->SetGUID( MF_MT_SUBTYPE, isFloat ? MFAudioFormat_Float : MFAudioFormat_PCM ); + _mediaType->SetUINT32( MF_MT_AUDIO_NUM_CHANNELS, channelCount ); + _mediaType->SetUINT32( MF_MT_AUDIO_SAMPLES_PER_SECOND, inSampleRate ); + _mediaType->SetUINT32( MF_MT_AUDIO_BLOCK_ALIGNMENT, _bytesPerSample * channelCount ); + _mediaType->SetUINT32( MF_MT_AUDIO_AVG_BYTES_PER_SECOND, _bytesPerSample * channelCount * inSampleRate ); + _mediaType->SetUINT32( MF_MT_AUDIO_BITS_PER_SAMPLE, bitsPerSample ); + _mediaType->SetUINT32( MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE ); + + MFCreateMediaType( &_inputMediaType ); + _mediaType->CopyAllItems( _inputMediaType ); + + _transform->SetInputType( 0, _inputMediaType, 0 ); + + MFCreateMediaType( &_outputMediaType ); + _mediaType->CopyAllItems( _outputMediaType ); + + _outputMediaType->SetUINT32( MF_MT_AUDIO_SAMPLES_PER_SECOND, outSampleRate ); + _outputMediaType->SetUINT32( MF_MT_AUDIO_AVG_BYTES_PER_SECOND, _bytesPerSample * channelCount * outSampleRate ); + + _transform->SetOutputType( 0, _outputMediaType, 0 ); + + // 4. Send stream start messages to Resampler + + _transform->ProcessMessage( MFT_MESSAGE_COMMAND_FLUSH, 0 ); + _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0 ); + _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0 ); + } + + ~WasapiResampler() + { + // 8. Send stream stop messages to Resampler + + _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_END_OF_STREAM, 0 ); + _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_END_STREAMING, 0 ); + + // 9. Cleanup + + MFShutdown(); + + SAFE_RELEASE( _transformUnk ); + SAFE_RELEASE( _transform ); + SAFE_RELEASE( _mediaType ); + SAFE_RELEASE( _inputMediaType ); + SAFE_RELEASE( _outputMediaType ); + + #ifdef __IWMResamplerProps_FWD_DEFINED__ + SAFE_RELEASE( _resamplerProps ); + #endif + } + + void Convert( char* outBuffer, const char* inBuffer, unsigned int inSampleCount, unsigned int& outSampleCount ) + { + unsigned int inputBufferSize = _bytesPerSample * _channelCount * inSampleCount; + if ( _sampleRatio == 1 ) + { + // no sample rate conversion required + memcpy( outBuffer, inBuffer, inputBufferSize ); + outSampleCount = inSampleCount; + return; + } + + unsigned int outputBufferSize = ( unsigned int ) ceilf( inputBufferSize * _sampleRatio ) + ( _bytesPerSample * _channelCount ); + + IMFMediaBuffer* rInBuffer; + IMFSample* rInSample; + BYTE* rInByteBuffer = NULL; + + // 5. Create Sample object from input data + + MFCreateMemoryBuffer( inputBufferSize, &rInBuffer ); + + rInBuffer->Lock( &rInByteBuffer, NULL, NULL ); + memcpy( rInByteBuffer, inBuffer, inputBufferSize ); + rInBuffer->Unlock(); + rInByteBuffer = NULL; + + rInBuffer->SetCurrentLength( inputBufferSize ); + + MFCreateSample( &rInSample ); + rInSample->AddBuffer( rInBuffer ); + + // 6. Pass input data to Resampler + + _transform->ProcessInput( 0, rInSample, 0 ); + + SAFE_RELEASE( rInBuffer ); + SAFE_RELEASE( rInSample ); + + // 7. Perform sample rate conversion + + IMFMediaBuffer* rOutBuffer = NULL; + BYTE* rOutByteBuffer = NULL; + + MFT_OUTPUT_DATA_BUFFER rOutDataBuffer; + DWORD rStatus; + DWORD rBytes = outputBufferSize; // maximum bytes accepted per ProcessOutput + + // 7.1 Create Sample object for output data + + memset( &rOutDataBuffer, 0, sizeof rOutDataBuffer ); + MFCreateSample( &( rOutDataBuffer.pSample ) ); + MFCreateMemoryBuffer( rBytes, &rOutBuffer ); + rOutDataBuffer.pSample->AddBuffer( rOutBuffer ); + rOutDataBuffer.dwStreamID = 0; + rOutDataBuffer.dwStatus = 0; + rOutDataBuffer.pEvents = NULL; + + // 7.2 Get output data from Resampler + + if ( _transform->ProcessOutput( 0, 1, &rOutDataBuffer, &rStatus ) == MF_E_TRANSFORM_NEED_MORE_INPUT ) + { + outSampleCount = 0; + SAFE_RELEASE( rOutBuffer ); + SAFE_RELEASE( rOutDataBuffer.pSample ); + return; + } + + // 7.3 Write output data to outBuffer + + SAFE_RELEASE( rOutBuffer ); + rOutDataBuffer.pSample->ConvertToContiguousBuffer( &rOutBuffer ); + rOutBuffer->GetCurrentLength( &rBytes ); + + rOutBuffer->Lock( &rOutByteBuffer, NULL, NULL ); + memcpy( outBuffer, rOutByteBuffer, rBytes ); + rOutBuffer->Unlock(); + rOutByteBuffer = NULL; + + outSampleCount = rBytes / _bytesPerSample / _channelCount; + SAFE_RELEASE( rOutBuffer ); + SAFE_RELEASE( rOutDataBuffer.pSample ); + } + +private: + unsigned int _bytesPerSample; + unsigned int _channelCount; + float _sampleRatio; + + IUnknown* _transformUnk; + IMFTransform* _transform; + IMFMediaType* _mediaType; + IMFMediaType* _inputMediaType; + IMFMediaType* _outputMediaType; + + #ifdef __IWMResamplerProps_FWD_DEFINED__ + IWMResamplerProps* _resamplerProps; + #endif +}; + +//----------------------------------------------------------------------------- + // A structure to hold various information related to the WASAPI implementation. struct WasapiHandle { @@ -3899,10 +4170,9 @@ RtApiWasapi::RtApiWasapi() CLSCTX_ALL, __uuidof( IMMDeviceEnumerator ), ( void** ) &deviceEnumerator_ ); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::RtApiWasapi: Unable to instantiate device enumerator"; - error( RtAudioError::DRIVER_ERROR ); - } + // If this runs on an old Windows, it will fail. Ignore and proceed. + if ( FAILED( hr ) ) + deviceEnumerator_ = NULL; } //----------------------------------------------------------------------------- @@ -3929,6 +4199,9 @@ unsigned int RtApiWasapi::getDeviceCount( void ) IMMDeviceCollection* captureDevices = NULL; IMMDeviceCollection* renderDevices = NULL; + if ( !deviceEnumerator_ ) + return 0; + // Count capture devices errorText_.clear(); HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); @@ -4130,11 +4403,14 @@ RtAudio::DeviceInfo RtApiWasapi::getDeviceInfo( unsigned int device ) info.duplexChannels = 0; } - // sample rates (WASAPI only supports the one native sample rate) - info.preferredSampleRate = deviceFormat->nSamplesPerSec; - + // sample rates info.sampleRates.clear(); - info.sampleRates.push_back( deviceFormat->nSamplesPerSec ); + + // allow support for all sample rates as we have a built-in sample rate converter + for ( unsigned int i = 0; i < MAX_SAMPLE_RATES; i++ ) { + info.sampleRates.push_back( SAMPLE_RATES[i] ); + } + info.preferredSampleRate = deviceFormat->nSamplesPerSec; // native format info.nativeFormats = 0; @@ -4411,7 +4687,6 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne WAVEFORMATEX* deviceFormat = NULL; unsigned int bufferBytes; stream_.state = STREAM_STOPPED; - RtAudio::DeviceInfo deviceInfo; // create API Handle if not already created if ( !stream_.apiHandle ) @@ -4452,20 +4727,6 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne goto Exit; } - deviceInfo = getDeviceInfo( device ); - - // validate sample rate - if ( sampleRate != deviceInfo.preferredSampleRate ) - { - errorType = RtAudioError::INVALID_USE; - std::stringstream ss; - ss << "RtApiWasapi::probeDeviceOpen: " << sampleRate - << "Hz sample rate not supported. This device only supports " - << deviceInfo.preferredSampleRate << "Hz."; - errorText_ = ss.str(); - goto Exit; - } - // determine whether index falls within capture or render devices if ( device >= renderDeviceCount ) { if ( mode != INPUT ) { @@ -4549,7 +4810,7 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne stream_.nUserChannels[mode] = channels; stream_.channelOffset[mode] = firstChannel; stream_.userFormat = format; - stream_.deviceFormat[mode] = deviceInfo.nativeFormats; + stream_.deviceFormat[mode] = getDeviceInfo( device ).nativeFormats; if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; @@ -4560,7 +4821,8 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne // Set flags for buffer conversion. stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] || - stream_.nUserChannels != stream_.nDeviceChannels ) + stream_.nUserChannels[0] != stream_.nDeviceChannels[0] || + stream_.nUserChannels[1] != stream_.nDeviceChannels[1] ) stream_.doConvertBuffer[mode] = true; else if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) @@ -4649,8 +4911,12 @@ void RtApiWasapi::wasapiThread() WAVEFORMATEX* captureFormat = NULL; WAVEFORMATEX* renderFormat = NULL; + float captureSrRatio = 0.0f; + float renderSrRatio = 0.0f; WasapiBuffer captureBuffer; WasapiBuffer renderBuffer; + WasapiResampler* captureResampler = NULL; + WasapiResampler* renderResampler = NULL; // declare local stream variables RtAudioCallback callback = ( RtAudioCallback ) stream_.callbackInfo.callback; @@ -4658,11 +4924,15 @@ void RtApiWasapi::wasapiThread() unsigned long captureFlags = 0; unsigned int bufferFrameCount = 0; unsigned int numFramesPadding = 0; - bool callbackPushed = false; + unsigned int convBufferSize = 0; + bool callbackPushed = true; bool callbackPulled = false; bool callbackStopped = false; int callbackResult = 0; + // convBuffer is used to store converted buffers between WASAPI and the user + char* convBuffer = NULL; + unsigned int convBuffSize = 0; unsigned int deviceBuffSize = 0; errorText_.clear(); @@ -4685,8 +4955,16 @@ void RtApiWasapi::wasapiThread() goto Exit; } + // init captureResampler + captureResampler = new WasapiResampler( stream_.deviceFormat[INPUT] == RTAUDIO_FLOAT32 || stream_.deviceFormat[INPUT] == RTAUDIO_FLOAT64, + formatBytes( stream_.deviceFormat[INPUT] ) * 8, stream_.nDeviceChannels[INPUT], + captureFormat->nSamplesPerSec, stream_.sampleRate ); + + captureSrRatio = ( ( float ) captureFormat->nSamplesPerSec / stream_.sampleRate ); + // initialize capture stream according to desire buffer size - REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) stream_.bufferSize * 10000000 / captureFormat->nSamplesPerSec ); + float desiredBufferSize = stream_.bufferSize * captureSrRatio; + REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) desiredBufferSize * 10000000 / captureFormat->nSamplesPerSec ); if ( !captureClient ) { hr = captureAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, @@ -4733,7 +5011,7 @@ void RtApiWasapi::wasapiThread() } // scale outBufferSize according to stream->user sample rate ratio - unsigned int outBufferSize = ( unsigned int ) stream_.bufferSize * stream_.nDeviceChannels[INPUT]; + unsigned int outBufferSize = ( unsigned int ) ceilf( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT]; inBufferSize *= stream_.nDeviceChannels[INPUT]; // set captureBuffer size @@ -4762,8 +5040,16 @@ void RtApiWasapi::wasapiThread() goto Exit; } + // init renderResampler + renderResampler = new WasapiResampler( stream_.deviceFormat[OUTPUT] == RTAUDIO_FLOAT32 || stream_.deviceFormat[OUTPUT] == RTAUDIO_FLOAT64, + formatBytes( stream_.deviceFormat[OUTPUT] ) * 8, stream_.nDeviceChannels[OUTPUT], + stream_.sampleRate, renderFormat->nSamplesPerSec ); + + renderSrRatio = ( ( float ) renderFormat->nSamplesPerSec / stream_.sampleRate ); + // initialize render stream according to desire buffer size - REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) stream_.bufferSize * 10000000 / renderFormat->nSamplesPerSec ); + float desiredBufferSize = stream_.bufferSize * renderSrRatio; + REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) desiredBufferSize * 10000000 / renderFormat->nSamplesPerSec ); if ( !renderClient ) { hr = renderAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, @@ -4810,7 +5096,7 @@ void RtApiWasapi::wasapiThread() } // scale inBufferSize according to user->stream sample rate ratio - unsigned int inBufferSize = ( unsigned int ) stream_.bufferSize * stream_.nDeviceChannels[OUTPUT]; + unsigned int inBufferSize = ( unsigned int ) ceilf( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT]; outBufferSize *= stream_.nDeviceChannels[OUTPUT]; // set renderBuffer size @@ -4831,20 +5117,30 @@ void RtApiWasapi::wasapiThread() } } - if ( stream_.mode == INPUT ) { - using namespace std; // for roundf + // malloc buffer memory + if ( stream_.mode == INPUT ) + { + using namespace std; // for ceilf + convBuffSize = ( size_t ) ( ceilf( stream_.bufferSize * captureSrRatio ) ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); } - else if ( stream_.mode == OUTPUT ) { + else if ( stream_.mode == OUTPUT ) + { + convBuffSize = ( size_t ) ( ceilf( stream_.bufferSize * renderSrRatio ) ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); } - else if ( stream_.mode == DUPLEX ) { + else if ( stream_.mode == DUPLEX ) + { + convBuffSize = std::max( ( size_t ) ( ceilf( stream_.bufferSize * captureSrRatio ) ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), + ( size_t ) ( ceilf( stream_.bufferSize * renderSrRatio ) ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); deviceBuffSize = std::max( stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); } + convBuffSize *= 2; // allow overflow for *SrRatio remainders + convBuffer = ( char* ) malloc( convBuffSize ); stream_.deviceBuffer = ( char* ) malloc( deviceBuffSize ); - if ( !stream_.deviceBuffer ) { + if ( !convBuffer || !stream_.deviceBuffer ) { errorType = RtAudioError::MEMORY_ERROR; errorText_ = "RtApiWasapi::wasapiThread: Error allocating device buffer memory."; goto Exit; @@ -4856,15 +5152,46 @@ void RtApiWasapi::wasapiThread() // Callback Input // ============== // 1. Pull callback buffer from inputBuffer - // 2. If 1. was successful: Convert callback buffer to user format + // 2. If 1. was successful: Convert callback buffer to user sample rate and channel count + // Convert callback buffer to user format - if ( captureAudioClient ) { - // Pull callback buffer from inputBuffer - callbackPulled = captureBuffer.pullBuffer( stream_.deviceBuffer, - ( unsigned int ) stream_.bufferSize * stream_.nDeviceChannels[INPUT], - stream_.deviceFormat[INPUT] ); + if ( captureAudioClient ) + { + int samplesToPull = ( unsigned int ) floorf( stream_.bufferSize * captureSrRatio ); + if ( captureSrRatio != 1 ) + { + // account for remainders + samplesToPull--; + } + + convBufferSize = 0; + while ( convBufferSize < stream_.bufferSize ) + { + // Pull callback buffer from inputBuffer + callbackPulled = captureBuffer.pullBuffer( convBuffer, + samplesToPull * stream_.nDeviceChannels[INPUT], + stream_.deviceFormat[INPUT] ); + + if ( !callbackPulled ) + { + break; + } + + // Convert callback buffer to user sample rate + unsigned int deviceBufferOffset = convBufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); + unsigned int convSamples = 0; + + captureResampler->Convert( stream_.deviceBuffer + deviceBufferOffset, + convBuffer, + samplesToPull, + convSamples ); + + convBufferSize += convSamples; + samplesToPull = 1; // now pull one sample at a time until we have stream_.bufferSize samples + } - if ( callbackPulled ) { + if ( callbackPulled ) + { if ( stream_.doConvertBuffer[INPUT] ) { // Convert callback buffer to user format convertBuffer( stream_.userBuffer[INPUT], @@ -4938,20 +5265,33 @@ void RtApiWasapi::wasapiThread() // Callback Output // =============== // 1. Convert callback buffer to stream format - // 2. Push callback buffer into outputBuffer + // 2. Convert callback buffer to stream sample rate and channel count + // 3. Push callback buffer into outputBuffer - if ( renderAudioClient && callbackPulled ) { - if ( stream_.doConvertBuffer[OUTPUT] ) { - // Convert callback buffer to stream format - convertBuffer( stream_.deviceBuffer, - stream_.userBuffer[OUTPUT], - stream_.convertInfo[OUTPUT] ); + if ( renderAudioClient && callbackPulled ) + { + // if the last call to renderBuffer.PushBuffer() was successful + if ( callbackPushed || convBufferSize == 0 ) + { + if ( stream_.doConvertBuffer[OUTPUT] ) + { + // Convert callback buffer to stream format + convertBuffer( stream_.deviceBuffer, + stream_.userBuffer[OUTPUT], + stream_.convertInfo[OUTPUT] ); + } + + // Convert callback buffer to stream sample rate + renderResampler->Convert( convBuffer, + stream_.deviceBuffer, + stream_.bufferSize, + convBufferSize ); } // Push callback buffer into outputBuffer - callbackPushed = renderBuffer.pushBuffer( stream_.deviceBuffer, - stream_.bufferSize * stream_.nDeviceChannels[OUTPUT], + callbackPushed = renderBuffer.pushBuffer( convBuffer, + convBufferSize * stream_.nDeviceChannels[OUTPUT], stream_.deviceFormat[OUTPUT] ); } else { @@ -5085,7 +5425,10 @@ void RtApiWasapi::wasapiThread() // if the callback buffer was pushed renderBuffer reset callbackPulled flag if ( callbackPushed ) { + // unsetting the callbackPulled flag lets the stream know that + // the audio device is ready for another callback output buffer. callbackPulled = false; + // tick stream time RtApi::tickStreamTime(); } @@ -5097,15 +5440,17 @@ Exit: CoTaskMemFree( captureFormat ); CoTaskMemFree( renderFormat ); + free ( convBuffer ); + delete renderResampler; + delete captureResampler; + CoUninitialize(); + if ( !errorText_.empty() ) + error( errorType ); + // update stream state stream_.state = STREAM_STOPPED; - - if ( errorText_.empty() ) - return; - else - error( errorType ); } //******************** End of __WINDOWS_WASAPI__ *********************// @@ -5121,6 +5466,8 @@ Exit: // Various revisions for RtAudio 4.0 by Gary Scavone, April 2007 // Changed device query structure for RtAudio 4.0.7, January 2010 +#include +#include #include #include #include