29#ifndef JUCE_ALSA_LOGGING
30 #define JUCE_ALSA_LOGGING 0
34 #define JUCE_ALSA_LOG(dbgtext) { juce::String tempDbgBuf ("ALSA: "); tempDbgBuf << dbgtext; Logger::writeToLog (tempDbgBuf); DBG (tempDbgBuf); }
35 #define JUCE_CHECKED_RESULT(x) (logErrorMessage (x, __LINE__))
37 static int logErrorMessage (
int err,
int lineNum)
40 JUCE_ALSA_LOG (
"Error: line " << lineNum <<
": code " << err <<
" (" << snd_strerror (err) <<
")");
45 #define JUCE_ALSA_LOG(x) {}
46 #define JUCE_CHECKED_RESULT(x) (x)
49#define JUCE_ALSA_FAILED(x) failed (x)
51static void getDeviceSampleRates (snd_pcm_t* handle, Array<double>& rates)
53 snd_pcm_hw_params_t* hwParams;
54 snd_pcm_hw_params_alloca (&hwParams);
56 for (
const auto rateToTry : SampleRateHelpers::getAllSampleRates())
58 if (snd_pcm_hw_params_any (handle, hwParams) >= 0
59 && snd_pcm_hw_params_test_rate (handle, hwParams, (
unsigned int) rateToTry, 0) == 0)
61 rates.addIfNotAlreadyThere (rateToTry);
66static void getDeviceNumChannels (snd_pcm_t* handle,
unsigned int* minChans,
unsigned int* maxChans)
68 snd_pcm_hw_params_t *params;
69 snd_pcm_hw_params_alloca (¶ms);
71 if (snd_pcm_hw_params_any (handle, params) >= 0)
73 snd_pcm_hw_params_get_channels_min (params, minChans);
74 snd_pcm_hw_params_get_channels_max (params, maxChans);
76 JUCE_ALSA_LOG (
"getDeviceNumChannels: " << (
int) *minChans <<
" " << (
int) *maxChans);
79 *maxChans =
jmin (*maxChans, 256u);
80 *minChans =
jmin (*minChans, *maxChans);
84 JUCE_ALSA_LOG (
"getDeviceNumChannels failed");
88static void getDeviceProperties (
const String& deviceID,
89 unsigned int& minChansOut,
90 unsigned int& maxChansOut,
91 unsigned int& minChansIn,
92 unsigned int& maxChansIn,
97 minChansOut = maxChansOut = minChansIn = maxChansIn = 0;
99 if (deviceID.isEmpty())
102 JUCE_ALSA_LOG (
"getDeviceProperties(" << deviceID.toUTF8().getAddress() <<
")");
104 snd_pcm_info_t* info;
105 snd_pcm_info_alloca (&info);
109 snd_pcm_t* pcmHandle;
111 if (JUCE_CHECKED_RESULT (snd_pcm_open (&pcmHandle, deviceID.toUTF8().getAddress(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) >= 0)
113 getDeviceNumChannels (pcmHandle, &minChansOut, &maxChansOut);
114 getDeviceSampleRates (pcmHandle, rates);
116 snd_pcm_close (pcmHandle);
122 snd_pcm_t* pcmHandle;
124 if (JUCE_CHECKED_RESULT (snd_pcm_open (&pcmHandle, deviceID.toUTF8(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK) >= 0))
126 getDeviceNumChannels (pcmHandle, &minChansIn, &maxChansIn);
128 if (rates.size() == 0)
129 getDeviceSampleRates (pcmHandle, rates);
131 snd_pcm_close (pcmHandle);
136static void ensureMinimumNumBitsSet (BigInteger& chans,
int minNumChans)
140 while (chans.countNumberOfSetBits() < minNumChans)
144static void silentErrorHandler (
const char*,
int,
const char*,
int,
const char*,...) {}
150 ALSADevice (
const String& devID,
bool forInput)
153 numChannelsRunning (0),
159 JUCE_ALSA_LOG (
"snd_pcm_open (" << deviceID.toUTF8().getAddress() <<
", forInput=" << (
int) forInput <<
")");
161 int err = snd_pcm_open (&handle, deviceID.toUTF8(),
162 forInput ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK,
167 error <<
"The device \"" << deviceID <<
"\" is busy (another application is using it).";
168 else if (-err == ENOENT)
169 error <<
"The device \"" << deviceID <<
"\" is not available.";
171 error <<
"Could not open " << (forInput ?
"input" :
"output") <<
" device \"" << deviceID
172 <<
"\": " << snd_strerror (err) <<
" (" << err <<
")";
174 JUCE_ALSA_LOG (
"snd_pcm_open failed; " << error);
185 if (handle !=
nullptr)
187 snd_pcm_close (handle);
192 bool setParameters (
unsigned int sampleRate,
int numChannels,
int bufferSize)
194 if (handle ==
nullptr)
197 JUCE_ALSA_LOG (
"ALSADevice::setParameters(" << deviceID <<
", "
198 << (
int) sampleRate <<
", " << numChannels <<
", " << bufferSize <<
")");
200 snd_pcm_hw_params_t* hwParams;
201 snd_pcm_hw_params_alloca (&hwParams);
203 if (snd_pcm_hw_params_any (handle, hwParams) < 0)
207 error =
"Broken configuration for this PCM: no configurations available";
211 if (snd_pcm_hw_params_set_access (handle, hwParams, SND_PCM_ACCESS_RW_INTERLEAVED) >= 0)
212 isInterleaved =
true;
213 else if (snd_pcm_hw_params_set_access (handle, hwParams, SND_PCM_ACCESS_RW_NONINTERLEAVED) >= 0)
214 isInterleaved =
false;
221 enum { isFloatBit = 1 << 16, isLittleEndianBit = 1 << 17, onlyUseLower24Bits = 1 << 18 };
223 const int formatsToTry[] = { SND_PCM_FORMAT_FLOAT_LE, 32 | isFloatBit | isLittleEndianBit,
224 SND_PCM_FORMAT_FLOAT_BE, 32 | isFloatBit,
225 SND_PCM_FORMAT_S32_LE, 32 | isLittleEndianBit,
226 SND_PCM_FORMAT_S32_BE, 32,
227 SND_PCM_FORMAT_S24_3LE, 24 | isLittleEndianBit,
228 SND_PCM_FORMAT_S24_3BE, 24,
229 SND_PCM_FORMAT_S24_LE, 32 | isLittleEndianBit | onlyUseLower24Bits,
230 SND_PCM_FORMAT_S16_LE, 16 | isLittleEndianBit,
231 SND_PCM_FORMAT_S16_BE, 16 };
236 if (snd_pcm_hw_params_set_format (handle, hwParams, (_snd_pcm_format) formatsToTry [i]) >= 0)
238 const int type = formatsToTry [i + 1];
239 bitDepth = type & 255;
241 converter.reset (createConverter (isInput, bitDepth,
242 (type & isFloatBit) != 0,
243 (type & isLittleEndianBit) != 0,
244 (type & onlyUseLower24Bits) != 0,
253 error =
"device doesn't support a compatible PCM format";
254 JUCE_ALSA_LOG (
"Error: " + error);
259 unsigned int periods = 4;
260 snd_pcm_uframes_t samplesPerPeriod = (snd_pcm_uframes_t) bufferSize;
262 if (JUCE_ALSA_FAILED (snd_pcm_hw_params_set_rate_near (handle, hwParams, &sampleRate,
nullptr))
263 || JUCE_ALSA_FAILED (snd_pcm_hw_params_set_channels (handle, hwParams, (
unsigned int ) numChannels))
264 || JUCE_ALSA_FAILED (snd_pcm_hw_params_set_periods_near (handle, hwParams, &periods, &dir))
265 || JUCE_ALSA_FAILED (snd_pcm_hw_params_set_period_size_near (handle, hwParams, &samplesPerPeriod, &dir))
266 || JUCE_ALSA_FAILED (snd_pcm_hw_params (handle, hwParams)))
271 snd_pcm_uframes_t frames = 0;
273 if (JUCE_ALSA_FAILED (snd_pcm_hw_params_get_period_size (hwParams, &frames, &dir))
274 || JUCE_ALSA_FAILED (snd_pcm_hw_params_get_periods (hwParams, &periods, &dir)))
277 latency = (
int) frames * ((
int) periods - 1);
279 JUCE_ALSA_LOG (
"frames: " << (
int) frames <<
", periods: " << (
int) periods
280 <<
", samplesPerPeriod: " << (
int) samplesPerPeriod);
282 snd_pcm_sw_params_t* swParams;
283 snd_pcm_sw_params_alloca (&swParams);
284 snd_pcm_uframes_t boundary;
286 if (JUCE_ALSA_FAILED (snd_pcm_sw_params_current (handle, swParams))
287 || JUCE_ALSA_FAILED (snd_pcm_sw_params_get_boundary (swParams, &boundary))
288 || JUCE_ALSA_FAILED (snd_pcm_sw_params_set_silence_threshold (handle, swParams, 0))
289 || JUCE_ALSA_FAILED (snd_pcm_sw_params_set_silence_size (handle, swParams, boundary))
290 || JUCE_ALSA_FAILED (snd_pcm_sw_params_set_start_threshold (handle, swParams, samplesPerPeriod))
291 || JUCE_ALSA_FAILED (snd_pcm_sw_params_set_stop_threshold (handle, swParams, boundary))
292 || JUCE_ALSA_FAILED (snd_pcm_sw_params (handle, swParams)))
297 #if JUCE_ALSA_LOGGING
300 snd_output_stdio_attach (&out, stderr, 0);
301 snd_pcm_hw_params_dump (hwParams, out);
302 snd_pcm_sw_params_dump (swParams, out);
305 numChannelsRunning = numChannels;
311 bool writeToOutputDevice (AudioBuffer<float>& outputChannelBuffer,
const int numSamples)
313 jassert (numChannelsRunning <= outputChannelBuffer.getNumChannels());
314 float*
const*
const data = outputChannelBuffer.getArrayOfWritePointers();
315 snd_pcm_sframes_t numDone = 0;
319 scratch.ensureSize ((
size_t) ((
int)
sizeof (
float) * numSamples * numChannelsRunning),
false);
321 for (
int i = 0; i < numChannelsRunning; ++i)
322 converter->convertSamples (scratch.getData(), i, data[i], 0, numSamples);
324 numDone = snd_pcm_writei (handle, scratch.getData(), (snd_pcm_uframes_t) numSamples);
328 for (
int i = 0; i < numChannelsRunning; ++i)
329 converter->convertSamples (data[i], data[i], numSamples);
331 numDone = snd_pcm_writen (handle, (
void**) data, (snd_pcm_uframes_t) numSamples);
336 if (numDone == -(EPIPE))
339 if (JUCE_ALSA_FAILED (snd_pcm_recover (handle, (
int) numDone, 1 )))
343 if (numDone < numSamples)
344 JUCE_ALSA_LOG (
"Did not write all samples: numDone: " << numDone <<
", numSamples: " << numSamples);
349 bool readFromInputDevice (AudioBuffer<float>& inputChannelBuffer,
const int numSamples)
351 jassert (numChannelsRunning <= inputChannelBuffer.getNumChannels());
352 float*
const*
const data = inputChannelBuffer.getArrayOfWritePointers();
356 scratch.ensureSize ((
size_t) ((
int)
sizeof (
float) * numSamples * numChannelsRunning),
false);
357 scratch.fillWith (0);
359 auto num = snd_pcm_readi (handle, scratch.getData(), (snd_pcm_uframes_t) numSamples);
366 if (JUCE_ALSA_FAILED (snd_pcm_recover (handle, (
int) num, 1 )))
371 if (num < numSamples)
372 JUCE_ALSA_LOG (
"Did not read all samples: num: " << num <<
", numSamples: " << numSamples);
374 for (
int i = 0; i < numChannelsRunning; ++i)
375 converter->convertSamples (data[i], 0, scratch.getData(), i, numSamples);
379 auto num = snd_pcm_readn (handle, (
void**) data, (snd_pcm_uframes_t) numSamples);
386 if (JUCE_ALSA_FAILED (snd_pcm_recover (handle, (
int) num, 1 )))
390 if (num < numSamples)
391 JUCE_ALSA_LOG (
"Did not read all samples: num: " << num <<
", numSamples: " << numSamples);
393 for (
int i = 0; i < numChannelsRunning; ++i)
394 converter->convertSamples (data[i], data[i], numSamples);
403 int bitDepth, numChannelsRunning, latency;
404 int underrunCount = 0, overrunCount = 0;
415 template <
class SampleType>
416 struct ConverterHelper
418 static AudioData::Converter* createConverter (
const bool forInput,
const bool isLittleEndian,
const int numInterleavedChannels,
bool interleaved)
421 return create<AudioData::Interleaved> (forInput, isLittleEndian, numInterleavedChannels);
423 return create<AudioData::NonInterleaved> (forInput, isLittleEndian, numInterleavedChannels);
427 template <
class InterleavedType>
428 static AudioData::Converter* create (
const bool forInput,
const bool isLittleEndian,
const int numInterleavedChannels)
432 using DestType = AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst>;
435 return new AudioData::ConverterInstance <AudioData::Pointer <SampleType, AudioData::LittleEndian, InterleavedType, AudioData::Const>, DestType> (numInterleavedChannels, 1);
437 return new AudioData::ConverterInstance <AudioData::Pointer <SampleType, AudioData::BigEndian, InterleavedType, AudioData::Const>, DestType> (numInterleavedChannels, 1);
440 using SourceType = AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const>;
443 return new AudioData::ConverterInstance <SourceType, AudioData::Pointer <SampleType, AudioData::LittleEndian, InterleavedType, AudioData::NonConst>> (1, numInterleavedChannels);
445 return new AudioData::ConverterInstance <SourceType, AudioData::Pointer <SampleType, AudioData::BigEndian, InterleavedType, AudioData::NonConst>> (1, numInterleavedChannels);
449 static AudioData::Converter* createConverter (
bool forInput,
int bitDepth,
450 bool isFloat,
bool isLittleEndian,
bool useOnlyLower24Bits,
451 int numInterleavedChannels,
454 JUCE_ALSA_LOG (
"format: bitDepth=" << bitDepth <<
", isFloat=" << (
int) isFloat
455 <<
", isLittleEndian=" << (
int) isLittleEndian <<
", numChannels=" << numInterleavedChannels);
457 if (isFloat)
return ConverterHelper <AudioData::Float32>::createConverter (forInput, isLittleEndian, numInterleavedChannels, interleaved);
458 if (bitDepth == 16)
return ConverterHelper <AudioData::Int16> ::createConverter (forInput, isLittleEndian, numInterleavedChannels, interleaved);
459 if (bitDepth == 24)
return ConverterHelper <AudioData::Int24> ::createConverter (forInput, isLittleEndian, numInterleavedChannels, interleaved);
463 if (useOnlyLower24Bits)
464 return ConverterHelper <AudioData::Int24in32>::createConverter (forInput, isLittleEndian, numInterleavedChannels, interleaved);
466 return ConverterHelper <AudioData::Int32>::createConverter (forInput, isLittleEndian, numInterleavedChannels, interleaved);
470 bool failed (
const int errorNum)
475 error = snd_strerror (errorNum);
476 JUCE_ALSA_LOG (
"ALSA error: " << error);
484class ALSAThread final :
public Thread
487 ALSAThread (
const String& inputDeviceID,
const String& outputDeviceID)
488 : Thread (
"JUCE ALSA"),
489 inputId (inputDeviceID),
490 outputId (outputDeviceID)
492 initialiseRatesAndChannels();
495 ~ALSAThread()
override
500 void open (BigInteger inputChannels,
501 BigInteger outputChannels,
502 double newSampleRate,
508 sampleRate = newSampleRate;
509 bufferSize = newBufferSize;
511 int maxInputsRequested = inputChannels.getHighestBit() + 1;
512 maxInputsRequested =
jmax ((
int) minChansIn,
jmin ((
int) maxChansIn, maxInputsRequested));
514 inputChannelBuffer.setSize (maxInputsRequested, bufferSize);
515 inputChannelBuffer.clear();
516 inputChannelDataForCallback.clear();
517 currentInputChans.clear();
519 if (inputChannels.getHighestBit() >= 0)
521 for (
int i = 0; i < maxInputsRequested; ++i)
523 if (inputChannels[i])
525 inputChannelDataForCallback.add (inputChannelBuffer.getReadPointer (i));
526 currentInputChans.setBit (i);
531 ensureMinimumNumBitsSet (outputChannels, (
int) minChansOut);
533 int maxOutputsRequested = outputChannels.getHighestBit() + 1;
534 maxOutputsRequested =
jmax ((
int) minChansOut,
jmin ((
int) maxChansOut, maxOutputsRequested));
536 outputChannelBuffer.setSize (maxOutputsRequested, bufferSize);
537 outputChannelBuffer.clear();
538 outputChannelDataForCallback.clear();
539 currentOutputChans.clear();
545 if (inputChannelDataForCallback.size() > 0 && inputId.isNotEmpty())
547 inputDevice.reset (
new ALSADevice (inputId,
true));
549 if (inputDevice->error.isNotEmpty())
551 error = inputDevice->error;
556 ensureMinimumNumBitsSet (currentInputChans, (
int) minChansIn);
558 if (! inputDevice->setParameters ((
unsigned int) sampleRate,
559 jlimit ((
int) minChansIn, (
int) maxChansIn, currentInputChans.getHighestBit() + 1),
562 error = inputDevice->error;
567 inputLatency = inputDevice->latency;
570 if (outputChannels.getHighestBit() >= 0)
572 for (
int i = 0; i < maxOutputsRequested; ++i)
574 if (outputChannels[i])
576 outputChannelDataForCallback.add (outputChannelBuffer.getWritePointer (i));
577 currentOutputChans.setBit (i);
582 if (outputChannelDataForCallback.size() > 0 && outputId.isNotEmpty())
584 outputDevice.reset (
new ALSADevice (outputId,
false));
586 if (outputDevice->error.isNotEmpty())
588 error = outputDevice->error;
589 outputDevice.reset();
593 if (! outputDevice->setParameters ((
unsigned int) sampleRate,
594 jlimit ((
int) minChansOut, (
int) maxChansOut,
595 currentOutputChans.getHighestBit() + 1),
598 error = outputDevice->error;
599 outputDevice.reset();
603 outputLatency = outputDevice->latency;
606 if (outputDevice ==
nullptr && inputDevice ==
nullptr)
608 error =
"no channels";
612 if (outputDevice !=
nullptr && inputDevice !=
nullptr)
613 snd_pcm_link (outputDevice->handle, inputDevice->handle);
615 if (inputDevice !=
nullptr && JUCE_ALSA_FAILED (snd_pcm_prepare (inputDevice->handle)))
618 if (outputDevice !=
nullptr && JUCE_ALSA_FAILED (snd_pcm_prepare (outputDevice->handle)))
621 startThread (Priority::high);
625 while (numCallbacks == 0)
629 if (--count < 0 || ! isThreadRunning())
631 error =
"device didn't start";
639 if (isThreadRunning())
645 signalThreadShouldExit();
647 const int callbacksToStop = numCallbacks;
649 if ((! waitForThreadToExit (400)) && audioIoInProgress && numCallbacks == callbacksToStop)
651 JUCE_ALSA_LOG (
"Thread is stuck in i/o.. Is pulseaudio suspended?");
653 if (outputDevice !=
nullptr) outputDevice->closeNow();
654 if (inputDevice !=
nullptr) inputDevice->closeNow();
661 outputDevice.reset();
663 inputChannelBuffer.setSize (1, 1);
664 outputChannelBuffer.setSize (1, 1);
669 void setCallback (AudioIODeviceCallback*
const newCallback)
noexcept
672 callback = newCallback;
677 while (! threadShouldExit())
679 if (inputDevice !=
nullptr && inputDevice->handle !=
nullptr)
681 if (outputDevice ==
nullptr || outputDevice->handle ==
nullptr)
683 JUCE_ALSA_FAILED (snd_pcm_wait (inputDevice->handle, 2000));
685 if (threadShouldExit())
688 auto avail = snd_pcm_avail_update (inputDevice->handle);
691 JUCE_ALSA_FAILED (snd_pcm_recover (inputDevice->handle, (
int) avail, 0));
694 audioIoInProgress =
true;
696 if (! inputDevice->readFromInputDevice (inputChannelBuffer, bufferSize))
698 JUCE_ALSA_LOG (
"Read failure");
702 audioIoInProgress =
false;
705 if (threadShouldExit())
712 if (callback !=
nullptr)
714 callback->audioDeviceIOCallbackWithContext (inputChannelDataForCallback.getRawDataPointer(),
715 inputChannelDataForCallback.size(),
716 outputChannelDataForCallback.getRawDataPointer(),
717 outputChannelDataForCallback.size(),
723 for (
int i = 0; i < outputChannelDataForCallback.size(); ++i)
724 zeromem (outputChannelDataForCallback[i], (
size_t) bufferSize *
sizeof (
float));
728 if (outputDevice !=
nullptr && outputDevice->handle !=
nullptr)
730 JUCE_ALSA_FAILED (snd_pcm_wait (outputDevice->handle, 2000));
732 if (threadShouldExit())
735 auto avail = snd_pcm_avail_update (outputDevice->handle);
738 JUCE_ALSA_FAILED (snd_pcm_recover (outputDevice->handle, (
int) avail, 0));
740 audioIoInProgress =
true;
742 if (! outputDevice->writeToOutputDevice (outputChannelBuffer, bufferSize))
744 JUCE_ALSA_LOG (
"write failure");
748 audioIoInProgress =
false;
752 audioIoInProgress =
false;
755 int getBitDepth() const noexcept
757 if (outputDevice !=
nullptr)
758 return outputDevice->bitDepth;
760 if (inputDevice !=
nullptr)
761 return inputDevice->bitDepth;
766 int getXRunCount() const noexcept
770 if (outputDevice !=
nullptr)
771 result += outputDevice->underrunCount;
773 if (inputDevice !=
nullptr)
774 result += inputDevice->overrunCount;
781 double sampleRate = 0;
782 int bufferSize = 0, outputLatency = 0, inputLatency = 0;
783 BigInteger currentInputChans, currentOutputChans;
785 Array<double> sampleRates;
786 StringArray channelNamesOut, channelNamesIn;
787 AudioIODeviceCallback* callback =
nullptr;
791 const String inputId, outputId;
796 CriticalSection callbackLock;
798 AudioBuffer<float> inputChannelBuffer, outputChannelBuffer;
799 Array<const float*> inputChannelDataForCallback;
800 Array<float*> outputChannelDataForCallback;
802 unsigned int minChansOut = 0, maxChansOut = 0;
803 unsigned int minChansIn = 0, maxChansIn = 0;
805 bool failed (
const int errorNum)
810 error = snd_strerror (errorNum);
811 JUCE_ALSA_LOG (
"ALSA error: " << error);
815 void initialiseRatesAndChannels()
818 channelNamesOut.clear();
819 channelNamesIn.clear();
824 unsigned int dummy = 0;
826 getDeviceProperties (inputId, dummy, dummy, minChansIn, maxChansIn, sampleRates,
false,
true);
827 getDeviceProperties (outputId, minChansOut, maxChansOut, dummy, dummy, sampleRates,
true,
false);
829 for (
unsigned int i = 0; i < maxChansOut; ++i)
830 channelNamesOut.add (
"channel " + String ((
int) i + 1));
832 for (
unsigned int i = 0; i < maxChansIn; ++i)
833 channelNamesIn.add (
"channel " + String ((
int) i + 1));
841class ALSAAudioIODevice final :
public AudioIODevice
844 ALSAAudioIODevice (
const String& deviceName,
845 const String& deviceTypeName,
846 const String& inputDeviceID,
847 const String& outputDeviceID)
848 : AudioIODevice (deviceName, deviceTypeName),
849 inputId (inputDeviceID),
850 outputId (outputDeviceID),
851 internal (inputDeviceID, outputDeviceID)
855 ~ALSAAudioIODevice()
override
860 StringArray getOutputChannelNames()
override {
return internal.channelNamesOut; }
861 StringArray getInputChannelNames()
override {
return internal.channelNamesIn; }
863 Array<double> getAvailableSampleRates()
override {
return internal.sampleRates; }
865 Array<int> getAvailableBufferSizes()
override
870 for (
int i = 0; i < 50; ++i)
876 : (n < 2048 ? 128 : 256)));
882 int getDefaultBufferSize()
override {
return 512; }
884 String
open (
const BigInteger& inputChannels,
885 const BigInteger& outputChannels,
887 int bufferSizeSamples)
override
891 if (bufferSizeSamples <= 0)
892 bufferSizeSamples = getDefaultBufferSize();
896 for (
int i = 0; i <
internal.sampleRates.size(); ++i)
898 double rate =
internal.sampleRates[i];
908 internal.open (inputChannels, outputChannels,
909 sampleRate, bufferSizeSamples);
915 void close()
override
922 bool isOpen()
override {
return isOpen_; }
923 bool isPlaying()
override {
return isStarted &&
internal.error.isEmpty(); }
924 String getLastError()
override {
return internal.error; }
926 int getCurrentBufferSizeSamples()
override {
return internal.bufferSize; }
927 double getCurrentSampleRate()
override {
return internal.sampleRate; }
928 int getCurrentBitDepth()
override {
return internal.getBitDepth(); }
930 BigInteger getActiveOutputChannels()
const override {
return internal.currentOutputChans; }
931 BigInteger getActiveInputChannels()
const override {
return internal.currentInputChans; }
933 int getOutputLatencyInSamples()
override {
return internal.outputLatency; }
934 int getInputLatencyInSamples()
override {
return internal.inputLatency; }
936 int getXRunCount() const noexcept
override {
return internal.getXRunCount(); }
938 void start (AudioIODeviceCallback* callback)
override
943 if (callback !=
nullptr)
944 callback->audioDeviceAboutToStart (
this);
948 isStarted = (callback !=
nullptr);
953 auto oldCallback =
internal.callback;
957 if (oldCallback !=
nullptr)
958 oldCallback->audioDeviceStopped();
961 String inputId, outputId;
964 bool isOpen_ =
false, isStarted =
false;
970class ALSAAudioIODeviceType final :
public AudioIODeviceType
973 ALSAAudioIODeviceType (
bool onlySoundcards,
const String& deviceTypeName)
974 : AudioIODeviceType (deviceTypeName),
975 listOnlySoundcards (onlySoundcards)
977 #if ! JUCE_ALSA_LOGGING
978 snd_lib_error_set_handler (&silentErrorHandler);
982 ~ALSAAudioIODeviceType()
override
984 #if ! JUCE_ALSA_LOGGING
985 snd_lib_error_set_handler (
nullptr);
988 snd_config_update_free_global();
992 void scanForDevices()
override
1000 outputNames.clear();
1003 JUCE_ALSA_LOG (
"scanForDevices()");
1005 if (listOnlySoundcards)
1006 enumerateAlsaSoundcards();
1008 enumerateAlsaPCMDevices();
1010 inputNames.appendNumbersToDuplicates (
false,
true);
1011 outputNames.appendNumbersToDuplicates (
false,
true);
1014 StringArray getDeviceNames (
bool wantInputNames)
const override
1018 return wantInputNames ? inputNames : outputNames;
1021 int getDefaultDeviceIndex (
bool forInput)
const override
1025 auto idx = (forInput ? inputIds : outputIds).indexOf (
"default");
1026 return idx >= 0 ? idx : 0;
1029 bool hasSeparateInputsAndOutputs()
const override {
return true; }
1031 int getIndexOfDevice (AudioIODevice* device,
bool asInput)
const override
1035 if (
auto* d =
dynamic_cast<ALSAAudioIODevice*
> (device))
1036 return asInput ? inputIds.indexOf (d->inputId)
1037 : outputIds.indexOf (d->outputId);
1042 AudioIODevice* createDevice (
const String& outputDeviceName,
1043 const String& inputDeviceName)
override
1047 auto inputIndex = inputNames.indexOf (inputDeviceName);
1048 auto outputIndex = outputNames.indexOf (outputDeviceName);
1050 String deviceName (outputIndex >= 0 ? outputDeviceName
1053 if (inputIndex >= 0 || outputIndex >= 0)
1054 return new ALSAAudioIODevice (deviceName, getTypeName(),
1055 inputIds [inputIndex],
1056 outputIds [outputIndex]);
1063 StringArray inputNames, outputNames, inputIds, outputIds;
1064 bool hasScanned =
false;
1065 const bool listOnlySoundcards;
1067 bool testDevice (
const String&
id,
const String& outputName,
const String& inputName)
1069 unsigned int minChansOut = 0, maxChansOut = 0;
1070 unsigned int minChansIn = 0, maxChansIn = 0;
1071 Array<double> rates;
1073 bool isInput = inputName.isNotEmpty(), isOutput = outputName.isNotEmpty();
1074 getDeviceProperties (
id, minChansOut, maxChansOut, minChansIn, maxChansIn, rates, isOutput, isInput);
1076 isInput = maxChansIn > 0;
1077 isOutput = maxChansOut > 0;
1079 if ((isInput || isOutput) && rates.size() > 0)
1081 JUCE_ALSA_LOG (
"testDevice: '" <<
id.toUTF8().getAddress() <<
"' -> isInput: "
1082 << (
int) isInput <<
", isOutput: " << (
int) isOutput);
1086 inputNames.add (inputName);
1092 outputNames.add (outputName);
1096 return isInput || isOutput;
1102 void enumerateAlsaSoundcards()
1104 snd_ctl_t* handle =
nullptr;
1105 snd_ctl_card_info_t* info =
nullptr;
1106 snd_ctl_card_info_alloca (&info);
1110 while (outputIds.size() + inputIds.size() <= 64)
1112 snd_card_next (&cardNum);
1117 if (JUCE_CHECKED_RESULT (snd_ctl_open (&handle, (
"hw:" + String (cardNum)).toRawUTF8(), SND_CTL_NONBLOCK)) >= 0)
1119 if (JUCE_CHECKED_RESULT (snd_ctl_card_info (handle, info)) >= 0)
1121 String cardId (snd_ctl_card_info_get_id (info));
1123 if (cardId.removeCharacters (
"0123456789").isEmpty())
1124 cardId = String (cardNum);
1126 String cardName = snd_ctl_card_info_get_name (info);
1128 if (cardName.isEmpty())
1133 snd_pcm_info_t* pcmInfo;
1134 snd_pcm_info_alloca (&pcmInfo);
1138 if (snd_ctl_pcm_next_device (handle, &device) < 0 || device < 0)
1141 snd_pcm_info_set_device (pcmInfo, (
unsigned int) device);
1143 for (
unsigned int subDevice = 0, nbSubDevice = 1; subDevice < nbSubDevice; ++subDevice)
1145 snd_pcm_info_set_subdevice (pcmInfo, subDevice);
1146 snd_pcm_info_set_stream (pcmInfo, SND_PCM_STREAM_CAPTURE);
1147 const bool isInput = (snd_ctl_pcm_info (handle, pcmInfo) >= 0);
1149 snd_pcm_info_set_stream (pcmInfo, SND_PCM_STREAM_PLAYBACK);
1150 const bool isOutput = (snd_ctl_pcm_info (handle, pcmInfo) >= 0);
1152 if (! (isInput || isOutput))
1155 if (nbSubDevice == 1)
1156 nbSubDevice = snd_pcm_info_get_subdevices_count (pcmInfo);
1160 if (nbSubDevice == 1)
1162 id <<
"hw:" << cardId <<
"," << device;
1163 name << cardName <<
", " << snd_pcm_info_get_name (pcmInfo);
1167 id <<
"hw:" << cardId <<
"," << device <<
"," << (
int) subDevice;
1168 name << cardName <<
", " << snd_pcm_info_get_name (pcmInfo)
1169 <<
" {" << snd_pcm_info_get_subdevice_name (pcmInfo) <<
"}";
1172 JUCE_ALSA_LOG (
"Soundcard ID: " <<
id <<
", name: '" << name
1173 <<
", isInput:" << (
int) isInput
1174 <<
", isOutput:" << (
int) isOutput <<
"\n");
1178 inputNames.add (name);
1184 outputNames.add (name);
1191 JUCE_CHECKED_RESULT (snd_ctl_close (handle));
1200 void enumerateAlsaPCMDevices()
1202 void** hints =
nullptr;
1204 if (JUCE_CHECKED_RESULT (snd_device_name_hint (-1,
"pcm", &hints)) == 0)
1206 for (
char** h = (
char**) hints; *h; ++h)
1208 const String id (hintToString (*h,
"NAME"));
1209 const String description (hintToString (*h,
"DESC"));
1210 const String ioid (hintToString (*h,
"IOID"));
1212 JUCE_ALSA_LOG (
"ID: " <<
id <<
"; desc: " << description <<
"; ioid: " << ioid);
1214 String ss =
id.fromFirstOccurrenceOf (
"=",
false,
false)
1215 .upToFirstOccurrenceOf (
",",
false,
false);
1218 ||
id.startsWith (
"default:") ||
id.startsWith (
"sysdefault:")
1219 ||
id.startsWith (
"plughw:") ||
id ==
"null")
1222 String name (description.replace (
"\n",
"; "));
1227 bool isOutput = (ioid !=
"Input");
1228 bool isInput = (ioid !=
"Output");
1232 isInput = isInput && !
id.startsWith (
"dmix");
1233 isOutput = isOutput && !
id.startsWith (
"dsnoop");
1237 inputNames.add (name);
1243 outputNames.add (name);
1248 snd_device_name_free_hint (hints);
1252 if (! outputIds.contains (
"default"))
1253 testDevice (
"default",
"Default ALSA Output",
"Default ALSA Input");
1256 if (! outputIds.contains (
"pulse"))
1257 testDevice (
"pulse",
"Pulseaudio output",
"Pulseaudio input");
1260 auto idx = outputIds.indexOf (
"pulse");
1261 outputIds.move (idx, 0);
1262 outputNames.move (idx, 0);
1264 idx = inputIds.indexOf (
"pulse");
1265 inputIds.move (idx, 0);
1266 inputNames.move (idx, 0);
1268 idx = outputIds.indexOf (
"default");
1269 outputIds.move (idx, 0);
1270 outputNames.move (idx, 0);
1272 idx = inputIds.indexOf (
"default");
1273 inputIds.move (idx, 0);
1274 inputNames.move (idx, 0);
1277 static String hintToString (
const void* hints,
const char* type)
1279 char* hint = snd_device_name_get_hint (hints, type);
1291static inline AudioIODeviceType* createAudioIODeviceType_ALSA_Soundcards()
1293 return new ALSAAudioIODeviceType (
true,
"ALSA HW");
1296static inline AudioIODeviceType* createAudioIODeviceType_ALSA_PCMDevices()
1298 return new ALSAAudioIODeviceType (
false,
"ALSA");
static String fromUTF8(const char *utf8buffer, int bufferSizeBytes=-1)
Creates a String from a UTF-8 encoded buffer.
CriticalSection::ScopedLockType ScopedLock
Automatically locks and unlocks a CriticalSection object.
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
constexpr Type jmax(Type a, Type b)
Returns the larger of two values.
Type jlimit(Type lowerLimit, Type upperLimit, Type valueToConstrain) noexcept
Constrains a value to keep it within a given range.
constexpr int numElementsInArray(Type(&)[N]) noexcept
Handy function for getting the number of elements in a simple const C array.
void zeromem(void *memory, size_t numBytes) noexcept
Fills a block of memory with zeros.