Merge remote-tracking branch 'upstream/pr/136'
authorStephen Sinclair <radarsat1@gmail.com>
Tue, 16 Oct 2018 13:01:30 +0000 (15:01 +0200)
committerStephen Sinclair <radarsat1@gmail.com>
Tue, 16 Oct 2018 13:01:30 +0000 (15:01 +0200)
1  2 
CMakeLists.txt
RtAudio.cpp
RtAudio.h

diff --combined CMakeLists.txt
index 77ffdb07b852e7666dc35e660fd4bde5621191f7,44206eb06e00e49a0b188307dfd2029b0fbdc351..d220bc7500f8b943ecbfeb503281e1941411d4a0
@@@ -178,7 -178,7 +178,7 @@@ endif(
  # WASAPI
  if (RTAUDIO_API_WASAPI)
    set(NEED_WIN32LIBS ON)
 -  list(APPEND LINKLIBS uuid ksuser)
 +  list(APPEND LINKLIBS ksuser mfplat mfuuid wmcodecdspuuid)
    list(APPEND API_DEFS "-D__WINDOWS_WASAPI__")
    list(APPEND API_LIST "wasapi")
  endif()
@@@ -216,6 -216,7 +216,7 @@@ if(BUILD_SHARED_LIBS
  
    # Set compile-time definitions
    target_compile_definitions(rtaudio PRIVATE ${API_DEFS})
+   target_compile_definitions(rtaudio PRIVATE RTAUDIO_EXPORT)
  
    target_link_libraries(rtaudio ${LINKLIBS})
  endif()
@@@ -257,7 -258,6 +258,7 @@@ message(STATUS "Compiling with support 
  # PkgConfig file
  string(REPLACE ";" " " req "${PKGCONFIG_REQUIRES}")
  string(REPLACE ";" " " api "${API_DEFS}")
 +set(prefix ${CMAKE_INSTALL_PREFIX})
  configure_file("rtaudio.pc.in" "rtaudio.pc" @ONLY)
  
  # Add install rule.
@@@ -279,10 -279,10 +280,10 @@@ else(
  endif()
  
  # Create CMake configuration export file.
 -file(WRITE ${CMAKE_BINARY_DIR}/RtAudioConfig.cmake "include(\${CMAKE_CURRENT_LIST_DIR}/RtAudioTargets.cmake)")
 +file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/RtAudioConfig.cmake "include(\${CMAKE_CURRENT_LIST_DIR}/RtAudioTargets.cmake)")
  
  # Install CMake configuration export file.
 -install(FILES ${CMAKE_BINARY_DIR}/RtAudioConfig.cmake
 +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/RtAudioConfig.cmake
          DESTINATION ${RTAUDIO_CMAKE_DESTINATION})
  
  # Export library target (build-tree).
@@@ -296,12 -296,12 +297,12 @@@ install(EXPORT RtAudioTarget
  
  # Configure uninstall target.
  configure_file(
 -    "${CMAKE_SOURCE_DIR}/cmake/RtAudioConfigUninstall.cmake.in"
 -    "${CMAKE_BINARY_DIR}/RtAudioConfigUninstall.cmake" @ONLY)
 +    "${CMAKE_CURRENT_SOURCE_DIR}/cmake/RtAudioConfigUninstall.cmake.in"
 +    "${CMAKE_CURRENT_BINARY_DIR}/RtAudioConfigUninstall.cmake" @ONLY)
  
  # Create uninstall target.
  add_custom_target(uninstall
 -    COMMAND ${CMAKE_COMMAND} -P ${CMAKE_BINARY_DIR}/RtAudioConfigUninstall.cmake)
 +    COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/RtAudioConfigUninstall.cmake)
  
  install(
      FILES ${CMAKE_CURRENT_BINARY_DIR}/rtaudio.pc
diff --combined RtAudio.cpp
index 77bd4b6594ac36c9661037dabef2bb1265675d2a,1e480d4ef7883a702036b1cbda407425983e35f9..5bc1055c718b55b74354a698f8379b421561078e
@@@ -1,4 -1,4 +1,4 @@@
 -/************************************************************************/
 +/************************************************************************/
  /*! \class RtAudio
      \brief Realtime audio i/o C++ classes.
  
@@@ -98,39 -98,95 +98,95 @@@ std::string RtAudio :: getVersion( voi
    return RTAUDIO_VERSION;
  }
  
- void RtAudio :: getCompiledApi( std::vector<RtAudio::Api> &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<bool b> class StaticAssert { private: StaticAssert() {} };
+ template<> class StaticAssert<true>{ public: StaticAssert() {} };
+ class StaticAssertions { StaticAssertions() {
+   StaticAssert<rtaudio_num_api_names == RtAudio::NUM_APIS>();
+ }};
+ void RtAudio :: getCompiledApi( std::vector<RtAudio::Api> &apis )
+ {
+   apis = std::vector<RtAudio::Api>(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 +2029,7 @@@ unsigned int RtApiJack :: getDeviceCoun
    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 +2068,7 @@@ RtAudio::DeviceInfo RtApiJack :: getDev
    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;
    // 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 );
  
    // 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 +2224,7 @@@ bool RtApiJack :: probeDeviceOpen( unsi
    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;
      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.
    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);
@@@ -2455,7 -2509,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;
  
    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;
@@@ -3689,32 -3743,11 +3745,32 @@@ static const char* getAsioErrorString( 
  #ifndef INITGUID
    #define INITGUID
  #endif
 +
 +#include <mfapi.h>
 +#include <mferror.h>
 +#include <mfplay.h>
 +#include <mftransform.h>
 +#include <wmcodecdsp.h>
 +
  #include <audioclient.h>
  #include <avrt.h>
  #include <mmdeviceapi.h>
  #include <functiondiscoverykeys_devpkey.h>
 -#include <sstream>
 +
 +#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
  
  //=============================================================================
  
@@@ -3888,198 -3921,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
  {
@@@ -4114,9 -3955,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;
  }
  
  //-----------------------------------------------------------------------------
@@@ -4143,9 -3985,6 +4199,9 @@@ unsigned int RtApiWasapi::getDeviceCoun
    IMMDeviceCollection* captureDevices = NULL;
    IMMDeviceCollection* renderDevices = NULL;
  
 +  if ( !deviceEnumerator_ )
 +    return 0;
 +
    // Count capture devices
    errorText_.clear();
    HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices );
@@@ -4347,14 -4186,11 +4403,14 @@@ RtAudio::DeviceInfo RtApiWasapi::getDev
      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;
@@@ -4631,6 -4467,7 +4687,6 @@@ bool RtApiWasapi::probeDeviceOpen( unsi
    WAVEFORMATEX* deviceFormat = NULL;
    unsigned int bufferBytes;
    stream_.state = STREAM_STOPPED;
 -  RtAudio::DeviceInfo deviceInfo;
  
    // create API Handle if not already created
    if ( !stream_.apiHandle )
      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 ) {
    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;
    // 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 )
@@@ -4855,12 -4705,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;
    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();
        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,
      }
  
      // 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
        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,
      }
  
      // 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
      }
    }
  
 -  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;
        // 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 )
 +      {
 +        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;
  
 -      if ( captureAudioClient ) {
 -        // Pull callback buffer from inputBuffer
 -        callbackPulled = captureBuffer.pullBuffer( stream_.deviceBuffer,
 -                                                   ( unsigned int ) stream_.bufferSize * stream_.nDeviceChannels[INPUT],
 -                                                   stream_.deviceFormat[INPUT] );
 +          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],
      // 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 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] );
  
 -    if ( renderAudioClient && callbackPulled ) {
 -      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 {
  
      // 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();
      }
@@@ -5384,17 -5153,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__ *********************//
diff --combined RtAudio.h
index f37b9c268df86f01ca4cfd5ed014548909056a2c,7eb7ac8da628bfb80ac681a4895795071f670f90..d6038dbc976ad23d2e10fe1bae1d64be08f3df5f
+++ b/RtAudio.h
  #define RTAUDIO_VERSION "5.0.0"
  
  #if defined _WIN32 || defined __CYGWIN__
-   #define RTAUDIO_DLL_PUBLIC
+   #if defined(RTAUDIO_EXPORT)
+     #define RTAUDIO_DLL_PUBLIC __declspec(dllexport)
+   #else
+     #define RTAUDIO_DLL_PUBLIC
+   #endif
  #else
    #if __GNUC__ >= 4
      #define RTAUDIO_DLL_PUBLIC __attribute__( (visibility( "default" )) )
@@@ -285,7 -289,8 +289,8 @@@ class RTAUDIO_DLL_PUBLIC RtAudi
      WINDOWS_WASAPI, /*!< The Microsoft WASAPI API. */
      WINDOWS_ASIO,   /*!< The Steinberg Audio Stream I/O API. */
      WINDOWS_DS,     /*!< The Microsoft Direct Sound API. */
-     RTAUDIO_DUMMY   /*!< A compilable but non-functional API. */
+     RTAUDIO_DUMMY,  /*!< A compilable but non-functional API. */
+     NUM_APIS        /*!< Number of values in this enum. */
    };
  
    //! The public device information structure for returning queried values.
    */
    static void getCompiledApi( std::vector<RtAudio::Api> &apis );
  
+   //! Return the name of a specified compiled audio API.
+   /*!
+     This obtains a short lower-case name used for identification purposes.
+     This value is guaranteed to remain identical across library versions.
+     If the API is unknown, this function will return the empty string.
+   */
+   static std::string getApiName( RtAudio::Api api );
+   //! Return the display name of a specified compiled audio API.
+   /*!
+     This obtains a long name used for display purposes.
+     If the API is unknown, this function will return the empty string.
+   */
+   static std::string getApiDisplayName( RtAudio::Api api );
+   //! Return the compiled audio API having the given name.
+   /*!
+     A case insensitive comparison will check the specified name
+     against the list of compiled APIs, and return the one which
+     matches. On failure, the function returns UNSPECIFIED.
+   */
+   static RtAudio::Api getCompiledApiByName( const std::string &name );
    //! The class constructor.
    /*!
      The constructor performs minor initialization tasks.  An exception
@@@ -1013,7 -1041,7 +1041,7 @@@ class RtApiWasapi : public RtAp
  {
  public:
    RtApiWasapi();
 -  ~RtApiWasapi();
 +  virtual ~RtApiWasapi();
  
    RtAudio::Api getCurrentApi( void ) { return RtAudio::WINDOWS_WASAPI; }
    unsigned int getDeviceCount( void );