+ _endthreadex( 0 );
+ return 0;
+}
+
+#include "tchar.h"
+
+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] );
+#else
+ s.append( std::string( name ) );
+#endif
+
+ return s;
+}
+
+static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid,
+ LPCTSTR description,
+ LPCTSTR module,
+ LPVOID lpContext )
+{
+ EnumInfo *info = (EnumInfo *) lpContext;
+
+ HRESULT hr;
+ if ( info->isInput == true ) {
+ DSCCAPS caps;
+ LPDIRECTSOUNDCAPTURE object;
+
+ hr = DirectSoundCaptureCreate( lpguid, &object, NULL );
+ if ( hr != DS_OK ) return TRUE;
+
+ caps.dwSize = sizeof(caps);
+ hr = object->GetCaps( &caps );
+ if ( hr == DS_OK ) {
+ if ( caps.dwChannels > 0 && caps.dwFormats > 0 )
+ info->counter++;
+ }
+ object->Release();
+ }
+ else {
+ DSCAPS caps;
+ LPDIRECTSOUND object;
+ hr = DirectSoundCreate( lpguid, &object, NULL );
+ if ( hr != DS_OK ) return TRUE;
+
+ caps.dwSize = sizeof(caps);
+ hr = object->GetCaps( &caps );
+ if ( hr == DS_OK ) {
+ if ( caps.dwFlags & DSCAPS_PRIMARYMONO || caps.dwFlags & DSCAPS_PRIMARYSTEREO )
+ info->counter++;
+ }
+ object->Release();
+ }
+
+ if ( info->getDefault && lpguid == NULL ) return FALSE;
+
+ if ( info->findIndex && info->counter > info->index ) {
+ info->id = lpguid;
+ info->name = convertTChar( description );
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static char* getErrorString( int code )
+{
+ switch ( code ) {
+
+ case DSERR_ALLOCATED:
+ return "Already allocated";
+
+ case DSERR_CONTROLUNAVAIL:
+ return "Control unavailable";
+
+ case DSERR_INVALIDPARAM:
+ return "Invalid parameter";
+
+ case DSERR_INVALIDCALL:
+ return "Invalid call";
+
+ case DSERR_GENERIC:
+ return "Generic error";
+
+ case DSERR_PRIOLEVELNEEDED:
+ return "Priority level needed";
+
+ case DSERR_OUTOFMEMORY:
+ return "Out of memory";
+
+ case DSERR_BADFORMAT:
+ return "The sample rate or the channel format is not supported";
+
+ case DSERR_UNSUPPORTED:
+ return "Not supported";
+
+ case DSERR_NODRIVER:
+ return "No driver";
+
+ case DSERR_ALREADYINITIALIZED:
+ return "Already initialized";
+
+ case DSERR_NOAGGREGATION:
+ return "No aggregation";
+
+ case DSERR_BUFFERLOST:
+ return "Buffer lost";
+
+ case DSERR_OTHERAPPHASPRIO:
+ return "Another application already has priority";
+
+ case DSERR_UNINITIALIZED:
+ return "Uninitialized";
+
+ default:
+ return "DirectSound unknown error";
+ }
+}
+//******************** End of __WINDOWS_DS__ *********************//
+#endif
+
+
+#if defined(__LINUX_ALSA__)
+
+#include <alsa/asoundlib.h>
+#include <unistd.h>
+
+// A structure to hold various information related to the ALSA API
+// implementation.
+struct AlsaHandle {
+ snd_pcm_t *handles[2];
+ bool synchronized;
+ bool xrun[2];
+
+ AlsaHandle()
+ :synchronized(false) { xrun[0] = false; xrun[1] = false; }
+};
+
+extern "C" void *alsaCallbackHandler( void * ptr );
+
+RtApiAlsa :: RtApiAlsa()
+{
+ // Nothing to do here.
+}
+
+RtApiAlsa :: ~RtApiAlsa()
+{
+ if ( stream_.state != STREAM_CLOSED ) closeStream();
+}
+
+unsigned int RtApiAlsa :: getDeviceCount( void )
+{
+ unsigned nDevices = 0;
+ int result, subdevice, card;
+ char name[64];
+ snd_ctl_t *handle;
+
+ // Count cards and devices
+ card = -1;
+ snd_card_next( &card );
+ while ( card >= 0 ) {
+ sprintf( name, "hw:%d", card );
+ result = snd_ctl_open( &handle, name, 0 );
+ if ( result < 0 ) {
+ errorStream_ << "RtApiAlsa::getDeviceCount: control open, card = " << card << ", " << snd_strerror( result ) << ".";
+ errorText_ = errorStream_.str();
+ error( RtError::WARNING );
+ goto nextcard;
+ }
+ subdevice = -1;
+ while( 1 ) {
+ result = snd_ctl_pcm_next_device( handle, &subdevice );
+ if ( result < 0 ) {
+ errorStream_ << "RtApiAlsa::getDeviceCount: control next device, card = " << card << ", " << snd_strerror( result ) << ".";
+ errorText_ = errorStream_.str();
+ error( RtError::WARNING );
+ break;
+ }
+ if ( subdevice < 0 )
+ break;
+ nDevices++;
+ }
+ nextcard:
+ snd_ctl_close( handle );
+ snd_card_next( &card );
+ }
+
+ return nDevices;
+}
+
+RtAudio::DeviceInfo RtApiAlsa :: getDeviceInfo( unsigned int device )
+{
+ RtAudio::DeviceInfo info;
+ info.probed = false;
+
+ unsigned nDevices = 0;
+ int result, subdevice, card;
+ char name[64];
+ snd_ctl_t *chandle;
+
+ // Count cards and devices
+ card = -1;
+ snd_card_next( &card );
+ while ( card >= 0 ) {
+ sprintf( name, "hw:%d", card );
+ result = snd_ctl_open( &chandle, name, SND_CTL_NONBLOCK );
+ if ( result < 0 ) {
+ errorStream_ << "RtApiAlsa::getDeviceInfo: control open, card = " << card << ", " << snd_strerror( result ) << ".";
+ errorText_ = errorStream_.str();
+ error( RtError::WARNING );
+ goto nextcard;
+ }
+ subdevice = -1;
+ while( 1 ) {
+ result = snd_ctl_pcm_next_device( chandle, &subdevice );
+ if ( result < 0 ) {
+ errorStream_ << "RtApiAlsa::getDeviceInfo: control next device, card = " << card << ", " << snd_strerror( result ) << ".";
+ errorText_ = errorStream_.str();
+ error( RtError::WARNING );
+ break;
+ }
+ if ( subdevice < 0 ) break;
+ if ( nDevices == device ) {
+ sprintf( name, "hw:%d,%d", card, subdevice );
+ goto foundDevice;
+ }
+ nDevices++;
+ }
+ nextcard:
+ snd_ctl_close( chandle );
+ snd_card_next( &card );
+ }
+
+ if ( nDevices == 0 ) {
+ errorText_ = "RtApiAlsa::getDeviceInfo: no devices found!";
+ error( RtError::INVALID_USE );
+ }
+
+ if ( device >= nDevices ) {
+ errorText_ = "RtApiAlsa::getDeviceInfo: device ID is invalid!";
+ error( RtError::INVALID_USE );
+ }
+
+ foundDevice:
+
+ int openMode = SND_PCM_ASYNC;
+ snd_pcm_stream_t stream;
+ snd_pcm_info_t *pcminfo;
+ snd_pcm_info_alloca( &pcminfo );
+ snd_pcm_t *phandle;
+ snd_pcm_hw_params_t *params;
+ snd_pcm_hw_params_alloca( ¶ms );
+
+ // First try for playback
+ 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 );
+
+ 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 );
+ goto captureProbe;
+ }
+
+ // The device is open ... fill the parameter structure.
+ result = snd_pcm_hw_params_any( phandle, params );
+ if ( result < 0 ) {
+ snd_pcm_close( phandle );
+ errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << ".";
+ errorText_ = errorStream_.str();
+ error( RtError::WARNING );
+ goto captureProbe;
+ }
+
+ // Get output channel information.
+ unsigned int value;
+ result = snd_pcm_hw_params_get_channels_max( params, &value );
+ if ( result < 0 ) {
+ snd_pcm_close( phandle );
+ errorStream_ << "RtApiAlsa::getDeviceInfo: error getting device (" << name << ") output channels, " << snd_strerror( result ) << ".";
+ errorText_ = errorStream_.str();
+ error( RtError::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;
+ }
+
+ 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 );
+ if ( info.outputChannels == 0 ) return info;
+ goto probeParameters;
+ }
+
+ // The device is open ... fill the parameter structure.
+ result = snd_pcm_hw_params_any( phandle, params );
+ if ( result < 0 ) {
+ snd_pcm_close( phandle );
+ errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << ".";
+ errorText_ = errorStream_.str();
+ error( RtError::WARNING );
+ if ( info.outputChannels == 0 ) return info;
+ goto probeParameters;
+ }
+
+ result = snd_pcm_hw_params_get_channels_max( params, &value );
+ if ( result < 0 ) {
+ snd_pcm_close( phandle );
+ errorStream_ << "RtApiAlsa::getDeviceInfo: error getting device (" << name << ") input channels, " << snd_strerror( result ) << ".";
+ errorText_ = errorStream_.str();
+ error( RtError::WARNING );
+ if ( info.outputChannels == 0 ) return info;
+ goto probeParameters;
+ }
+ info.inputChannels = value;
+ snd_pcm_close( phandle );
+
+ // If device opens for both playback and capture, we determine the channels.
+ if ( info.outputChannels > 0 && info.inputChannels > 0 )
+ info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels;
+
+ // ALSA doesn't provide default devices so we'll use the first available one.
+ if ( device == 0 && info.outputChannels > 0 )
+ info.isDefaultOutput = true;
+ if ( device == 0 && info.inputChannels > 0 )
+ info.isDefaultInput = true;
+
+ probeParameters:
+ // At this point, we just need to figure out the supported data
+ // formats and sample rates. We'll proceed by opening the device in
+ // the direction with the maximum number of channels, or playback if
+ // they are equal. This might limit our sample rate options, but so
+ // be it.
+
+ if ( info.outputChannels >= info.inputChannels )
+ stream = SND_PCM_STREAM_PLAYBACK;
+ else
+ stream = SND_PCM_STREAM_CAPTURE;
+ snd_pcm_info_set_stream( pcminfo, stream );
+
+ 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 );
+ return info;
+ }
+
+ // The device is open ... fill the parameter structure.
+ result = snd_pcm_hw_params_any( phandle, params );
+ if ( result < 0 ) {
+ snd_pcm_close( phandle );
+ errorStream_ << "RtApiAlsa::getDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << ".";
+ errorText_ = errorStream_.str();
+ error( RtError::WARNING );
+ return info;
+ }
+
+ // Test our discrete set of sample rate values.
+ info.sampleRates.clear();
+ for ( unsigned int i=0; i<MAX_SAMPLE_RATES; i++ ) {
+ if ( snd_pcm_hw_params_test_rate( phandle, params, SAMPLE_RATES[i], 0 ) == 0 )
+ info.sampleRates.push_back( SAMPLE_RATES[i] );
+ }
+ if ( info.sampleRates.size() == 0 ) {
+ snd_pcm_close( phandle );
+ errorStream_ << "RtApiAlsa::getDeviceInfo: no supported sample rates found for device (" << name << ").";
+ errorText_ = errorStream_.str();
+ error( RtError::WARNING );
+ return info;
+ }
+
+ // Probe the supported data formats ... we don't care about endian-ness just yet
+ snd_pcm_format_t format;
+ info.nativeFormats = 0;
+ format = SND_PCM_FORMAT_S8;
+ if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 )
+ info.nativeFormats |= RTAUDIO_SINT8;
+ format = SND_PCM_FORMAT_S16;
+ if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 )
+ info.nativeFormats |= RTAUDIO_SINT16;
+ format = SND_PCM_FORMAT_S24;
+ if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 )
+ info.nativeFormats |= RTAUDIO_SINT24;
+ format = SND_PCM_FORMAT_S32;
+ if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 )
+ info.nativeFormats |= RTAUDIO_SINT32;
+ format = SND_PCM_FORMAT_FLOAT;
+ if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 )
+ info.nativeFormats |= RTAUDIO_FLOAT32;
+ format = SND_PCM_FORMAT_FLOAT64;
+ if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 )
+ info.nativeFormats |= RTAUDIO_FLOAT64;
+
+ // Check that we have at least one supported format
+ if ( info.nativeFormats == 0 ) {
+ errorStream_ << "RtApiAlsa::getDeviceInfo: pcm device (" << name << ") data format not supported by RtAudio.";
+ errorText_ = errorStream_.str();
+ error( RtError::WARNING );
+ return info;
+ }
+
+ // Get the device name
+ char *cardname;
+ result = snd_card_get_name( card, &cardname );
+ if ( result >= 0 )
+ sprintf( name, "hw:%s,%d", cardname, subdevice );
+ info.name = name;
+
+ // That's all ... close the device and return
+ snd_pcm_close( phandle );
+ info.probed = true;
+ return info;
+}
+
+bool RtApiAlsa :: probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels,
+ unsigned int firstChannel, unsigned int sampleRate,
+ RtAudioFormat format, unsigned int *bufferSize,
+ RtAudio::StreamOptions *options )
+
+{
+#if defined(__RTAUDIO_DEBUG__)
+ snd_output_t *out;
+ snd_output_stdio_attach(&out, stderr, 0);
+#endif
+
+ // I'm not using the "plug" interface ... too much inconsistent behavior.
+
+ unsigned nDevices = 0;
+ int result, subdevice, card;
+ char name[64];
+ snd_ctl_t *chandle;
+
+ // Count cards and devices
+ card = -1;
+ snd_card_next( &card );
+ while ( card >= 0 ) {
+ sprintf( name, "hw:%d", card );
+ result = snd_ctl_open( &chandle, name, SND_CTL_NONBLOCK );
+ if ( result < 0 ) {
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: control open, card = " << card << ", " << snd_strerror( result ) << ".";
+ errorText_ = errorStream_.str();
+ return FAILURE;
+ }
+ subdevice = -1;
+ while( 1 ) {
+ result = snd_ctl_pcm_next_device( chandle, &subdevice );
+ if ( result < 0 ) break;
+ if ( subdevice < 0 ) break;
+ if ( nDevices == device ) {
+ sprintf( name, "hw:%d,%d", card, subdevice );
+ goto foundDevice;
+ }
+ nDevices++;
+ }
+ snd_ctl_close( chandle );
+ snd_card_next( &card );
+ }
+
+ if ( nDevices == 0 ) {
+ // This should not happen because a check is made before this function is called.
+ errorText_ = "RtApiAlsa::probeDeviceOpen: no devices found!";
+ return FAILURE;
+ }
+
+ if ( device >= nDevices ) {
+ // This should not happen because a check is made before this function is called.
+ errorText_ = "RtApiAlsa::probeDeviceOpen: device ID is invalid!";
+ return FAILURE;
+ }
+
+ foundDevice:
+
+ snd_pcm_stream_t stream;
+ if ( mode == OUTPUT )
+ stream = SND_PCM_STREAM_PLAYBACK;
+ else
+ stream = SND_PCM_STREAM_CAPTURE;
+
+ snd_pcm_t *phandle;
+ int openMode = SND_PCM_ASYNC;
+ result = snd_pcm_open( &phandle, name, stream, openMode );
+ if ( result < 0 ) {
+ if ( mode == OUTPUT )
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device (" << name << ") won't open for output.";
+ else
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device (" << name << ") won't open for input.";
+ errorText_ = errorStream_.str();
+ return FAILURE;
+ }
+
+ // Fill the parameter structure.
+ snd_pcm_hw_params_t *hw_params;
+ snd_pcm_hw_params_alloca( &hw_params );
+ result = snd_pcm_hw_params_any( phandle, hw_params );
+ if ( result < 0 ) {
+ snd_pcm_close( phandle );
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting pcm device (" << name << ") parameters, " << snd_strerror( result ) << ".";
+ errorText_ = errorStream_.str();
+ return FAILURE;
+ }
+
+#if defined(__RTAUDIO_DEBUG__)
+ fprintf( stderr, "\nRtApiAlsa: dump hardware params just after device open:\n\n" );
+ snd_pcm_hw_params_dump( hw_params, out );
+#endif
+
+ // Set access ... check user preference.
+ if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) {
+ stream_.userInterleaved = false;
+ result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED );
+ if ( result < 0 ) {
+ result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED );
+ stream_.deviceInterleaved[mode] = true;
+ }
+ else
+ stream_.deviceInterleaved[mode] = false;
+ }
+ else {
+ stream_.userInterleaved = true;
+ result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED );
+ if ( result < 0 ) {
+ result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED );
+ stream_.deviceInterleaved[mode] = false;
+ }
+ else
+ stream_.deviceInterleaved[mode] = true;
+ }
+
+ if ( result < 0 ) {
+ snd_pcm_close( phandle );
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting pcm device (" << name << ") access, " << snd_strerror( result ) << ".";
+ errorText_ = errorStream_.str();
+ return FAILURE;
+ }
+
+ // Determine how to set the device format.
+ stream_.userFormat = format;
+ snd_pcm_format_t deviceFormat = SND_PCM_FORMAT_UNKNOWN;
+
+ if ( format == RTAUDIO_SINT8 )
+ deviceFormat = SND_PCM_FORMAT_S8;
+ else if ( format == RTAUDIO_SINT16 )
+ deviceFormat = SND_PCM_FORMAT_S16;
+ else if ( format == RTAUDIO_SINT24 )
+ deviceFormat = SND_PCM_FORMAT_S24;
+ else if ( format == RTAUDIO_SINT32 )
+ deviceFormat = SND_PCM_FORMAT_S32;
+ else if ( format == RTAUDIO_FLOAT32 )
+ deviceFormat = SND_PCM_FORMAT_FLOAT;
+ else if ( format == RTAUDIO_FLOAT64 )
+ deviceFormat = SND_PCM_FORMAT_FLOAT64;
+
+ if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat) == 0) {
+ stream_.deviceFormat[mode] = format;
+ goto setFormat;
+ }
+
+ // The user requested format is not natively supported by the device.
+ deviceFormat = SND_PCM_FORMAT_FLOAT64;
+ if ( snd_pcm_hw_params_test_format( phandle, hw_params, deviceFormat ) == 0 ) {
+ stream_.deviceFormat[mode] = RTAUDIO_FLOAT64;
+ goto setFormat;
+ }
+
+ deviceFormat = SND_PCM_FORMAT_FLOAT;
+ if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) {
+ stream_.deviceFormat[mode] = RTAUDIO_FLOAT32;
+ goto setFormat;
+ }
+
+ deviceFormat = SND_PCM_FORMAT_S32;
+ if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) {
+ stream_.deviceFormat[mode] = RTAUDIO_SINT32;
+ goto setFormat;
+ }
+
+ deviceFormat = SND_PCM_FORMAT_S24;
+ if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) {
+ stream_.deviceFormat[mode] = RTAUDIO_SINT24;
+ goto setFormat;
+ }
+
+ deviceFormat = SND_PCM_FORMAT_S16;
+ if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) {
+ stream_.deviceFormat[mode] = RTAUDIO_SINT16;
+ goto setFormat;
+ }
+
+ deviceFormat = SND_PCM_FORMAT_S8;
+ if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) {
+ stream_.deviceFormat[mode] = RTAUDIO_SINT8;
+ goto setFormat;
+ }
+
+ // If we get here, no supported format was found.
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device " << device << " data format not supported by RtAudio.";
+ errorText_ = errorStream_.str();
+ return FAILURE;
+
+ setFormat:
+ result = snd_pcm_hw_params_set_format( phandle, hw_params, deviceFormat );
+ if ( result < 0 ) {
+ snd_pcm_close( phandle );
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting pcm device (" << name << ") data format, " << snd_strerror( result ) << ".";
+ errorText_ = errorStream_.str();
+ return FAILURE;
+ }
+
+ // Determine whether byte-swaping is necessary.
+ stream_.doByteSwap[mode] = false;
+ if ( deviceFormat != SND_PCM_FORMAT_S8 ) {
+ result = snd_pcm_format_cpu_endian( deviceFormat );
+ if ( result == 0 )
+ stream_.doByteSwap[mode] = true;
+ else if (result < 0) {
+ snd_pcm_close( phandle );
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting pcm device (" << name << ") endian-ness, " << snd_strerror( result ) << ".";
+ errorText_ = errorStream_.str();
+ return FAILURE;
+ }
+ }
+
+ // Set the sample rate.
+ result = snd_pcm_hw_params_set_rate_near( phandle, hw_params, (unsigned int*) &sampleRate, 0 );
+ if ( result < 0 ) {
+ snd_pcm_close( phandle );
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting sample rate on device (" << name << "), " << snd_strerror( result ) << ".";
+ errorText_ = errorStream_.str();
+ return FAILURE;
+ }
+
+ // Determine the number of channels for this device. We support a possible
+ // minimum device channel number > than the value requested by the user.
+ stream_.nUserChannels[mode] = channels;
+ unsigned int value;
+ result = snd_pcm_hw_params_get_channels_max( hw_params, &value );
+ unsigned int deviceChannels = value;
+ if ( result < 0 || deviceChannels < channels + firstChannel ) {
+ snd_pcm_close( phandle );
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: requested channel parameters not supported by device (" << name << "), " << snd_strerror( result ) << ".";
+ errorText_ = errorStream_.str();
+ return FAILURE;
+ }
+
+ result = snd_pcm_hw_params_get_channels_min( hw_params, &value );
+ if ( result < 0 ) {
+ snd_pcm_close( phandle );
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting minimum channels for device (" << name << "), " << snd_strerror( result ) << ".";
+ errorText_ = errorStream_.str();
+ return FAILURE;
+ }
+ deviceChannels = value;
+ if ( deviceChannels < channels + firstChannel ) deviceChannels = channels + firstChannel;
+ stream_.nDeviceChannels[mode] = deviceChannels;
+
+ // Set the device channels.
+ result = snd_pcm_hw_params_set_channels( phandle, hw_params, deviceChannels );
+ if ( result < 0 ) {
+ snd_pcm_close( phandle );
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting channels for device (" << name << "), " << snd_strerror( result ) << ".";
+ errorText_ = errorStream_.str();
+ return FAILURE;
+ }
+
+ // Set the buffer number, which in ALSA is referred to as the "period".
+ int dir;
+ unsigned int periods = 0;
+ if ( options ) periods = options->numberOfBuffers;
+ if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) periods = 2;
+ // Even though the hardware might allow 1 buffer, it won't work reliably.
+ if ( periods < 2 ) periods = 2;
+ result = snd_pcm_hw_params_set_periods_near( phandle, hw_params, &periods, &dir );
+ if ( result < 0 ) {
+ snd_pcm_close( phandle );
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting periods for device (" << name << "), " << snd_strerror( result ) << ".";
+ errorText_ = errorStream_.str();
+ return FAILURE;
+ }
+
+ // Set the buffer (or period) size.
+ snd_pcm_uframes_t periodSize = *bufferSize;
+ result = snd_pcm_hw_params_set_period_size_near( phandle, hw_params, &periodSize, &dir );
+ if ( result < 0 ) {
+ snd_pcm_close( phandle );
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting period size for device (" << name << "), " << snd_strerror( result ) << ".";
+ errorText_ = errorStream_.str();
+ return FAILURE;
+ }
+ *bufferSize = periodSize;
+
+ // 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 ) {
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: system error setting buffer size for duplex stream on device (" << name << ").";
+ errorText_ = errorStream_.str();
+ return FAILURE;
+ }
+
+ stream_.bufferSize = *bufferSize;
+
+ // Install the hardware configuration
+ result = snd_pcm_hw_params( phandle, hw_params );
+ if ( result < 0 ) {
+ snd_pcm_close( phandle );
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: error installing hardware configuration on device (" << name << "), " << snd_strerror( result ) << ".";
+ errorText_ = errorStream_.str();
+ return FAILURE;
+ }
+
+#if defined(__RTAUDIO_DEBUG__)
+ fprintf(stderr, "\nRtApiAlsa: dump hardware params after installation:\n\n");
+ snd_pcm_hw_params_dump( hw_params, out );
+#endif
+
+ // Set the software configuration to fill buffers with zeros and prevent device stopping on xruns.
+ snd_pcm_sw_params_t *sw_params = NULL;
+ snd_pcm_sw_params_alloca( &sw_params );
+ snd_pcm_sw_params_current( phandle, sw_params );
+ snd_pcm_sw_params_set_start_threshold( phandle, sw_params, *bufferSize );
+ snd_pcm_sw_params_set_stop_threshold( phandle, sw_params, 0x7fffffff );
+ snd_pcm_sw_params_set_silence_threshold( phandle, sw_params, 0 );
+ snd_pcm_sw_params_set_silence_size( phandle, sw_params, INT_MAX );
+ result = snd_pcm_sw_params( phandle, sw_params );
+ if ( result < 0 ) {
+ snd_pcm_close( phandle );
+ errorStream_ << "RtApiAlsa::probeDeviceOpen: error installing software configuration on device (" << name << "), " << snd_strerror( result ) << ".";
+ errorText_ = errorStream_.str();
+ return FAILURE;
+ }
+
+#if defined(__RTAUDIO_DEBUG__)
+ fprintf(stderr, "\nRtApiAlsa: dump software params after installation:\n\n");
+ snd_pcm_sw_params_dump( sw_params, out );
+#endif
+
+ // Set flags for buffer conversion
+ stream_.doConvertBuffer[mode] = false;
+ if ( stream_.userFormat != stream_.deviceFormat[mode] )
+ stream_.doConvertBuffer[mode] = true;
+ if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] )
+ stream_.doConvertBuffer[mode] = true;
+ if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] &&
+ stream_.nUserChannels[mode] > 1 )
+ stream_.doConvertBuffer[mode] = true;
+
+ // Allocate the ApiHandle if necessary and then save.
+ AlsaHandle *apiInfo = 0;
+ if ( stream_.apiHandle == 0 ) {
+ try {
+ apiInfo = (AlsaHandle *) new AlsaHandle;
+ }
+ catch ( std::bad_alloc& ) {
+ errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating AlsaHandle memory.";
+ goto error;
+ }
+ stream_.apiHandle = (void *) apiInfo;
+ apiInfo->handles[0] = 0;
+ apiInfo->handles[1] = 0;
+ }
+ else {
+ apiInfo = (AlsaHandle *) stream_.apiHandle;
+ }
+ apiInfo->handles[mode] = phandle;
+
+ // Allocate necessary internal buffers.
+ unsigned long bufferBytes;
+ bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat );
+ stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 );
+ if ( stream_.userBuffer[mode] == NULL ) {
+ errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating user buffer memory.";
+ goto error;
+ }
+
+ 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_ = "RtApiAlsa::probeDeviceOpen: error allocating device buffer memory.";
+ goto error;
+ }
+ }
+ }
+
+ stream_.sampleRate = sampleRate;
+ stream_.nBuffers = periods;
+ stream_.device[mode] = device;
+ stream_.state = STREAM_STOPPED;
+
+ // Setup the buffer conversion information structure.
+ if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel );
+
+ // Setup thread if necessary.
+ if ( stream_.mode == OUTPUT && mode == INPUT ) {
+ // We had already set up an output stream.
+ stream_.mode = DUPLEX;
+ // Link the streams if possible.
+ apiInfo->synchronized = false;
+ if ( snd_pcm_link( apiInfo->handles[0], apiInfo->handles[1] ) == 0 )
+ apiInfo->synchronized = true;
+ else {
+ errorText_ = "RtApiAlsa::probeDeviceOpen: unable to synchronize input and output devices.";
+ error( RtError::WARNING );
+ }
+ }
+ else {
+ stream_.mode = mode;
+
+ // Setup callback thread.
+ stream_.callbackInfo.object = (void *) this;
+
+ // Set the thread attributes for joinable and realtime scheduling
+ // priority. The higher priority will only take affect if the
+ // program is run as root or suid.
+ 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)
+ pthread_attr_setschedpolicy( &attr, SCHED_RR );
+#else
+ pthread_attr_setschedpolicy( &attr, SCHED_OTHER );
+#endif
+
+ stream_.callbackInfo.isRunning = true;
+ result = pthread_create( &stream_.callbackInfo.thread, &attr, alsaCallbackHandler, &stream_.callbackInfo );
+ pthread_attr_destroy( &attr );
+ if ( result ) {
+ stream_.callbackInfo.isRunning = false;
+ errorText_ = "RtApiAlsa::error creating callback thread!";
+ goto error;
+ }
+ }
+
+ return SUCCESS;
+
+ error:
+ if ( apiInfo ) {
+ if ( apiInfo->handles[0] ) snd_pcm_close( apiInfo->handles[0] );
+ if ( apiInfo->handles[1] ) snd_pcm_close( apiInfo->handles[1] );
+ delete apiInfo;
+ 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;
+}
+
+void RtApiAlsa :: closeStream()
+{
+ if ( stream_.state == STREAM_CLOSED ) {
+ errorText_ = "RtApiAlsa::closeStream(): no open stream to close!";
+ error( RtError::WARNING );
+ return;
+ }
+
+ stream_.callbackInfo.isRunning = false;
+ pthread_join( stream_.callbackInfo.thread, NULL );
+
+ AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle;
+ if ( stream_.state == STREAM_RUNNING ) {
+ stream_.state = STREAM_STOPPED;
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX )
+ snd_pcm_drop( apiInfo->handles[0] );
+ if ( stream_.mode == INPUT || stream_.mode == DUPLEX )
+ snd_pcm_drop( apiInfo->handles[1] );
+ }
+
+ if ( apiInfo ) {
+ if ( apiInfo->handles[0] ) snd_pcm_close( apiInfo->handles[0] );
+ if ( apiInfo->handles[1] ) snd_pcm_close( apiInfo->handles[1] );
+ delete apiInfo;
+ 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;
+ }
+
+ stream_.mode = UNINITIALIZED;
+ stream_.state = STREAM_CLOSED;
+}
+
+void RtApiAlsa :: startStream()
+{
+ // This method calls snd_pcm_prepare if the device isn't already in that state.
+
+ verifyStream();
+ if ( stream_.state == STREAM_RUNNING ) {
+ errorText_ = "RtApiAlsa::startStream(): the stream is already running!";
+ error( RtError::WARNING );
+ return;
+ }
+
+ MUTEX_LOCK( &stream_.mutex );
+
+ int result = 0;
+ snd_pcm_state_t state;
+ AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle;
+ snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles;
+ if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) {
+ state = snd_pcm_state( handle[0] );
+ if ( state != SND_PCM_STATE_PREPARED ) {
+ result = snd_pcm_prepare( handle[0] );
+ if ( result < 0 ) {
+ errorStream_ << "RtApiAlsa::startStream: error preparing output pcm device, " << snd_strerror( result ) << ".";
+ errorText_ = errorStream_.str();
+ goto unlock;
+ }