|
|
|
@@ -38,7 +38,7 @@ |
|
|
|
*/
|
|
|
|
/************************************************************************/
|
|
|
|
|
|
|
|
// RtAudio: Version 4.0.8
|
|
|
|
// RtAudio: Version 4.0.9
|
|
|
|
|
|
|
|
#include "RtAudio.h"
|
|
|
|
#include <iostream>
|
|
|
|
@@ -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<streamCount; i++ ) {
|
|
|
|
|
|
|
|
property.mSelector = kAudioStreamPropertyVirtualFormat;
|
|
|
|
result = AudioObjectGetPropertyData( streamIDs[firstStream+i], &property, 0, NULL, &dataSize, &description );
|
|
|
|
// Only change the sample rate if off by more than 1 Hz.
|
|
|
|
if ( fabs( nominalRate - (double)sampleRate ) > 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<UInt32, UInt32> > physicalFormats;
|
|
|
|
formatFlags = description.mFormatFlags | kLinearPCMFormatFlagIsFloat & ~kLinearPCMFormatFlagIsSignedInteger;
|
|
|
|
physicalFormats.push_back( std::pair<Float32, UInt32>( 32, formatFlags ) );
|
|
|
|
formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked) & ~kLinearPCMFormatFlagIsFloat;
|
|
|
|
physicalFormats.push_back( std::pair<Float32, UInt32>( 32, formatFlags ) );
|
|
|
|
physicalFormats.push_back( std::pair<Float32, UInt32>( 24, formatFlags ) ); // 24-bit packed
|
|
|
|
formatFlags &= ~( kAudioFormatFlagIsPacked | kAudioFormatFlagIsAlignedHigh );
|
|
|
|
physicalFormats.push_back( std::pair<Float32, UInt32>( 24.2, formatFlags ) ); // 24-bit in 4 bytes, aligned low
|
|
|
|
formatFlags |= kAudioFormatFlagIsAlignedHigh;
|
|
|
|
physicalFormats.push_back( std::pair<Float32, UInt32>( 24.4, formatFlags ) ); // 24-bit in 4 bytes, aligned high
|
|
|
|
formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked) & ~kLinearPCMFormatFlagIsFloat;
|
|
|
|
physicalFormats.push_back( std::pair<Float32, UInt32>( 16, formatFlags ) );
|
|
|
|
physicalFormats.push_back( std::pair<Float32, UInt32>( 8, formatFlags ) );
|
|
|
|
|
|
|
|
bool setPhysicalFormat = false;
|
|
|
|
for( unsigned int i=0; i<physicalFormats.size(); i++ ) {
|
|
|
|
testDescription = description;
|
|
|
|
testDescription.mBitsPerChannel = 8;
|
|
|
|
testDescription.mBytesPerFrame = testDescription.mBitsPerChannel/8 * testDescription.mChannelsPerFrame;
|
|
|
|
testDescription.mBitsPerChannel = (UInt32) physicalFormats[i].first;
|
|
|
|
testDescription.mFormatFlags = physicalFormats[i].second;
|
|
|
|
if ( (24 == (UInt32)physicalFormats[i].first) && ~( physicalFormats[i].second & kAudioFormatFlagIsPacked ) )
|
|
|
|
testDescription.mBytesPerFrame = 4 * testDescription.mChannelsPerFrame;
|
|
|
|
else
|
|
|
|
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 ) {
|
|
|
|
errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting physical data format for device (" << device << ").";
|
|
|
|
errorText_ = errorStream_.str();
|
|
|
|
return FAILURE;
|
|
|
|
result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &testDescription );
|
|
|
|
if ( result == noErr ) {
|
|
|
|
setPhysicalFormat = true;
|
|
|
|
//std::cout << "Updated physical stream format:" << std::endl;
|
|
|
|
//std::cout << " mBitsPerChan = " << testDescription.mBitsPerChannel << std::endl;
|
|
|
|
//std::cout << " aligned high = " << (testDescription.mFormatFlags & kAudioFormatFlagIsAlignedHigh) << ", isPacked = " << (testDescription.mFormatFlags & kAudioFormatFlagIsPacked) << std::endl;
|
|
|
|
//std::cout << " bytesPerFrame = " << testDescription.mBytesPerFrame << std::endl;
|
|
|
|
//std::cout << " sample rate = " << testDescription.mSampleRate << std::endl;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !setPhysicalFormat ) {
|
|
|
|
errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting physical data format for device (" << device << ").";
|
|
|
|
errorText_ = errorStream_.str();
|
|
|
|
return FAILURE;
|
|
|
|
}
|
|
|
|
} // done setting virtual/physical formats.
|
|
|
|
|
|
|
|
// Get the stream latency. There can be latency in both the device
|
|
|
|
// and the stream. First, attempt to get the device latency on the
|
|
|
|
// master channel or the first open channel. Errors that might
|
|
|
|
// occur here are not deemed critical.
|
|
|
|
|
|
|
|
// Get the stream / device latency.
|
|
|
|
UInt32 latency;
|
|
|
|
dataSize = sizeof( UInt32 );
|
|
|
|
property.mSelector = kAudioDevicePropertyLatency;
|
|
|
|
@@ -1104,16 +1136,6 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne |
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now try to get the stream latency. For multiple streams, I assume the
|
|
|
|
// latency is equal for each.
|
|
|
|
result = AudioObjectGetPropertyData( streamIDs[firstStream], &property, 0, NULL, &dataSize, &latency );
|
|
|
|
if ( result == kAudioHardwareNoError ) stream_.latency[ mode ] += latency;
|
|
|
|
else {
|
|
|
|
errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream latency for device (" << device << ").";
|
|
|
|
errorText_ = errorStream_.str();
|
|
|
|
error( RtError::WARNING );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Byte-swapping: According to AudioHardware.h, the stream data will
|
|
|
|
// always be presented in native-endian format, so we should never
|
|
|
|
// need to byte swap.
|
|
|
|
@@ -1176,7 +1198,8 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne |
|
|
|
unsigned long bufferBytes;
|
|
|
|
bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat );
|
|
|
|
// stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 );
|
|
|
|
stream_.userBuffer[mode] = (char *) malloc( bufferBytes );
|
|
|
|
stream_.userBuffer[mode] = (char *) malloc( bufferBytes * sizeof(char) );
|
|
|
|
memset( stream_.userBuffer[mode], 0, bufferBytes * sizeof(char) );
|
|
|
|
if ( stream_.userBuffer[mode] == NULL ) {
|
|
|
|
errorText_ = "RtApiCore::probeDeviceOpen: error allocating user buffer memory.";
|
|
|
|
goto error;
|
|
|
|
@@ -1241,7 +1264,7 @@ bool RtApiCore :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne |
|
|
|
|
|
|
|
// Setup the device property listener for over/underload.
|
|
|
|
property.mSelector = kAudioDeviceProcessorOverload;
|
|
|
|
result = AudioObjectAddPropertyListener( id, &property, deviceListener, (void *) handle );
|
|
|
|
result = AudioObjectAddPropertyListener( id, &property, xrunListener, (void *) handle );
|
|
|
|
|
|
|
|
return SUCCESS;
|
|
|
|
|
|
|
|
@@ -2742,14 +2765,14 @@ bool RtApiAsio :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne |
|
|
|
return FAILURE;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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();
|
|
|
|
|
|
|
|
// 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; i<wcslen( name ); i++ )
|
|
|
|
s.push_back( name[i] );
|
|
|
|
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);
|
|
|
|
#else
|
|
|
|
s.append( std::string( name ) );
|
|
|
|
std::string s( name );
|
|
|
|
#endif
|
|
|
|
|
|
|
|
return s;
|
|
|
|
@@ -5128,10 +5147,11 @@ struct AlsaHandle { |
|
|
|
snd_pcm_t *handles[2];
|
|
|
|
bool synchronized;
|
|
|
|
bool xrun[2];
|
|
|
|
pthread_cond_t runnable;
|
|
|
|
pthread_cond_t runnable_cv;
|
|
|
|
bool runnable;
|
|
|
|
|
|
|
|
AlsaHandle()
|
|
|
|
:synchronized(false) { xrun[0] = false; xrun[1] = false; }
|
|
|
|
:synchronized(false), runnable(false) { xrun[0] = false; xrun[1] = false; }
|
|
|
|
};
|
|
|
|
|
|
|
|
extern "C" void *alsaCallbackHandler( void * ptr );
|
|
|
|
@@ -5819,7 +5839,7 @@ bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigne |
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( pthread_cond_init( &apiInfo->runnable, 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;
|
|
|
|
|