/*
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
This file is part of the JUCE library .
Copyright ( c ) 2015 - ROLI Ltd .
Permission is granted to use this software under the terms of either :
a ) the GPL v2 ( or any later version )
b ) the Affero GPL v3
Details of these licenses can be found at : www . gnu . org / licenses
JUCE is distributed in the hope that it will be useful , but WITHOUT ANY
WARRANTY ; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE . See the GNU General Public License for more details .
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
To release a closed - source product which uses JUCE , commercial licenses are
available : visit www . juce . com for more information .
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
*/
namespace
{
# ifndef JUCE_ALSA_LOGGING
# define JUCE_ALSA_LOGGING 0
# endif
# if JUCE_ALSA_LOGGING
# define JUCE_ALSA_LOG(dbgtext) { juce::String tempDbgBuf ("ALSA: "); tempDbgBuf << dbgtext; Logger::writeToLog (tempDbgBuf); DBG (tempDbgBuf); }
# define JUCE_CHECKED_RESULT(x) (logErrorMessage (x, __LINE__))
static int logErrorMessage ( int err , int lineNum )
{
if ( err < 0 )
JUCE_ALSA_LOG ( " Error: line " < < lineNum < < " : code " < < err < < " ( " < < snd_strerror ( err ) < < " ) " ) ;
return err ;
}
# else
# define JUCE_ALSA_LOG(x) {}
# define JUCE_CHECKED_RESULT(x) (x)
# endif
# define JUCE_ALSA_FAILED(x) failed (x)
static void getDeviceSampleRates ( snd_pcm_t * handle , Array < double > & rates )
{
const int ratesToTry [ ] = { 22050 , 32000 , 44100 , 48000 , 88200 , 96000 , 176400 , 192000 , 0 } ;
snd_pcm_hw_params_t * hwParams ;
snd_pcm_hw_params_alloca ( & hwParams ) ;
for ( int i = 0 ; ratesToTry [ i ] ! = 0 ; + + i )
{
if ( snd_pcm_hw_params_any ( handle , hwParams ) > = 0
& & snd_pcm_hw_params_test_rate ( handle , hwParams , ( unsigned int ) ratesToTry [ i ] , 0 ) = = 0 )
{
rates . addIfNotAlreadyThere ( ( double ) ratesToTry [ i ] ) ;
}
}
}
static void getDeviceNumChannels ( snd_pcm_t * handle , unsigned int * minChans , unsigned int * maxChans )
{
snd_pcm_hw_params_t * params ;
snd_pcm_hw_params_alloca ( & params ) ;
if ( snd_pcm_hw_params_any ( handle , params ) > = 0 )
{
snd_pcm_hw_params_get_channels_min ( params , minChans ) ;
snd_pcm_hw_params_get_channels_max ( params , maxChans ) ;
JUCE_ALSA_LOG ( " getDeviceNumChannels: " < < ( int ) * minChans < < " " < < ( int ) * maxChans ) ;
// some virtual devices (dmix for example) report 10000 channels , we have to clamp these values
* maxChans = jmin ( * maxChans , 32u ) ;
* minChans = jmin ( * minChans , * maxChans ) ;
}
else
{
JUCE_ALSA_LOG ( " getDeviceNumChannels failed " ) ;
}
}
static void getDeviceProperties ( const String & deviceID ,
unsigned int & minChansOut ,
unsigned int & maxChansOut ,
unsigned int & minChansIn ,
unsigned int & maxChansIn ,
Array < double > & rates ,
bool testOutput ,
bool testInput )
{
minChansOut = maxChansOut = minChansIn = maxChansIn = 0 ;
if ( deviceID . isEmpty ( ) )
return ;
JUCE_ALSA_LOG ( " getDeviceProperties( " < < deviceID . toUTF8 ( ) . getAddress ( ) < < " ) " ) ;
snd_pcm_info_t * info ;
snd_pcm_info_alloca ( & info ) ;
if ( testOutput )
{
snd_pcm_t * pcmHandle ;
if ( JUCE_CHECKED_RESULT ( snd_pcm_open ( & pcmHandle , deviceID . toUTF8 ( ) . getAddress ( ) , SND_PCM_STREAM_PLAYBACK , SND_PCM_NONBLOCK ) ) > = 0 )
{
getDeviceNumChannels ( pcmHandle , & minChansOut , & maxChansOut ) ;
getDeviceSampleRates ( pcmHandle , rates ) ;
snd_pcm_close ( pcmHandle ) ;
}
}
if ( testInput )
{
snd_pcm_t * pcmHandle ;
if ( JUCE_CHECKED_RESULT ( snd_pcm_open ( & pcmHandle , deviceID . toUTF8 ( ) , SND_PCM_STREAM_CAPTURE , SND_PCM_NONBLOCK ) > = 0 ) )
{
getDeviceNumChannels ( pcmHandle , & minChansIn , & maxChansIn ) ;
if ( rates . size ( ) = = 0 )
getDeviceSampleRates ( pcmHandle , rates ) ;
snd_pcm_close ( pcmHandle ) ;
}
}
}
static void ensureMinimumNumBitsSet ( BigInteger & chans , int minNumChans )
{
int i = 0 ;
while ( chans . countNumberOfSetBits ( ) < minNumChans )
chans . setBit ( i + + ) ;
}
static void silentErrorHandler ( const char * , int , const char * , int , const char * , . . . ) { }
//==============================================================================
class ALSADevice
{
public :
ALSADevice ( const String & devID , bool forInput )
: handle ( nullptr ) ,
bitDepth ( 16 ) ,
numChannelsRunning ( 0 ) ,
latency ( 0 ) ,
deviceID ( devID ) ,
isInput ( forInput ) ,
isInterleaved ( true )
{
JUCE_ALSA_LOG ( " snd_pcm_open ( " < < deviceID . toUTF8 ( ) . getAddress ( ) < < " , forInput= " < < forInput < < " ) " ) ;
int err = snd_pcm_open ( & handle , deviceID . toUTF8 ( ) ,
forInput ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK ,
SND_PCM_ASYNC ) ;
if ( err < 0 )
{
if ( - err = = EBUSY )
error < < " The device \" " < < deviceID < < " \" is busy (another application is using it). " ;
else if ( - err = = ENOENT )
error < < " The device \" " < < deviceID < < " \" is not available. " ;
else
error < < " Could not open " < < ( forInput ? " input " : " output " ) < < " device \" " < < deviceID
< < " \" : " < < snd_strerror ( err ) < < " ( " < < err < < " ) " ;
JUCE_ALSA_LOG ( " snd_pcm_open failed; " < < error ) ;
}
}
~ ALSADevice ( )
{
closeNow ( ) ;
}
void closeNow ( )
{
if ( handle ! = nullptr )
{
snd_pcm_close ( handle ) ;
handle = nullptr ;
}
}
bool setParameters ( unsigned int sampleRate , int numChannels , int bufferSize )
{
if ( handle = = nullptr )
return false ;
JUCE_ALSA_LOG ( " ALSADevice::setParameters( " < < deviceID < < " , "
< < ( int ) sampleRate < < " , " < < numChannels < < " , " < < bufferSize < < " ) " ) ;
snd_pcm_hw_params_t * hwParams ;
snd_pcm_hw_params_alloca ( & hwParams ) ;
if ( snd_pcm_hw_params_any ( handle , hwParams ) < 0 )
{
// this is the error message that aplay returns when an error happens here,
// it is a bit more explicit that "Invalid parameter"
error = " Broken configuration for this PCM: no configurations available " ;
return false ;
}
if ( snd_pcm_hw_params_set_access ( handle , hwParams , SND_PCM_ACCESS_RW_INTERLEAVED ) > = 0 ) // works better for plughw..
isInterleaved = true ;
else if ( snd_pcm_hw_params_set_access ( handle , hwParams , SND_PCM_ACCESS_RW_NONINTERLEAVED ) > = 0 )
isInterleaved = false ;
else
{
jassertfalse ;
return false ;
}
enum { isFloatBit = 1 < < 16 , isLittleEndianBit = 1 < < 17 , onlyUseLower24Bits = 1 < < 18 } ;
const int formatsToTry [ ] = { SND_PCM_FORMAT_FLOAT_LE , 32 | isFloatBit | isLittleEndianBit ,
SND_PCM_FORMAT_FLOAT_BE , 32 | isFloatBit ,
SND_PCM_FORMAT_S32_LE , 32 | isLittleEndianBit ,
SND_PCM_FORMAT_S32_BE , 32 ,
SND_PCM_FORMAT_S24_3LE , 24 | isLittleEndianBit ,
SND_PCM_FORMAT_S24_3BE , 24 ,
SND_PCM_FORMAT_S24_LE , 32 | isLittleEndianBit | onlyUseLower24Bits ,
SND_PCM_FORMAT_S16_LE , 16 | isLittleEndianBit ,
SND_PCM_FORMAT_S16_BE , 16 } ;
bitDepth = 0 ;
for ( int i = 0 ; i < numElementsInArray ( formatsToTry ) ; i + = 2 )
{
if ( snd_pcm_hw_params_set_format ( handle , hwParams , ( _snd_pcm_format ) formatsToTry [ i ] ) > = 0 )
{
const int type = formatsToTry [ i + 1 ] ;
bitDepth = type & 255 ;
converter = createConverter ( isInput , bitDepth ,
( type & isFloatBit ) ! = 0 ,
( type & isLittleEndianBit ) ! = 0 ,
( type & onlyUseLower24Bits ) ! = 0 ,
numChannels ) ;
break ;
}
}
if ( bitDepth = = 0 )
{
error = " device doesn't support a compatible PCM format " ;
JUCE_ALSA_LOG ( " Error: " + error ) ;
return false ;
}
int dir = 0 ;
unsigned int periods = 4 ;
snd_pcm_uframes_t samplesPerPeriod = ( snd_pcm_uframes_t ) bufferSize ;
if ( JUCE_ALSA_FAILED ( snd_pcm_hw_params_set_rate_near ( handle , hwParams , & sampleRate , 0 ) )
| | JUCE_ALSA_FAILED ( snd_pcm_hw_params_set_channels ( handle , hwParams , ( unsigned int ) numChannels ) )
| | JUCE_ALSA_FAILED ( snd_pcm_hw_params_set_periods_near ( handle , hwParams , & periods , & dir ) )
| | JUCE_ALSA_FAILED ( snd_pcm_hw_params_set_period_size_near ( handle , hwParams , & samplesPerPeriod , & dir ) )
| | JUCE_ALSA_FAILED ( snd_pcm_hw_params ( handle , hwParams ) ) )
{
return false ;
}
snd_pcm_uframes_t frames = 0 ;
if ( JUCE_ALSA_FAILED ( snd_pcm_hw_params_get_period_size ( hwParams , & frames , & dir ) )
| | JUCE_ALSA_FAILED ( snd_pcm_hw_params_get_periods ( hwParams , & periods , & dir ) ) )
latency = 0 ;
else
latency = ( int ) frames * ( ( int ) periods - 1 ) ; // (this is the method JACK uses to guess the latency..)
JUCE_ALSA_LOG ( " frames: " < < ( int ) frames < < " , periods: " < < ( int ) periods
< < " , samplesPerPeriod: " < < ( int ) samplesPerPeriod ) ;
snd_pcm_sw_params_t * swParams ;
snd_pcm_sw_params_alloca ( & swParams ) ;
snd_pcm_uframes_t boundary ;
if ( JUCE_ALSA_FAILED ( snd_pcm_sw_params_current ( handle , swParams ) )
| | JUCE_ALSA_FAILED ( snd_pcm_sw_params_get_boundary ( swParams , & boundary ) )
| | JUCE_ALSA_FAILED ( snd_pcm_sw_params_set_silence_threshold ( handle , swParams , 0 ) )
| | JUCE_ALSA_FAILED ( snd_pcm_sw_params_set_silence_size ( handle , swParams , boundary ) )
| | JUCE_ALSA_FAILED ( snd_pcm_sw_params_set_start_threshold ( handle , swParams , samplesPerPeriod ) )
| | JUCE_ALSA_FAILED ( snd_pcm_sw_params_set_stop_threshold ( handle , swParams , boundary ) )
| | JUCE_ALSA_FAILED ( snd_pcm_sw_params ( handle , swParams ) ) )
{
return false ;
}
# if JUCE_ALSA_LOGGING
// enable this to dump the config of the devices that get opened
snd_output_t * out ;
snd_output_stdio_attach ( & out , stderr , 0 ) ;
snd_pcm_hw_params_dump ( hwParams , out ) ;
snd_pcm_sw_params_dump ( swParams , out ) ;
# endif
numChannelsRunning = numChannels ;
return true ;
}
//==============================================================================
bool writeToOutputDevice ( AudioSampleBuffer & outputChannelBuffer , const int numSamples )
{
jassert ( numChannelsRunning < = outputChannelBuffer . getNumChannels ( ) ) ;
float * const * const data = outputChannelBuffer . getArrayOfWritePointers ( ) ;
snd_pcm_sframes_t numDone = 0 ;
if ( isInterleaved )
{
scratch . ensureSize ( ( size_t ) ( ( int ) sizeof ( float ) * numSamples * numChannelsRunning ) , false ) ;
for ( int i = 0 ; i < numChannelsRunning ; + + i )
converter - > convertSamples ( scratch . getData ( ) , i , data [ i ] , 0 , numSamples ) ;
numDone = snd_pcm_writei ( handle , scratch . getData ( ) , ( snd_pcm_uframes_t ) numSamples ) ;
}
else
{
for ( int i = 0 ; i < numChannelsRunning ; + + i )
converter - > convertSamples ( data [ i ] , data [ i ] , numSamples ) ;
numDone = snd_pcm_writen ( handle , ( void * * ) data , ( snd_pcm_uframes_t ) numSamples ) ;
}
if ( numDone < 0 & & JUCE_ALSA_FAILED ( snd_pcm_recover ( handle , ( int ) numDone , 1 /* silent */ ) ) )
return false ;
if ( numDone < numSamples )
JUCE_ALSA_LOG ( " Did not write all samples: numDone: " < < numDone < < " , numSamples: " < < numSamples ) ;
return true ;
}
bool readFromInputDevice ( AudioSampleBuffer & inputChannelBuffer , const int numSamples )
{
jassert ( numChannelsRunning < = inputChannelBuffer . getNumChannels ( ) ) ;
float * const * const data = inputChannelBuffer . getArrayOfWritePointers ( ) ;
if ( isInterleaved )
{
scratch . ensureSize ( ( size_t ) ( ( int ) sizeof ( float ) * numSamples * numChannelsRunning ) , false ) ;
scratch . fillWith ( 0 ) ; // (not clearing this data causes warnings in valgrind)
snd_pcm_sframes_t num = snd_pcm_readi ( handle , scratch . getData ( ) , ( snd_pcm_uframes_t ) numSamples ) ;
if ( num < 0 & & JUCE_ALSA_FAILED ( snd_pcm_recover ( handle , ( int ) num , 1 /* silent */ ) ) )
return false ;
if ( num < numSamples )
JUCE_ALSA_LOG ( " Did not read all samples: num: " < < num < < " , numSamples: " < < numSamples ) ;
for ( int i = 0 ; i < numChannelsRunning ; + + i )
converter - > convertSamples ( data [ i ] , 0 , scratch . getData ( ) , i , numSamples ) ;
}
else
{
snd_pcm_sframes_t num = snd_pcm_readn ( handle , ( void * * ) data , ( snd_pcm_uframes_t ) numSamples ) ;
if ( num < 0 & & JUCE_ALSA_FAILED ( snd_pcm_recover ( handle , ( int ) num , 1 /* silent */ ) ) )
return false ;
if ( num < numSamples )
JUCE_ALSA_LOG ( " Did not read all samples: num: " < < num < < " , numSamples: " < < numSamples ) ;
for ( int i = 0 ; i < numChannelsRunning ; + + i )
converter - > convertSamples ( data [ i ] , data [ i ] , numSamples ) ;
}
return true ;
}
//==============================================================================
snd_pcm_t * handle ;
String error ;
int bitDepth , numChannelsRunning , latency ;
private :
//==============================================================================
String deviceID ;
const bool isInput ;
bool isInterleaved ;
MemoryBlock scratch ;
ScopedPointer < AudioData : : Converter > converter ;
//==============================================================================
template < class SampleType >
struct ConverterHelper
{
static AudioData : : Converter * createConverter ( const bool forInput , const bool isLittleEndian , const int numInterleavedChannels )
{
if ( forInput )
{
typedef AudioData : : Pointer < AudioData : : Float32 , AudioData : : NativeEndian , AudioData : : NonInterleaved , AudioData : : NonConst > DestType ;
if ( isLittleEndian )
return new AudioData : : ConverterInstance < AudioData : : Pointer < SampleType , AudioData : : LittleEndian , AudioData : : Interleaved , AudioData : : Const > , DestType > ( numInterleavedChannels , 1 ) ;
return new AudioData : : ConverterInstance < AudioData : : Pointer < SampleType , AudioData : : BigEndian , AudioData : : Interleaved , AudioData : : Const > , DestType > ( numInterleavedChannels , 1 ) ;
}
typedef AudioData : : Pointer < AudioData : : Float32 , AudioData : : NativeEndian , AudioData : : NonInterleaved , AudioData : : Const > SourceType ;
if ( isLittleEndian )
return new AudioData : : ConverterInstance < SourceType , AudioData : : Pointer < SampleType , AudioData : : LittleEndian , AudioData : : Interleaved , AudioData : : NonConst > > ( 1 , numInterleavedChannels ) ;
return new AudioData : : ConverterInstance < SourceType , AudioData : : Pointer < SampleType , AudioData : : BigEndian , AudioData : : Interleaved , AudioData : : NonConst > > ( 1 , numInterleavedChannels ) ;
}
} ;
static AudioData : : Converter * createConverter ( bool forInput , int bitDepth ,
bool isFloat , bool isLittleEndian , bool useOnlyLower24Bits ,
int numInterleavedChannels )
{
JUCE_ALSA_LOG ( " format: bitDepth= " < < bitDepth < < " , isFloat= " < < isFloat
< < " , isLittleEndian= " < < isLittleEndian < < " , numChannels= " < < numInterleavedChannels ) ;
if ( isFloat ) return ConverterHelper < AudioData : : Float32 > : : createConverter ( forInput , isLittleEndian , numInterleavedChannels ) ;
if ( bitDepth = = 16 ) return ConverterHelper < AudioData : : Int16 > : : createConverter ( forInput , isLittleEndian , numInterleavedChannels ) ;
if ( bitDepth = = 24 ) return ConverterHelper < AudioData : : Int24 > : : createConverter ( forInput , isLittleEndian , numInterleavedChannels ) ;
jassert ( bitDepth = = 32 ) ;
if ( useOnlyLower24Bits )
return ConverterHelper < AudioData : : Int24in32 > : : createConverter ( forInput , isLittleEndian , numInterleavedChannels ) ;
return ConverterHelper < AudioData : : Int32 > : : createConverter ( forInput , isLittleEndian , numInterleavedChannels ) ;
}
//==============================================================================
bool failed ( const int errorNum )
{
if ( errorNum > = 0 )
return false ;
error = snd_strerror ( errorNum ) ;
JUCE_ALSA_LOG ( " ALSA error: " < < error ) ;
return true ;
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR ( ALSADevice )
} ;
//==============================================================================
class ALSAThread : public Thread
{
public :
ALSAThread ( const String & inputDeviceID , const String & outputDeviceID )
: Thread ( " Juce ALSA " ) ,
sampleRate ( 0 ) ,
bufferSize ( 0 ) ,
outputLatency ( 0 ) ,
inputLatency ( 0 ) ,
callback ( 0 ) ,
inputId ( inputDeviceID ) ,
outputId ( outputDeviceID ) ,
numCallbacks ( 0 ) ,
audioIoInProgress ( false ) ,
inputChannelBuffer ( 1 , 1 ) ,
outputChannelBuffer ( 1 , 1 )
{
initialiseRatesAndChannels ( ) ;
}
~ ALSAThread ( )
{
close ( ) ;
}
void open ( BigInteger inputChannels ,
BigInteger outputChannels ,
const double newSampleRate ,
const int newBufferSize )
{
close ( ) ;
error . clear ( ) ;
sampleRate = newSampleRate ;
bufferSize = newBufferSize ;
int maxInputsRequested = inputChannels . getHighestBit ( ) + 1 ;
maxInputsRequested = jmax ( ( int ) minChansIn , jmin ( ( int ) maxChansIn , maxInputsRequested ) ) ;
inputChannelBuffer . setSize ( maxInputsRequested , bufferSize ) ;
inputChannelBuffer . clear ( ) ;
inputChannelDataForCallback . clear ( ) ;
currentInputChans . clear ( ) ;
if ( inputChannels . getHighestBit ( ) > = 0 )
{
for ( int i = 0 ; i < maxInputsRequested ; + + i )
{
if ( inputChannels [ i ] )
{
inputChannelDataForCallback . add ( inputChannelBuffer . getReadPointer ( i ) ) ;
currentInputChans . setBit ( i ) ;
}
}
}
ensureMinimumNumBitsSet ( outputChannels , ( int ) minChansOut ) ;
int maxOutputsRequested = outputChannels . getHighestBit ( ) + 1 ;
maxOutputsRequested = jmax ( ( int ) minChansOut , jmin ( ( int ) maxChansOut , maxOutputsRequested ) ) ;
outputChannelBuffer . setSize ( maxOutputsRequested , bufferSize ) ;
outputChannelBuffer . clear ( ) ;
outputChannelDataForCallback . clear ( ) ;
currentOutputChans . clear ( ) ;
if ( outputChannels . getHighestBit ( ) > = 0 )
{
for ( int i = 0 ; i < maxOutputsRequested ; + + i )
{
if ( outputChannels [ i ] )
{
outputChannelDataForCallback . add ( outputChannelBuffer . getWritePointer ( i ) ) ;
currentOutputChans . setBit ( i ) ;
}
}
}
if ( outputChannelDataForCallback . size ( ) > 0 & & outputId . isNotEmpty ( ) )
{
outputDevice = new ALSADevice ( outputId , false ) ;
if ( outputDevice - > error . isNotEmpty ( ) )
{
error = outputDevice - > error ;
outputDevice = nullptr ;
return ;
}
if ( ! outputDevice - > setParameters ( ( unsigned int ) sampleRate ,
jlimit ( ( int ) minChansOut , ( int ) maxChansOut ,
currentOutputChans . getHighestBit ( ) + 1 ) ,
bufferSize ) )
{
error = outputDevice - > error ;
outputDevice = nullptr ;
return ;
}
outputLatency = outputDevice - > latency ;
}
if ( inputChannelDataForCallback . size ( ) > 0 & & inputId . isNotEmpty ( ) )
{
inputDevice = new ALSADevice ( inputId , true ) ;
if ( inputDevice - > error . isNotEmpty ( ) )
{
error = inputDevice - > error ;
inputDevice = nullptr ;
return ;
}
ensureMinimumNumBitsSet ( currentInputChans , ( int ) minChansIn ) ;
if ( ! inputDevice - > setParameters ( ( unsigned int ) sampleRate ,
jlimit ( ( int ) minChansIn , ( int ) maxChansIn , currentInputChans . getHighestBit ( ) + 1 ) ,
bufferSize ) )
{
error = inputDevice - > error ;
inputDevice = nullptr ;
return ;
}
inputLatency = inputDevice - > latency ;
}
if ( outputDevice = = nullptr & & inputDevice = = nullptr )
{
error = " no channels " ;
return ;
}
if ( outputDevice ! = nullptr & & inputDevice ! = nullptr )
snd_pcm_link ( outputDevice - > handle , inputDevice - > handle ) ;
if ( inputDevice ! = nullptr & & JUCE_ALSA_FAILED ( snd_pcm_prepare ( inputDevice - > handle ) ) )
return ;
if ( outputDevice ! = nullptr & & JUCE_ALSA_FAILED ( snd_pcm_prepare ( outputDevice - > handle ) ) )
return ;
startThread ( 9 ) ;
int count = 1000 ;
while ( numCallbacks = = 0 )
{
sleep ( 5 ) ;
if ( - - count < 0 | | ! isThreadRunning ( ) )
{
error = " device didn't start " ;
break ;
}
}
}
void close ( )
{
if ( isThreadRunning ( ) )
{
// problem: when pulseaudio is suspended (with pasuspend) , the ALSAThread::run is just stuck in
// snd_pcm_writei -- no error, no nothing it just stays stuck. So the only way I found to exit "nicely"
// (that is without the "killing thread by force" of stopThread) , is to just call snd_pcm_close from
// here which will cause the thread to resume, and exit
signalThreadShouldExit ( ) ;
const int callbacksToStop = numCallbacks ;
if ( ( ! waitForThreadToExit ( 400 ) ) & & audioIoInProgress & & numCallbacks = = callbacksToStop )
{
JUCE_ALSA_LOG ( " Thread is stuck in i/o.. Is pulseaudio suspended? " ) ;
if ( outputDevice ! = nullptr ) outputDevice - > closeNow ( ) ;
if ( inputDevice ! = nullptr ) inputDevice - > closeNow ( ) ;
}
}
stopThread ( 6000 ) ;
inputDevice = nullptr ;
outputDevice = nullptr ;
inputChannelBuffer . setSize ( 1 , 1 ) ;
outputChannelBuffer . setSize ( 1 , 1 ) ;
numCallbacks = 0 ;
}
void setCallback ( AudioIODeviceCallback * const newCallback ) noexcept
{
const ScopedLock sl ( callbackLock ) ;
callback = newCallback ;
}
void run ( ) override
{
while ( ! threadShouldExit ( ) )
{
if ( inputDevice ! = nullptr & & inputDevice - > handle ! = nullptr )
{
if ( outputDevice = = nullptr | | outputDevice - > handle = = nullptr )
{
JUCE_ALSA_FAILED ( snd_pcm_wait ( inputDevice - > handle , 2000 ) ) ;
if ( threadShouldExit ( ) )
break ;
snd_pcm_sframes_t avail = snd_pcm_avail_update ( inputDevice - > handle ) ;
if ( avail < 0 )
JUCE_ALSA_FAILED ( snd_pcm_recover ( inputDevice - > handle , ( int ) avail , 0 ) ) ;
}
audioIoInProgress = true ;
if ( ! inputDevice - > readFromInputDevice ( inputChannelBuffer , bufferSize ) )
{
JUCE_ALSA_LOG ( " Read failure " ) ;
break ;
}
audioIoInProgress = false ;
}
if ( threadShouldExit ( ) )
break ;
{
const ScopedLock sl ( callbackLock ) ;
+ + numCallbacks ;
if ( callback ! = nullptr )
{
callback - > audioDeviceIOCallback ( inputChannelDataForCallback . getRawDataPointer ( ) ,
inputChannelDataForCallback . size ( ) ,
outputChannelDataForCallback . getRawDataPointer ( ) ,
outputChannelDataForCallback . size ( ) ,
bufferSize ) ;
}
else
{
for ( int i = 0 ; i < outputChannelDataForCallback . size ( ) ; + + i )
zeromem ( outputChannelDataForCallback [ i ] , sizeof ( float ) * ( size_t ) bufferSize ) ;
}
}
if ( outputDevice ! = nullptr & & outputDevice - > handle ! = nullptr )
{
JUCE_ALSA_FAILED ( snd_pcm_wait ( outputDevice - > handle , 2000 ) ) ;
if ( threadShouldExit ( ) )
break ;
snd_pcm_sframes_t avail = snd_pcm_avail_update ( outputDevice - > handle ) ;
if ( avail < 0 )
JUCE_ALSA_FAILED ( snd_pcm_recover ( outputDevice - > handle , ( int ) avail , 0 ) ) ;
audioIoInProgress = true ;
if ( ! outputDevice - > writeToOutputDevice ( outputChannelBuffer , bufferSize ) )
{
JUCE_ALSA_LOG ( " write failure " ) ;
break ;
}
audioIoInProgress = false ;
}
}
audioIoInProgress = false ;
}
int getBitDepth ( ) const noexcept
{
if ( outputDevice ! = nullptr )
return outputDevice - > bitDepth ;
if ( inputDevice ! = nullptr )
return inputDevice - > bitDepth ;
return 16 ;
}
//==============================================================================
String error ;
double sampleRate ;
int bufferSize , outputLatency , inputLatency ;
BigInteger currentInputChans , currentOutputChans ;
Array < double > sampleRates ;
StringArray channelNamesOut , channelNamesIn ;
AudioIODeviceCallback * callback ;
private :
//==============================================================================
const String inputId , outputId ;
ScopedPointer < ALSADevice > outputDevice , inputDevice ;
int numCallbacks ;
bool audioIoInProgress ;
CriticalSection callbackLock ;
AudioSampleBuffer inputChannelBuffer , outputChannelBuffer ;
Array < const float * > inputChannelDataForCallback ;
Array < float * > outputChannelDataForCallback ;
unsigned int minChansOut , maxChansOut ;
unsigned int minChansIn , maxChansIn ;
bool failed ( const int errorNum )
{
if ( errorNum > = 0 )
return false ;
error = snd_strerror ( errorNum ) ;
JUCE_ALSA_LOG ( " ALSA error: " < < error ) ;
return true ;
}
void initialiseRatesAndChannels ( )
{
sampleRates . clear ( ) ;
channelNamesOut . clear ( ) ;
channelNamesIn . clear ( ) ;
minChansOut = 0 ;
maxChansOut = 0 ;
minChansIn = 0 ;
maxChansIn = 0 ;
unsigned int dummy = 0 ;
getDeviceProperties ( inputId , dummy , dummy , minChansIn , maxChansIn , sampleRates , false , true ) ;
getDeviceProperties ( outputId , minChansOut , maxChansOut , dummy , dummy , sampleRates , true , false ) ;
for ( unsigned int i = 0 ; i < maxChansOut ; + + i )
channelNamesOut . add ( " channel " + String ( ( int ) i + 1 ) ) ;
for ( unsigned int i = 0 ; i < maxChansIn ; + + i )
channelNamesIn . add ( " channel " + String ( ( int ) i + 1 ) ) ;
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR ( ALSAThread )
} ;
//==============================================================================
class ALSAAudioIODevice : public AudioIODevice
{
public :
ALSAAudioIODevice ( const String & deviceName ,
const String & deviceTypeName ,
const String & inputDeviceID ,
const String & outputDeviceID )
: AudioIODevice ( deviceName , deviceTypeName ) ,
inputId ( inputDeviceID ) ,
outputId ( outputDeviceID ) ,
isOpen_ ( false ) ,
isStarted ( false ) ,
internal ( inputDeviceID , outputDeviceID )
{
}
~ ALSAAudioIODevice ( )
{
close ( ) ;
}
StringArray getOutputChannelNames ( ) override { return internal . channelNamesOut ; }
StringArray getInputChannelNames ( ) override { return internal . channelNamesIn ; }
Array < double > getAvailableSampleRates ( ) override { return internal . sampleRates ; }
Array < int > getAvailableBufferSizes ( ) override
{
Array < int > r ;
int n = 16 ;
for ( int i = 0 ; i < 50 ; + + i )
{
r . add ( n ) ;
n + = n < 64 ? 16
: ( n < 512 ? 32
: ( n < 1024 ? 64
: ( n < 2048 ? 128 : 256 ) ) ) ;
}
return r ;
}
int getDefaultBufferSize ( ) override { return 512 ; }
String open ( const BigInteger & inputChannels ,
const BigInteger & outputChannels ,
double sampleRate ,
int bufferSizeSamples ) override
{
close ( ) ;
if ( bufferSizeSamples < = 0 )
bufferSizeSamples = getDefaultBufferSize ( ) ;
if ( sampleRate < = 0 )
{
for ( int i = 0 ; i < internal . sampleRates . size ( ) ; + + i )
{
double rate = internal . sampleRates [ i ] ;
if ( rate > = 44100 )
{
sampleRate = rate ;
break ;
}
}
}
internal . open ( inputChannels , outputChannels ,
sampleRate , bufferSizeSamples ) ;
isOpen_ = internal . error . isEmpty ( ) ;
return internal . error ;
}
void close ( ) override
{
stop ( ) ;
internal . close ( ) ;
isOpen_ = false ;
}
bool isOpen ( ) override { return isOpen_ ; }
bool isPlaying ( ) override { return isStarted & & internal . error . isEmpty ( ) ; }
String getLastError ( ) override { return internal . error ; }
int getCurrentBufferSizeSamples ( ) override { return internal . bufferSize ; }
double getCurrentSampleRate ( ) override { return internal . sampleRate ; }
int getCurrentBitDepth ( ) override { return internal . getBitDepth ( ) ; }
BigInteger getActiveOutputChannels ( ) const override { return internal . currentOutputChans ; }
BigInteger getActiveInputChannels ( ) const override { return internal . currentInputChans ; }
int getOutputLatencyInSamples ( ) override { return internal . outputLatency ; }
int getInputLatencyInSamples ( ) override { return internal . inputLatency ; }
void start ( AudioIODeviceCallback * callback ) override
{
if ( ! isOpen_ )
callback = nullptr ;
if ( callback ! = nullptr )
callback - > audioDeviceAboutToStart ( this ) ;
internal . setCallback ( callback ) ;
isStarted = ( callback ! = nullptr ) ;
}
void stop ( ) override
{
AudioIODeviceCallback * const oldCallback = internal . callback ;
start ( nullptr ) ;
if ( oldCallback ! = nullptr )
oldCallback - > audioDeviceStopped ( ) ;
}
String inputId , outputId ;
private :
bool isOpen_ , isStarted ;
ALSAThread internal ;
} ;
//==============================================================================
class ALSAAudioIODeviceType : public AudioIODeviceType
{
public :
ALSAAudioIODeviceType ( bool onlySoundcards , const String & deviceTypeName )
: AudioIODeviceType ( deviceTypeName ) ,
hasScanned ( false ) ,
listOnlySoundcards ( onlySoundcards )
{
# if ! JUCE_ALSA_LOGGING
snd_lib_error_set_handler ( & silentErrorHandler ) ;
# endif
}
~ ALSAAudioIODeviceType ( )
{
# if ! JUCE_ALSA_LOGGING
snd_lib_error_set_handler ( nullptr ) ;
# endif
snd_config_update_free_global ( ) ; // prevent valgrind from screaming about alsa leaks
}
//==============================================================================
void scanForDevices ( )
{
if ( hasScanned )
return ;
hasScanned = true ;
inputNames . clear ( ) ;
inputIds . clear ( ) ;
outputNames . clear ( ) ;
outputIds . clear ( ) ;
JUCE_ALSA_LOG ( " scanForDevices() " ) ;
if ( listOnlySoundcards )
enumerateAlsaSoundcards ( ) ;
else
enumerateAlsaPCMDevices ( ) ;
inputNames . appendNumbersToDuplicates ( false , true ) ;
outputNames . appendNumbersToDuplicates ( false , true ) ;
}
StringArray getDeviceNames ( bool wantInputNames ) const
{
jassert ( hasScanned ) ; // need to call scanForDevices() before doing this
return wantInputNames ? inputNames : outputNames ;
}
int getDefaultDeviceIndex ( bool forInput ) const
{
jassert ( hasScanned ) ; // need to call scanForDevices() before doing this
const int idx = ( forInput ? inputIds : outputIds ) . indexOf ( " default " ) ;
return idx > = 0 ? idx : 0 ;
}
bool hasSeparateInputsAndOutputs ( ) const { return true ; }
int getIndexOfDevice ( AudioIODevice * device , bool asInput ) const
{
jassert ( hasScanned ) ; // need to call scanForDevices() before doing this
if ( ALSAAudioIODevice * d = dynamic_cast < ALSAAudioIODevice * > ( device ) )
return asInput ? inputIds . indexOf ( d - > inputId )
: outputIds . indexOf ( d - > outputId ) ;
return - 1 ;
}
AudioIODevice * createDevice ( const String & outputDeviceName ,
const String & inputDeviceName )
{
jassert ( hasScanned ) ; // need to call scanForDevices() before doing this
const int inputIndex = inputNames . indexOf ( inputDeviceName ) ;
const int outputIndex = outputNames . indexOf ( outputDeviceName ) ;
String deviceName ( outputIndex > = 0 ? outputDeviceName
: inputDeviceName ) ;
if ( inputIndex > = 0 | | outputIndex > = 0 )
return new ALSAAudioIODevice ( deviceName , getTypeName ( ) ,
inputIds [ inputIndex ] ,
outputIds [ outputIndex ] ) ;
return nullptr ;
}
private :
//==============================================================================
StringArray inputNames , outputNames , inputIds , outputIds ;
bool hasScanned , listOnlySoundcards ;
bool testDevice ( const String & id , const String & outputName , const String & inputName )
{
unsigned int minChansOut = 0 , maxChansOut = 0 ;
unsigned int minChansIn = 0 , maxChansIn = 0 ;
Array < double > rates ;
bool isInput = inputName . isNotEmpty ( ) , isOutput = outputName . isNotEmpty ( ) ;
getDeviceProperties ( id , minChansOut , maxChansOut , minChansIn , maxChansIn , rates , isOutput , isInput ) ;
isInput = maxChansIn > 0 ;
isOutput = maxChansOut > 0 ;
if ( ( isInput | | isOutput ) & & rates . size ( ) > 0 )
{
JUCE_ALSA_LOG ( " testDevice: ' " < < id . toUTF8 ( ) . getAddress ( ) < < " ' -> isInput: "
< < isInput < < " , isOutput: " < < isOutput ) ;
if ( isInput )
{
inputNames . add ( inputName ) ;
inputIds . add ( id ) ;
}
if ( isOutput )
{
outputNames . add ( outputName ) ;
outputIds . add ( id ) ;
}
return isInput | | isOutput ;
}
return false ;
}
void enumerateAlsaSoundcards ( )
{
snd_ctl_t * handle = nullptr ;
snd_ctl_card_info_t * info = nullptr ;
snd_ctl_card_info_alloca ( & info ) ;
int cardNum = - 1 ;
while ( outputIds . size ( ) + inputIds . size ( ) < = 64 )
{
snd_card_next ( & cardNum ) ;
if ( cardNum < 0 )
break ;
if ( JUCE_CHECKED_RESULT ( snd_ctl_open ( & handle , ( " hw: " + String ( cardNum ) ) . toUTF8 ( ) , SND_CTL_NONBLOCK ) ) > = 0 )
{
if ( JUCE_CHECKED_RESULT ( snd_ctl_card_info ( handle , info ) ) > = 0 )
{
String cardId ( snd_ctl_card_info_get_id ( info ) ) ;
if ( cardId . removeCharacters ( " 0123456789 " ) . isEmpty ( ) )
cardId = String ( cardNum ) ;
String cardName = snd_ctl_card_info_get_name ( info ) ;
if ( cardName . isEmpty ( ) )
cardName = cardId ;
int device = - 1 ;
snd_pcm_info_t * pcmInfo ;
snd_pcm_info_alloca ( & pcmInfo ) ;
for ( ; ; )
{
if ( snd_ctl_pcm_next_device ( handle , & device ) < 0 | | device < 0 )
break ;
snd_pcm_info_set_device ( pcmInfo , ( unsigned int ) device ) ;
for ( unsigned int subDevice = 0 , nbSubDevice = 1 ; subDevice < nbSubDevice ; + + subDevice )
{
snd_pcm_info_set_subdevice ( pcmInfo , subDevice ) ;
snd_pcm_info_set_stream ( pcmInfo , SND_PCM_STREAM_CAPTURE ) ;
const bool isInput = ( snd_ctl_pcm_info ( handle , pcmInfo ) > = 0 ) ;
snd_pcm_info_set_stream ( pcmInfo , SND_PCM_STREAM_PLAYBACK ) ;
const bool isOutput = ( snd_ctl_pcm_info ( handle , pcmInfo ) > = 0 ) ;
if ( ! ( isInput | | isOutput ) )
continue ;
if ( nbSubDevice = = 1 )
nbSubDevice = snd_pcm_info_get_subdevices_count ( pcmInfo ) ;
String id , name ;
if ( nbSubDevice = = 1 )
{
id < < " hw: " < < cardId < < " , " < < device ;
name < < cardName < < " , " < < snd_pcm_info_get_name ( pcmInfo ) ;
}
else
{
id < < " hw: " < < cardId < < " , " < < device < < " , " < < ( int ) subDevice ;
name < < cardName < < " , " < < snd_pcm_info_get_name ( pcmInfo )
< < " { " < < snd_pcm_info_get_subdevice_name ( pcmInfo ) < < " } " ;
}
JUCE_ALSA_LOG ( " Soundcard ID: " < < id < < " , name: ' " < < name
< < " , isInput: " < < isInput < < " , isOutput: " < < isOutput < < " \n " ) ;
if ( isInput )
{
inputNames . add ( name ) ;
inputIds . add ( id ) ;
}
if ( isOutput )
{
outputNames . add ( name ) ;
outputIds . add ( id ) ;
}
}
}
}
JUCE_CHECKED_RESULT ( snd_ctl_close ( handle ) ) ;
}
}
}
/* Enumerates all ALSA output devices (as output by the command aplay -L)
Does not try to open the devices ( with " testDevice " for example ) ,
so that it also finds devices that are busy and not yet available .
*/
void enumerateAlsaPCMDevices ( )
{
void * * hints = nullptr ;
if ( JUCE_CHECKED_RESULT ( snd_device_name_hint ( - 1 , " pcm " , & hints ) ) = = 0 )
{
for ( char * * h = ( char * * ) hints ; * h ; + + h )
{
const String id ( hintToString ( * h , " NAME " ) ) ;
const String description ( hintToString ( * h , " DESC " ) ) ;
const String ioid ( hintToString ( * h , " IOID " ) ) ;
JUCE_ALSA_LOG ( " ID: " < < id < < " ; desc: " < < description < < " ; ioid: " < < ioid ) ;
String ss = id . fromFirstOccurrenceOf ( " = " , false , false )
. upToFirstOccurrenceOf ( " , " , false , false ) ;
if ( id . isEmpty ( )
| | id . startsWith ( " default: " ) | | id . startsWith ( " sysdefault: " )
| | id . startsWith ( " plughw: " ) | | id = = " null " )
continue ;
String name ( description . replace ( " \n " , " ; " ) ) ;
if ( name . isEmpty ( ) )
name = id ;
bool isOutput = ( ioid ! = " Input " ) ;
bool isInput = ( ioid ! = " Output " ) ;
// alsa is stupid here, it advertises dmix and dsnoop as input/output devices, but
// opening dmix as input, or dsnoop as output will trigger errors..
isInput = isInput & & ! id . startsWith ( " dmix " ) ;
isOutput = isOutput & & ! id . startsWith ( " dsnoop " ) ;
if ( isInput )
{
inputNames . add ( name ) ;
inputIds . add ( id ) ;
}
if ( isOutput )
{
outputNames . add ( name ) ;
outputIds . add ( id ) ;
}
}
snd_device_name_free_hint ( hints ) ;
}
// sometimes the "default" device is not listed, but it is nice to see it explicitely in the list
if ( ! outputIds . contains ( " default " ) )
testDevice ( " default " , " Default ALSA Output " , " Default ALSA Input " ) ;
// same for the pulseaudio plugin
if ( ! outputIds . contains ( " pulse " ) )
testDevice ( " pulse " , " Pulseaudio output " , " Pulseaudio input " ) ;
// make sure the default device is listed first, and followed by the pulse device (if present)
int idx = outputIds . indexOf ( " pulse " ) ;
outputIds . move ( idx , 0 ) ;
outputNames . move ( idx , 0 ) ;
idx = inputIds . indexOf ( " pulse " ) ;
inputIds . move ( idx , 0 ) ;
inputNames . move ( idx , 0 ) ;
idx = outputIds . indexOf ( " default " ) ;
outputIds . move ( idx , 0 ) ;
outputNames . move ( idx , 0 ) ;
idx = inputIds . indexOf ( " default " ) ;
inputIds . move ( idx , 0 ) ;
inputNames . move ( idx , 0 ) ;
}
static String hintToString ( const void * hints , const char * type )
{
char * const hint = snd_device_name_get_hint ( hints , type ) ;
const String s ( String : : fromUTF8 ( hint ) ) ;
: : free ( hint ) ;
return s ;
}
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR ( ALSAAudioIODeviceType )
} ;
}
//==============================================================================
AudioIODeviceType * createAudioIODeviceType_ALSA_Soundcards ( )
{
return new ALSAAudioIODeviceType ( true , " ALSA HW " ) ;
}
AudioIODeviceType * createAudioIODeviceType_ALSA_PCMDevices ( )
{
return new ALSAAudioIODeviceType ( false , " ALSA " ) ;
}
AudioIODeviceType * AudioIODeviceType : : createAudioIODeviceType_ALSA ( )
{
return createAudioIODeviceType_ALSA_PCMDevices ( ) ;
}