From: Gary Scavone Date: Thu, 11 Aug 2011 16:33:05 +0000 (+0000) Subject: Updates for release 4.0.9, including OS-X fixes for 10.6 and 10.7 (gs). X-Git-Tag: 4.0.9~1^2~2 X-Git-Url: https://git.carlh.net/gitweb/?a=commitdiff_plain;h=6d437609d09bd364ec7dc9f1e4800e2e2680be2c;p=rtaudio.git Updates for release 4.0.9, including OS-X fixes for 10.6 and 10.7 (gs). --- diff --git a/RtAudio.cpp b/RtAudio.cpp index 7dd1460..ab50cbb 100644 --- a/RtAudio.cpp +++ b/RtAudio.cpp @@ -38,7 +38,7 @@ */ /************************************************************************/ -// RtAudio: Version 4.0.8 +// RtAudio: Version 4.0.9 #include "RtAudio.h" #include @@ -392,12 +392,6 @@ unsigned int RtApi :: getStreamSampleRate( void ) // quite a bit of extra code and most likely, a user program wouldn't // be prepared for the result anyway. However, we do provide a flag // to the client callback function to inform of an over/underrun. -// -// The mechanism for querying and setting system parameters was -// updated (and perhaps simplified) in OS-X version 10.4. However, -// since 10.4 support is not necessarily available to all users, I've -// decided not to update the respective code at this time. Perhaps -// this will happen when Apple makes 10.4 free for everyone. :-) // A structure to hold various information related to the CoreAudio API // implementation. @@ -418,9 +412,23 @@ struct CoreHandle { :deviceBuffer(0), drainCounter(0), internalDrain(false) { nStreams[0] = 1; nStreams[1] = 1; id[0] = 0; id[1] = 0; xrun[0] = false; xrun[1] = false; } }; -RtApiCore :: RtApiCore() +RtApiCore:: RtApiCore() { - // Nothing to do here. +#if defined( AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER ) + // This is a largely undocumented but absolutely necessary + // requirement starting with OS-X 10.6. If not called, queries and + // updates to various audio device properties are not handled + // correctly. + CFRunLoopRef theRunLoop = NULL; + AudioObjectPropertyAddress property = { kAudioHardwarePropertyRunLoop, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + OSStatus result = AudioObjectSetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop); + if ( result != noErr ) { + errorText_ = "RtApiCore::RtApiCore: error setting run loop property!"; + error( RtError::WARNING ); + } +#endif } RtApiCore :: ~RtApiCore() @@ -732,7 +740,7 @@ OSStatus callbackHandler( AudioDeviceID inDevice, return kAudioHardwareNoError; } -OSStatus deviceListener( AudioObjectID inDevice, +OSStatus xrunListener( AudioObjectID inDevice, UInt32 nAddresses, const AudioObjectPropertyAddress properties[], void* handlePointer ) @@ -750,6 +758,21 @@ OSStatus deviceListener( AudioObjectID inDevice, return kAudioHardwareNoError; } +OSStatus rateListener( AudioObjectID inDevice, + UInt32 nAddresses, + const AudioObjectPropertyAddress properties[], + void* ratePointer ) +{ + + Float64 *rate = (Float64 *) ratePointer; + UInt32 dataSize = sizeof( Float64 ); + AudioObjectPropertyAddress property = { kAudioDevicePropertyNominalSampleRate, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + AudioObjectGetPropertyData( inDevice, &property, 0, NULL, &dataSize, rate ); + return kAudioHardwareNoError; +} + bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, @@ -924,30 +947,6 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne stream_.bufferSize = *bufferSize; stream_.nBuffers = 1; - // Check and if necessary, change the sample rate for the device. - Float64 nominalRate; - dataSize = sizeof( Float64 ); - property.mSelector = kAudioDevicePropertyNominalSampleRate; - result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &nominalRate ); - - if ( result != noErr ) { - errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting current sample rate."; - errorText_ = errorStream_.str(); - return FAILURE; - } - - // Only change the sample rate if off by more than 1 Hz. - if ( fabs( nominalRate - (double)sampleRate ) > 1.0 ) { - nominalRate = (Float64) sampleRate; - result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &nominalRate ); - - if ( result != noErr ) { - errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate for device (" << device << ")."; - errorText_ = errorStream_.str(); - return FAILURE; - } - } - // Try to set "hog" mode ... it's not clear to me this is working. if ( options && options->flags & RTAUDIO_HOG_DEVICE ) { pid_t hog_pid; @@ -971,126 +970,159 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne } } - // Get the stream ID(s) so we can set the stream format. - AudioStreamID streamIDs[ nStreams ]; - dataSize = nStreams * sizeof( AudioStreamID ); - property.mSelector = kAudioDevicePropertyStreams; - result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &streamIDs ); + // Check and if necessary, change the sample rate for the device. + Float64 nominalRate; + dataSize = sizeof( Float64 ); + property.mSelector = kAudioDevicePropertyNominalSampleRate; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &nominalRate ); if ( result != noErr ) { - errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream ID(s) for device (" << device << ")."; + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting current sample rate."; errorText_ = errorStream_.str(); return FAILURE; } - // Now set the stream format for each stream. Also, check the - // physical format of the device and change that if necessary. - AudioStreamBasicDescription description; - dataSize = sizeof( AudioStreamBasicDescription ); - - bool updateFormat; - for ( UInt32 i=0; i 1.0 ) { + // Set a property listener for the sample rate change + Float64 reportedRate = 0.0; + AudioObjectPropertyAddress tmp = { kAudioDevicePropertyNominalSampleRate, kAudioObjectPropertyScopeGlobal, kAudioObjectPropertyElementMaster }; + result = AudioObjectAddPropertyListener( id, &tmp, rateListener, (void *) &reportedRate ); if ( result != noErr ) { - errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream format for device (" << device << ")."; + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate property listener for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } - // Set the sample rate and data format id. However, only make the - // change if the sample rate is not within 1.0 of the desired - // rate and the format is not linear pcm. - updateFormat = false; - if ( fabs( description.mSampleRate - (double)sampleRate ) > 1.0 ) { - description.mSampleRate = (double) sampleRate; - updateFormat = true; - } + nominalRate = (Float64) sampleRate; + result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &nominalRate ); - if ( description.mFormatID != kAudioFormatLinearPCM ) { - description.mFormatID = kAudioFormatLinearPCM; - updateFormat = true; + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; } - if ( updateFormat ) { - result = AudioObjectSetPropertyData( streamIDs[firstStream+i], &property, 0, NULL, dataSize, &description ); - if ( result != noErr ) { - errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate or data format for device (" << device << ")."; - errorText_ = errorStream_.str(); - return FAILURE; - } + // Now wait until the reported nominal rate is what we just set. + UInt32 microCounter = 0; + while ( reportedRate != nominalRate ) { + microCounter += 5000; + if ( microCounter > 5000000 ) break; + usleep( 5000 ); } - // Now check the physical format. - property.mSelector = kAudioStreamPropertyPhysicalFormat; - result = AudioObjectGetPropertyData( streamIDs[firstStream+i], &property, 0, NULL, &dataSize, &description ); - if ( result != noErr ) { - errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream physical format for device (" << device << ")."; + // Remove the property listener. + AudioObjectRemovePropertyListener( id, &tmp, rateListener, (void *) &reportedRate ); + + if ( microCounter > 5000000 ) { + errorStream_ << "RtApiCore::probeDeviceOpen: timeout waiting for sample rate update for device (" << device << ")."; errorText_ = errorStream_.str(); return FAILURE; } + } - if ( description.mFormatID != kAudioFormatLinearPCM || description.mBitsPerChannel < 24 ) { - description.mFormatID = kAudioFormatLinearPCM; - AudioStreamBasicDescription testDescription = description; - unsigned long formatFlags; + // Now set the stream format for all streams. Also, check the + // physical format of the device and change that if necessary. + AudioStreamBasicDescription description; + dataSize = sizeof( AudioStreamBasicDescription ); + property.mSelector = kAudioStreamPropertyVirtualFormat; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &description ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream format for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } - // We'll try higher bit rates first and then work our way down. - testDescription.mBitsPerChannel = 32; - testDescription.mBytesPerFrame = testDescription.mBitsPerChannel/8 * testDescription.mChannelsPerFrame; - testDescription.mBytesPerPacket = testDescription.mBytesPerFrame * testDescription.mFramesPerPacket; - formatFlags = description.mFormatFlags | kLinearPCMFormatFlagIsFloat & ~kLinearPCMFormatFlagIsSignedInteger; - testDescription.mFormatFlags = formatFlags; - result = AudioObjectSetPropertyData( streamIDs[firstStream+i], &property, 0, NULL, dataSize, &testDescription ); - if ( result == noErr ) continue; + // Set the sample rate and data format id. However, only make the + // change if the sample rate is not within 1.0 of the desired + // rate and the format is not linear pcm. + bool updateFormat = false; + if ( fabs( description.mSampleRate - (Float64)sampleRate ) > 1.0 ) { + description.mSampleRate = (Float64) sampleRate; + updateFormat = true; + } - testDescription = description; - testDescription.mBitsPerChannel = 32; - testDescription.mBytesPerFrame = testDescription.mBitsPerChannel/8 * testDescription.mChannelsPerFrame; - testDescription.mBytesPerPacket = testDescription.mBytesPerFrame * testDescription.mFramesPerPacket; - formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsSignedInteger) & ~kLinearPCMFormatFlagIsFloat; - testDescription.mFormatFlags = formatFlags; - result = AudioObjectSetPropertyData( streamIDs[firstStream+i], &property, 0, NULL, dataSize, &testDescription ); - if ( result == noErr ) continue; + if ( description.mFormatID != kAudioFormatLinearPCM ) { + description.mFormatID = kAudioFormatLinearPCM; + updateFormat = true; + } - testDescription = description; - testDescription.mBitsPerChannel = 24; - testDescription.mBytesPerFrame = testDescription.mBitsPerChannel/8 * testDescription.mChannelsPerFrame; - testDescription.mBytesPerPacket = testDescription.mBytesPerFrame * testDescription.mFramesPerPacket; - testDescription.mFormatFlags = formatFlags; - result = AudioObjectSetPropertyData( streamIDs[firstStream+i], &property, 0, NULL, dataSize, &testDescription ); - if ( result == noErr ) continue; + if ( updateFormat ) { + result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &description ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate or data format for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + } - testDescription = description; - testDescription.mBitsPerChannel = 16; - testDescription.mBytesPerFrame = testDescription.mBitsPerChannel/8 * testDescription.mChannelsPerFrame; - testDescription.mBytesPerPacket = testDescription.mBytesPerFrame * testDescription.mFramesPerPacket; - testDescription.mFormatFlags = formatFlags; - result = AudioObjectSetPropertyData( streamIDs[firstStream+i], &property, 0, NULL, dataSize, &testDescription ); - if ( result == noErr ) continue; + // Now check the physical format. + property.mSelector = kAudioStreamPropertyPhysicalFormat; + result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &description ); + if ( result != noErr ) { + errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream physical format for device (" << device << ")."; + errorText_ = errorStream_.str(); + return FAILURE; + } + //std::cout << "Current physical stream format:" << std::endl; + //std::cout << " mBitsPerChan = " << description.mBitsPerChannel << std::endl; + //std::cout << " aligned high = " << (description.mFormatFlags & kAudioFormatFlagIsAlignedHigh) << ", isPacked = " << (description.mFormatFlags & kAudioFormatFlagIsPacked) << std::endl; + //std::cout << " bytesPerFrame = " << description.mBytesPerFrame << std::endl; + //std::cout << " sample rate = " << description.mSampleRate << std::endl; + + if ( description.mFormatID != kAudioFormatLinearPCM || description.mBitsPerChannel < 16 ) { + description.mFormatID = kAudioFormatLinearPCM; + //description.mSampleRate = (Float64) sampleRate; + AudioStreamBasicDescription testDescription = description; + UInt32 formatFlags; + + // We'll try higher bit rates first and then work our way down. + std::vector< std::pair > physicalFormats; + 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 ) ); + physicalFormats.push_back( std::pair( 24, formatFlags ) ); // 24-bit packed + formatFlags &= ~( kAudioFormatFlagIsPacked | kAudioFormatFlagIsAlignedHigh ); + physicalFormats.push_back( std::pair( 24.2, formatFlags ) ); // 24-bit in 4 bytes, aligned low + formatFlags |= kAudioFormatFlagIsAlignedHigh; + physicalFormats.push_back( std::pair( 24.4, formatFlags ) ); // 24-bit in 4 bytes, aligned high + formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked) & ~kLinearPCMFormatFlagIsFloat; + physicalFormats.push_back( std::pair( 16, formatFlags ) ); + physicalFormats.push_back( std::pair( 8, formatFlags ) ); + + bool setPhysicalFormat = false; + for( unsigned int i=0; isaveDeviceInfo(); - // Only load the driver once for duplex stream. if ( mode != INPUT || stream_.mode != OUTPUT ) { + // The getDeviceInfo() function will not work when a stream is open + // because ASIO does not allow multiple devices to run at the same + // time. Thus, we'll probe the system before opening a stream and + // save the results for use by getDeviceInfo(). + this->saveDeviceInfo(); + if ( !drivers.loadDriver( driverName ) ) { errorStream_ << "RtApiAsio::probeDeviceOpen: unable to load driver (" << driverName << ")."; errorText_ = errorStream_.str(); @@ -3804,7 +3827,7 @@ RtAudio::DeviceInfo RtApiDs :: getDeviceInfo( unsigned int device ) } if ( found == false ) info.sampleRates.push_back( rates[i] ); } - sort( info.sampleRates.begin(), info.sampleRates.end() ); + std::sort( info.sampleRates.begin(), info.sampleRates.end() ); // If device opens for both playback and capture, we determine the channels. if ( info.outputChannels > 0 && info.inputChannels > 0 ) @@ -4969,16 +4992,12 @@ extern "C" unsigned __stdcall callbackHandler( void *ptr ) std::string convertTChar( LPCTSTR name ) { - std::string s; - #if defined( UNICODE ) || defined( _UNICODE ) - // Yes, this conversion doesn't make sense for two-byte characters - // but RtAudio is currently written to return an std::string of - // one-byte chars for the device name. - for ( unsigned int i=0; irunnable, NULL ) ) { + if ( pthread_cond_init( &apiInfo->runnable_cv, NULL ) ) { errorText_ = "RtApiAlsa::probeDeviceOpen: error initializing pthread condition variable."; goto error; } @@ -5932,7 +5952,7 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne error: if ( apiInfo ) { - pthread_cond_destroy( &apiInfo->runnable ); + pthread_cond_destroy( &apiInfo->runnable_cv ); if ( apiInfo->handles[0] ) snd_pcm_close( apiInfo->handles[0] ); if ( apiInfo->handles[1] ) snd_pcm_close( apiInfo->handles[1] ); delete apiInfo; @@ -5965,8 +5985,10 @@ void RtApiAlsa :: closeStream() AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; stream_.callbackInfo.isRunning = false; MUTEX_LOCK( &stream_.mutex ); - if ( stream_.state == STREAM_STOPPED ) - pthread_cond_signal( &apiInfo->runnable ); + if ( stream_.state == STREAM_STOPPED ) { + apiInfo->runnable = true; + pthread_cond_signal( &apiInfo->runnable_cv ); + } MUTEX_UNLOCK( &stream_.mutex ); pthread_join( stream_.callbackInfo.thread, NULL ); @@ -5979,7 +6001,7 @@ void RtApiAlsa :: closeStream() } if ( apiInfo ) { - pthread_cond_destroy( &apiInfo->runnable ); + pthread_cond_destroy( &apiInfo->runnable_cv ); if ( apiInfo->handles[0] ) snd_pcm_close( apiInfo->handles[0] ); if ( apiInfo->handles[1] ) snd_pcm_close( apiInfo->handles[1] ); delete apiInfo; @@ -6046,10 +6068,10 @@ void RtApiAlsa :: startStream() stream_.state = STREAM_RUNNING; unlock: + apiInfo->runnable = true; + pthread_cond_signal( &apiInfo->runnable_cv ); MUTEX_UNLOCK( &stream_.mutex ); - pthread_cond_signal( &apiInfo->runnable ); - if ( result >= 0 ) return; error( RtError::SYSTEM_ERROR ); } @@ -6154,7 +6176,9 @@ void RtApiAlsa :: callbackEvent() AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; if ( stream_.state == STREAM_STOPPED ) { MUTEX_LOCK( &stream_.mutex ); - pthread_cond_wait( &apiInfo->runnable, &stream_.mutex ); + while ( !apiInfo->runnable ) + pthread_cond_wait( &apiInfo->runnable_cv, &stream_.mutex ); + if ( stream_.state != STREAM_RUNNING ) { MUTEX_UNLOCK( &stream_.mutex ); return; diff --git a/RtAudio.h b/RtAudio.h index d8591c0..6032368 100644 --- a/RtAudio.h +++ b/RtAudio.h @@ -42,7 +42,7 @@ \file RtAudio.h */ -// RtAudio: Version 4.0.8 +// RtAudio: Version 4.0.9 #ifndef __RTAUDIO_H #define __RTAUDIO_H diff --git a/doc/doxygen/tutorial.txt b/doc/doxygen/tutorial.txt index 1c0ecd1..3ed7a9e 100644 --- a/doc/doxygen/tutorial.txt +++ b/doc/doxygen/tutorial.txt @@ -32,7 +32,7 @@ Devices are now re-enumerated every time the RtAudio::getDeviceCount(), RtAudio: \section download Download -Latest Release (12 April 2011): Version 4.0.8 +Latest Release (11 August 2011): Version 4.0.9 \section documentation Documentation Links diff --git a/doc/release.txt b/doc/release.txt index 512c583..ae3025e 100644 --- a/doc/release.txt +++ b/doc/release.txt @@ -2,6 +2,13 @@ RtAudio - a set of C++ classes that provide a common API for realtime audio inpu By Gary P. Scavone, 2001-2011. +v4.0.9: (?? August 2011) +- fix for ASIO problem enumerating devices after opening duplex stream (Oliver Larkin) +- fix for OS-X problems setting sample rate and bits-per-sample +- updates for OS-X "Lion" +- updates for wide character support in Windows DS (UNICODE) +- fix for possible ALSA callback thread hang (thanks to Tristan Matthews) + v4.0.8: (12 April 2011) - fix for MinGW4 problem enumerating and setting sample rates (iasiothiscallresolver, Dmitry Kostjuchenko) - fix for OS-X problem handling device names in some languages (CFString conversion, Vincent Bénony)