-/************************************************************************/
+/************************************************************************/
/*! \class RtAudio
\brief Realtime audio i/o C++ classes.
#include <climits>
#include <cmath>
#include <algorithm>
+#include <cmath>
// Static variable definitions.
const unsigned int RtApi::MAX_SAMPLE_RATES = 14;
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 );
-#endif
-#if defined(__LINUX_ALSA__)
- apis.push_back( LINUX_ALSA );
+ RtAudio::UNIX_JACK,
#endif
#if defined(__LINUX_PULSE__)
- apis.push_back( LINUX_PULSE );
+ RtAudio::LINUX_PULSE,
+#endif
+#if defined(__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 )
struct timeval then;
struct timeval now;
- if ( stream_.state != STREAM_RUNNING || stream_.streamTime == 0.0 )
+ if ( stream_.state != STREAM_RUNNING || (stream_.lastTickTimestamp.tv_sec == 0 && stream_.lastTickTimestamp.tv_usec == 0) )
return stream_.streamTime;
gettimeofday( &now, NULL );
then = stream_.lastTickTimestamp;
return stream_.streamTime +
((now.tv_sec + 0.000001 * now.tv_usec) -
- (then.tv_sec + 0.000001 * then.tv_usec));
+ (then.tv_sec + 0.000001 * then.tv_usec));
#else
return stream_.streamTime;
#endif
return stream_.sampleRate;
}
+void RtApi :: startStream( void )
+{
+#if defined( HAVE_GETTIMEOFDAY )
+ stream_.lastTickTimestamp.tv_sec = 0;
+ stream_.lastTickTimestamp.tv_usec = 0;
+#endif
+}
+
// *************************************************** //
//
void RtApiCore :: startStream( void )
{
verifyStream();
+ RtApi::startStream();
if ( stream_.state == STREAM_RUNNING ) {
errorText_ = "RtApiCore::startStream(): the stream is already running!";
error( RtAudioError::WARNING );
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 ) {
channelsLeft -= streamChannels;
}
}
-
+
if ( stream_.doConvertBuffer[1] ) { // convert from our internal "device" buffer
convertBuffer( stream_.userBuffer[1],
stream_.deviceBuffer,
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;
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 );
static int jackXrun( void *infoPointer )
{
- JackHandle *handle = (JackHandle *) infoPointer;
+ JackHandle *handle = *((JackHandle **) infoPointer);
if ( handle->ports[0] ) handle->xrun[0] = true;
if ( handle->ports[1] ) handle->xrun[1] = true;
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);
else {
stream_.mode = mode;
jack_set_process_callback( handle->client, jackCallbackHandler, (void *) &stream_.callbackInfo );
- jack_set_xrun_callback( handle->client, jackXrun, (void *) &handle );
+ jack_set_xrun_callback( handle->client, jackXrun, (void *) &stream_.apiHandle );
jack_on_shutdown( handle->client, jackShutdown, (void *) &stream_.callbackInfo );
}
void RtApiJack :: startStream( void )
{
verifyStream();
+ RtApi::startStream();
if ( stream_.state == STREAM_RUNNING ) {
errorText_ = "RtApiJack::startStream(): the stream is already running!";
error( RtAudioError::WARNING );
return;
}
+ #if defined( HAVE_GETTIMEOFDAY )
+ gettimeofday( &stream_.lastTickTimestamp, NULL );
+ #endif
+
JackHandle *handle = (JackHandle *) stream_.apiHandle;
int result = jack_activate( handle->client );
if ( result ) {
// 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;
// CoInitialize beforehand, but it must be for appartment threading
// (in which case, CoInitilialize will return S_FALSE here).
coInitialized_ = false;
- HRESULT hr = CoInitialize( NULL );
+ HRESULT hr = CoInitialize( NULL );
if ( FAILED(hr) ) {
errorText_ = "RtApiAsio::ASIO requires a single-threaded appartment. Call CoInitializeEx(0,COINIT_APARTMENTTHREADED)";
error( RtAudioError::WARNING );
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 );
errorText_ = errorStream_.str();
goto error;
}
- buffersAllocated = true;
+ buffersAllocated = true;
stream_.state = STREAM_STOPPED;
// Set flags for buffer conversion.
void RtApiAsio :: startStream()
{
verifyStream();
+ RtApi::startStream();
if ( stream_.state == STREAM_RUNNING ) {
errorText_ = "RtApiAsio::startStream(): the stream is already running!";
error( RtAudioError::WARNING );
return;
}
+ #if defined( HAVE_GETTIMEOFDAY )
+ gettimeofday( &stream_.lastTickTimestamp, NULL );
+ #endif
+
AsioHandle *handle = (AsioHandle *) stream_.apiHandle;
ASIOError result = ASIOStart();
if ( result != ASE_OK ) {
static const char* getAsioErrorString( ASIOError result )
{
- struct Messages
+ struct Messages
{
ASIOError value;
const char*message;
};
- static const Messages m[] =
+ static const Messages m[] =
{
{ ASE_NotPresent, "Hardware input or output is not present or available." },
{ ASE_HWMalfunction, "Hardware is malfunctioning." },
#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
//=============================================================================
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
}
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
}
//-----------------------------------------------------------------------------
+// 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 UserO ut->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
{
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;
}
//-----------------------------------------------------------------------------
IMMDeviceCollection* captureDevices = NULL;
IMMDeviceCollection* renderDevices = NULL;
+ if ( !deviceEnumerator_ )
+ return 0;
+
// Count capture devices
errorText_.clear();
HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices );
hr = audioClient->GetMixFormat( &deviceFormat );
if ( FAILED( hr ) ) {
- errorText_ = "RtApiWasapi::getDeviceInfo: Unable to retrieve device mix format.";
+ char error[256];
+ snprintf(error, sizeof(error), "RtApiWasapi::getDeviceInfo: Unable to retrieve device mix format (%d)", hr);
+ errorText_ = error;
goto Exit;
}
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;
void RtApiWasapi::startStream( void )
{
verifyStream();
+ RtApi::startStream();
if ( stream_.state == STREAM_RUNNING ) {
errorText_ = "RtApiWasapi::startStream: The stream is already running.";
return;
}
+ #if defined( HAVE_GETTIMEOFDAY )
+ gettimeofday( &stream_.lastTickTimestamp, NULL );
+ #endif
+
// update stream state
stream_.state = STREAM_RUNNING;
// 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.";
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.";
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::stringstring 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 index falls within capture devices
if ( device >= renderDeviceCount ) {
if ( mode != INPUT ) {
errorType = RtAudioError::INVALID_USE;
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;
+ }
+
+ hr = captureAudioClient->GetMixFormat( &deviceFormat );
+ if ( FAILED( hr ) ) {
+ errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device mix format.";
goto Exit;
}
- // retrieve renderAudioClient from devicePtr
+ 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 ) ) {
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;
}
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 )
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 loopbackEnabled = stream_.device[INPUT] == stream_.device[OUTPUT];
+ 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();
+ 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 );
}
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;
}
- // initialize capture stream according to desire buffer size
- REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) stream_.bufferSize * 10000000 / captureFormat->nSamplesPerSec );
+ // 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 );
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;
}
// 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
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;
}
- // initialize render stream according to desire buffer size
- REFERENCE_TIME desiredBufferPeriod = ( REFERENCE_TIME ) ( ( float ) stream_.bufferSize * 10000000 / renderFormat->nSamplesPerSec );
+ // 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 );
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;
}
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;
}
// 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
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;
- }
}
- 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] ) );
}
- stream_.deviceBuffer = ( char* ) malloc( deviceBuffSize );
- if ( !stream_.deviceBuffer ) {
+ convBuffSize *= 2; // allow overflow for *SrRatio remainders
+ 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;
}
// 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;
+ }
- if ( captureAudioClient ) {
- // Pull callback buffer from inputBuffer
- callbackPulled = captureBuffer.pullBuffer( stream_.deviceBuffer,
- ( unsigned int ) stream_.bufferSize * stream_.nDeviceChannels[INPUT],
- stream_.deviceFormat[INPUT] );
+ // Convert callback buffer to user sample rate
+ unsigned int deviceBufferOffset = convBufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] );
+ unsigned int convSamples = 0;
- if ( callbackPulled ) {
+ 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 ( stream_.doConvertBuffer[INPUT] ) {
// Convert callback buffer to user format
convertBuffer( stream_.userBuffer[INPUT],
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;
}
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;
}
// 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] );
+
+ }
+ 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
+ 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 ( 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
&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;
}
// 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;
}
}
// 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;
}
}
// 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;
}
}
// 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;
}
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;
}
// 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;
}
}
// 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;
}
}
// 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;
}
}
// 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();
}
}
CoTaskMemFree( captureFormat );
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__ *********************//
#if defined(__WINDOWS_DS__) // Windows DirectSound API
// Modified by Robin Davies, October 2005
-// - Improvements to DirectX pointer chasing.
+// - Improvements to DirectX pointer chasing.
// - Bug fix for non-power-of-two Asio granularity used by Edirol PCR-A30.
// - Auto-call CoInitialize for DSOUND and ASIO platforms.
// Various revisions for RtAudio 4.0 by Gary Scavone, April 2007
// Changed device query structure for RtAudio 4.0.7, January 2010
+#include <windows.h>
+#include <process.h>
#include <mmsystem.h>
#include <mmreg.h>
#include <dsound.h>
void *id[2];
void *buffer[2];
bool xrun[2];
- UINT bufferPointer[2];
+ UINT bufferPointer[2];
DWORD dsBufferSize[2];
DWORD dsPointerLeadTime[2]; // the number of bytes ahead of the safe pointer to lead by.
HANDLE condition;
void RtApiDs :: startStream()
{
verifyStream();
+ RtApi::startStream();
if ( stream_.state == STREAM_RUNNING ) {
errorText_ = "RtApiDs::startStream(): the stream is already running!";
error( RtAudioError::WARNING );
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
// increasing timer accuracy). On greater windows (Win2K or later),
// this is already in effect.
- timeBeginPeriod( 1 );
+ timeBeginPeriod( 1 );
buffersRolling = false;
duplexPrerollBytes = 0;
void RtApiDs :: stopStream()
{
verifyStream();
+ RtApi::startStream();
if ( stream_.state == STREAM_STOPPED ) {
errorText_ = "RtApiDs::stopStream(): the stream is already stopped!";
error( RtAudioError::WARNING );
}
if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {
-
+
LPDIRECTSOUNDBUFFER dsBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0];
if ( handle->drainCounter > 1 ) { // write zeros to the output stream
}
if ( dsPointerBetween( nextWritePointer, safeWritePointer, currentWritePointer, dsBufferSize )
- || dsPointerBetween( endWrite, safeWritePointer, currentWritePointer, dsBufferSize ) ) {
+ || dsPointerBetween( endWrite, safeWritePointer, currentWritePointer, dsBufferSize ) ) {
// We've strayed into the forbidden zone ... resync the read pointer.
handle->xrun[0] = true;
nextWritePointer = safeWritePointer + handle->dsPointerLeadTime[0] - bufferBytes;
if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset
DWORD endRead = nextReadPointer + bufferBytes;
- // Handling depends on whether we are INPUT or DUPLEX.
+ // Handling depends on whether we are INPUT or DUPLEX.
// If we're in INPUT mode then waiting is a good thing. If we're in DUPLEX mode,
// then a wait here will drag the write pointers into the forbidden zone.
- //
- // In DUPLEX mode, rather than wait, we will back off the read pointer until
- // it's in a safe position. This causes dropouts, but it seems to be the only
- // practical way to sync up the read and write pointers reliably, given the
- // the very complex relationship between phase and increment of the read and write
+ //
+ // In DUPLEX mode, rather than wait, we will back off the read pointer until
+ // it's in a safe position. This causes dropouts, but it seems to be the only
+ // practical way to sync up the read and write pointers reliably, given the
+ // the very complex relationship between phase and increment of the read and write
// pointers.
//
// In order to minimize audible dropouts in DUPLEX mode, we will
error( RtAudioError::SYSTEM_ERROR );
return;
}
-
+
if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset
}
}
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;
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 );
nDevices++;
}
nextcard:
- snd_ctl_close( handle );
+ if ( handle )
+ snd_ctl_close( handle );
snd_card_next( &card );
}
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;
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 );
nDevices++;
}
nextcard:
- snd_ctl_close( chandle );
+ if ( chandle )
+ snd_ctl_close( chandle );
snd_card_next( &card );
}
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.
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 ) {
- // We previously attempted to increase the audio callback priority
- // to SCHED_RR here via the attributes. However, while no errors
- // were reported in doing so, it did not work. So, now this is
- // done in the alsaCallbackHandler function.
stream_.callbackInfo.doRealtime = true;
+ struct sched_param param;
int priority = options->priority;
int min = sched_get_priority_min( SCHED_RR );
int max = sched_get_priority_max( SCHED_RR );
if ( priority < min ) priority = min;
else if ( priority > max ) priority = max;
- stream_.callbackInfo.priority = priority;
+ param.sched_priority = priority;
+
+ // Set the policy BEFORE the priority. Otherwise it fails.
+ pthread_attr_setschedpolicy(&attr, SCHED_RR);
+ pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM);
+ // This is definitely required. Otherwise it fails.
+ pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
+ pthread_attr_setschedparam(&attr, ¶m);
}
+ else
+ pthread_attr_setschedpolicy( &attr, SCHED_OTHER );
+#else
+ pthread_attr_setschedpolicy( &attr, SCHED_OTHER );
#endif
stream_.callbackInfo.isRunning = true;
result = pthread_create( &stream_.callbackInfo.thread, &attr, alsaCallbackHandler, &stream_.callbackInfo );
pthread_attr_destroy( &attr );
if ( result ) {
- stream_.callbackInfo.isRunning = false;
- errorText_ = "RtApiAlsa::error creating callback thread!";
- goto error;
+ // Failed. Try instead with default attributes.
+ result = pthread_create( &stream_.callbackInfo.thread, NULL, alsaCallbackHandler, &stream_.callbackInfo );
+ if ( result ) {
+ stream_.callbackInfo.isRunning = false;
+ errorText_ = "RtApiAlsa::error creating callback thread!";
+ goto error;
+ }
}
}
// This method calls snd_pcm_prepare if the device isn't already in that state.
verifyStream();
+ RtApi::startStream();
if ( stream_.state == STREAM_RUNNING ) {
errorText_ = "RtApiAlsa::startStream(): the stream is already running!";
error( RtAudioError::WARNING );
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;
AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle;
snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles;
if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {
- if ( apiInfo->synchronized )
+ if ( apiInfo->synchronized )
result = snd_pcm_drop( handle[0] );
else
result = snd_pcm_drain( handle[0] );
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 ) {
- pthread_t tID = pthread_self(); // ID of this thread
- sched_param prio = { info->priority }; // scheduling priority of thread
- pthread_setschedparam( tID, SCHED_RR, &prio );
+ std::cerr << "RtAudio alsa: " <<
+ (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") <<
+ "running realtime scheduling" << std::endl;
}
#endif
#include <pulse/error.h>
#include <pulse/simple.h>
+#include <pulse/pulseaudio.h>
#include <cstdio>
static const unsigned int SUPPORTED_SAMPLERATES[] = { 8000, 16000, 22050, 32000,
return 1;
}
+void RtApiPulse::sinkInfoCallback(pa_context*, const pa_sink_info* info, int, void* arg)
+{
+ RtApiPulse* api = (RtApiPulse *) arg;
+ if (info) {
+ api->channels_ = info->sample_spec.channels;
+ }
+ pa_threaded_mainloop_signal(api->mainloop_, 0);
+}
+
+void RtApiPulse::contextStateCallback(pa_context* c, void* arg)
+{
+ pa_threaded_mainloop* mainloop = (pa_threaded_mainloop*) arg;
+
+ switch (pa_context_get_state(c)) {
+ case PA_CONTEXT_READY:
+ case PA_CONTEXT_TERMINATED:
+ case PA_CONTEXT_FAILED:
+ pa_threaded_mainloop_signal(mainloop, 0);
+ break;
+ default:
+ break;
+ }
+}
+
RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int /*device*/ )
{
+ /* Set up some defaults in case we crash and burn */
RtAudio::DeviceInfo info;
info.probed = true;
info.name = "PulseAudio";
info.preferredSampleRate = 48000;
info.nativeFormats = RTAUDIO_SINT16 | RTAUDIO_SINT32 | RTAUDIO_FLOAT32;
+ /* Get the number of output channels from pulseaudio. A simple task, you say?
+ "What is your mainloop?" */
+ mainloop_ = pa_threaded_mainloop_new();
+ if (!mainloop_) {
+ return info;
+ }
+
+ pa_threaded_mainloop_start(mainloop_);
+ pa_threaded_mainloop_lock(mainloop_);
+
+ /* "And what is your context?" */
+ pa_context* context = pa_context_new(pa_threaded_mainloop_get_api(mainloop_), "RtAudio");
+ if (!context) {
+ pa_threaded_mainloop_unlock(mainloop_);
+ pa_threaded_mainloop_stop(mainloop_);
+ pa_threaded_mainloop_free(mainloop_);
+ mainloop_ = 0;
+ return info;
+ }
+
+ pa_context_set_state_callback(context, contextStateCallback, mainloop_);
+
+ pa_context_connect(context, 0, (pa_context_flags_t) 0, 0);
+
+ /* "And what is your favourite colour?" */
+ int connected = 0;
+ pa_context_state_t state = pa_context_get_state(context);
+ for (; !connected; state = pa_context_get_state(context)) {
+ switch (state) {
+ case PA_CONTEXT_READY:
+ connected = 1;
+ continue;
+ case PA_CONTEXT_FAILED:
+ case PA_CONTEXT_TERMINATED:
+ /* Blue! No, I mean red! */
+ pa_threaded_mainloop_unlock(mainloop_);
+ pa_context_disconnect(context);
+ pa_context_unref(context);
+ pa_threaded_mainloop_stop(mainloop_);
+ pa_threaded_mainloop_free(mainloop_);
+ mainloop_ = 0;
+ return info;
+ default:
+ pa_threaded_mainloop_wait(mainloop_);
+ break;
+ }
+ }
+
+ pa_operation* op = pa_context_get_sink_info_by_index(context, 0, sinkInfoCallback, this);
+
+ if (op) {
+ pa_operation_unref(op);
+ }
+
+ pa_threaded_mainloop_wait(mainloop_);
+ pa_threaded_mainloop_unlock(mainloop_);
+
+ pa_context_disconnect(context);
+ pa_context_unref(context);
+
+ pa_threaded_mainloop_stop(mainloop_);
+ pa_threaded_mainloop_free(mainloop_);
+ mainloop_ = 0;
+
+ info.outputChannels = channels_;
+
return info;
}
RtApiPulse *context = static_cast<RtApiPulse *>( cbi->object );
volatile bool *isRunning = &cbi->isRunning;
+#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_ ") <<
+ "running realtime scheduling" << std::endl;
+ }
+#endif
+
while ( *isRunning ) {
pthread_testcancel();
context->callbackEvent();
else
bytes = stream_.nUserChannels[INPUT] * stream_.bufferSize *
formatBytes( stream_.userFormat );
-
+
if ( pa_simple_read( pah->s_rec, pulse_in, bytes, &pa_error ) < 0 ) {
errorStream_ << "RtApiPulse::callbackEvent: audio read error, " <<
pa_strerror( pa_error ) << ".";
MUTEX_UNLOCK( &stream_.mutex );
RtApi::tickStreamTime();
+ if (pah->s_play) {
+ int e = 0;
+ pa_usec_t const lat = pa_simple_get_latency(pah->s_play, &e);
+ if (e == 0) {
+ stream_.latency[0] = lat * stream_.sampleRate / 1000000;
+ }
+ }
+
if ( doStopStream == 1 )
stopStream();
}
void RtApiPulse::startStream( void )
{
+ RtApi::startStream();
PulseAudioHandle *pah = static_cast<PulseAudioHandle *>( stream_.apiHandle );
if ( stream_.state == STREAM_CLOSED ) {
MUTEX_LOCK( &stream_.mutex );
+ #if defined( HAVE_GETTIMEOFDAY )
+ gettimeofday( &stream_.lastTickTimestamp, NULL );
+ #endif
+
stream_.state = STREAM_RUNNING;
pah->runnable = true;
}
stream_.state = STREAM_STOPPED;
+ pah->runnable = false;
MUTEX_LOCK( &stream_.mutex );
if ( pah && pah->s_play ) {
}
stream_.state = STREAM_STOPPED;
+ pah->runnable = false;
MUTEX_LOCK( &stream_.mutex );
if ( pah && pah->s_play ) {
if ( device != 0 ) return false;
if ( mode != INPUT && mode != OUTPUT ) return false;
- if ( channels != 1 && channels != 2 ) {
- errorText_ = "RtApiPulse::probeDeviceOpen: unsupported number of channels.";
- return false;
- }
ss.channels = channels;
if ( firstChannel != 0 ) return false;
}
break;
case OUTPUT:
- pah->s_play = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_PLAYBACK, NULL, "Playback", &ss, NULL, NULL, &error );
+ /* XXX: hard-coded for DCP-o-matic */
+ pa_channel_map map;
+ pa_channel_map_init(&map);
+ /* XXX: need to check 7.1 */
+ map.channels = channels;
+
+ if (channels > 0) {
+ map.map[0] = PA_CHANNEL_POSITION_FRONT_LEFT;
+ }
+ if (channels > 1) {
+ map.map[1] = PA_CHANNEL_POSITION_FRONT_RIGHT;
+ }
+ if (channels > 2) {
+ map.map[2] = PA_CHANNEL_POSITION_FRONT_CENTER;
+ }
+ if (channels > 3) {
+ map.map[3] = PA_CHANNEL_POSITION_LFE;
+ }
+ if (channels > 4) {
+ map.map[4] = PA_CHANNEL_POSITION_REAR_LEFT;
+ }
+ if (channels > 5) {
+ map.map[5] = PA_CHANNEL_POSITION_REAR_RIGHT;
+ }
+ if (channels > 6) {
+ map.map[6] = PA_CHANNEL_POSITION_SIDE_LEFT;
+ }
+ if (channels > 7) {
+ map.map[7] = PA_CHANNEL_POSITION_SIDE_RIGHT;
+ }
+
+ pah->s_play = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_PLAYBACK, NULL, "Playback", &ss, &map, NULL, &error );
if ( !pah->s_play ) {
errorText_ = "RtApiPulse::probeDeviceOpen: error connecting output to PulseAudio server.";
goto error;
if ( !stream_.callbackInfo.isRunning ) {
stream_.callbackInfo.object = this;
+
+ stream_.state = STREAM_STOPPED;
+ // Set the thread attributes for joinable and realtime scheduling
+ // priority (optional). The higher priority will only take affect
+ // if the program is run as root or suid. Note, under Linux
+ // processes with CAP_SYS_NICE privilege, a user can change
+ // scheduling policy and priority (thus need not be root). See
+ // POSIX "capabilities".
+ pthread_attr_t attr;
+ pthread_attr_init( &attr );
+ pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE );
+#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;
+ int priority = options->priority;
+ int min = sched_get_priority_min( SCHED_RR );
+ int max = sched_get_priority_max( SCHED_RR );
+ if ( priority < min ) priority = min;
+ else if ( priority > max ) priority = max;
+ param.sched_priority = priority;
+
+ // Set the policy BEFORE the priority. Otherwise it fails.
+ pthread_attr_setschedpolicy(&attr, SCHED_RR);
+ pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM);
+ // This is definitely required. Otherwise it fails.
+ pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
+ pthread_attr_setschedparam(&attr, ¶m);
+ }
+ else
+ pthread_attr_setschedpolicy( &attr, SCHED_OTHER );
+#else
+ pthread_attr_setschedpolicy( &attr, SCHED_OTHER );
+#endif
+
stream_.callbackInfo.isRunning = true;
- if ( pthread_create( &pah->thread, NULL, pulseaudio_callback, (void *)&stream_.callbackInfo) != 0 ) {
- errorText_ = "RtApiPulse::probeDeviceOpen: error creating thread.";
- goto error;
+ int result = pthread_create( &pah->thread, &attr, pulseaudio_callback, (void *)&stream_.callbackInfo);
+ pthread_attr_destroy(&attr);
+ if(result != 0) {
+ // Failed. Try instead with default attributes.
+ result = pthread_create( &pah->thread, NULL, pulseaudio_callback, (void *)&stream_.callbackInfo);
+ if(result != 0) {
+ stream_.callbackInfo.isRunning = false;
+ errorText_ = "RtApiPulse::probeDeviceOpen: error creating thread.";
+ goto error;
+ }
}
}
- stream_.state = STREAM_STOPPED;
- return true;
-
+ return SUCCESS;
+
error:
if ( pah && stream_.callbackInfo.isRunning ) {
pthread_cond_destroy( &pah->runnable_cv );
stream_.deviceBuffer = 0;
}
+ stream_.state = STREAM_CLOSED;
return FAILURE;
}
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;
int priority = options->priority;
int min = sched_get_priority_min( SCHED_RR );
if ( priority < min ) priority = min;
else if ( priority > max ) priority = max;
param.sched_priority = priority;
- pthread_attr_setschedparam( &attr, ¶m );
- pthread_attr_setschedpolicy( &attr, SCHED_RR );
+
+ // Set the policy BEFORE the priority. Otherwise it fails.
+ pthread_attr_setschedpolicy(&attr, SCHED_RR);
+ pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM);
+ // This is definitely required. Otherwise it fails.
+ pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
+ pthread_attr_setschedparam(&attr, ¶m);
}
else
pthread_attr_setschedpolicy( &attr, SCHED_OTHER );
result = pthread_create( &stream_.callbackInfo.thread, &attr, ossCallbackHandler, &stream_.callbackInfo );
pthread_attr_destroy( &attr );
if ( result ) {
- stream_.callbackInfo.isRunning = false;
- errorText_ = "RtApiOss::error creating callback thread!";
- goto error;
+ // Failed. Try instead with default attributes.
+ result = pthread_create( &stream_.callbackInfo.thread, NULL, ossCallbackHandler, &stream_.callbackInfo );
+ if ( result ) {
+ stream_.callbackInfo.isRunning = false;
+ errorText_ = "RtApiOss::error creating callback thread!";
+ goto error;
+ }
}
}
stream_.deviceBuffer = 0;
}
+ stream_.state = STREAM_CLOSED;
return FAILURE;
}
void RtApiOss :: startStream()
{
verifyStream();
+ RtApi::startStream();
if ( stream_.state == STREAM_RUNNING ) {
errorText_ = "RtApiOss::startStream(): the stream is already running!";
error( RtAudioError::WARNING );
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
RtApiOss *object = (RtApiOss *) info->object;
bool *isRunning = &info->isRunning;
+#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_ ") <<
+ "running realtime scheduling" << std::endl;
+ }
+#endif
+
while ( *isRunning == true ) {
pthread_testcancel();
object->callbackEvent();
// End:
//
// vim: et sts=2 sw=2
-