X-Git-Url: https://git.carlh.net/gitweb/?a=blobdiff_plain;f=RtAudio.cpp;h=0a7d9ebda0537975a6c247acde50b46ae201d797;hb=ee76af899f7f27f4784d3bbae27f2c63f0f1e6ce;hp=4a3966b5700ba3e16caa1a5897e41bde199a3dcf;hpb=692c1b860c5ccc43420263f12a4d6a46f987b8a6;p=rtaudio-cdist.git diff --git a/RtAudio.cpp b/RtAudio.cpp index 4a3966b..0a7d9eb 100644 --- a/RtAudio.cpp +++ b/RtAudio.cpp @@ -10,7 +10,7 @@ RtAudio WWW site: http://www.music.mcgill.ca/~gary/rtaudio/ RtAudio: realtime audio i/o C++ classes - Copyright (c) 2001-2011 Gary P. Scavone + Copyright (c) 2001-2013 Gary P. Scavone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files @@ -38,7 +38,7 @@ */ /************************************************************************/ -// RtAudio: Version 4.0.9 +// RtAudio: Version 4.0.12 #include "RtAudio.h" #include @@ -58,7 +58,7 @@ const unsigned int RtApi::SAMPLE_RATES[] = { #define MUTEX_DESTROY(A) DeleteCriticalSection(A) #define MUTEX_LOCK(A) EnterCriticalSection(A) #define MUTEX_UNLOCK(A) LeaveCriticalSection(A) -#elif defined(__LINUX_ALSA__) || defined(__UNIX_JACK__) || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__) +#elif defined(__LINUX_ALSA__) || defined(__LINUX_PULSE__) || defined(__UNIX_JACK__) || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__) // pthread API #define MUTEX_INITIALIZE(A) pthread_mutex_init(A, NULL) #define MUTEX_DESTROY(A) pthread_mutex_destroy(A) @@ -75,6 +75,11 @@ const unsigned int RtApi::SAMPLE_RATES[] = { // // *************************************************** // +std::string RtAudio :: getVersion( void ) throw() +{ + return std::string( RTAUDIO_VERSION ); +} + void RtAudio :: getCompiledApi( std::vector &apis ) throw() { apis.clear(); @@ -87,6 +92,9 @@ void RtAudio :: getCompiledApi( std::vector &apis ) throw() #if defined(__LINUX_ALSA__) apis.push_back( LINUX_ALSA ); #endif +#if defined(__LINUX_PULSE__) + apis.push_back( LINUX_PULSE ); +#endif #if defined(__LINUX_OSS__) apis.push_back( LINUX_OSS ); #endif @@ -106,6 +114,10 @@ void RtAudio :: getCompiledApi( std::vector &apis ) throw() void RtAudio :: openRtApi( RtAudio::Api api ) { + if ( rtapi_ ) + delete rtapi_; + rtapi_ = 0; + #if defined(__UNIX_JACK__) if ( api == UNIX_JACK ) rtapi_ = new RtApiJack(); @@ -114,6 +126,10 @@ void RtAudio :: openRtApi( RtAudio::Api api ) if ( api == LINUX_ALSA ) rtapi_ = new RtApiAlsa(); #endif +#if defined(__LINUX_PULSE__) + if ( api == LINUX_PULSE ) + rtapi_ = new RtApiPulse(); +#endif #if defined(__LINUX_OSS__) if ( api == LINUX_OSS ) rtapi_ = new RtApiOss(); @@ -165,7 +181,8 @@ RtAudio :: RtAudio( RtAudio::Api api ) throw() // definition __RTAUDIO_DUMMY__ is automatically defined if no // API-specific definitions are passed to the compiler. But just in // case something weird happens, we'll print out an error message. - std::cerr << "\nRtAudio: no compiled API support found ... critical error!!\n\n"; + std::string errorText = "\nRtAudio: no compiled API support found ... critical error!!\n\n"; + throw( RtAudioError( errorText, RtAudioError::UNSPECIFIED ) ); } RtAudio :: ~RtAudio() throw() @@ -178,11 +195,12 @@ void RtAudio :: openStream( RtAudio::StreamParameters *outputParameters, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, void *userData, - RtAudio::StreamOptions *options ) + RtAudio::StreamOptions *options, + RtAudioErrorCallback errorCallback ) { return rtapi_->openStream( outputParameters, inputParameters, format, sampleRate, bufferFrames, callback, - userData, options ); + userData, options, errorCallback ); } // *************************************************** // @@ -201,6 +219,7 @@ RtApi :: RtApi() stream_.userBuffer[1] = 0; MUTEX_INITIALIZE( &stream_.mutex ); showWarnings_ = true; + firstErrorOccurred_ = false; } RtApi :: ~RtApi() @@ -213,31 +232,37 @@ void RtApi :: openStream( RtAudio::StreamParameters *oParams, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, void *userData, - RtAudio::StreamOptions *options ) + RtAudio::StreamOptions *options, + RtAudioErrorCallback errorCallback ) { if ( stream_.state != STREAM_CLOSED ) { errorText_ = "RtApi::openStream: a stream is already open!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return; } if ( oParams && oParams->nChannels < 1 ) { errorText_ = "RtApi::openStream: a non-NULL output StreamParameters structure cannot have an nChannels value less than one."; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return; } if ( iParams && iParams->nChannels < 1 ) { errorText_ = "RtApi::openStream: a non-NULL input StreamParameters structure cannot have an nChannels value less than one."; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return; } if ( oParams == NULL && iParams == NULL ) { errorText_ = "RtApi::openStream: input and output StreamParameters structures are both NULL!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return; } if ( formatBytes(format) == 0 ) { errorText_ = "RtApi::openStream: 'format' parameter value is undefined."; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return; } unsigned int nDevices = getDeviceCount(); @@ -246,7 +271,8 @@ void RtApi :: openStream( RtAudio::StreamParameters *oParams, oChannels = oParams->nChannels; if ( oParams->deviceId >= nDevices ) { errorText_ = "RtApi::openStream: output device parameter value is invalid."; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return; } } @@ -255,7 +281,8 @@ void RtApi :: openStream( RtAudio::StreamParameters *oParams, iChannels = iParams->nChannels; if ( iParams->deviceId >= nDevices ) { errorText_ = "RtApi::openStream: input device parameter value is invalid."; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return; } } @@ -266,7 +293,10 @@ void RtApi :: openStream( RtAudio::StreamParameters *oParams, result = probeDeviceOpen( oParams->deviceId, OUTPUT, oChannels, oParams->firstChannel, sampleRate, format, bufferFrames, options ); - if ( result == false ) error( RtError::SYSTEM_ERROR ); + if ( result == false ) { + error( RtAudioError::SYSTEM_ERROR ); + return; + } } if ( iChannels > 0 ) { @@ -275,12 +305,14 @@ void RtApi :: openStream( RtAudio::StreamParameters *oParams, sampleRate, format, bufferFrames, options ); if ( result == false ) { if ( oChannels > 0 ) closeStream(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } } stream_.callbackInfo.callback = (void *) callback; stream_.callbackInfo.userData = userData; + stream_.callbackInfo.errorCallback = (void *) errorCallback; if ( options ) options->numberOfBuffers = stream_.nBuffers; stream_.state = STREAM_STOPPED; @@ -304,10 +336,10 @@ void RtApi :: closeStream( void ) return; } -bool RtApi :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, - unsigned int firstChannel, unsigned int sampleRate, - RtAudioFormat format, unsigned int *bufferSize, - RtAudio::StreamOptions *options ) +bool RtApi :: probeDeviceOpen( unsigned int /*device*/, StreamMode /*mode*/, unsigned int /*channels*/, + unsigned int /*firstChannel*/, unsigned int /*sampleRate*/, + RtAudioFormat /*format*/, unsigned int * /*bufferSize*/, + RtAudio::StreamOptions * /*options*/ ) { // MUST be implemented in subclasses! return FAILURE; @@ -426,7 +458,7 @@ RtApiCore:: RtApiCore() OSStatus result = AudioObjectSetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop); if ( result != noErr ) { errorText_ = "RtApiCore::RtApiCore: error setting run loop property!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); } #endif } @@ -447,7 +479,7 @@ unsigned int RtApiCore :: getDeviceCount( void ) OSStatus result = AudioObjectGetPropertyDataSize( kAudioObjectSystemObject, &propertyAddress, 0, NULL, &dataSize ); if ( result != noErr ) { errorText_ = "RtApiCore::getDeviceCount: OS-X error getting device info!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return 0; } @@ -465,7 +497,7 @@ unsigned int RtApiCore :: getDefaultInputDevice( void ) OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, &id ); if ( result != noErr ) { errorText_ = "RtApiCore::getDefaultInputDevice: OS-X system error getting device."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return 0; } @@ -475,7 +507,7 @@ unsigned int RtApiCore :: getDefaultInputDevice( void ) result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, (void *) &deviceList ); if ( result != noErr ) { errorText_ = "RtApiCore::getDefaultInputDevice: OS-X system error getting device IDs."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return 0; } @@ -483,7 +515,7 @@ unsigned int RtApiCore :: getDefaultInputDevice( void ) if ( id == deviceList[i] ) return i; errorText_ = "RtApiCore::getDefaultInputDevice: No default device found!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return 0; } @@ -498,7 +530,7 @@ unsigned int RtApiCore :: getDefaultOutputDevice( void ) OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, &id ); if ( result != noErr ) { errorText_ = "RtApiCore::getDefaultOutputDevice: OS-X system error getting device."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return 0; } @@ -508,7 +540,7 @@ unsigned int RtApiCore :: getDefaultOutputDevice( void ) result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, (void *) &deviceList ); if ( result != noErr ) { errorText_ = "RtApiCore::getDefaultOutputDevice: OS-X system error getting device IDs."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return 0; } @@ -516,7 +548,7 @@ unsigned int RtApiCore :: getDefaultOutputDevice( void ) if ( id == deviceList[i] ) return i; errorText_ = "RtApiCore::getDefaultOutputDevice: No default device found!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return 0; } @@ -529,12 +561,14 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) unsigned int nDevices = getDeviceCount(); if ( nDevices == 0 ) { errorText_ = "RtApiCore::getDeviceInfo: no devices found!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return info; } if ( device >= nDevices ) { errorText_ = "RtApiCore::getDeviceInfo: device ID is invalid!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return info; } AudioDeviceID deviceList[ nDevices ]; @@ -546,7 +580,7 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) 0, NULL, &dataSize, (void *) &deviceList ); if ( result != noErr ) { errorText_ = "RtApiCore::getDeviceInfo: OS-X system error getting device IDs."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -561,7 +595,7 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceInfo: system error (" << getErrorCode( result ) << ") getting device manufacturer."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -579,7 +613,7 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceInfo: system error (" << getErrorCode( result ) << ") getting device name."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -601,7 +635,7 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) if ( result != noErr || dataSize == 0 ) { errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting output stream configuration info for device (" << device << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -609,7 +643,7 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) bufferList = (AudioBufferList *) malloc( dataSize ); if ( bufferList == NULL ) { errorText_ = "RtApiCore::getDeviceInfo: memory error allocating output AudioBufferList."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -618,7 +652,7 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) free( bufferList ); errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting output stream configuration for device (" << device << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -634,7 +668,7 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) if ( result != noErr || dataSize == 0 ) { errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting input stream configuration info for device (" << device << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -642,7 +676,7 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) bufferList = (AudioBufferList *) malloc( dataSize ); if ( bufferList == NULL ) { errorText_ = "RtApiCore::getDeviceInfo: memory error allocating input AudioBufferList."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -651,7 +685,7 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) free( bufferList ); errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting input stream configuration for device (" << device << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -676,7 +710,7 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) if ( result != kAudioHardwareNoError || dataSize == 0 ) { errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting sample rate info."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -686,26 +720,45 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) if ( result != kAudioHardwareNoError ) { errorStream_ << "RtApiCore::getDeviceInfo: system error (" << getErrorCode( result ) << ") getting sample rates."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } - Float64 minimumRate = 100000000.0, maximumRate = 0.0; + // The sample rate reporting mechanism is a bit of a mystery. It + // seems that it can either return individual rates or a range of + // rates. I assume that if the min / max range values are the same, + // then that represents a single supported rate and if the min / max + // range values are different, the device supports an arbitrary + // range of values (though there might be multiple ranges, so we'll + // use the most conservative range). + Float64 minimumRate = 1.0, maximumRate = 10000000000.0; + bool haveValueRange = false; + info.sampleRates.clear(); for ( UInt32 i=0; i maximumRate ) maximumRate = rangeList[i].mMaximum; + if ( rangeList[i].mMinimum == rangeList[i].mMaximum ) + info.sampleRates.push_back( (unsigned int) rangeList[i].mMinimum ); + else { + haveValueRange = true; + if ( rangeList[i].mMinimum > minimumRate ) minimumRate = rangeList[i].mMinimum; + if ( rangeList[i].mMaximum < maximumRate ) maximumRate = rangeList[i].mMaximum; + } } - info.sampleRates.clear(); - for ( unsigned int k=0; k= (unsigned int) minimumRate && SAMPLE_RATES[k] <= (unsigned int) maximumRate ) - info.sampleRates.push_back( SAMPLE_RATES[k] ); + if ( haveValueRange ) { + for ( unsigned int k=0; k= (unsigned int) minimumRate && SAMPLE_RATES[k] <= (unsigned int) maximumRate ) + info.sampleRates.push_back( SAMPLE_RATES[k] ); + } } + // Sort and remove any redundant values + std::sort( info.sampleRates.begin(), info.sampleRates.end() ); + info.sampleRates.erase( unique( info.sampleRates.begin(), info.sampleRates.end() ), info.sampleRates.end() ); + if ( info.sampleRates.size() == 0 ) { errorStream_ << "RtApiCore::probeDeviceInfo: No supported sample rates found for device (" << device << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -723,13 +776,13 @@ RtAudio::DeviceInfo RtApiCore :: getDeviceInfo( unsigned int device ) return info; } -OSStatus callbackHandler( AudioDeviceID inDevice, - const AudioTimeStamp* inNow, - const AudioBufferList* inInputData, - const AudioTimeStamp* inInputTime, - AudioBufferList* outOutputData, - const AudioTimeStamp* inOutputTime, - void* infoPointer ) +static OSStatus callbackHandler( AudioDeviceID inDevice, + const AudioTimeStamp* /*inNow*/, + const AudioBufferList* inInputData, + const AudioTimeStamp* /*inInputTime*/, + AudioBufferList* outOutputData, + const AudioTimeStamp* /*inOutputTime*/, + void* infoPointer ) { CallbackInfo *info = (CallbackInfo *) infoPointer; @@ -740,10 +793,10 @@ OSStatus callbackHandler( AudioDeviceID inDevice, return kAudioHardwareNoError; } -OSStatus xrunListener( AudioObjectID inDevice, - UInt32 nAddresses, - const AudioObjectPropertyAddress properties[], - void* handlePointer ) +static OSStatus xrunListener( AudioObjectID /*inDevice*/, + UInt32 nAddresses, + const AudioObjectPropertyAddress properties[], + void* handlePointer ) { CoreHandle *handle = (CoreHandle *) handlePointer; for ( UInt32 i=0; i > physicalFormats; - formatFlags = description.mFormatFlags | kLinearPCMFormatFlagIsFloat & ~kLinearPCMFormatFlagIsSignedInteger; + formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsFloat) & ~kLinearPCMFormatFlagIsSignedInteger; physicalFormats.push_back( std::pair( 32, formatFlags ) ); formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked) & ~kLinearPCMFormatFlagIsFloat; physicalFormats.push_back( std::pair( 32, formatFlags ) ); @@ -1132,7 +1184,7 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne else { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting device latency for device (" << device << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); } } @@ -1287,6 +1339,7 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne stream_.deviceBuffer = 0; } + stream_.state = STREAM_CLOSED; return FAILURE; } @@ -1294,7 +1347,7 @@ void RtApiCore :: closeStream( void ) { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiCore::closeStream(): no open stream to close!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -1347,12 +1400,10 @@ void RtApiCore :: startStream( void ) verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiCore::startStream(): the stream is already running!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } - MUTEX_LOCK( &stream_.mutex ); - OSStatus result = noErr; CoreHandle *handle = (CoreHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { @@ -1381,10 +1432,8 @@ void RtApiCore :: startStream( void ) stream_.state = STREAM_RUNNING; unlock: - MUTEX_UNLOCK( &stream_.mutex ); - if ( result == noErr ) return; - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); } void RtApiCore :: stopStream( void ) @@ -1392,14 +1441,7 @@ void RtApiCore :: stopStream( void ) verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiCore::stopStream(): the stream is already stopped!"; - error( RtError::WARNING ); - return; - } - - MUTEX_LOCK( &stream_.mutex ); - - if ( stream_.state == STREAM_STOPPED ) { - MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::WARNING ); return; } @@ -1412,9 +1454,7 @@ void RtApiCore :: stopStream( void ) pthread_cond_wait( &handle->condition, &stream_.mutex ); // block until signaled } - MUTEX_UNLOCK( &stream_.mutex ); result = AudioDeviceStop( handle->id[0], callbackHandler ); - MUTEX_LOCK( &stream_.mutex ); if ( result != noErr ) { errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping callback procedure on device (" << stream_.device[0] << ")."; errorText_ = errorStream_.str(); @@ -1424,9 +1464,7 @@ void RtApiCore :: stopStream( void ) if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.device[0] != stream_.device[1] ) ) { - MUTEX_UNLOCK( &stream_.mutex ); result = AudioDeviceStop( handle->id[1], callbackHandler ); - MUTEX_LOCK( &stream_.mutex ); if ( result != noErr ) { errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping input callback procedure on device (" << stream_.device[1] << ")."; errorText_ = errorStream_.str(); @@ -1437,10 +1475,8 @@ void RtApiCore :: stopStream( void ) stream_.state = STREAM_STOPPED; unlock: - MUTEX_UNLOCK( &stream_.mutex ); - if ( result == noErr ) return; - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); } void RtApiCore :: abortStream( void ) @@ -1448,7 +1484,7 @@ void RtApiCore :: abortStream( void ) verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiCore::abortStream(): the stream is already stopped!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -1458,14 +1494,28 @@ void RtApiCore :: abortStream( void ) stopStream(); } +// This function will be called by a spawned thread when the user +// callback function signals that the stream should be stopped or +// aborted. It is better to handle it this way because the +// callbackEvent() function probably should return before the AudioDeviceStop() +// function is called. +static void *coreStopStream( void *ptr ) +{ + CallbackInfo *info = (CallbackInfo *) ptr; + RtApiCore *object = (RtApiCore *) info->object; + + object->stopStream(); + pthread_exit( NULL ); +} + bool RtApiCore :: callbackEvent( AudioDeviceID deviceId, const AudioBufferList *inBufferList, const AudioBufferList *outBufferList ) { - if ( stream_.state == STREAM_STOPPED ) return SUCCESS; + if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiCore::callbackEvent(): the stream is closed ... this shouldn't happen!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return FAILURE; } @@ -1474,21 +1524,16 @@ bool RtApiCore :: callbackEvent( AudioDeviceID deviceId, // Check if we were draining the stream and signal is finished. if ( handle->drainCounter > 3 ) { + ThreadHandle threadId; + + stream_.state = STREAM_STOPPING; if ( handle->internalDrain == true ) - stopStream(); + pthread_create( &threadId, NULL, coreStopStream, info ); else // external call to stopStream() pthread_cond_signal( &handle->condition ); return SUCCESS; } - MUTEX_LOCK( &stream_.mutex ); - - // The state might change while waiting on a mutex. - if ( stream_.state == STREAM_STOPPED ) { - MUTEX_UNLOCK( &stream_.mutex ); - return SUCCESS; - } - AudioDeviceID outputDevice = handle->id[0]; // Invoke user callback to get fresh output data UNLESS we are @@ -1507,15 +1552,18 @@ bool RtApiCore :: callbackEvent( AudioDeviceID deviceId, handle->xrun[1] = false; } - handle->drainCounter = callback( stream_.userBuffer[0], stream_.userBuffer[1], - stream_.bufferSize, streamTime, status, info->userData ); - if ( handle->drainCounter == 2 ) { - MUTEX_UNLOCK( &stream_.mutex ); + int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], + stream_.bufferSize, streamTime, status, info->userData ); + if ( cbReturnValue == 2 ) { + stream_.state = STREAM_STOPPING; + handle->drainCounter = 2; abortStream(); return SUCCESS; } - else if ( handle->drainCounter == 1 ) + else if ( cbReturnValue == 1 ) { + handle->drainCounter = 1; handle->internalDrain = true; + } } if ( stream_.mode == OUTPUT || ( stream_.mode == DUPLEX && deviceId == outputDevice ) ) { @@ -1713,7 +1761,7 @@ bool RtApiCore :: callbackEvent( AudioDeviceID deviceId, } unlock: - MUTEX_UNLOCK( &stream_.mutex ); + //MUTEX_UNLOCK( &stream_.mutex ); RtApi::tickStreamTime(); return SUCCESS; @@ -1814,8 +1862,7 @@ struct JackHandle { :client(0), drainCounter(0), internalDrain(false) { ports[0] = 0; ports[1] = 0; xrun[0] = false; xrun[1] = false; } }; -ThreadHandle threadId; -void jackSilentError( const char * ) {}; +static void jackSilentError( const char * ) {}; RtApiJack :: RtApiJack() { @@ -1874,7 +1921,7 @@ RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) jack_client_t *client = jack_client_open( "RtApiJackInfo", options, status ); if ( client == 0 ) { errorText_ = "RtApiJack::getDeviceInfo: Jack server not found or connection error!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -1901,8 +1948,10 @@ RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) } if ( device >= nDevices ) { + jack_client_close( client ); errorText_ = "RtApiJack::getDeviceInfo: device ID is invalid!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return info; } // Get the current jack server sample rate. @@ -1931,7 +1980,7 @@ RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) if ( info.outputChannels == 0 && info.inputChannels == 0 ) { jack_client_close(client); errorText_ = "RtApiJack::getDeviceInfo: error determining Jack input/output channels!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -1953,7 +2002,7 @@ RtAudio::DeviceInfo RtApiJack :: getDeviceInfo( unsigned int device ) return info; } -int jackCallbackHandler( jack_nframes_t nframes, void *infoPointer ) +static int jackCallbackHandler( jack_nframes_t nframes, void *infoPointer ) { CallbackInfo *info = (CallbackInfo *) infoPointer; @@ -1967,7 +2016,7 @@ int jackCallbackHandler( jack_nframes_t nframes, void *infoPointer ) // server signals that it is shutting down. It is necessary to handle // it this way because the jackShutdown() function must return before // the jack_deactivate() function (in closeStream()) will return. -extern "C" void *jackCloseStream( void *ptr ) +static void *jackCloseStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiJack *object = (RtApiJack *) info->object; @@ -1976,7 +2025,7 @@ extern "C" void *jackCloseStream( void *ptr ) pthread_exit( NULL ); } -void jackShutdown( void *infoPointer ) +static void jackShutdown( void *infoPointer ) { CallbackInfo *info = (CallbackInfo *) infoPointer; RtApiJack *object = (RtApiJack *) info->object; @@ -1988,11 +2037,12 @@ void jackShutdown( void *infoPointer ) // other problem occurred and we should close the stream. if ( object->isStreamRunning() == false ) return; + ThreadHandle threadId; pthread_create( &threadId, NULL, jackCloseStream, info ); std::cerr << "\nRtApiJack: the Jack server is shutting down this client ... stream stopped and closed!!\n" << std::endl; } -int jackXrun( void *infoPointer ) +static int jackXrun( void *infoPointer ) { JackHandle *handle = (JackHandle *) infoPointer; @@ -2020,7 +2070,7 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne client = jack_client_open( "RtApiJack", jackoptions, status ); if ( client == 0 ) { errorText_ = "RtApiJack::probeDeviceOpen: Jack server not found or connection error!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return FAILURE; } } @@ -2086,8 +2136,17 @@ bool RtApiJack :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne // Get the latency of the JACK port. ports = jack_get_ports( client, deviceName.c_str(), NULL, flag ); - if ( ports[ firstChannel ] ) - stream_.latency[mode] = jack_port_get_latency( jack_port_by_name( client, ports[ firstChannel ] ) ); + if ( ports[ firstChannel ] ) { + // Added by Ge Wang + jack_latency_callback_mode_t cbmode = (mode == INPUT ? JackCaptureLatency : JackPlaybackLatency); + // the range (usually the min and max are equal) + jack_latency_range_t latrange; latrange.min = latrange.max = 0; + // get the latency range + jack_port_get_latency_range( jack_port_by_name( client, ports[firstChannel] ), cbmode, &latrange ); + // be optimistic, use the min! + stream_.latency[mode] = latrange.min; + //stream_.latency[mode] = jack_port_get_latency( jack_port_by_name( client, ports[ firstChannel ] ) ); + } free( ports ); // The jack server always uses 32-bit floating-point data. @@ -2248,7 +2307,7 @@ void RtApiJack :: closeStream( void ) { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiJack::closeStream(): no open stream to close!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -2290,12 +2349,10 @@ void RtApiJack :: startStream( void ) verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiJack::startStream(): the stream is already running!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } - MUTEX_LOCK(&stream_.mutex); - JackHandle *handle = (JackHandle *) stream_.apiHandle; int result = jack_activate( handle->client ); if ( result ) { @@ -2357,10 +2414,8 @@ void RtApiJack :: startStream( void ) stream_.state = STREAM_RUNNING; unlock: - MUTEX_UNLOCK(&stream_.mutex); - if ( result == 0 ) return; - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); } void RtApiJack :: stopStream( void ) @@ -2368,14 +2423,7 @@ void RtApiJack :: stopStream( void ) verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiJack::stopStream(): the stream is already stopped!"; - error( RtError::WARNING ); - return; - } - - MUTEX_LOCK( &stream_.mutex ); - - if ( stream_.state == STREAM_STOPPED ) { - MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::WARNING ); return; } @@ -2390,8 +2438,6 @@ void RtApiJack :: stopStream( void ) jack_deactivate( handle->client ); stream_.state = STREAM_STOPPED; - - MUTEX_UNLOCK( &stream_.mutex ); } void RtApiJack :: abortStream( void ) @@ -2399,7 +2445,7 @@ void RtApiJack :: abortStream( void ) verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiJack::abortStream(): the stream is already stopped!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -2414,27 +2460,26 @@ void RtApiJack :: abortStream( void ) // aborted. It is necessary to handle it this way because the // callbackEvent() function must return before the jack_deactivate() // function will return. -extern "C" void *jackStopStream( void *ptr ) +static void *jackStopStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiJack *object = (RtApiJack *) info->object; object->stopStream(); - pthread_exit( NULL ); } bool RtApiJack :: callbackEvent( unsigned long nframes ) { - if ( stream_.state == STREAM_STOPPED ) return SUCCESS; + if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiCore::callbackEvent(): the stream is closed ... this shouldn't happen!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return FAILURE; } if ( stream_.bufferSize != nframes ) { errorText_ = "RtApiCore::callbackEvent(): the JACK buffer size has changed ... cannot process!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return FAILURE; } @@ -2443,6 +2488,9 @@ bool RtApiJack :: callbackEvent( unsigned long nframes ) // Check if we were draining the stream and signal is finished. if ( handle->drainCounter > 3 ) { + ThreadHandle threadId; + + stream_.state = STREAM_STOPPING; if ( handle->internalDrain == true ) pthread_create( &threadId, NULL, jackStopStream, info ); else @@ -2450,14 +2498,6 @@ bool RtApiJack :: callbackEvent( unsigned long nframes ) return SUCCESS; } - MUTEX_LOCK( &stream_.mutex ); - - // The state might change while waiting on a mutex. - if ( stream_.state == STREAM_STOPPED ) { - MUTEX_UNLOCK( &stream_.mutex ); - return SUCCESS; - } - // Invoke user callback first, to get fresh output data. if ( handle->drainCounter == 0 ) { RtAudioCallback callback = (RtAudioCallback) info->callback; @@ -2471,16 +2511,19 @@ bool RtApiJack :: callbackEvent( unsigned long nframes ) status |= RTAUDIO_INPUT_OVERFLOW; handle->xrun[1] = false; } - handle->drainCounter = callback( stream_.userBuffer[0], stream_.userBuffer[1], - stream_.bufferSize, streamTime, status, info->userData ); - if ( handle->drainCounter == 2 ) { - MUTEX_UNLOCK( &stream_.mutex ); + int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], + stream_.bufferSize, streamTime, status, info->userData ); + if ( cbReturnValue == 2 ) { + stream_.state = STREAM_STOPPING; + handle->drainCounter = 2; ThreadHandle id; pthread_create( &id, NULL, jackStopStream, info ); return SUCCESS; } - else if ( handle->drainCounter == 1 ) + else if ( cbReturnValue == 1 ) { + handle->drainCounter = 1; handle->internalDrain = true; + } } jack_default_audio_sample_t *jackbuffer; @@ -2535,8 +2578,6 @@ bool RtApiJack :: callbackEvent( unsigned long nframes ) } unlock: - MUTEX_UNLOCK(&stream_.mutex); - RtApi::tickStreamTime(); return SUCCESS; } @@ -2567,11 +2608,11 @@ bool RtApiJack :: callbackEvent( unsigned long nframes ) #include "asiodrivers.h" #include -AsioDrivers drivers; -ASIOCallbacks asioCallbacks; -ASIODriverInfo driverInfo; -CallbackInfo *asioCallbackInfo; -bool asioXRun; +static AsioDrivers drivers; +static ASIOCallbacks asioCallbacks; +static ASIODriverInfo driverInfo; +static CallbackInfo *asioCallbackInfo; +static bool asioXRun; struct AsioHandle { int drainCounter; // Tracks callback counts when draining @@ -2585,8 +2626,8 @@ struct AsioHandle { // Function declarations (definitions at end of section) static const char* getAsioErrorString( ASIOError result ); -void sampleRateChanged( ASIOSampleRate sRate ); -long asioMessages( long selector, long value, void* message, double* opt ); +static void sampleRateChanged( ASIOSampleRate sRate ); +static long asioMessages( long selector, long value, void* message, double* opt ); RtApiAsio :: RtApiAsio() { @@ -2597,7 +2638,7 @@ RtApiAsio :: RtApiAsio() HRESULT hr = CoInitialize( NULL ); if ( FAILED(hr) ) { errorText_ = "RtApiAsio::ASIO requires a single-threaded appartment. Call CoInitializeEx(0,COINIT_APARTMENTTHREADED)"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); } coInitialized_ = true; @@ -2628,19 +2669,21 @@ RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) unsigned int nDevices = getDeviceCount(); if ( nDevices == 0 ) { errorText_ = "RtApiAsio::getDeviceInfo: no devices found!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return info; } if ( device >= nDevices ) { errorText_ = "RtApiAsio::getDeviceInfo: device ID is invalid!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return info; } // If a stream is already open, we cannot probe other devices. Thus, use the saved results. if ( stream_.state != STREAM_CLOSED ) { if ( device >= devices_.size() ) { errorText_ = "RtApiAsio::getDeviceInfo: device ID was not present before stream was opened."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } return devices_[ device ]; @@ -2651,7 +2694,7 @@ RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::getDeviceInfo: unable to get driver name (" << getAsioErrorString( result ) << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -2660,7 +2703,7 @@ RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) if ( !drivers.loadDriver( driverName ) ) { errorStream_ << "RtApiAsio::getDeviceInfo: unable to load driver (" << driverName << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -2668,7 +2711,7 @@ RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") initializing driver (" << driverName << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -2679,7 +2722,7 @@ RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) drivers.removeCurrentDriver(); errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") getting channel count (" << driverName << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -2706,7 +2749,7 @@ RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) drivers.removeCurrentDriver(); errorStream_ << "RtApiAsio::getDeviceInfo: error (" << getAsioErrorString( result ) << ") getting driver channel info (" << driverName << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -2719,6 +2762,8 @@ RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) info.nativeFormats |= RTAUDIO_FLOAT32; else if ( channelInfo.type == ASIOSTFloat64MSB || channelInfo.type == ASIOSTFloat64LSB ) info.nativeFormats |= RTAUDIO_FLOAT64; + else if ( channelInfo.type == ASIOSTInt24MSB || channelInfo.type == ASIOSTInt24LSB ) + info.nativeFormats |= RTAUDIO_SINT24; if ( info.outputChannels > 0 ) if ( getDefaultOutputDevice() == device ) info.isDefaultOutput = true; @@ -2730,7 +2775,7 @@ RtAudio::DeviceInfo RtApiAsio :: getDeviceInfo( unsigned int device ) return info; } -void bufferSwitch( long index, ASIOBool processNow ) +static void bufferSwitch( long index, ASIOBool processNow ) { RtApiAsio *object = (RtApiAsio *) asioCallbackInfo->object; object->callbackEvent( index ); @@ -2871,6 +2916,10 @@ bool RtApiAsio :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne stream_.deviceFormat[mode] = RTAUDIO_FLOAT64; if ( channelInfo.type == ASIOSTFloat64MSB ) stream_.doByteSwap[mode] = true; } + else if ( channelInfo.type == ASIOSTInt24MSB || channelInfo.type == ASIOSTInt24LSB ) { + stream_.deviceFormat[mode] = RTAUDIO_SINT24; + if ( channelInfo.type == ASIOSTInt24MSB ) stream_.doByteSwap[mode] = true; + } if ( stream_.deviceFormat[mode] == 0 ) { drivers.removeCurrentDriver(); @@ -3060,7 +3109,7 @@ bool RtApiAsio :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting latency."; errorText_ = errorStream_.str(); - error( RtError::WARNING); // warn but don't fail + error( RtAudioError::WARNING); // warn but don't fail } else { stream_.latency[0] = outputLatency; @@ -3106,7 +3155,7 @@ void RtApiAsio :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAsio::closeStream(): no open stream to close!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -3149,12 +3198,10 @@ void RtApiAsio :: startStream() verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiAsio::startStream(): the stream is already running!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } - //MUTEX_LOCK( &stream_.mutex ); - AsioHandle *handle = (AsioHandle *) stream_.apiHandle; ASIOError result = ASIOStart(); if ( result != ASE_OK ) { @@ -3170,12 +3217,10 @@ void RtApiAsio :: startStream() asioXRun = false; unlock: - //MUTEX_UNLOCK( &stream_.mutex ); - stopThreadCalled = false; if ( result == ASE_OK ) return; - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); } void RtApiAsio :: stopStream() @@ -3183,27 +3228,15 @@ void RtApiAsio :: stopStream() verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiAsio::stopStream(): the stream is already stopped!"; - error( RtError::WARNING ); - return; - } - - /* - MUTEX_LOCK( &stream_.mutex ); - - if ( stream_.state == STREAM_STOPPED ) { - MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::WARNING ); return; } - */ AsioHandle *handle = (AsioHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter == 0 ) { handle->drainCounter = 2; - // MUTEX_UNLOCK( &stream_.mutex ); WaitForSingleObject( handle->condition, INFINITE ); // block until signaled - //ResetEvent( handle->condition ); - // MUTEX_LOCK( &stream_.mutex ); } } @@ -3215,10 +3248,8 @@ void RtApiAsio :: stopStream() errorText_ = errorStream_.str(); } - // MUTEX_UNLOCK( &stream_.mutex ); - if ( result == ASE_OK ) return; - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); } void RtApiAsio :: abortStream() @@ -3226,7 +3257,7 @@ void RtApiAsio :: abortStream() verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiAsio::abortStream(): the stream is already stopped!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -3244,24 +3275,22 @@ void RtApiAsio :: abortStream() // aborted. It is necessary to handle it this way because the // callbackEvent() function must return before the ASIOStop() // function will return. -extern "C" unsigned __stdcall asioStopStream( void *ptr ) +static unsigned __stdcall asioStopStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiAsio *object = (RtApiAsio *) info->object; object->stopStream(); - _endthreadex( 0 ); return 0; } bool RtApiAsio :: callbackEvent( long bufferIndex ) { - if ( stream_.state == STREAM_STOPPED ) return SUCCESS; - if ( stopThreadCalled ) return SUCCESS; + if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAsio::callbackEvent(): the stream is closed ... this shouldn't happen!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return FAILURE; } @@ -3270,22 +3299,18 @@ bool RtApiAsio :: callbackEvent( long bufferIndex ) // Check if we were draining the stream and signal if finished. if ( handle->drainCounter > 3 ) { + + stream_.state = STREAM_STOPPING; if ( handle->internalDrain == false ) SetEvent( handle->condition ); else { // spawn a thread to stop the stream unsigned threadId; - stopThreadCalled = true; stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &asioStopStream, &stream_.callbackInfo, 0, &threadId ); } return SUCCESS; } - /*MUTEX_LOCK( &stream_.mutex ); - - // The state might change while waiting on a mutex. - if ( stream_.state == STREAM_STOPPED ) goto unlock; */ - // Invoke user callback to get fresh output data UNLESS we are // draining stream. if ( handle->drainCounter == 0 ) { @@ -3300,19 +3325,20 @@ bool RtApiAsio :: callbackEvent( long bufferIndex ) status |= RTAUDIO_INPUT_OVERFLOW; asioXRun = false; } - handle->drainCounter = callback( stream_.userBuffer[0], stream_.userBuffer[1], + int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, info->userData ); - if ( handle->drainCounter == 2 ) { - // MUTEX_UNLOCK( &stream_.mutex ); - // abortStream(); + if ( cbReturnValue == 2 ) { + stream_.state = STREAM_STOPPING; + handle->drainCounter = 2; unsigned threadId; - stopThreadCalled = true; stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &asioStopStream, &stream_.callbackInfo, 0, &threadId ); return SUCCESS; } - else if ( handle->drainCounter == 1 ) + else if ( cbReturnValue == 1 ) { + handle->drainCounter = 1; handle->internalDrain = true; + } } unsigned int nChannels, bufferBytes, i, j; @@ -3408,13 +3434,11 @@ bool RtApiAsio :: callbackEvent( long bufferIndex ) // drivers apparently do not function correctly without it. ASIOOutputReady(); - // MUTEX_UNLOCK( &stream_.mutex ); - RtApi::tickStreamTime(); return SUCCESS; } -void sampleRateChanged( ASIOSampleRate sRate ) +static void sampleRateChanged( ASIOSampleRate sRate ) { // The ASIO documentation says that this usually only happens during // external sync. Audio processing is not stopped by the driver, @@ -3426,7 +3450,7 @@ void sampleRateChanged( ASIOSampleRate sRate ) try { object->stopStream(); } - catch ( RtError &exception ) { + catch ( RtAudioError &exception ) { std::cerr << "\nRtApiAsio: sampleRateChanged() error (" << exception.getMessage() << ")!\n" << std::endl; return; } @@ -3434,7 +3458,7 @@ void sampleRateChanged( ASIOSampleRate sRate ) std::cerr << "\nRtApiAsio: driver reports sample rate changed to " << sRate << " ... stream stopped!!!\n" << std::endl; } -long asioMessages( long selector, long value, void* message, double* opt ) +static long asioMessages( long selector, long value, void* message, double* opt ) { long ret = 0; @@ -3512,7 +3536,7 @@ static const char* getAsioErrorString( ASIOError result ) const char*message; }; - static Messages m[] = + static const Messages m[] = { { ASE_NotPresent, "Hardware input or output is not present or available." }, { ASE_HWMalfunction, "Hardware is malfunctioning." }, @@ -3593,7 +3617,7 @@ static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, static const char* getErrorString( int code ); -extern "C" unsigned __stdcall callbackHandler( void *ptr ); +static unsigned __stdcall callbackHandler( void *ptr ); struct DsDevice { LPGUID id[2]; @@ -3605,7 +3629,10 @@ struct DsDevice { : found(false) { validId[0] = false; validId[1] = false; } }; -std::vector< DsDevice > dsDevices; +struct DsProbeData { + bool isInput; + std::vector* dsDevices; +}; RtApiDs :: RtApiDs() { @@ -3643,30 +3670,32 @@ unsigned int RtApiDs :: getDeviceCount( void ) dsDevices[i].found = false; // Query DirectSound devices. - bool isInput = false; - HRESULT result = DirectSoundEnumerate( (LPDSENUMCALLBACK) deviceQueryCallback, &isInput ); + struct DsProbeData probeInfo; + probeInfo.isInput = false; + probeInfo.dsDevices = &dsDevices; + HRESULT result = DirectSoundEnumerate( (LPDSENUMCALLBACK) deviceQueryCallback, &probeInfo ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::getDeviceCount: error (" << getErrorString( result ) << ") enumerating output devices!"; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); } // Query DirectSoundCapture devices. - isInput = true; - result = DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK) deviceQueryCallback, &isInput ); + probeInfo.isInput = true; + result = DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK) deviceQueryCallback, &probeInfo ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::getDeviceCount: error (" << getErrorString( result ) << ") enumerating input devices!"; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); } // Clean out any devices that may have disappeared. std::vector< int > indices; for ( unsigned int i=0; i= dsDevices.size() ) { errorText_ = "RtApiDs::getDeviceInfo: device ID is invalid!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return info; } HRESULT result; @@ -3699,7 +3730,7 @@ RtAudio::DeviceInfo RtApiDs :: getDeviceInfo( unsigned int device ) if ( FAILED( result ) ) { errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") opening output device (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); goto probeInput; } @@ -3709,7 +3740,7 @@ RtAudio::DeviceInfo RtApiDs :: getDeviceInfo( unsigned int device ) output->Release(); errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") getting capabilities!"; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); goto probeInput; } @@ -3746,7 +3777,7 @@ RtAudio::DeviceInfo RtApiDs :: getDeviceInfo( unsigned int device ) if ( FAILED( result ) ) { errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") opening input device (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -3757,7 +3788,7 @@ RtAudio::DeviceInfo RtApiDs :: getDeviceInfo( unsigned int device ) input->Release(); errorStream_ << "RtApiDs::getDeviceInfo: error (" << getErrorString( result ) << ") getting object capabilities (" << dsDevices[ device ].name << ")!"; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -3915,7 +3946,7 @@ bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned // Determine the device buffer size. By default, we'll use the value // defined above (32K), but we will grow it to make allowances for // very large software buffer sizes. - DWORD dsBufferSize = MINIMUM_DEVICE_BUFFER_SIZE;; + DWORD dsBufferSize = MINIMUM_DEVICE_BUFFER_SIZE; DWORD dsPointerLeadTime = 0; void *ohandle = 0, *bhandle = 0; @@ -4342,6 +4373,7 @@ bool RtApiDs :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned stream_.deviceBuffer = 0; } + stream_.state = STREAM_CLOSED; return FAILURE; } @@ -4349,7 +4381,7 @@ void RtApiDs :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiDs::closeStream(): no open stream to close!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -4404,12 +4436,10 @@ void RtApiDs :: startStream() verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiDs::startStream(): the stream is already running!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } - //MUTEX_LOCK( &stream_.mutex ); - DsHandle *handle = (DsHandle *) stream_.apiHandle; // Increase scheduler frequency on lesser windows (a side-effect of @@ -4454,9 +4484,7 @@ void RtApiDs :: startStream() stream_.state = STREAM_RUNNING; unlock: - // MUTEX_UNLOCK( &stream_.mutex ); - - if ( FAILED( result ) ) error( RtError::SYSTEM_ERROR ); + if ( FAILED( result ) ) error( RtAudioError::SYSTEM_ERROR ); } void RtApiDs :: stopStream() @@ -4464,18 +4492,9 @@ void RtApiDs :: stopStream() verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiDs::stopStream(): the stream is already stopped!"; - error( RtError::WARNING ); - return; - } - - /* - MUTEX_LOCK( &stream_.mutex ); - - if ( stream_.state == STREAM_STOPPED ) { - MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::WARNING ); return; } - */ HRESULT result = 0; LPVOID audioPtr; @@ -4484,10 +4503,7 @@ void RtApiDs :: stopStream() if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter == 0 ) { handle->drainCounter = 2; - // MUTEX_UNLOCK( &stream_.mutex ); WaitForSingleObject( handle->condition, INFINITE ); // block until signaled - //ResetEvent( handle->condition ); - // MUTEX_LOCK( &stream_.mutex ); } stream_.state = STREAM_STOPPED; @@ -4565,9 +4581,7 @@ void RtApiDs :: stopStream() unlock: timeEndPeriod( 1 ); // revert to normal scheduler frequency on lesser windows. - // MUTEX_UNLOCK( &stream_.mutex ); - - if ( FAILED( result ) ) error( RtError::SYSTEM_ERROR ); + if ( FAILED( result ) ) error( RtAudioError::SYSTEM_ERROR ); } void RtApiDs :: abortStream() @@ -4575,7 +4589,7 @@ void RtApiDs :: abortStream() verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiDs::abortStream(): the stream is already stopped!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -4587,14 +4601,14 @@ void RtApiDs :: abortStream() void RtApiDs :: callbackEvent() { - if ( stream_.state == STREAM_STOPPED ) { + if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) { Sleep( 50 ); // sleep 50 milliseconds return; } if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiDs::callbackEvent(): the stream is closed ... this shouldn't happen!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -4603,6 +4617,8 @@ void RtApiDs :: callbackEvent() // Check if we were draining the stream and signal is finished. if ( handle->drainCounter > stream_.nBuffers + 2 ) { + + stream_.state = STREAM_STOPPING; if ( handle->internalDrain == false ) SetEvent( handle->condition ); else @@ -4610,16 +4626,6 @@ void RtApiDs :: callbackEvent() return; } - /* - MUTEX_LOCK( &stream_.mutex ); - - // The state might change while waiting on a mutex. - if ( stream_.state == STREAM_STOPPED ) { - MUTEX_UNLOCK( &stream_.mutex ); - return; - } - */ - // Invoke user callback to get fresh output data UNLESS we are // draining stream. if ( handle->drainCounter == 0 ) { @@ -4634,15 +4640,18 @@ void RtApiDs :: callbackEvent() status |= RTAUDIO_INPUT_OVERFLOW; handle->xrun[1] = false; } - handle->drainCounter = callback( stream_.userBuffer[0], stream_.userBuffer[1], - stream_.bufferSize, streamTime, status, info->userData ); - if ( handle->drainCounter == 2 ) { - // MUTEX_UNLOCK( &stream_.mutex ); + int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], + stream_.bufferSize, streamTime, status, info->userData ); + if ( cbReturnValue == 2 ) { + stream_.state = STREAM_STOPPING; + handle->drainCounter = 2; abortStream(); return; } - else if ( handle->drainCounter == 1 ) + else if ( cbReturnValue == 1 ) { + handle->drainCounter = 1; handle->internalDrain = true; + } } HRESULT result; @@ -4685,26 +4694,30 @@ void RtApiDs :: callbackEvent() if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } result = dsCaptureBuffer->GetCurrentPosition( NULL, &startSafeReadPointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } while ( true ) { result = dsWriteBuffer->GetCurrentPosition( NULL, &safeWritePointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } result = dsCaptureBuffer->GetCurrentPosition( NULL, &safeReadPointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } if ( safeWritePointer != startSafeWritePointer && safeReadPointer != startSafeReadPointer ) break; Sleep( 1 ); @@ -4724,7 +4737,8 @@ void RtApiDs :: callbackEvent() if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } handle->bufferPointer[0] = safeWritePointer + handle->dsPointerLeadTime[0]; if ( handle->bufferPointer[0] >= handle->dsBufferSize[0] ) handle->bufferPointer[0] -= handle->dsBufferSize[0]; @@ -4774,7 +4788,8 @@ void RtApiDs :: callbackEvent() if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } // We will copy our output buffer into the region between @@ -4814,7 +4829,8 @@ void RtApiDs :: callbackEvent() if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking buffer during playback!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } // Copy our buffer into the DS buffer @@ -4826,7 +4842,8 @@ void RtApiDs :: callbackEvent() if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking buffer during playback!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } nextWritePointer = ( nextWritePointer + bufferSize1 + bufferSize2 ) % dsBufferSize; handle->bufferPointer[0] = nextWritePointer; @@ -4860,7 +4877,8 @@ void RtApiDs :: callbackEvent() if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset @@ -4908,7 +4926,7 @@ void RtApiDs :: callbackEvent() } } else { // mode == INPUT - while ( safeReadPointer < endRead ) { + while ( safeReadPointer < endRead && stream_.callbackInfo.isRunning ) { // See comments for playback. double millis = (endRead - safeReadPointer) * 1000.0; millis /= ( formatBytes(stream_.deviceFormat[1]) * stream_.nDeviceChannels[1] * stream_.sampleRate); @@ -4920,7 +4938,8 @@ void RtApiDs :: callbackEvent() if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset @@ -4933,7 +4952,8 @@ void RtApiDs :: callbackEvent() if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking capture buffer!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } if ( duplexPrerollBytes <= 0 ) { @@ -4953,7 +4973,8 @@ void RtApiDs :: callbackEvent() if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking capture buffer!"; errorText_ = errorStream_.str(); - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); + return; } handle->bufferPointer[1] = nextReadPointer; @@ -4969,15 +4990,13 @@ void RtApiDs :: callbackEvent() } unlock: - // MUTEX_UNLOCK( &stream_.mutex ); - RtApi::tickStreamTime(); } // Definitions for utility functions and callbacks // specific to the DirectSound implementation. -extern "C" unsigned __stdcall callbackHandler( void *ptr ) +static unsigned __stdcall callbackHandler( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiDs *object = (RtApiDs *) info->object; @@ -4993,12 +5012,12 @@ extern "C" unsigned __stdcall callbackHandler( void *ptr ) #include "tchar.h" -std::string convertTChar( LPCTSTR name ) +static std::string convertTChar( LPCTSTR name ) { #if defined( UNICODE ) || defined( _UNICODE ) int length = WideCharToMultiByte(CP_UTF8, 0, name, -1, NULL, 0, NULL, NULL); - std::string s( length, 0 ); - length = WideCharToMultiByte(CP_UTF8, 0, name, wcslen(name), &s[0], length, NULL, NULL); + std::string s( length-1, '\0' ); + WideCharToMultiByte(CP_UTF8, 0, name, -1, &s[0], length, NULL, NULL); #else std::string s( name ); #endif @@ -5011,11 +5030,12 @@ static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, LPCTSTR module, LPVOID lpContext ) { - bool *isInput = (bool *) lpContext; + struct DsProbeData& probeInfo = *(struct DsProbeData*) lpContext; + std::vector& dsDevices = *probeInfo.dsDevices; HRESULT hr; bool validDevice = false; - if ( *isInput == true ) { + if ( probeInfo.isInput == true ) { DSCCAPS caps; LPDIRECTSOUNDCAPTURE object; @@ -5047,13 +5067,14 @@ static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, // If good device, then save its name and guid. std::string name = convertTChar( description ); - if ( name == "Primary Sound Driver" || name == "Primary Sound Capture Driver" ) + //if ( name == "Primary Sound Driver" || name == "Primary Sound Capture Driver" ) + if ( lpguid == NULL ) name = "Default Device"; if ( validDevice ) { for ( unsigned int i=0; i= nDevices ) { errorText_ = "RtApiAlsa::getDeviceInfo: device ID is invalid!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return info; } foundDevice: @@ -5268,9 +5306,10 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) // Thus, use the saved results. if ( stream_.state != STREAM_CLOSED && ( stream_.device[0] == device || stream_.device[1] == device ) ) { + snd_ctl_close( chandle ); if ( device >= devices_.size() ) { errorText_ = "RtApiAlsa::getDeviceInfo: device ID was not present before stream was opened."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } return devices_[ device ]; @@ -5284,23 +5323,25 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) snd_pcm_hw_params_t *params; snd_pcm_hw_params_alloca( ¶ms ); - // First try for playback + // First try for playback unless default device (which has subdev -1) stream = SND_PCM_STREAM_PLAYBACK; - snd_pcm_info_set_device( pcminfo, subdevice ); - snd_pcm_info_set_subdevice( pcminfo, 0 ); snd_pcm_info_set_stream( pcminfo, stream ); + if ( subdevice != -1 ) { + snd_pcm_info_set_device( pcminfo, subdevice ); + snd_pcm_info_set_subdevice( pcminfo, 0 ); - result = snd_ctl_pcm_info( chandle, pcminfo ); - if ( result < 0 ) { - // Device probably doesn't support playback. - goto captureProbe; + result = snd_ctl_pcm_info( chandle, pcminfo ); + if ( result < 0 ) { + // Device probably doesn't support playback. + goto captureProbe; + } } result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); goto captureProbe; } @@ -5310,7 +5351,7 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); goto captureProbe; } @@ -5321,30 +5362,34 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: error getting device (" << name << ") output channels, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); goto captureProbe; } info.outputChannels = value; snd_pcm_close( phandle ); captureProbe: - // Now try for capture stream = SND_PCM_STREAM_CAPTURE; snd_pcm_info_set_stream( pcminfo, stream ); - result = snd_ctl_pcm_info( chandle, pcminfo ); - snd_ctl_close( chandle ); - if ( result < 0 ) { - // Device probably doesn't support capture. - if ( info.outputChannels == 0 ) return info; - goto probeParameters; + // Now try for capture unless default device (with subdev = -1) + if ( subdevice != -1 ) { + result = snd_ctl_pcm_info( chandle, pcminfo ); + snd_ctl_close( chandle ); + if ( result < 0 ) { + // Device probably doesn't support capture. + if ( info.outputChannels == 0 ) return info; + goto probeParameters; + } } + else + snd_ctl_close( chandle ); result = snd_pcm_open( &phandle, name, stream, openMode | SND_PCM_NONBLOCK); if ( result < 0 ) { errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); if ( info.outputChannels == 0 ) return info; goto probeParameters; } @@ -5355,7 +5400,7 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); if ( info.outputChannels == 0 ) return info; goto probeParameters; } @@ -5365,7 +5410,7 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: error getting device (" << name << ") input channels, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); if ( info.outputChannels == 0 ) return info; goto probeParameters; } @@ -5399,7 +5444,7 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) if ( result < 0 ) { errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -5409,7 +5454,7 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -5423,7 +5468,7 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: no supported sample rates found for device (" << name << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -5451,17 +5496,20 @@ RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device ) // Check that we have at least one supported format if ( info.nativeFormats == 0 ) { + snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::getDeviceInfo: pcm device (" << name << ") data format not supported by RtAudio."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } // Get the device name char *cardname; result = snd_card_get_name( card, &cardname ); - if ( result >= 0 ) + if ( result >= 0 ) { sprintf( name, "hw:%s,%d", cardname, subdevice ); + free( cardname ); + } info.name = name; // That's all ... close the device and return @@ -5528,6 +5576,15 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne snd_card_next( &card ); } + result = snd_ctl_open( &chandle, "default", SND_CTL_NONBLOCK ); + if ( result == 0 ) { + if ( nDevices == device ) { + strcpy( name, "default" ); + goto foundDevice; + } + nDevices++; + } + if ( nDevices == 0 ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiAlsa::probeDeviceOpen: no devices found!"; @@ -5672,6 +5729,7 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne } // If we get here, no supported format was found. + snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device " << device << " data format not supported by RtAudio."; errorText_ = errorStream_.str(); return FAILURE; @@ -5769,6 +5827,7 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne // If attempting to setup a duplex stream, the bufferSize parameter // MUST be the same in both directions! if ( stream_.mode == OUTPUT && mode == INPUT && *bufferSize != stream_.bufferSize ) { + snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: system error setting buffer size for duplex stream on device (" << name << ")."; errorText_ = errorStream_.str(); return FAILURE; @@ -5855,6 +5914,7 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne apiInfo = (AlsaHandle *) stream_.apiHandle; } apiInfo->handles[mode] = phandle; + phandle = 0; // Allocate necessary internal buffers. unsigned long bufferBytes; @@ -5905,7 +5965,7 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne apiInfo->synchronized = true; else { errorText_ = "RtApiAlsa::probeDeviceOpen: unable to synchronize input and output devices."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); } } else { @@ -5923,22 +5983,21 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne pthread_attr_t attr; pthread_attr_init( &attr ); pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); + #ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { - struct sched_param param; + // 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; 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; - pthread_attr_setschedparam( &attr, ¶m ); - pthread_attr_setschedpolicy( &attr, SCHED_RR ); + stream_.callbackInfo.priority = priority; } - else - pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); -#else - pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); #endif stream_.callbackInfo.isRunning = true; @@ -5962,6 +6021,8 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne stream_.apiHandle = 0; } + if ( phandle) snd_pcm_close( phandle ); + for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); @@ -5974,6 +6035,7 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne stream_.deviceBuffer = 0; } + stream_.state = STREAM_CLOSED; return FAILURE; } @@ -5981,7 +6043,7 @@ void RtApiAlsa :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAlsa::closeStream(): no open stream to close!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -6034,7 +6096,7 @@ void RtApiAlsa :: startStream() verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiAlsa::startStream(): the stream is already running!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -6057,6 +6119,7 @@ void RtApiAlsa :: startStream() } if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) { + result = snd_pcm_drop(handle[1]); // fix to remove stale data received since device has been open state = snd_pcm_state( handle[1] ); if ( state != SND_PCM_STATE_PREPARED ) { result = snd_pcm_prepare( handle[1] ); @@ -6071,12 +6134,12 @@ void RtApiAlsa :: startStream() stream_.state = STREAM_RUNNING; unlock: - apiInfo->runnable = true; + apiInfo->runnable = false; // fixes high CPU usage when stopped pthread_cond_signal( &apiInfo->runnable_cv ); MUTEX_UNLOCK( &stream_.mutex ); if ( result >= 0 ) return; - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); } void RtApiAlsa :: stopStream() @@ -6084,18 +6147,13 @@ void RtApiAlsa :: stopStream() verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiAlsa::stopStream(): the stream is already stopped!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); - //if ( stream_.state == STREAM_STOPPED ) { - // MUTEX_UNLOCK( &stream_.mutex ); - // return; - //} - int result = 0; AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; @@ -6121,11 +6179,11 @@ void RtApiAlsa :: stopStream() } unlock: - stream_.state = STREAM_STOPPED; + apiInfo->runnable = false; // fixes high CPU usage when stopped MUTEX_UNLOCK( &stream_.mutex ); if ( result >= 0 ) return; - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); } void RtApiAlsa :: abortStream() @@ -6133,18 +6191,13 @@ void RtApiAlsa :: abortStream() verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiAlsa::abortStream(): the stream is already stopped!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); - //if ( stream_.state == STREAM_STOPPED ) { - // MUTEX_UNLOCK( &stream_.mutex ); - // return; - //} - int result = 0; AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; @@ -6167,11 +6220,10 @@ void RtApiAlsa :: abortStream() } unlock: - stream_.state = STREAM_STOPPED; MUTEX_UNLOCK( &stream_.mutex ); if ( result >= 0 ) return; - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); } void RtApiAlsa :: callbackEvent() @@ -6191,7 +6243,7 @@ void RtApiAlsa :: callbackEvent() if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAlsa::callbackEvent(): the stream is closed ... this shouldn't happen!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -6274,7 +6326,7 @@ void RtApiAlsa :: callbackEvent() errorStream_ << "RtApiAlsa::callbackEvent: audio read error, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } - error( RtError::WARNING ); + error( RtAudioError::WARNING ); goto tryOutput; } @@ -6344,7 +6396,7 @@ void RtApiAlsa :: callbackEvent() errorStream_ << "RtApiAlsa::callbackEvent: audio write error, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } - error( RtError::WARNING ); + error( RtAudioError::WARNING ); goto unlock; } @@ -6360,12 +6412,20 @@ void RtApiAlsa :: callbackEvent() if ( doStopStream == 1 ) this->stopStream(); } -extern "C" void *alsaCallbackHandler( void *ptr ) +static void *alsaCallbackHandler( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiAlsa *object = (RtApiAlsa *) info->object; bool *isRunning = &info->isRunning; +#ifdef SCHED_RR // Undefined with some OSes (eg: NetBSD 1.6.x with GNU Pthread) + 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 ); + } +#endif + while ( *isRunning == true ) { pthread_testcancel(); object->callbackEvent(); @@ -6377,55 +6437,534 @@ extern "C" void *alsaCallbackHandler( void *ptr ) //******************** End of __LINUX_ALSA__ *********************// #endif +#if defined(__LINUX_PULSE__) -#if defined(__LINUX_OSS__) +// Code written by Peter Meerwald, pmeerw@pmeerw.net +// and Tristan Matthews. -#include -#include -#include -#include -#include "soundcard.h" -#include -#include +#include +#include +#include -extern "C" void *ossCallbackHandler(void * ptr); +static const unsigned int SUPPORTED_SAMPLERATES[] = { 8000, 16000, 22050, 32000, + 44100, 48000, 96000, 0}; -// A structure to hold various information related to the OSS API -// implementation. -struct OssHandle { - int id[2]; // device ids - bool xrun[2]; - bool triggered; - pthread_cond_t runnable; +struct rtaudio_pa_format_mapping_t { + RtAudioFormat rtaudio_format; + pa_sample_format_t pa_format; +}; - OssHandle() - :triggered(false) { id[0] = 0; id[1] = 0; xrun[0] = false; xrun[1] = false; } +static const rtaudio_pa_format_mapping_t supported_sampleformats[] = { + {RTAUDIO_SINT16, PA_SAMPLE_S16LE}, + {RTAUDIO_SINT32, PA_SAMPLE_S32LE}, + {RTAUDIO_FLOAT32, PA_SAMPLE_FLOAT32LE}, + {0, PA_SAMPLE_INVALID}}; + +struct PulseAudioHandle { + pa_simple *s_play; + pa_simple *s_rec; + pthread_t thread; + pthread_cond_t runnable_cv; + bool runnable; + PulseAudioHandle() : s_play(0), s_rec(0), runnable(false) { } }; -RtApiOss :: RtApiOss() +RtApiPulse::~RtApiPulse() { - // Nothing to do here. + if ( stream_.state != STREAM_CLOSED ) + closeStream(); } -RtApiOss :: ~RtApiOss() +unsigned int RtApiPulse::getDeviceCount( void ) { - if ( stream_.state != STREAM_CLOSED ) closeStream(); + return 1; } -unsigned int RtApiOss :: getDeviceCount( void ) +RtAudio::DeviceInfo RtApiPulse::getDeviceInfo( unsigned int device ) { - int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); - if ( mixerfd == -1 ) { - errorText_ = "RtApiOss::getDeviceCount: error opening '/dev/mixer'."; - error( RtError::WARNING ); - return 0; - } + RtAudio::DeviceInfo info; + info.probed = true; + info.name = "PulseAudio"; + info.outputChannels = 2; + info.inputChannels = 2; + info.duplexChannels = 2; + info.isDefaultOutput = true; + info.isDefaultInput = true; - oss_sysinfo sysinfo; + for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) + info.sampleRates.push_back( *sr ); + + info.nativeFormats = RTAUDIO_SINT16 | RTAUDIO_SINT32 | RTAUDIO_FLOAT32; + + return info; +} + +static void *pulseaudio_callback( void * user ) +{ + CallbackInfo *cbi = static_cast( user ); + RtApiPulse *context = static_cast( cbi->object ); + volatile bool *isRunning = &cbi->isRunning; + + while ( *isRunning ) { + pthread_testcancel(); + context->callbackEvent(); + } + + pthread_exit( NULL ); +} + +void RtApiPulse::closeStream( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + stream_.callbackInfo.isRunning = false; + if ( pah ) { + MUTEX_LOCK( &stream_.mutex ); + if ( stream_.state == STREAM_STOPPED ) { + pah->runnable = true; + pthread_cond_signal( &pah->runnable_cv ); + } + MUTEX_UNLOCK( &stream_.mutex ); + + pthread_join( pah->thread, 0 ); + if ( pah->s_play ) { + pa_simple_flush( pah->s_play, NULL ); + pa_simple_free( pah->s_play ); + } + if ( pah->s_rec ) + pa_simple_free( pah->s_rec ); + + pthread_cond_destroy( &pah->runnable_cv ); + delete pah; + stream_.apiHandle = 0; + } + + if ( stream_.userBuffer[0] ) { + free( stream_.userBuffer[0] ); + stream_.userBuffer[0] = 0; + } + if ( stream_.userBuffer[1] ) { + free( stream_.userBuffer[1] ); + stream_.userBuffer[1] = 0; + } + + stream_.state = STREAM_CLOSED; + stream_.mode = UNINITIALIZED; +} + +void RtApiPulse::callbackEvent( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + if ( stream_.state == STREAM_STOPPED ) { + MUTEX_LOCK( &stream_.mutex ); + while ( !pah->runnable ) + pthread_cond_wait( &pah->runnable_cv, &stream_.mutex ); + + if ( stream_.state != STREAM_RUNNING ) { + MUTEX_UNLOCK( &stream_.mutex ); + return; + } + MUTEX_UNLOCK( &stream_.mutex ); + } + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiPulse::callbackEvent(): the stream is closed ... " + "this shouldn't happen!"; + error( RtAudioError::WARNING ); + return; + } + + RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; + double streamTime = getStreamTime(); + RtAudioStreamStatus status = 0; + int doStopStream = callback( stream_.userBuffer[OUTPUT], stream_.userBuffer[INPUT], + stream_.bufferSize, streamTime, status, + stream_.callbackInfo.userData ); + + if ( doStopStream == 2 ) { + abortStream(); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + void *pulse_in = stream_.doConvertBuffer[INPUT] ? stream_.deviceBuffer : stream_.userBuffer[INPUT]; + void *pulse_out = stream_.doConvertBuffer[OUTPUT] ? stream_.deviceBuffer : stream_.userBuffer[OUTPUT]; + + if ( stream_.state != STREAM_RUNNING ) + goto unlock; + + int pa_error; + size_t bytes; + if (stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { + if ( stream_.doConvertBuffer[OUTPUT] ) { + convertBuffer( stream_.deviceBuffer, + stream_.userBuffer[OUTPUT], + stream_.convertInfo[OUTPUT] ); + bytes = stream_.nDeviceChannels[OUTPUT] * stream_.bufferSize * + formatBytes( stream_.deviceFormat[OUTPUT] ); + } else + bytes = stream_.nUserChannels[OUTPUT] * stream_.bufferSize * + formatBytes( stream_.userFormat ); + + if ( pa_simple_write( pah->s_play, pulse_out, bytes, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::callbackEvent: audio write error, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + } + } + + if ( stream_.mode == INPUT || stream_.mode == DUPLEX) { + if ( stream_.doConvertBuffer[INPUT] ) + bytes = stream_.nDeviceChannels[INPUT] * stream_.bufferSize * + formatBytes( stream_.deviceFormat[INPUT] ); + 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 ) << "."; + errorText_ = errorStream_.str(); + error( RtAudioError::WARNING ); + } + if ( stream_.doConvertBuffer[INPUT] ) { + convertBuffer( stream_.userBuffer[INPUT], + stream_.deviceBuffer, + stream_.convertInfo[INPUT] ); + } + } + + unlock: + MUTEX_UNLOCK( &stream_.mutex ); + RtApi::tickStreamTime(); + + if ( doStopStream == 1 ) + stopStream(); +} + +void RtApiPulse::startStream( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiPulse::startStream(): the stream is not open!"; + error( RtAudioError::INVALID_USE ); + return; + } + if ( stream_.state == STREAM_RUNNING ) { + errorText_ = "RtApiPulse::startStream(): the stream is already running!"; + error( RtAudioError::WARNING ); + return; + } + + MUTEX_LOCK( &stream_.mutex ); + + stream_.state = STREAM_RUNNING; + + pah->runnable = true; + pthread_cond_signal( &pah->runnable_cv ); + MUTEX_UNLOCK( &stream_.mutex ); +} + +void RtApiPulse::stopStream( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiPulse::stopStream(): the stream is not open!"; + error( RtAudioError::INVALID_USE ); + return; + } + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiPulse::stopStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + stream_.state = STREAM_STOPPED; + MUTEX_LOCK( &stream_.mutex ); + + if ( pah && pah->s_play ) { + int pa_error; + if ( pa_simple_drain( pah->s_play, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::stopStream: error draining output device, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + } + + stream_.state = STREAM_STOPPED; + MUTEX_UNLOCK( &stream_.mutex ); +} + +void RtApiPulse::abortStream( void ) +{ + PulseAudioHandle *pah = static_cast( stream_.apiHandle ); + + if ( stream_.state == STREAM_CLOSED ) { + errorText_ = "RtApiPulse::abortStream(): the stream is not open!"; + error( RtAudioError::INVALID_USE ); + return; + } + if ( stream_.state == STREAM_STOPPED ) { + errorText_ = "RtApiPulse::abortStream(): the stream is already stopped!"; + error( RtAudioError::WARNING ); + return; + } + + stream_.state = STREAM_STOPPED; + MUTEX_LOCK( &stream_.mutex ); + + if ( pah && pah->s_play ) { + int pa_error; + if ( pa_simple_flush( pah->s_play, &pa_error ) < 0 ) { + errorStream_ << "RtApiPulse::abortStream: error flushing output device, " << + pa_strerror( pa_error ) << "."; + errorText_ = errorStream_.str(); + MUTEX_UNLOCK( &stream_.mutex ); + error( RtAudioError::SYSTEM_ERROR ); + return; + } + } + + stream_.state = STREAM_STOPPED; + MUTEX_UNLOCK( &stream_.mutex ); +} + +bool RtApiPulse::probeDeviceOpen( unsigned int device, StreamMode mode, + unsigned int channels, unsigned int firstChannel, + unsigned int sampleRate, RtAudioFormat format, + unsigned int *bufferSize, RtAudio::StreamOptions *options ) +{ + PulseAudioHandle *pah = 0; + unsigned long bufferBytes = 0; + pa_sample_spec ss; + + 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; + + bool sr_found = false; + for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) { + if ( sampleRate == *sr ) { + sr_found = true; + stream_.sampleRate = sampleRate; + ss.rate = sampleRate; + break; + } + } + if ( !sr_found ) { + errorText_ = "RtApiPulse::probeDeviceOpen: unsupported sample rate."; + return false; + } + + bool sf_found = 0; + for ( const rtaudio_pa_format_mapping_t *sf = supported_sampleformats; + sf->rtaudio_format && sf->pa_format != PA_SAMPLE_INVALID; ++sf ) { + if ( format == sf->rtaudio_format ) { + sf_found = true; + stream_.userFormat = sf->rtaudio_format; + ss.format = sf->pa_format; + break; + } + } + if ( !sf_found ) { + errorText_ = "RtApiPulse::probeDeviceOpen: unsupported sample format."; + return false; + } + + // Set interleaving parameters. + if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; + else stream_.userInterleaved = true; + stream_.deviceInterleaved[mode] = true; + stream_.nBuffers = 1; + stream_.doByteSwap[mode] = false; + stream_.doConvertBuffer[mode] = channels > 1 && !stream_.userInterleaved; + stream_.deviceFormat[mode] = stream_.userFormat; + stream_.nUserChannels[mode] = channels; + stream_.nDeviceChannels[mode] = channels + firstChannel; + stream_.channelOffset[mode] = 0; + + // Allocate necessary internal buffers. + bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); + stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); + if ( stream_.userBuffer[mode] == NULL ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error allocating user buffer memory."; + goto error; + } + stream_.bufferSize = *bufferSize; + + if ( stream_.doConvertBuffer[mode] ) { + + bool makeBuffer = true; + bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); + if ( mode == INPUT ) { + if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { + unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); + if ( bufferBytes <= bytesOut ) makeBuffer = false; + } + } + + if ( makeBuffer ) { + bufferBytes *= *bufferSize; + if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); + stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); + if ( stream_.deviceBuffer == NULL ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error allocating device buffer memory."; + goto error; + } + } + } + + stream_.device[mode] = device; + + // Setup the buffer conversion information structure. + if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); + + if ( !stream_.apiHandle ) { + PulseAudioHandle *pah = new PulseAudioHandle; + if ( !pah ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error allocating memory for handle."; + goto error; + } + + stream_.apiHandle = pah; + if ( pthread_cond_init( &pah->runnable_cv, NULL ) != 0 ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error creating condition variable."; + goto error; + } + } + pah = static_cast( stream_.apiHandle ); + + int error; + std::string streamName = "RtAudio"; + if ( !options->streamName.empty() ) streamName = options->streamName; + switch ( mode ) { + case INPUT: + pa_buffer_attr buffer_attr; + buffer_attr.fragsize = bufferBytes; + buffer_attr.maxlength = -1; + + pah->s_rec = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_RECORD, NULL, "Record", &ss, NULL, &buffer_attr, &error ); + if ( !pah->s_rec ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error connecting input to PulseAudio server."; + goto error; + } + break; + case OUTPUT: + pah->s_play = pa_simple_new( NULL, "RtAudio", PA_STREAM_PLAYBACK, NULL, "Playback", &ss, NULL, NULL, &error ); + if ( !pah->s_play ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error connecting output to PulseAudio server."; + goto error; + } + break; + default: + goto error; + } + + if ( stream_.mode == UNINITIALIZED ) + stream_.mode = mode; + else if ( stream_.mode == mode ) + goto error; + else + stream_.mode = DUPLEX; + + if ( !stream_.callbackInfo.isRunning ) { + stream_.callbackInfo.object = this; + stream_.callbackInfo.isRunning = true; + if ( pthread_create( &pah->thread, NULL, pulseaudio_callback, (void *)&stream_.callbackInfo) != 0 ) { + errorText_ = "RtApiPulse::probeDeviceOpen: error creating thread."; + goto error; + } + } + + stream_.state = STREAM_STOPPED; + return true; + + error: + if ( pah && stream_.callbackInfo.isRunning ) { + pthread_cond_destroy( &pah->runnable_cv ); + delete pah; + stream_.apiHandle = 0; + } + + for ( int i=0; i<2; i++ ) { + if ( stream_.userBuffer[i] ) { + free( stream_.userBuffer[i] ); + stream_.userBuffer[i] = 0; + } + } + + if ( stream_.deviceBuffer ) { + free( stream_.deviceBuffer ); + stream_.deviceBuffer = 0; + } + + return FAILURE; +} + +//******************** End of __LINUX_PULSE__ *********************// +#endif + +#if defined(__LINUX_OSS__) + +#include +#include +#include +#include +#include +#include +#include + +static void *ossCallbackHandler(void * ptr); + +// A structure to hold various information related to the OSS API +// implementation. +struct OssHandle { + int id[2]; // device ids + bool xrun[2]; + bool triggered; + pthread_cond_t runnable; + + OssHandle() + :triggered(false) { id[0] = 0; id[1] = 0; xrun[0] = false; xrun[1] = false; } +}; + +RtApiOss :: RtApiOss() +{ + // Nothing to do here. +} + +RtApiOss :: ~RtApiOss() +{ + if ( stream_.state != STREAM_CLOSED ) closeStream(); +} + +unsigned int RtApiOss :: getDeviceCount( void ) +{ + int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); + if ( mixerfd == -1 ) { + errorText_ = "RtApiOss::getDeviceCount: error opening '/dev/mixer'."; + error( RtAudioError::WARNING ); + return 0; + } + + oss_sysinfo sysinfo; if ( ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ) == -1 ) { close( mixerfd ); errorText_ = "RtApiOss::getDeviceCount: error getting sysinfo, OSS version >= 4.0 is required."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return 0; } @@ -6441,7 +6980,7 @@ RtAudio::DeviceInfo RtApiOss :: getDeviceInfo( unsigned int device ) int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); if ( mixerfd == -1 ) { errorText_ = "RtApiOss::getDeviceInfo: error opening '/dev/mixer'."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -6450,7 +6989,7 @@ RtAudio::DeviceInfo RtApiOss :: getDeviceInfo( unsigned int device ) if ( result == -1 ) { close( mixerfd ); errorText_ = "RtApiOss::getDeviceInfo: error getting sysinfo, OSS version >= 4.0 is required."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -6458,13 +6997,15 @@ RtAudio::DeviceInfo RtApiOss :: getDeviceInfo( unsigned int device ) if ( nDevices == 0 ) { close( mixerfd ); errorText_ = "RtApiOss::getDeviceInfo: no devices found!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return info; } if ( device >= nDevices ) { close( mixerfd ); errorText_ = "RtApiOss::getDeviceInfo: device ID is invalid!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); + return info; } oss_audioinfo ainfo; @@ -6474,7 +7015,7 @@ RtAudio::DeviceInfo RtApiOss :: getDeviceInfo( unsigned int device ) if ( result == -1 ) { errorStream_ << "RtApiOss::getDeviceInfo: error getting device (" << ainfo.name << ") info."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -6503,7 +7044,7 @@ RtAudio::DeviceInfo RtApiOss :: getDeviceInfo( unsigned int device ) if ( info.nativeFormats == 0 ) { errorStream_ << "RtApiOss::getDeviceInfo: device (" << ainfo.name << ") data format not supported by RtAudio."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return info; } @@ -6530,7 +7071,7 @@ RtAudio::DeviceInfo RtApiOss :: getDeviceInfo( unsigned int device ) if ( info.sampleRates.size() == 0 ) { errorStream_ << "RtApiOss::getDeviceInfo: no supported sample rates found for device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); - error( RtError::WARNING ); + error( RtAudioError::WARNING ); } else { info.probed = true; @@ -6979,7 +7520,7 @@ void RtApiOss :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiOss::closeStream(): no open stream to close!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -7028,7 +7569,7 @@ void RtApiOss :: startStream() verifyStream(); if ( stream_.state == STREAM_RUNNING ) { errorText_ = "RtApiOss::startStream(): the stream is already running!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -7050,7 +7591,7 @@ void RtApiOss :: stopStream() verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiOss::stopStream(): the stream is already stopped!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -7087,7 +7628,7 @@ void RtApiOss :: stopStream() result = write( handle->id[0], buffer, samples * formatBytes(format) ); if ( result == -1 ) { errorText_ = "RtApiOss::stopStream: audio write error."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); } } @@ -7114,7 +7655,7 @@ void RtApiOss :: stopStream() MUTEX_UNLOCK( &stream_.mutex ); if ( result != -1 ) return; - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); } void RtApiOss :: abortStream() @@ -7122,7 +7663,7 @@ void RtApiOss :: abortStream() verifyStream(); if ( stream_.state == STREAM_STOPPED ) { errorText_ = "RtApiOss::abortStream(): the stream is already stopped!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -7160,7 +7701,7 @@ void RtApiOss :: abortStream() MUTEX_UNLOCK( &stream_.mutex ); if ( result != -1 ) return; - error( RtError::SYSTEM_ERROR ); + error( RtAudioError::SYSTEM_ERROR ); } void RtApiOss :: callbackEvent() @@ -7178,7 +7719,7 @@ void RtApiOss :: callbackEvent() if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiOss::callbackEvent(): the stream is closed ... this shouldn't happen!"; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return; } @@ -7248,7 +7789,7 @@ void RtApiOss :: callbackEvent() // specific means for determining that. handle->xrun[0] = true; errorText_ = "RtApiOss::callbackEvent: audio write error."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); // Continue on to input section. } } @@ -7275,7 +7816,7 @@ void RtApiOss :: callbackEvent() // specific means for determining that. handle->xrun[1] = true; errorText_ = "RtApiOss::callbackEvent: audio read error."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); goto unlock; } @@ -7295,7 +7836,7 @@ void RtApiOss :: callbackEvent() if ( doStopStream == 1 ) this->stopStream(); } -extern "C" void *ossCallbackHandler( void *ptr ) +static void *ossCallbackHandler( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiOss *object = (RtApiOss *) info->object; @@ -7321,20 +7862,41 @@ extern "C" void *ossCallbackHandler( void *ptr ) // This method can be modified to control the behavior of error // message printing. -void RtApi :: error( RtError::Type type ) +void RtApi :: error( RtAudioError::Type type ) { errorStream_.str(""); // clear the ostringstream - if ( type == RtError::WARNING && showWarnings_ == true ) + + RtAudioErrorCallback errorCallback = (RtAudioErrorCallback) stream_.callbackInfo.errorCallback; + if ( errorCallback ) { + // abortStream() can generate new error messages. Ignore them. Just keep original one. + + if ( firstErrorOccurred_ ) + return; + + firstErrorOccurred_ = true; + const std::string errorMessage = errorText_; + + if ( type != RtAudioError::WARNING && stream_.state != STREAM_STOPPED) { + stream_.callbackInfo.isRunning = false; // exit from the thread + abortStream(); + } + + errorCallback( type, errorMessage ); + firstErrorOccurred_ = false; + return; + } + + if ( type == RtAudioError::WARNING && showWarnings_ == true ) std::cerr << '\n' << errorText_ << "\n\n"; - else if ( type != RtError::WARNING ) - throw( RtError( errorText_, type ) ); + else if ( type != RtAudioError::WARNING ) + throw( RtAudioError( errorText_, type ) ); } void RtApi :: verifyStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApi:: a stream is not open!"; - error( RtError::INVALID_USE ); + error( RtAudioError::INVALID_USE ); } } @@ -7353,6 +7915,7 @@ void RtApi :: clearStreamInfo() stream_.callbackInfo.callback = 0; stream_.callbackInfo.userData = 0; stream_.callbackInfo.isRunning = false; + stream_.callbackInfo.errorCallback = 0; for ( int i=0; i<2; i++ ) { stream_.device[i] = 11111; stream_.doConvertBuffer[i] = false; @@ -7378,16 +7941,17 @@ unsigned int RtApi :: formatBytes( RtAudioFormat format ) { if ( format == RTAUDIO_SINT16 ) return 2; - else if ( format == RTAUDIO_SINT24 || format == RTAUDIO_SINT32 || - format == RTAUDIO_FLOAT32 ) + else if ( format == RTAUDIO_SINT32 || format == RTAUDIO_FLOAT32 ) return 4; else if ( format == RTAUDIO_FLOAT64 ) return 8; + else if ( format == RTAUDIO_SINT24 ) + return 3; else if ( format == RTAUDIO_SINT8 ) return 1; errorText_ = "RtApi::formatBytes: undefined format."; - error( RtError::WARNING ); + error( RtAudioError::WARNING ); return 0; } @@ -7515,11 +8079,11 @@ void RtApi :: convertBuffer( char *outBuffer, char *inBuffer, ConvertInfo &info } } else if (info.inFormat == RTAUDIO_SINT24) { - Int32 *in = (Int32 *)inBuffer; + Int24 *in = (Int24 *)inBuffer; scale = 1.0 / 8388607.5; for (unsigned int i=0; i>= 8; + out[info.outOffset[j]] = (Int32) (in[info.inOffset[j]] >> 8); + //out[info.outOffset[j]] >>= 8; } in += info.inJump; out += info.outJump; @@ -7799,10 +8363,10 @@ void RtApi :: convertBuffer( char *outBuffer, char *inBuffer, ConvertInfo &info } } else if (info.inFormat == RTAUDIO_SINT24) { - Int32 *in = (Int32 *)inBuffer; + Int24 *in = (Int24 *)inBuffer; for (unsigned int i=0; i> 8) & 0x0000ffff); + out[info.outOffset[j]] = (Int16) (in[info.inOffset[j]].asInt() >> 8); } in += info.inJump; out += info.outJump; @@ -7863,10 +8427,10 @@ void RtApi :: convertBuffer( char *outBuffer, char *inBuffer, ConvertInfo &info } } else if (info.inFormat == RTAUDIO_SINT24) { - Int32 *in = (Int32 *)inBuffer; + Int24 *in = (Int24 *)inBuffer; for (unsigned int i=0; i> 16) & 0x000000ff); + out[info.outOffset[j]] = (signed char) (in[info.inOffset[j]].asInt() >> 16); } in += info.inJump; out += info.outJump; @@ -7905,9 +8469,9 @@ void RtApi :: convertBuffer( char *outBuffer, char *inBuffer, ConvertInfo &info } } - //static inline uint16_t bswap_16(uint16_t x) { return (x>>8) | (x<<8); } - //static inline uint32_t bswap_32(uint32_t x) { return (bswap_16(x&0xffff)<<16) | (bswap_16(x>>16)); } - //static inline uint64_t bswap_64(uint64_t x) { return (((unsigned long long)bswap_32(x&0xffffffffull))<<32) | (bswap_32(x>>32)); } +//static inline uint16_t bswap_16(uint16_t x) { return (x>>8) | (x<<8); } +//static inline uint32_t bswap_32(uint32_t x) { return (bswap_16(x&0xffff)<<16) | (bswap_16(x>>16)); } +//static inline uint64_t bswap_64(uint64_t x) { return (((unsigned long long)bswap_32(x&0xffffffffull))<<32) | (bswap_32(x>>32)); } void RtApi :: byteSwapBuffer( char *buffer, unsigned int samples, RtAudioFormat format ) { @@ -7926,8 +8490,7 @@ void RtApi :: byteSwapBuffer( char *buffer, unsigned int samples, RtAudioFormat ptr += 2; } } - else if ( format == RTAUDIO_SINT24 || - format == RTAUDIO_SINT32 || + else if ( format == RTAUDIO_SINT32 || format == RTAUDIO_FLOAT32 ) { for ( unsigned int i=0; i