X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=RtAudio.cpp;h=82622adf7d05e788e76b6fa6e233a001b176baba;hb=refs%2Fheads%2Ffix-wasapi-resampler-crash;hp=7fb2798f90092f7333e147f036da8141900a9105;hpb=470f62b4afada9b89b19d1bd57955c417d2e7005;p=rtaudio.git diff --git a/RtAudio.cpp b/RtAudio.cpp index 7fb2798..82622ad 100644 --- a/RtAudio.cpp +++ b/RtAudio.cpp @@ -1,4 +1,4 @@ -/************************************************************************/ +/************************************************************************/ /*! \class RtAudio \brief Realtime audio i/o C++ classes. @@ -7,10 +7,11 @@ and OSS), Macintosh OS X (CoreAudio and Jack), and Windows (DirectSound, ASIO and WASAPI) operating systems. + RtAudio GitHub site: https://github.com/thestk/rtaudio RtAudio WWW site: http://www.music.mcgill.ca/~gary/rtaudio/ RtAudio: realtime audio i/o C++ classes - Copyright (c) 2001-2017 Gary P. Scavone + Copyright (c) 2001-2019 Gary P. Scavone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files @@ -38,7 +39,7 @@ */ /************************************************************************/ -// RtAudio: Version 5.0.0 +// RtAudio: Version 5.1.0 #include "RtAudio.h" #include @@ -98,39 +99,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 ) @@ -1421,15 +1478,17 @@ void RtApiCore :: closeStream( void ) errorText_ = "RtApiCore::closeStream(): error removing property listener!"; error( RtAudioError::WARNING ); } - } - if ( stream_.state == STREAM_RUNNING ) - AudioDeviceStop( handle->id[0], callbackHandler ); + #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) - AudioDeviceDestroyIOProcID( handle->id[0], handle->procId[0] ); -#else - // deprecated in favor of AudioDeviceDestroyIOProcID() - AudioDeviceRemoveIOProc( handle->id[0], callbackHandler ); + if ( stream_.state == STREAM_RUNNING ) + AudioDeviceStop( handle->id[0], handle->procId[0] ); + AudioDeviceDestroyIOProcID( handle->id[0], handle->procId[0] ); +#else // deprecated behaviour + if ( stream_.state == STREAM_RUNNING ) + AudioDeviceStop( handle->id[0], callbackHandler ); + AudioDeviceRemoveIOProc( handle->id[0], callbackHandler ); #endif + } } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { @@ -1444,15 +1503,17 @@ void RtApiCore :: closeStream( void ) errorText_ = "RtApiCore::closeStream(): error removing property listener!"; error( RtAudioError::WARNING ); } - } - if ( stream_.state == STREAM_RUNNING ) - AudioDeviceStop( handle->id[1], callbackHandler ); + #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) - AudioDeviceDestroyIOProcID( handle->id[1], handle->procId[1] ); -#else - // deprecated in favor of AudioDeviceDestroyIOProcID() - AudioDeviceRemoveIOProc( handle->id[1], callbackHandler ); + if ( stream_.state == STREAM_RUNNING ) + AudioDeviceStop( handle->id[1], handle->procId[1] ); + AudioDeviceDestroyIOProcID( handle->id[1], handle->procId[1] ); +#else // deprecated behaviour + if ( stream_.state == STREAM_RUNNING ) + AudioDeviceStop( handle->id[1], callbackHandler ); + AudioDeviceRemoveIOProc( handle->id[1], callbackHandler ); #endif + } } for ( int i=0; i<2; i++ ) { @@ -1485,11 +1546,19 @@ void RtApiCore :: startStream( void ) return; } +#if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); +#endif + OSStatus result = noErr; CoreHandle *handle = (CoreHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + result = AudioDeviceStart( handle->id[0], handle->procId[0] ); +#else // deprecated behaviour result = AudioDeviceStart( handle->id[0], callbackHandler ); +#endif if ( result != noErr ) { errorStream_ << "RtApiCore::startStream: system error (" << getErrorCode( result ) << ") starting callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); @@ -1500,7 +1569,11 @@ void RtApiCore :: startStream( void ) if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + result = AudioDeviceStart( handle->id[1], handle->procId[1] ); +#else // deprecated behaviour result = AudioDeviceStart( handle->id[1], callbackHandler ); +#endif if ( result != noErr ) { errorStream_ << "RtApiCore::startStream: system error starting input callback procedure on device (" << stream_.device[1] << ")."; errorText_ = errorStream_.str(); @@ -1535,7 +1608,11 @@ void RtApiCore :: stopStream( void ) pthread_cond_wait( &handle->condition, &stream_.mutex ); // block until signaled } +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + result = AudioDeviceStop( handle->id[0], handle->procId[0] ); +#else // deprecated behaviour result = AudioDeviceStop( handle->id[0], callbackHandler ); +#endif if ( result != noErr ) { errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); @@ -1545,7 +1622,11 @@ void RtApiCore :: stopStream( void ) if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { +#if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) + result = AudioDeviceStop( handle->id[0], handle->procId[1] ); +#else // deprecated behaviour result = AudioDeviceStop( handle->id[1], callbackHandler ); +#endif if ( result != noErr ) { errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping input callback procedure on device (" << stream_.device[1] << ")."; errorText_ = errorStream_.str(); @@ -1845,7 +1926,10 @@ bool RtApiCore :: callbackEvent( AudioDeviceID deviceId, unlock: //MUTEX_UNLOCK( &stream_.mutex ); - RtApi::tickStreamTime(); + // Make sure to only tick duplex stream time once if using two devices + if ( stream_.mode != DUPLEX || (stream_.mode == DUPLEX && handle->id[0] != handle->id[1] && deviceId == handle->id[0] ) ) + RtApi::tickStreamTime(); + return SUCCESS; } @@ -2443,6 +2527,10 @@ void RtApiJack :: startStream( void ) return; } + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + JackHandle *handle = (JackHandle *) stream_.apiHandle; int result = jack_activate( handle->client ); if ( result ) { @@ -3165,8 +3253,8 @@ bool RtApiAsio :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne result = ASIOCreateBuffers( handle->bufferInfos, nChannels, stream_.bufferSize, &asioCallbacks ); if ( result != ASE_OK ) { // Standard method failed. This can happen with strict/misbehaving drivers that return valid buffer size ranges - // but only accept the preferred buffer size as parameter for ASIOCreateBuffers. eg. Creatives ASIO driver - // in that case, let's be naïve and try that instead + // but only accept the preferred buffer size as parameter for ASIOCreateBuffers (e.g. Creative's ASIO driver). + // In that case, let's be naïve and try that instead. *bufferSize = preferSize; stream_.bufferSize = *bufferSize; result = ASIOCreateBuffers( handle->bufferInfos, nChannels, stream_.bufferSize, &asioCallbacks ); @@ -3322,6 +3410,10 @@ void RtApiAsio :: startStream() return; } + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + AsioHandle *handle = (AsioHandle *) stream_.apiHandle; ASIOError result = ASIOStart(); if ( result != ASE_OK ) { @@ -3689,18 +3781,32 @@ static const char* getAsioErrorString( ASIOError result ) #ifndef INITGUID #define INITGUID #endif + +#include +#include +#include +#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 -#pragma comment( lib, "mfplat.lib" ) -#pragma comment( lib, "wmcodecdspuuid" ) +#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 //============================================================================= @@ -3759,8 +3865,9 @@ public: relOutIndex += bufferSize_; } - // "in" index can end on the "out" index but cannot begin at it - if ( inIndex_ <= relOutIndex && inIndexEnd > relOutIndex ) { + // the "IN" index CAN BEGIN at the "OUT" index + // the "IN" index CANNOT END at the "OUT" index + if ( inIndex_ < relOutIndex && inIndexEnd >= relOutIndex ) { return false; // not enough space between "in" index and "out" index } @@ -3820,8 +3927,9 @@ public: relInIndex += bufferSize_; } - // "out" index can begin at and end on the "in" index - if ( outIndex_ < relInIndex && outIndexEnd > relInIndex ) { + // the "OUT" index CANNOT BEGIN at the "IN" index + // the "OUT" index CAN END at the "IN" index + if ( outIndex_ <= relInIndex && outIndexEnd > relInIndex ) { return false; // not enough space between "out" index and "in" index } @@ -3887,10 +3995,13 @@ public: , _sampleRatio( ( float ) outSampleRate / inSampleRate ) , _transformUnk( NULL ) , _transform( NULL ) - , _resamplerProps( NULL ) , _mediaType( NULL ) , _inputMediaType( NULL ) , _outputMediaType( NULL ) + + #ifdef __IWMResamplerProps_FWD_DEFINED__ + , _resamplerProps( NULL ) + #endif { // 1. Initialization @@ -3903,10 +4014,12 @@ public: _transformUnk->QueryInterface( IID_PPV_ARGS( &_transform ) ); - _transformUnk->QueryInterface( IID_PPV_ARGS( &_resamplerProps ) ); - _resamplerProps->SetHalfFilterLength( 60 ); // best conversion quality + #ifdef __IWMResamplerProps_FWD_DEFINED__ + _transformUnk->QueryInterface( IID_PPV_ARGS( &_resamplerProps ) ); + _resamplerProps->SetHalfFilterLength( 60 ); // best conversion quality + #endif - // 3. Specify input / output format + // 3. Specify input / output format MFCreateMediaType( &_mediaType ); _mediaType->SetGUID( MF_MT_MAJOR_TYPE, MFMediaType_Audio ); @@ -3933,17 +4046,17 @@ public: // 4. Send stream start messages to Resampler - _transform->ProcessMessage( MFT_MESSAGE_COMMAND_FLUSH, NULL ); - _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, NULL ); - _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_START_OF_STREAM, NULL ); + _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, NULL ); - _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_END_STREAMING, NULL ); + _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_END_OF_STREAM, 0 ); + _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_END_STREAMING, 0 ); // 9. Cleanup @@ -3951,13 +4064,16 @@ public: SAFE_RELEASE( _transformUnk ); SAFE_RELEASE( _transform ); - SAFE_RELEASE( _resamplerProps ); 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 ) + void Convert( char* outBuffer, const char* inBuffer, unsigned int inSampleCount, unsigned int& outSampleCount, int maxOutSampleCount = -1 ) { unsigned int inputBufferSize = _bytesPerSample * _channelCount * inSampleCount; if ( _sampleRatio == 1 ) @@ -3968,7 +4084,15 @@ public: return; } - unsigned int outputBufferSize = ( unsigned int ) ceilf( inputBufferSize * _sampleRatio ) + ( _bytesPerSample * _channelCount ); + unsigned int outputBufferSize = 0; + if ( maxOutSampleCount != -1 ) + { + outputBufferSize = _bytesPerSample * _channelCount * maxOutSampleCount; + } + else + { + outputBufferSize = ( unsigned int ) ceilf( inputBufferSize * _sampleRatio ) + ( _bytesPerSample * _channelCount ); + } IMFMediaBuffer* rInBuffer; IMFSample* rInSample; @@ -4004,7 +4128,7 @@ public: DWORD rStatus; DWORD rBytes = outputBufferSize; // maximum bytes accepted per ProcessOutput - // 7.1 Create Sample object for output data + // 7.1 Create Sample object for output data memset( &rOutDataBuffer, 0, sizeof rOutDataBuffer ); MFCreateSample( &( rOutDataBuffer.pSample ) ); @@ -4047,10 +4171,13 @@ private: IUnknown* _transformUnk; IMFTransform* _transform; - IWMResamplerProps* _resamplerProps; IMFMediaType* _mediaType; IMFMediaType* _inputMediaType; IMFMediaType* _outputMediaType; + + #ifdef __IWMResamplerProps_FWD_DEFINED__ + IWMResamplerProps* _resamplerProps; + #endif }; //----------------------------------------------------------------------------- @@ -4089,10 +4216,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; } //----------------------------------------------------------------------------- @@ -4119,6 +4245,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 ); @@ -4468,6 +4597,10 @@ void RtApiWasapi::startStream( void ) return; } + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + // update stream state stream_.state = STREAM_RUNNING; @@ -4507,26 +4640,6 @@ void RtApiWasapi::stopStream( void ) // Wait for the last buffer to play before stopping. Sleep( 1000 * stream_.bufferSize / stream_.sampleRate ); - // stop capture client if applicable - if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) { - HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient->Stop(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::stopStream: Unable to stop capture stream."; - error( RtAudioError::DRIVER_ERROR ); - return; - } - } - - // stop render client if applicable - if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) { - HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient->Stop(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::stopStream: Unable to stop render stream."; - error( RtAudioError::DRIVER_ERROR ); - return; - } - } - // close thread handle if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { errorText_ = "RtApiWasapi::stopStream: Unable to close callback thread."; @@ -4557,26 +4670,6 @@ void RtApiWasapi::abortStream( void ) Sleep( 1 ); } - // stop capture client if applicable - if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) { - HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient->Stop(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::abortStream: Unable to stop capture stream."; - error( RtAudioError::DRIVER_ERROR ); - return; - } - } - - // stop render client if applicable - if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) { - HRESULT hr = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient->Stop(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::abortStream: Unable to stop render stream."; - error( RtAudioError::DRIVER_ERROR ); - return; - } - } - // close thread handle if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { errorText_ = "RtApiWasapi::abortStream: Unable to close callback thread."; @@ -4644,7 +4737,7 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne goto Exit; } - // determine whether index falls within capture or render devices + // if device index falls within capture devices if ( device >= renderDeviceCount ) { if ( mode != INPUT ) { errorType = RtAudioError::INVALID_USE; @@ -4664,28 +4757,66 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &captureAudioClient ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device audio client."; + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device audio client."; goto Exit; } hr = captureAudioClient->GetMixFormat( &deviceFormat ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device mix format."; + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device mix format."; goto Exit; } stream_.nDeviceChannels[mode] = deviceFormat->nChannels; captureAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); } - else { - if ( mode != OUTPUT ) { - errorType = RtAudioError::INVALID_USE; - errorText_ = "RtApiWasapi::probeDeviceOpen: Render device selected as input device."; + + // if device index falls within render devices and is configured for loopback + if ( device < renderDeviceCount && mode == INPUT ) + { + // if renderAudioClient is not initialised, initialise it now + IAudioClient*& renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; + if ( !renderAudioClient ) + { + probeDeviceOpen( device, OUTPUT, channels, firstChannel, sampleRate, format, bufferSize, options ); + } + + // retrieve captureAudioClient from devicePtr + IAudioClient*& captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient; + + hr = renderDevices->Item( device, &devicePtr ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device handle."; + goto Exit; + } + + hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, + NULL, ( void** ) &captureAudioClient ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device audio client."; goto Exit; } - // retrieve renderAudioClient from devicePtr + hr = captureAudioClient->GetMixFormat( &deviceFormat ); + if ( FAILED( hr ) ) { + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device mix format."; + goto Exit; + } + + stream_.nDeviceChannels[mode] = deviceFormat->nChannels; + captureAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); + } + + // if device index falls within render devices and is configured for output + if ( device < renderDeviceCount && mode == OUTPUT ) + { + // if renderAudioClient is already initialised, don't initialise it again IAudioClient*& renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; + if ( renderAudioClient ) + { + methodResult = SUCCESS; + goto Exit; + } hr = renderDevices->Item( device, &devicePtr ); if ( FAILED( hr ) ) { @@ -4696,13 +4827,13 @@ bool RtApiWasapi::probeDeviceOpen( unsigned int device, StreamMode mode, unsigne hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &renderAudioClient ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device audio client."; + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device audio client."; goto Exit; } hr = renderAudioClient->GetMixFormat( &deviceFormat ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device mix format."; + errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device mix format."; goto Exit; } @@ -4738,14 +4869,15 @@ 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 ) stream_.doConvertBuffer[mode] = true; if ( stream_.doConvertBuffer[mode] ) - setConvertInfo( mode, 0 ); + setConvertInfo( mode, firstChannel ); // Allocate necessary internal buffers bufferBytes = stream_.nUserChannels[mode] * stream_.bufferSize * formatBytes( stream_.userFormat ); @@ -4837,10 +4969,11 @@ void RtApiWasapi::wasapiThread() // declare local stream variables RtAudioCallback callback = ( RtAudioCallback ) stream_.callbackInfo.callback; BYTE* streamBuffer = NULL; - unsigned long captureFlags = 0; + DWORD captureFlags = 0; unsigned int bufferFrameCount = 0; unsigned int numFramesPadding = 0; unsigned int convBufferSize = 0; + bool loopbackEnabled = stream_.device[INPUT] == stream_.device[OUTPUT]; bool callbackPushed = true; bool callbackPulled = false; bool callbackStopped = false; @@ -4851,14 +4984,15 @@ void RtApiWasapi::wasapiThread() unsigned int convBuffSize = 0; unsigned int deviceBuffSize = 0; - errorText_.clear(); + std::string errorText; RtAudioError::Type errorType = RtAudioError::DRIVER_ERROR; // Attempt to assign "Pro Audio" characteristic to thread HMODULE AvrtDll = LoadLibrary( (LPCTSTR) "AVRT.dll" ); if ( AvrtDll ) { DWORD taskIndex = 0; - TAvSetMmThreadCharacteristicsPtr AvSetMmThreadCharacteristicsPtr = ( TAvSetMmThreadCharacteristicsPtr ) GetProcAddress( AvrtDll, "AvSetMmThreadCharacteristicsW" ); + TAvSetMmThreadCharacteristicsPtr AvSetMmThreadCharacteristicsPtr = + ( TAvSetMmThreadCharacteristicsPtr ) (void(*)()) GetProcAddress( AvrtDll, "AvSetMmThreadCharacteristicsW" ); AvSetMmThreadCharacteristicsPtr( L"Pro Audio", &taskIndex ); FreeLibrary( AvrtDll ); } @@ -4867,7 +5001,7 @@ void RtApiWasapi::wasapiThread() if ( captureAudioClient ) { hr = captureAudioClient->GetMixFormat( &captureFormat ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; goto Exit; } @@ -4878,51 +5012,66 @@ void RtApiWasapi::wasapiThread() captureSrRatio = ( ( float ) captureFormat->nSamplesPerSec / stream_.sampleRate ); - // initialize capture stream according to desire buffer size - float desiredBufferSize = stream_.bufferSize * captureSrRatio; - REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) desiredBufferSize * 10000000 / captureFormat->nSamplesPerSec ); - if ( !captureClient ) { hr = captureAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, - AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - desiredBufferPeriod, - desiredBufferPeriod, + loopbackEnabled ? AUDCLNT_STREAMFLAGS_LOOPBACK : AUDCLNT_STREAMFLAGS_EVENTCALLBACK, + 0, + 0, captureFormat, NULL ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to initialize capture audio client."; + errorText = "RtApiWasapi::wasapiThread: Unable to initialize capture audio client."; goto Exit; } hr = captureAudioClient->GetService( __uuidof( IAudioCaptureClient ), ( void** ) &captureClient ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve capture client handle."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve capture client handle."; goto Exit; } - // configure captureEvent to trigger on every available capture buffer - captureEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); - if ( !captureEvent ) { - errorType = RtAudioError::SYSTEM_ERROR; - errorText_ = "RtApiWasapi::wasapiThread: Unable to create capture event."; - goto Exit; + // don't configure captureEvent if in loopback mode + if ( !loopbackEnabled ) + { + // configure captureEvent to trigger on every available capture buffer + captureEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); + if ( !captureEvent ) { + errorType = RtAudioError::SYSTEM_ERROR; + errorText = "RtApiWasapi::wasapiThread: Unable to create capture event."; + goto Exit; + } + + hr = captureAudioClient->SetEventHandle( captureEvent ); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to set capture event handle."; + goto Exit; + } + + ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent = captureEvent; } - hr = captureAudioClient->SetEventHandle( captureEvent ); + ( ( WasapiHandle* ) stream_.apiHandle )->captureClient = captureClient; + + // reset the capture stream + hr = captureAudioClient->Reset(); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to set capture event handle."; + errorText = "RtApiWasapi::wasapiThread: Unable to reset capture stream."; goto Exit; } - ( ( WasapiHandle* ) stream_.apiHandle )->captureClient = captureClient; - ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent = captureEvent; + // start the capture stream + hr = captureAudioClient->Start(); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to start capture stream."; + goto Exit; + } } unsigned int inBufferSize = 0; hr = captureAudioClient->GetBufferSize( &inBufferSize ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to get capture buffer size."; + errorText = "RtApiWasapi::wasapiThread: Unable to get capture buffer size."; goto Exit; } @@ -4932,27 +5081,13 @@ void RtApiWasapi::wasapiThread() // set captureBuffer size captureBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[INPUT] ) ); - - // reset the capture stream - hr = captureAudioClient->Reset(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to reset capture stream."; - goto Exit; - } - - // start the capture stream - hr = captureAudioClient->Start(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to start capture stream."; - goto Exit; - } } // start render stream if applicable if ( renderAudioClient ) { hr = renderAudioClient->GetMixFormat( &renderFormat ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; goto Exit; } @@ -4963,26 +5098,22 @@ void RtApiWasapi::wasapiThread() renderSrRatio = ( ( float ) renderFormat->nSamplesPerSec / stream_.sampleRate ); - // initialize render stream according to desire buffer size - float desiredBufferSize = stream_.bufferSize * renderSrRatio; - REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) desiredBufferSize * 10000000 / renderFormat->nSamplesPerSec ); - if ( !renderClient ) { hr = renderAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, - desiredBufferPeriod, - desiredBufferPeriod, + 0, + 0, renderFormat, NULL ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to initialize render audio client."; + errorText = "RtApiWasapi::wasapiThread: Unable to initialize render audio client."; goto Exit; } hr = renderAudioClient->GetService( __uuidof( IAudioRenderClient ), ( void** ) &renderClient ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render client handle."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render client handle."; goto Exit; } @@ -4990,24 +5121,38 @@ void RtApiWasapi::wasapiThread() renderEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); if ( !renderEvent ) { errorType = RtAudioError::SYSTEM_ERROR; - errorText_ = "RtApiWasapi::wasapiThread: Unable to create render event."; + errorText = "RtApiWasapi::wasapiThread: Unable to create render event."; goto Exit; } hr = renderAudioClient->SetEventHandle( renderEvent ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to set render event handle."; + errorText = "RtApiWasapi::wasapiThread: Unable to set render event handle."; goto Exit; } ( ( WasapiHandle* ) stream_.apiHandle )->renderClient = renderClient; ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent = renderEvent; + + // reset the render stream + hr = renderAudioClient->Reset(); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to reset render stream."; + goto Exit; + } + + // start the render stream + hr = renderAudioClient->Start(); + if ( FAILED( hr ) ) { + errorText = "RtApiWasapi::wasapiThread: Unable to start render stream."; + goto Exit; + } } unsigned int outBufferSize = 0; hr = renderAudioClient->GetBufferSize( &outBufferSize ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to get render buffer size."; + errorText = "RtApiWasapi::wasapiThread: Unable to get render buffer size."; goto Exit; } @@ -5017,20 +5162,6 @@ void RtApiWasapi::wasapiThread() // set renderBuffer size renderBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[OUTPUT] ) ); - - // reset the render stream - hr = renderAudioClient->Reset(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to reset render stream."; - goto Exit; - } - - // start the render stream - hr = renderAudioClient->Start(); - if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to start render stream."; - goto Exit; - } } // malloc buffer memory @@ -5054,11 +5185,11 @@ void RtApiWasapi::wasapiThread() } convBuffSize *= 2; // allow overflow for *SrRatio remainders - convBuffer = ( char* ) malloc( convBuffSize ); - stream_.deviceBuffer = ( char* ) malloc( deviceBuffSize ); + convBuffer = ( char* ) calloc( convBuffSize, 1 ); + stream_.deviceBuffer = ( char* ) calloc( deviceBuffSize, 1 ); if ( !convBuffer || !stream_.deviceBuffer ) { errorType = RtAudioError::MEMORY_ERROR; - errorText_ = "RtApiWasapi::wasapiThread: Error allocating device buffer memory."; + errorText = "RtApiWasapi::wasapiThread: Error allocating device buffer memory."; goto Exit; } @@ -5071,23 +5202,39 @@ void RtApiWasapi::wasapiThread() // 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( convBuffer, - ( unsigned int ) ( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT], - stream_.deviceFormat[INPUT] ); + if ( captureAudioClient ) + { + int samplesToPull = ( unsigned int ) floorf( stream_.bufferSize * captureSrRatio ); + + 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; + } - if ( callbackPulled ) { // Convert callback buffer to user sample rate - convertBufferWasapi( stream_.deviceBuffer, - convBuffer, - stream_.nDeviceChannels[INPUT], - captureFormat->nSamplesPerSec, - stream_.sampleRate, - ( unsigned int ) ( stream_.bufferSize * captureSrRatio ), - convBufferSize, - stream_.deviceFormat[INPUT] ); + unsigned int deviceBufferOffset = convBufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); + unsigned int convSamples = 0; + + captureResampler->Convert( stream_.deviceBuffer + deviceBufferOffset, + convBuffer, + samplesToPull, + convSamples, + convBufferSize == 0 ? -1 : stream_.bufferSize - convBufferSize ); + convBufferSize += convSamples; + samplesToPull = 1; // now pull one sample at a time until we have stream_.bufferSize samples + } + + if ( callbackPulled ) + { if ( stream_.doConvertBuffer[INPUT] ) { // Convert callback buffer to user format convertBuffer( stream_.userBuffer[INPUT], @@ -5122,18 +5269,21 @@ void RtApiWasapi::wasapiThread() captureFlags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY ? RTAUDIO_INPUT_OVERFLOW : 0, stream_.callbackInfo.userData ); + // tick stream time + RtApi::tickStreamTime(); + // Handle return value from callback if ( callbackResult == 1 ) { // instantiate a thread to stop this thread HANDLE threadHandle = CreateThread( NULL, 0, stopWasapiThread, this, 0, NULL ); if ( !threadHandle ) { errorType = RtAudioError::THREAD_ERROR; - errorText_ = "RtApiWasapi::wasapiThread: Unable to instantiate stream stop thread."; + errorText = "RtApiWasapi::wasapiThread: Unable to instantiate stream stop thread."; goto Exit; } else if ( !CloseHandle( threadHandle ) ) { errorType = RtAudioError::THREAD_ERROR; - errorText_ = "RtApiWasapi::wasapiThread: Unable to close stream stop thread handle."; + errorText = "RtApiWasapi::wasapiThread: Unable to close stream stop thread handle."; goto Exit; } @@ -5144,12 +5294,12 @@ void RtApiWasapi::wasapiThread() HANDLE threadHandle = CreateThread( NULL, 0, abortWasapiThread, this, 0, NULL ); if ( !threadHandle ) { errorType = RtAudioError::THREAD_ERROR; - errorText_ = "RtApiWasapi::wasapiThread: Unable to instantiate stream abort thread."; + errorText = "RtApiWasapi::wasapiThread: Unable to instantiate stream abort thread."; goto Exit; } else if ( !CloseHandle( threadHandle ) ) { errorType = RtAudioError::THREAD_ERROR; - errorText_ = "RtApiWasapi::wasapiThread: Unable to close stream abort thread handle."; + errorText = "RtApiWasapi::wasapiThread: Unable to close stream abort thread handle."; goto Exit; } @@ -5164,24 +5314,32 @@ void RtApiWasapi::wasapiThread() // 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] ); - } + } + else { + // no further conversion, simple copy userBuffer to deviceBuffer + memcpy( stream_.deviceBuffer, + stream_.userBuffer[OUTPUT], + stream_.bufferSize * stream_.nUserChannels[OUTPUT] * formatBytes( stream_.userFormat ) ); + } - // Convert callback buffer to stream sample rate - convertBufferWasapi( convBuffer, - stream_.deviceBuffer, - stream_.nDeviceChannels[OUTPUT], - stream_.sampleRate, - renderFormat->nSamplesPerSec, - stream_.bufferSize, - convBufferSize, - stream_.deviceFormat[OUTPUT] ); + // Convert callback buffer to stream sample rate + renderResampler->Convert( convBuffer, + stream_.deviceBuffer, + stream_.bufferSize, + convBufferSize ); + } // Push callback buffer into outputBuffer callbackPushed = renderBuffer.pushBuffer( convBuffer, @@ -5202,7 +5360,7 @@ void RtApiWasapi::wasapiThread() if ( captureAudioClient ) { // if the callback input buffer was not pulled from captureBuffer, wait for next capture event if ( !callbackPulled ) { - WaitForSingleObject( captureEvent, INFINITE ); + WaitForSingleObject( loopbackEnabled ? renderEvent : captureEvent, INFINITE ); } // Get capture buffer from stream @@ -5210,7 +5368,7 @@ void RtApiWasapi::wasapiThread() &bufferFrameCount, &captureFlags, NULL, NULL ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve capture buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve capture buffer."; goto Exit; } @@ -5223,7 +5381,7 @@ void RtApiWasapi::wasapiThread() // Release capture buffer hr = captureClient->ReleaseBuffer( bufferFrameCount ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; goto Exit; } } @@ -5232,7 +5390,7 @@ void RtApiWasapi::wasapiThread() // Inform WASAPI that capture was unsuccessful hr = captureClient->ReleaseBuffer( 0 ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; goto Exit; } } @@ -5242,7 +5400,7 @@ void RtApiWasapi::wasapiThread() // Inform WASAPI that capture was unsuccessful hr = captureClient->ReleaseBuffer( 0 ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; goto Exit; } } @@ -5264,13 +5422,13 @@ void RtApiWasapi::wasapiThread() // Get render buffer from stream hr = renderAudioClient->GetBufferSize( &bufferFrameCount ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer size."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer size."; goto Exit; } hr = renderAudioClient->GetCurrentPadding( &numFramesPadding ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer padding."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer padding."; goto Exit; } @@ -5279,7 +5437,7 @@ void RtApiWasapi::wasapiThread() if ( bufferFrameCount != 0 ) { hr = renderClient->GetBuffer( bufferFrameCount, &streamBuffer ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer."; goto Exit; } @@ -5292,7 +5450,7 @@ void RtApiWasapi::wasapiThread() // Release render buffer hr = renderClient->ReleaseBuffer( bufferFrameCount, 0 ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to release render buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to release render buffer."; goto Exit; } } @@ -5301,7 +5459,7 @@ void RtApiWasapi::wasapiThread() // Inform WASAPI that render was unsuccessful hr = renderClient->ReleaseBuffer( 0, 0 ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to release render buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to release render buffer."; goto Exit; } } @@ -5311,7 +5469,7 @@ void RtApiWasapi::wasapiThread() // Inform WASAPI that render was unsuccessful hr = renderClient->ReleaseBuffer( 0, 0 ); if ( FAILED( hr ) ) { - errorText_ = "RtApiWasapi::wasapiThread: Unable to release render buffer."; + errorText = "RtApiWasapi::wasapiThread: Unable to release render buffer."; goto Exit; } } @@ -5319,9 +5477,9 @@ 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(); } } @@ -5332,16 +5490,19 @@ Exit: CoTaskMemFree( renderFormat ); free ( convBuffer ); + delete renderResampler; + delete captureResampler; CoUninitialize(); // update stream state stream_.state = STREAM_STOPPED; - if ( errorText_.empty() ) - return; - else + if ( !errorText.empty() ) + { + errorText_ = errorText; error( errorType ); + } } //******************** End of __WINDOWS_WASAPI__ *********************// @@ -6238,6 +6399,10 @@ void RtApiDs :: startStream() return; } + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + DsHandle *handle = (DsHandle *) stream_.apiHandle; // Increase scheduler frequency on lesser windows (a side-effect of @@ -7005,7 +7170,7 @@ unsigned int RtApiAlsa :: getDeviceCount( void ) unsigned nDevices = 0; int result, subdevice, card; char name[64]; - snd_ctl_t *handle; + snd_ctl_t *handle = 0; // Count cards and devices card = -1; @@ -7014,6 +7179,7 @@ unsigned int RtApiAlsa :: getDeviceCount( void ) sprintf( name, "hw:%d", card ); result = snd_ctl_open( &handle, name, 0 ); if ( result < 0 ) { + handle = 0; errorStream_ << "RtApiAlsa::getDeviceCount: control open, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); @@ -7033,7 +7199,8 @@ unsigned int RtApiAlsa :: getDeviceCount( void ) nDevices++; } nextcard: - snd_ctl_close( handle ); + if ( handle ) + snd_ctl_close( handle ); snd_card_next( &card ); } @@ -7054,7 +7221,7 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) unsigned nDevices = 0; int result, subdevice, card; char name[64]; - snd_ctl_t *chandle; + snd_ctl_t *chandle = 0; // Count cards and devices card = -1; @@ -7064,6 +7231,7 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) sprintf( name, "hw:%d", card ); result = snd_ctl_open( &chandle, name, SND_CTL_NONBLOCK ); if ( result < 0 ) { + chandle = 0; errorStream_ << "RtApiAlsa::getDeviceInfo: control open, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RtAudioError::WARNING ); @@ -7086,7 +7254,8 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) nDevices++; } nextcard: - snd_ctl_close( chandle ); + if ( chandle ) + snd_ctl_close( chandle ); snd_card_next( &card ); } @@ -7395,10 +7564,12 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne if ( result == 0 ) { if ( nDevices == device ) { strcpy( name, "default" ); + snd_ctl_close( chandle ); goto foundDevice; } nDevices++; } + snd_ctl_close( chandle ); if ( nDevices == 0 ) { // This should not happen because a check is made before this function is called. @@ -7798,7 +7969,7 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne pthread_attr_t attr; pthread_attr_init( &attr ); pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); -#ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) +#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { stream_.callbackInfo.doRealtime = true; struct sched_param param; @@ -7928,6 +8099,10 @@ void RtApiAlsa :: startStream() MUTEX_LOCK( &stream_.mutex ); + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + int result = 0; snd_pcm_state_t state; AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; @@ -8247,7 +8422,7 @@ static void *alsaCallbackHandler( void *ptr ) RtApiAlsa *object = (RtApiAlsa *) info->object; bool *isRunning = &info->isRunning; -#ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) +#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if ( info->doRealtime ) { std::cerr << "RtAudio alsa: " << (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") << @@ -8335,7 +8510,7 @@ static void *pulseaudio_callback( void * user ) RtApiPulse *context = static_cast( cbi->object ); volatile bool *isRunning = &cbi->isRunning; -#ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) +#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if (cbi->doRealtime) { std::cerr << "RtAudio pulse: " << (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") << @@ -8499,6 +8674,10 @@ void RtApiPulse::startStream( void ) MUTEX_LOCK( &stream_.mutex ); + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + stream_.state = STREAM_RUNNING; pah->runnable = true; @@ -8524,15 +8703,18 @@ void RtApiPulse::stopStream( void ) stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); - if ( pah && pah->s_play ) { - int pa_error; - if ( pa_simple_drain( pah->s_play, &pa_error ) < 0 ) { - errorStream_ << "RtApiPulse::stopStream: error draining output device, " << - pa_strerror( pa_error ) << "."; - errorText_ = errorStream_.str(); - MUTEX_UNLOCK( &stream_.mutex ); - error( RtAudioError::SYSTEM_ERROR ); - return; + if ( pah ) { + pah->runnable = false; + if ( pah->s_play ) { + int pa_error; + if ( pa_simple_drain( pah->s_play, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::stopStream: error draining output device, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } } } @@ -8558,15 +8740,18 @@ void RtApiPulse::abortStream( void ) stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); - if ( pah && pah->s_play ) { - int pa_error; - if ( pa_simple_flush( pah->s_play, &pa_error ) < 0 ) { - errorStream_ << "RtApiPulse::abortStream: error flushing output device, " << - pa_strerror( pa_error ) << "."; - errorText_ = errorStream_.str(); - MUTEX_UNLOCK( &stream_.mutex ); - error( RtAudioError::SYSTEM_ERROR ); - return; + if ( pah ) { + pah->runnable = false; + if ( pah->s_play ) { + int pa_error; + if ( pa_simple_flush( pah->s_play, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::abortStream: error flushing output device, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } } } @@ -8641,6 +8826,8 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, stream_.doConvertBuffer[mode] = true; if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) stream_.doConvertBuffer[mode] = true; + if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] ) + stream_.doConvertBuffer[mode] = true; // Allocate necessary internal buffers. bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); @@ -8738,7 +8925,7 @@ bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, pthread_attr_t attr; pthread_attr_init( &attr ); pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); -#ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) +#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { stream_.callbackInfo.doRealtime = true; struct sched_param param; @@ -9359,7 +9546,7 @@ bool RtApiOss :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned pthread_attr_t attr; pthread_attr_init( &attr ); pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); -#ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) +#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { stream_.callbackInfo.doRealtime = true; struct sched_param param; @@ -9483,6 +9670,10 @@ void RtApiOss :: startStream() MUTEX_LOCK( &stream_.mutex ); + #if defined( HAVE_GETTIMEOFDAY ) + gettimeofday( &stream_.lastTickTimestamp, NULL ); + #endif + stream_.state = STREAM_RUNNING; // No need to do anything else here ... OSS automatically starts @@ -9750,7 +9941,7 @@ static void *ossCallbackHandler( void *ptr ) RtApiOss *object = (RtApiOss *) info->object; bool *isRunning = &info->isRunning; -#ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) +#ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if (info->doRealtime) { std::cerr << "RtAudio oss: " << (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") <<