JUCE-7.0.12-0-g4f43011b96 JUCE-7.0.12-0-g4f43011b96
JUCE — C++ application framework with suport for VST, VST3, LV2 audio plug-ins

« « « Anklang Documentation
Loading...
Searching...
No Matches
juce_ALSA_linux.cpp
Go to the documentation of this file.
1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 The code included in this file is provided under the terms of the ISC license
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12 To use, copy, modify, and/or distribute this software for any purpose with or
13 without fee is hereby granted provided that the above copyright notice and
14 this permission notice appear in all copies.
15
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18 DISCLAIMED.
19
20 ==============================================================================
21*/
22
23namespace juce
24{
25
26namespace
27{
28
29#ifndef JUCE_ALSA_LOGGING
30 #define JUCE_ALSA_LOGGING 0
31#endif
32
33#if JUCE_ALSA_LOGGING
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__))
36
37 static int logErrorMessage (int err, int lineNum)
38 {
39 if (err < 0)
40 JUCE_ALSA_LOG ("Error: line " << lineNum << ": code " << err << " (" << snd_strerror (err) << ")");
41
42 return err;
43 }
44#else
45 #define JUCE_ALSA_LOG(x) {}
46 #define JUCE_CHECKED_RESULT(x) (x)
47#endif
48
49#define JUCE_ALSA_FAILED(x) failed (x)
50
51static void getDeviceSampleRates (snd_pcm_t* handle, Array<double>& rates)
52{
55
56 for (const auto rateToTry : SampleRateHelpers::getAllSampleRates())
57 {
58 if (snd_pcm_hw_params_any (handle, hwParams) >= 0
59 && snd_pcm_hw_params_test_rate (handle, hwParams, (unsigned int) rateToTry, 0) == 0)
60 {
61 rates.addIfNotAlreadyThere (rateToTry);
62 }
63 }
64}
65
66static void getDeviceNumChannels (snd_pcm_t* handle, unsigned int* minChans, unsigned int* maxChans)
67{
68 snd_pcm_hw_params_t *params;
70
71 if (snd_pcm_hw_params_any (handle, params) >= 0)
72 {
75
76 JUCE_ALSA_LOG ("getDeviceNumChannels: " << (int) *minChans << " " << (int) *maxChans);
77
78 // some virtual devices (dmix for example) report 10000 channels , we have to clamp these values
79 *maxChans = jmin (*maxChans, 256u);
81 }
82 else
83 {
84 JUCE_ALSA_LOG ("getDeviceNumChannels failed");
85 }
86}
87
88static void getDeviceProperties (const String& deviceID,
89 unsigned int& minChansOut,
90 unsigned int& maxChansOut,
91 unsigned int& minChansIn,
92 unsigned int& maxChansIn,
93 Array<double>& rates,
94 bool testOutput,
95 bool testInput)
96{
97 minChansOut = maxChansOut = minChansIn = maxChansIn = 0;
98
99 if (deviceID.isEmpty())
100 return;
101
102 JUCE_ALSA_LOG ("getDeviceProperties(" << deviceID.toUTF8().getAddress() << ")");
103
104 snd_pcm_info_t* info;
105 snd_pcm_info_alloca (&info);
106
107 if (testOutput)
108 {
110
111 if (JUCE_CHECKED_RESULT (snd_pcm_open (&pcmHandle, deviceID.toUTF8().getAddress(), SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK)) >= 0)
112 {
113 getDeviceNumChannels (pcmHandle, &minChansOut, &maxChansOut);
115
117 }
118 }
119
120 if (testInput)
121 {
123
124 if (JUCE_CHECKED_RESULT (snd_pcm_open (&pcmHandle, deviceID.toUTF8(), SND_PCM_STREAM_CAPTURE, SND_PCM_NONBLOCK) >= 0))
125 {
126 getDeviceNumChannels (pcmHandle, &minChansIn, &maxChansIn);
127
128 if (rates.size() == 0)
130
132 }
133 }
134}
135
136static void ensureMinimumNumBitsSet (BigInteger& chans, int minNumChans)
137{
138 int i = 0;
139
140 while (chans.countNumberOfSetBits() < minNumChans)
141 chans.setBit (i++);
142}
143
144static void silentErrorHandler (const char*, int, const char*, int, const char*,...) {}
145
146//==============================================================================
147class ALSADevice
148{
149public:
150 ALSADevice (const String& devID, bool forInput)
151 : handle (nullptr),
152 bitDepth (16),
153 numChannelsRunning (0),
154 latency (0),
155 deviceID (devID),
156 isInput (forInput),
157 isInterleaved (true)
158 {
159 JUCE_ALSA_LOG ("snd_pcm_open (" << deviceID.toUTF8().getAddress() << ", forInput=" << (int) forInput << ")");
160
161 int err = snd_pcm_open (&handle, deviceID.toUTF8(),
164 if (err < 0)
165 {
166 if (-err == EBUSY)
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.";
170 else
171 error << "Could not open " << (forInput ? "input" : "output") << " device \"" << deviceID
172 << "\": " << snd_strerror (err) << " (" << err << ")";
173
174 JUCE_ALSA_LOG ("snd_pcm_open failed; " << error);
175 }
176 }
177
179 {
180 closeNow();
181 }
182
183 void closeNow()
184 {
185 if (handle != nullptr)
186 {
187 snd_pcm_close (handle);
188 handle = nullptr;
189 }
190 }
191
192 bool setParameters (unsigned int sampleRate, int numChannels, int bufferSize)
193 {
194 if (handle == nullptr)
195 return false;
196
197 JUCE_ALSA_LOG ("ALSADevice::setParameters(" << deviceID << ", "
198 << (int) sampleRate << ", " << numChannels << ", " << bufferSize << ")");
199
202
203 if (snd_pcm_hw_params_any (handle, hwParams) < 0)
204 {
205 // this is the error message that aplay returns when an error happens here,
206 // it is a bit more explicit that "Invalid parameter"
207 error = "Broken configuration for this PCM: no configurations available";
208 return false;
209 }
210
211 if (snd_pcm_hw_params_set_access (handle, hwParams, SND_PCM_ACCESS_RW_INTERLEAVED) >= 0) // works better for plughw..
212 isInterleaved = true;
214 isInterleaved = false;
215 else
216 {
218 return false;
219 }
220
221 enum { isFloatBit = 1 << 16, isLittleEndianBit = 1 << 17, onlyUseLower24Bits = 1 << 18 };
222
232 bitDepth = 0;
233
234 for (int i = 0; i < numElementsInArray (formatsToTry); i += 2)
235 {
237 {
238 const int type = formatsToTry [i + 1];
239 bitDepth = type & 255;
240
241 converter.reset (createConverter (isInput, bitDepth,
242 (type & isFloatBit) != 0,
243 (type & isLittleEndianBit) != 0,
244 (type & onlyUseLower24Bits) != 0,
245 numChannels,
246 isInterleaved));
247 break;
248 }
249 }
250
251 if (bitDepth == 0)
252 {
253 error = "device doesn't support a compatible PCM format";
254 JUCE_ALSA_LOG ("Error: " + error);
255 return false;
256 }
257
258 int dir = 0;
259 unsigned int periods = 4;
261
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)))
267 {
268 return false;
269 }
270
272
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)))
275 latency = 0;
276 else
277 latency = (int) frames * ((int) periods - 1); // (this is the method JACK uses to guess the latency..)
278
279 JUCE_ALSA_LOG ("frames: " << (int) frames << ", periods: " << (int) periods
280 << ", samplesPerPeriod: " << (int) samplesPerPeriod);
281
285
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))
291 || JUCE_ALSA_FAILED (snd_pcm_sw_params_set_stop_threshold (handle, swParams, boundary))
292 || JUCE_ALSA_FAILED (snd_pcm_sw_params (handle, swParams)))
293 {
294 return false;
295 }
296
297 #if JUCE_ALSA_LOGGING
298 // enable this to dump the config of the devices that get opened
299 snd_output_t* out;
300 snd_output_stdio_attach (&out, stderr, 0);
303 #endif
304
305 numChannelsRunning = numChannels;
306
307 return true;
308 }
309
310 //==============================================================================
311 bool writeToOutputDevice (AudioBuffer<float>& outputChannelBuffer, const int numSamples)
312 {
313 jassert (numChannelsRunning <= outputChannelBuffer.getNumChannels());
314 float* const* const data = outputChannelBuffer.getArrayOfWritePointers();
316
317 if (isInterleaved)
318 {
319 scratch.ensureSize ((size_t) ((int) sizeof (float) * numSamples * numChannelsRunning), false);
320
321 for (int i = 0; i < numChannelsRunning; ++i)
322 converter->convertSamples (scratch.getData(), i, data[i], 0, numSamples);
323
324 numDone = snd_pcm_writei (handle, scratch.getData(), (snd_pcm_uframes_t) numSamples);
325 }
326 else
327 {
328 for (int i = 0; i < numChannelsRunning; ++i)
329 converter->convertSamples (data[i], data[i], numSamples);
330
331 numDone = snd_pcm_writen (handle, (void**) data, (snd_pcm_uframes_t) numSamples);
332 }
333
334 if (numDone < 0)
335 {
336 if (numDone == -(EPIPE))
337 underrunCount++;
338
339 if (JUCE_ALSA_FAILED (snd_pcm_recover (handle, (int) numDone, 1 /* silent */)))
340 return false;
341 }
342
343 if (numDone < numSamples)
344 JUCE_ALSA_LOG ("Did not write all samples: numDone: " << numDone << ", numSamples: " << numSamples);
345
346 return true;
347 }
348
349 bool readFromInputDevice (AudioBuffer<float>& inputChannelBuffer, const int numSamples)
350 {
351 jassert (numChannelsRunning <= inputChannelBuffer.getNumChannels());
352 float* const* const data = inputChannelBuffer.getArrayOfWritePointers();
353
354 if (isInterleaved)
355 {
356 scratch.ensureSize ((size_t) ((int) sizeof (float) * numSamples * numChannelsRunning), false);
357 scratch.fillWith (0); // (not clearing this data causes warnings in valgrind)
358
359 auto num = snd_pcm_readi (handle, scratch.getData(), (snd_pcm_uframes_t) numSamples);
360
361 if (num < 0)
362 {
363 if (num == -(EPIPE))
364 overrunCount++;
365
366 if (JUCE_ALSA_FAILED (snd_pcm_recover (handle, (int) num, 1 /* silent */)))
367 return false;
368 }
369
370
371 if (num < numSamples)
372 JUCE_ALSA_LOG ("Did not read all samples: num: " << num << ", numSamples: " << numSamples);
373
374 for (int i = 0; i < numChannelsRunning; ++i)
375 converter->convertSamples (data[i], 0, scratch.getData(), i, numSamples);
376 }
377 else
378 {
379 auto num = snd_pcm_readn (handle, (void**) data, (snd_pcm_uframes_t) numSamples);
380
381 if (num < 0)
382 {
383 if (num == -(EPIPE))
384 overrunCount++;
385
386 if (JUCE_ALSA_FAILED (snd_pcm_recover (handle, (int) num, 1 /* silent */)))
387 return false;
388 }
389
390 if (num < numSamples)
391 JUCE_ALSA_LOG ("Did not read all samples: num: " << num << ", numSamples: " << numSamples);
392
393 for (int i = 0; i < numChannelsRunning; ++i)
394 converter->convertSamples (data[i], data[i], numSamples);
395 }
396
397 return true;
398 }
399
400 //==============================================================================
401 snd_pcm_t* handle;
402 String error;
403 int bitDepth, numChannelsRunning, latency;
404 int underrunCount = 0, overrunCount = 0;
405
406private:
407 //==============================================================================
408 String deviceID;
409 const bool isInput;
410 bool isInterleaved;
411 MemoryBlock scratch;
413
414 //==============================================================================
415 template <class SampleType>
416 struct ConverterHelper
417 {
418 static AudioData::Converter* createConverter (const bool forInput, const bool isLittleEndian, const int numInterleavedChannels, bool interleaved)
419 {
420 if (interleaved)
422
424 }
425
426 private:
427 template <class InterleavedType>
428 static AudioData::Converter* create (const bool forInput, const bool isLittleEndian, const int numInterleavedChannels)
429 {
430 if (forInput)
431 {
432 using DestType = AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::NonConst>;
433
434 if (isLittleEndian)
435 return new AudioData::ConverterInstance <AudioData::Pointer <SampleType, AudioData::LittleEndian, InterleavedType, AudioData::Const>, DestType> (numInterleavedChannels, 1);
436
437 return new AudioData::ConverterInstance <AudioData::Pointer <SampleType, AudioData::BigEndian, InterleavedType, AudioData::Const>, DestType> (numInterleavedChannels, 1);
438 }
439
440 using SourceType = AudioData::Pointer <AudioData::Float32, AudioData::NativeEndian, AudioData::NonInterleaved, AudioData::Const>;
441
442 if (isLittleEndian)
443 return new AudioData::ConverterInstance <SourceType, AudioData::Pointer <SampleType, AudioData::LittleEndian, InterleavedType, AudioData::NonConst>> (1, numInterleavedChannels);
444
445 return new AudioData::ConverterInstance <SourceType, AudioData::Pointer <SampleType, AudioData::BigEndian, InterleavedType, AudioData::NonConst>> (1, numInterleavedChannels);
446 }
447 };
448
449 static AudioData::Converter* createConverter (bool forInput, int bitDepth,
452 bool interleaved)
453 {
454 JUCE_ALSA_LOG ("format: bitDepth=" << bitDepth << ", isFloat=" << (int) isFloat
455 << ", isLittleEndian=" << (int) isLittleEndian << ", numChannels=" << numInterleavedChannels);
456
457 if (isFloat) return ConverterHelper <AudioData::Float32>::createConverter (forInput, isLittleEndian, numInterleavedChannels, interleaved);
460
461 jassert (bitDepth == 32);
462
464 return ConverterHelper <AudioData::Int24in32>::createConverter (forInput, isLittleEndian, numInterleavedChannels, interleaved);
465
466 return ConverterHelper <AudioData::Int32>::createConverter (forInput, isLittleEndian, numInterleavedChannels, interleaved);
467 }
468
469 //==============================================================================
470 bool failed (const int errorNum)
471 {
472 if (errorNum >= 0)
473 return false;
474
475 error = snd_strerror (errorNum);
476 JUCE_ALSA_LOG ("ALSA error: " << error);
477 return true;
478 }
479
481};
482
483//==============================================================================
484class ALSAThread final : public Thread
485{
486public:
487 ALSAThread (const String& inputDeviceID, const String& outputDeviceID)
488 : Thread ("JUCE ALSA"),
489 inputId (inputDeviceID),
490 outputId (outputDeviceID)
491 {
493 }
494
495 ~ALSAThread() override
496 {
497 close();
498 }
499
500 void open (BigInteger inputChannels,
501 BigInteger outputChannels,
502 double newSampleRate,
503 int newBufferSize)
504 {
505 close();
506
507 error.clear();
508 sampleRate = newSampleRate;
509 bufferSize = newBufferSize;
510
511 int maxInputsRequested = inputChannels.getHighestBit() + 1;
512 maxInputsRequested = jmax ((int) minChansIn, jmin ((int) maxChansIn, maxInputsRequested));
513
514 inputChannelBuffer.setSize (maxInputsRequested, bufferSize);
515 inputChannelBuffer.clear();
516 inputChannelDataForCallback.clear();
517 currentInputChans.clear();
518
519 if (inputChannels.getHighestBit() >= 0)
520 {
521 for (int i = 0; i < maxInputsRequested; ++i)
522 {
523 if (inputChannels[i])
524 {
525 inputChannelDataForCallback.add (inputChannelBuffer.getReadPointer (i));
526 currentInputChans.setBit (i);
527 }
528 }
529 }
530
531 ensureMinimumNumBitsSet (outputChannels, (int) minChansOut);
532
533 int maxOutputsRequested = outputChannels.getHighestBit() + 1;
534 maxOutputsRequested = jmax ((int) minChansOut, jmin ((int) maxChansOut, maxOutputsRequested));
535
536 outputChannelBuffer.setSize (maxOutputsRequested, bufferSize);
537 outputChannelBuffer.clear();
538 outputChannelDataForCallback.clear();
539 currentOutputChans.clear();
540
541 // Note that the input device is opened before an output, because we've heard
542 // of drivers where doing it in the reverse order mysteriously fails.. If this
543 // order also causes problems, let us know and we'll see if we can find a compromise!
544
545 if (inputChannelDataForCallback.size() > 0 && inputId.isNotEmpty())
546 {
547 inputDevice.reset (new ALSADevice (inputId, true));
548
549 if (inputDevice->error.isNotEmpty())
550 {
551 error = inputDevice->error;
552 inputDevice.reset();
553 return;
554 }
555
556 ensureMinimumNumBitsSet (currentInputChans, (int) minChansIn);
557
558 if (! inputDevice->setParameters ((unsigned int) sampleRate,
559 jlimit ((int) minChansIn, (int) maxChansIn, currentInputChans.getHighestBit() + 1),
560 bufferSize))
561 {
562 error = inputDevice->error;
563 inputDevice.reset();
564 return;
565 }
566
567 inputLatency = inputDevice->latency;
568 }
569
570 if (outputChannels.getHighestBit() >= 0)
571 {
572 for (int i = 0; i < maxOutputsRequested; ++i)
573 {
574 if (outputChannels[i])
575 {
576 outputChannelDataForCallback.add (outputChannelBuffer.getWritePointer (i));
577 currentOutputChans.setBit (i);
578 }
579 }
580 }
581
582 if (outputChannelDataForCallback.size() > 0 && outputId.isNotEmpty())
583 {
584 outputDevice.reset (new ALSADevice (outputId, false));
585
586 if (outputDevice->error.isNotEmpty())
587 {
588 error = outputDevice->error;
589 outputDevice.reset();
590 return;
591 }
592
593 if (! outputDevice->setParameters ((unsigned int) sampleRate,
594 jlimit ((int) minChansOut, (int) maxChansOut,
595 currentOutputChans.getHighestBit() + 1),
596 bufferSize))
597 {
598 error = outputDevice->error;
599 outputDevice.reset();
600 return;
601 }
602
603 outputLatency = outputDevice->latency;
604 }
605
606 if (outputDevice == nullptr && inputDevice == nullptr)
607 {
608 error = "no channels";
609 return;
610 }
611
612 if (outputDevice != nullptr && inputDevice != nullptr)
613 snd_pcm_link (outputDevice->handle, inputDevice->handle);
614
615 if (inputDevice != nullptr && JUCE_ALSA_FAILED (snd_pcm_prepare (inputDevice->handle)))
616 return;
617
618 if (outputDevice != nullptr && JUCE_ALSA_FAILED (snd_pcm_prepare (outputDevice->handle)))
619 return;
620
621 startThread (Priority::high);
622
623 int count = 1000;
624
625 while (numCallbacks == 0)
626 {
627 sleep (5);
628
629 if (--count < 0 || ! isThreadRunning())
630 {
631 error = "device didn't start";
632 break;
633 }
634 }
635 }
636
637 void close()
638 {
639 if (isThreadRunning())
640 {
641 // problem: when pulseaudio is suspended (with pasuspend) , the ALSAThread::run is just stuck in
642 // snd_pcm_writei -- no error, no nothing it just stays stuck. So the only way I found to exit "nicely"
643 // (that is without the "killing thread by force" of stopThread) , is to just call snd_pcm_close from
644 // here which will cause the thread to resume, and exit
645 signalThreadShouldExit();
646
647 const int callbacksToStop = numCallbacks;
648
649 if ((! waitForThreadToExit (400)) && audioIoInProgress && numCallbacks == callbacksToStop)
650 {
651 JUCE_ALSA_LOG ("Thread is stuck in i/o.. Is pulseaudio suspended?");
652
653 if (outputDevice != nullptr) outputDevice->closeNow();
654 if (inputDevice != nullptr) inputDevice->closeNow();
655 }
656 }
657
658 stopThread (6000);
659
660 inputDevice.reset();
661 outputDevice.reset();
662
663 inputChannelBuffer.setSize (1, 1);
664 outputChannelBuffer.setSize (1, 1);
665
666 numCallbacks = 0;
667 }
668
669 void setCallback (AudioIODeviceCallback* const newCallback) noexcept
670 {
671 const ScopedLock sl (callbackLock);
672 callback = newCallback;
673 }
674
675 void run() override
676 {
677 while (! threadShouldExit())
678 {
679 if (inputDevice != nullptr && inputDevice->handle != nullptr)
680 {
681 if (outputDevice == nullptr || outputDevice->handle == nullptr)
682 {
683 JUCE_ALSA_FAILED (snd_pcm_wait (inputDevice->handle, 2000));
684
685 if (threadShouldExit())
686 break;
687
688 auto avail = snd_pcm_avail_update (inputDevice->handle);
689
690 if (avail < 0)
691 JUCE_ALSA_FAILED (snd_pcm_recover (inputDevice->handle, (int) avail, 0));
692 }
693
694 audioIoInProgress = true;
695
696 if (! inputDevice->readFromInputDevice (inputChannelBuffer, bufferSize))
697 {
698 JUCE_ALSA_LOG ("Read failure");
699 break;
700 }
701
702 audioIoInProgress = false;
703 }
704
705 if (threadShouldExit())
706 break;
707
708 {
709 const ScopedLock sl (callbackLock);
710 ++numCallbacks;
711
712 if (callback != nullptr)
713 {
714 callback->audioDeviceIOCallbackWithContext (inputChannelDataForCallback.getRawDataPointer(),
715 inputChannelDataForCallback.size(),
716 outputChannelDataForCallback.getRawDataPointer(),
717 outputChannelDataForCallback.size(),
718 bufferSize,
719 {});
720 }
721 else
722 {
723 for (int i = 0; i < outputChannelDataForCallback.size(); ++i)
724 zeromem (outputChannelDataForCallback[i], (size_t) bufferSize * sizeof (float));
725 }
726 }
727
728 if (outputDevice != nullptr && outputDevice->handle != nullptr)
729 {
730 JUCE_ALSA_FAILED (snd_pcm_wait (outputDevice->handle, 2000));
731
732 if (threadShouldExit())
733 break;
734
735 auto avail = snd_pcm_avail_update (outputDevice->handle);
736
737 if (avail < 0)
738 JUCE_ALSA_FAILED (snd_pcm_recover (outputDevice->handle, (int) avail, 0));
739
740 audioIoInProgress = true;
741
742 if (! outputDevice->writeToOutputDevice (outputChannelBuffer, bufferSize))
743 {
744 JUCE_ALSA_LOG ("write failure");
745 break;
746 }
747
748 audioIoInProgress = false;
749 }
750 }
751
752 audioIoInProgress = false;
753 }
754
755 int getBitDepth() const noexcept
756 {
757 if (outputDevice != nullptr)
758 return outputDevice->bitDepth;
759
760 if (inputDevice != nullptr)
761 return inputDevice->bitDepth;
762
763 return 16;
764 }
765
766 int getXRunCount() const noexcept
767 {
768 int result = 0;
769
770 if (outputDevice != nullptr)
771 result += outputDevice->underrunCount;
772
773 if (inputDevice != nullptr)
774 result += inputDevice->overrunCount;
775
776 return result;
777 }
778
779 //==============================================================================
780 String error;
781 double sampleRate = 0;
782 int bufferSize = 0, outputLatency = 0, inputLatency = 0;
783 BigInteger currentInputChans, currentOutputChans;
784
785 Array<double> sampleRates;
786 StringArray channelNamesOut, channelNamesIn;
787 AudioIODeviceCallback* callback = nullptr;
788
789private:
790 //==============================================================================
791 const String inputId, outputId;
792 std::unique_ptr<ALSADevice> outputDevice, inputDevice;
793 std::atomic<int> numCallbacks { 0 };
794 std::atomic<bool> audioIoInProgress { false };
795
796 CriticalSection callbackLock;
797
798 AudioBuffer<float> inputChannelBuffer, outputChannelBuffer;
799 Array<const float*> inputChannelDataForCallback;
800 Array<float*> outputChannelDataForCallback;
801
802 unsigned int minChansOut = 0, maxChansOut = 0;
803 unsigned int minChansIn = 0, maxChansIn = 0;
804
805 bool failed (const int errorNum)
806 {
807 if (errorNum >= 0)
808 return false;
809
810 error = snd_strerror (errorNum);
811 JUCE_ALSA_LOG ("ALSA error: " << error);
812 return true;
813 }
814
816 {
817 sampleRates.clear();
818 channelNamesOut.clear();
819 channelNamesIn.clear();
820 minChansOut = 0;
821 maxChansOut = 0;
822 minChansIn = 0;
823 maxChansIn = 0;
824 unsigned int dummy = 0;
825
826 getDeviceProperties (inputId, dummy, dummy, minChansIn, maxChansIn, sampleRates, false, true);
827 getDeviceProperties (outputId, minChansOut, maxChansOut, dummy, dummy, sampleRates, true, false);
828
829 for (unsigned int i = 0; i < maxChansOut; ++i)
830 channelNamesOut.add ("channel " + String ((int) i + 1));
831
832 for (unsigned int i = 0; i < maxChansIn; ++i)
833 channelNamesIn.add ("channel " + String ((int) i + 1));
834 }
835
837};
838
839
840//==============================================================================
841class ALSAAudioIODevice final : public AudioIODevice
842{
843public:
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),
852 {
853 }
854
855 ~ALSAAudioIODevice() override
856 {
857 close();
858 }
859
860 StringArray getOutputChannelNames() override { return internal.channelNamesOut; }
861 StringArray getInputChannelNames() override { return internal.channelNamesIn; }
862
863 Array<double> getAvailableSampleRates() override { return internal.sampleRates; }
864
865 Array<int> getAvailableBufferSizes() override
866 {
867 Array<int> r;
868 int n = 16;
869
870 for (int i = 0; i < 50; ++i)
871 {
872 r.add (n);
873 n += n < 64 ? 16
874 : (n < 512 ? 32
875 : (n < 1024 ? 64
876 : (n < 2048 ? 128 : 256)));
877 }
878
879 return r;
880 }
881
882 int getDefaultBufferSize() override { return 512; }
883
884 String open (const BigInteger& inputChannels,
885 const BigInteger& outputChannels,
886 double sampleRate,
887 int bufferSizeSamples) override
888 {
889 close();
890
891 if (bufferSizeSamples <= 0)
892 bufferSizeSamples = getDefaultBufferSize();
893
894 if (sampleRate <= 0)
895 {
896 for (int i = 0; i < internal.sampleRates.size(); ++i)
897 {
898 double rate = internal.sampleRates[i];
899
900 if (rate >= 44100)
901 {
902 sampleRate = rate;
903 break;
904 }
905 }
906 }
907
908 internal.open (inputChannels, outputChannels,
909 sampleRate, bufferSizeSamples);
910
911 isOpen_ = internal.error.isEmpty();
912 return internal.error;
913 }
914
915 void close() override
916 {
917 stop();
918 internal.close();
919 isOpen_ = false;
920 }
921
922 bool isOpen() override { return isOpen_; }
923 bool isPlaying() override { return isStarted && internal.error.isEmpty(); }
924 String getLastError() override { return internal.error; }
925
926 int getCurrentBufferSizeSamples() override { return internal.bufferSize; }
927 double getCurrentSampleRate() override { return internal.sampleRate; }
928 int getCurrentBitDepth() override { return internal.getBitDepth(); }
929
930 BigInteger getActiveOutputChannels() const override { return internal.currentOutputChans; }
931 BigInteger getActiveInputChannels() const override { return internal.currentInputChans; }
932
933 int getOutputLatencyInSamples() override { return internal.outputLatency; }
934 int getInputLatencyInSamples() override { return internal.inputLatency; }
935
936 int getXRunCount() const noexcept override { return internal.getXRunCount(); }
937
938 void start (AudioIODeviceCallback* callback) override
939 {
940 if (! isOpen_)
941 callback = nullptr;
942
943 if (callback != nullptr)
944 callback->audioDeviceAboutToStart (this);
945
946 internal.setCallback (callback);
947
948 isStarted = (callback != nullptr);
949 }
950
951 void stop() override
952 {
953 auto oldCallback = internal.callback;
954
955 start (nullptr);
956
957 if (oldCallback != nullptr)
958 oldCallback->audioDeviceStopped();
959 }
960
961 String inputId, outputId;
962
963private:
964 bool isOpen_ = false, isStarted = false;
965 ALSAThread internal;
966};
967
968
969//==============================================================================
970class ALSAAudioIODeviceType final : public AudioIODeviceType
971{
972public:
973 ALSAAudioIODeviceType (bool onlySoundcards, const String& deviceTypeName)
974 : AudioIODeviceType (deviceTypeName),
975 listOnlySoundcards (onlySoundcards)
976 {
977 #if ! JUCE_ALSA_LOGGING
979 #endif
980 }
981
982 ~ALSAAudioIODeviceType() override
983 {
984 #if ! JUCE_ALSA_LOGGING
986 #endif
987
988 snd_config_update_free_global(); // prevent valgrind from screaming about alsa leaks
989 }
990
991 //==============================================================================
992 void scanForDevices() override
993 {
994 if (hasScanned)
995 return;
996
997 hasScanned = true;
998 inputNames.clear();
999 inputIds.clear();
1000 outputNames.clear();
1001 outputIds.clear();
1002
1003 JUCE_ALSA_LOG ("scanForDevices()");
1004
1005 if (listOnlySoundcards)
1007 else
1009
1010 inputNames.appendNumbersToDuplicates (false, true);
1011 outputNames.appendNumbersToDuplicates (false, true);
1012 }
1013
1014 StringArray getDeviceNames (bool wantInputNames) const override
1015 {
1016 jassert (hasScanned); // need to call scanForDevices() before doing this
1017
1018 return wantInputNames ? inputNames : outputNames;
1019 }
1020
1021 int getDefaultDeviceIndex (bool forInput) const override
1022 {
1023 jassert (hasScanned); // need to call scanForDevices() before doing this
1024
1025 auto idx = (forInput ? inputIds : outputIds).indexOf ("default");
1026 return idx >= 0 ? idx : 0;
1027 }
1028
1029 bool hasSeparateInputsAndOutputs() const override { return true; }
1030
1031 int getIndexOfDevice (AudioIODevice* device, bool asInput) const override
1032 {
1033 jassert (hasScanned); // need to call scanForDevices() before doing this
1034
1035 if (auto* d = dynamic_cast<ALSAAudioIODevice*> (device))
1036 return asInput ? inputIds.indexOf (d->inputId)
1037 : outputIds.indexOf (d->outputId);
1038
1039 return -1;
1040 }
1041
1042 AudioIODevice* createDevice (const String& outputDeviceName,
1043 const String& inputDeviceName) override
1044 {
1045 jassert (hasScanned); // need to call scanForDevices() before doing this
1046
1047 auto inputIndex = inputNames.indexOf (inputDeviceName);
1048 auto outputIndex = outputNames.indexOf (outputDeviceName);
1049
1050 String deviceName (outputIndex >= 0 ? outputDeviceName
1051 : inputDeviceName);
1052
1053 if (inputIndex >= 0 || outputIndex >= 0)
1054 return new ALSAAudioIODevice (deviceName, getTypeName(),
1055 inputIds [inputIndex],
1056 outputIds [outputIndex]);
1057
1058 return nullptr;
1059 }
1060
1061private:
1062 //==============================================================================
1063 StringArray inputNames, outputNames, inputIds, outputIds;
1064 bool hasScanned = false;
1065 const bool listOnlySoundcards;
1066
1067 bool testDevice (const String& id, const String& outputName, const String& inputName)
1068 {
1069 unsigned int minChansOut = 0, maxChansOut = 0;
1070 unsigned int minChansIn = 0, maxChansIn = 0;
1071 Array<double> rates;
1072
1073 bool isInput = inputName.isNotEmpty(), isOutput = outputName.isNotEmpty();
1074 getDeviceProperties (id, minChansOut, maxChansOut, minChansIn, maxChansIn, rates, isOutput, isInput);
1075
1076 isInput = maxChansIn > 0;
1077 isOutput = maxChansOut > 0;
1078
1079 if ((isInput || isOutput) && rates.size() > 0)
1080 {
1081 JUCE_ALSA_LOG ("testDevice: '" << id.toUTF8().getAddress() << "' -> isInput: "
1082 << (int) isInput << ", isOutput: " << (int) isOutput);
1083
1084 if (isInput)
1085 {
1086 inputNames.add (inputName);
1087 inputIds.add (id);
1088 }
1089
1090 if (isOutput)
1091 {
1092 outputNames.add (outputName);
1093 outputIds.add (id);
1094 }
1095
1096 return isInput || isOutput;
1097 }
1098
1099 return false;
1100 }
1101
1103 {
1104 snd_ctl_t* handle = nullptr;
1105 snd_ctl_card_info_t* info = nullptr;
1107
1108 int cardNum = -1;
1109
1110 while (outputIds.size() + inputIds.size() <= 64)
1111 {
1113
1114 if (cardNum < 0)
1115 break;
1116
1117 if (JUCE_CHECKED_RESULT (snd_ctl_open (&handle, ("hw:" + String (cardNum)).toRawUTF8(), SND_CTL_NONBLOCK)) >= 0)
1118 {
1119 if (JUCE_CHECKED_RESULT (snd_ctl_card_info (handle, info)) >= 0)
1120 {
1121 String cardId (snd_ctl_card_info_get_id (info));
1122
1123 if (cardId.removeCharacters ("0123456789").isEmpty())
1124 cardId = String (cardNum);
1125
1126 String cardName = snd_ctl_card_info_get_name (info);
1127
1128 if (cardName.isEmpty())
1129 cardName = cardId;
1130
1131 int device = -1;
1132
1135
1136 for (;;)
1137 {
1138 if (snd_ctl_pcm_next_device (handle, &device) < 0 || device < 0)
1139 break;
1140
1141 snd_pcm_info_set_device (pcmInfo, (unsigned int) device);
1142
1143 for (unsigned int subDevice = 0, nbSubDevice = 1; subDevice < nbSubDevice; ++subDevice)
1144 {
1147 const bool isInput = (snd_ctl_pcm_info (handle, pcmInfo) >= 0);
1148
1150 const bool isOutput = (snd_ctl_pcm_info (handle, pcmInfo) >= 0);
1151
1152 if (! (isInput || isOutput))
1153 continue;
1154
1155 if (nbSubDevice == 1)
1157
1158 String id, name;
1159
1160 if (nbSubDevice == 1)
1161 {
1162 id << "hw:" << cardId << "," << device;
1163 name << cardName << ", " << snd_pcm_info_get_name (pcmInfo);
1164 }
1165 else
1166 {
1167 id << "hw:" << cardId << "," << device << "," << (int) subDevice;
1168 name << cardName << ", " << snd_pcm_info_get_name (pcmInfo)
1169 << " {" << snd_pcm_info_get_subdevice_name (pcmInfo) << "}";
1170 }
1171
1172 JUCE_ALSA_LOG ("Soundcard ID: " << id << ", name: '" << name
1173 << ", isInput:" << (int) isInput
1174 << ", isOutput:" << (int) isOutput << "\n");
1175
1176 if (isInput)
1177 {
1178 inputNames.add (name);
1179 inputIds.add (id);
1180 }
1181
1182 if (isOutput)
1183 {
1184 outputNames.add (name);
1185 outputIds.add (id);
1186 }
1187 }
1188 }
1189 }
1190
1191 JUCE_CHECKED_RESULT (snd_ctl_close (handle));
1192 }
1193 }
1194 }
1195
1196 /* Enumerates all ALSA output devices (as output by the command aplay -L)
1197 Does not try to open the devices (with "testDevice" for example),
1198 so that it also finds devices that are busy and not yet available.
1199 */
1201 {
1202 void** hints = nullptr;
1203
1204 if (JUCE_CHECKED_RESULT (snd_device_name_hint (-1, "pcm", &hints)) == 0)
1205 {
1206 for (char** h = (char**) hints; *h; ++h)
1207 {
1208 const String id (hintToString (*h, "NAME"));
1209 const String description (hintToString (*h, "DESC"));
1210 const String ioid (hintToString (*h, "IOID"));
1211
1212 JUCE_ALSA_LOG ("ID: " << id << "; desc: " << description << "; ioid: " << ioid);
1213
1214 String ss = id.fromFirstOccurrenceOf ("=", false, false)
1215 .upToFirstOccurrenceOf (",", false, false);
1216
1217 if (id.isEmpty()
1218 || id.startsWith ("default:") || id.startsWith ("sysdefault:")
1219 || id.startsWith ("plughw:") || id == "null")
1220 continue;
1221
1222 String name (description.replace ("\n", "; "));
1223
1224 if (name.isEmpty())
1225 name = id;
1226
1227 bool isOutput = (ioid != "Input");
1228 bool isInput = (ioid != "Output");
1229
1230 // alsa is stupid here, it advertises dmix and dsnoop as input/output devices, but
1231 // opening dmix as input, or dsnoop as output will trigger errors..
1232 isInput = isInput && ! id.startsWith ("dmix");
1233 isOutput = isOutput && ! id.startsWith ("dsnoop");
1234
1235 if (isInput)
1236 {
1237 inputNames.add (name);
1238 inputIds.add (id);
1239 }
1240
1241 if (isOutput)
1242 {
1243 outputNames.add (name);
1244 outputIds.add (id);
1245 }
1246 }
1247
1249 }
1250
1251 // sometimes the "default" device is not listed, but it is nice to see it explicitly in the list
1252 if (! outputIds.contains ("default"))
1253 testDevice ("default", "Default ALSA Output", "Default ALSA Input");
1254
1255 // same for the pulseaudio plugin
1256 if (! outputIds.contains ("pulse"))
1257 testDevice ("pulse", "Pulseaudio output", "Pulseaudio input");
1258
1259 // make sure the default device is listed first, and followed by the pulse device (if present)
1260 auto idx = outputIds.indexOf ("pulse");
1261 outputIds.move (idx, 0);
1262 outputNames.move (idx, 0);
1263
1264 idx = inputIds.indexOf ("pulse");
1265 inputIds.move (idx, 0);
1266 inputNames.move (idx, 0);
1267
1268 idx = outputIds.indexOf ("default");
1269 outputIds.move (idx, 0);
1270 outputNames.move (idx, 0);
1271
1272 idx = inputIds.indexOf ("default");
1273 inputIds.move (idx, 0);
1274 inputNames.move (idx, 0);
1275 }
1276
1277 static String hintToString (const void* hints, const char* type)
1278 {
1279 char* hint = snd_device_name_get_hint (hints, type);
1280 auto s = String::fromUTF8 (hint);
1281 ::free (hint);
1282 return s;
1283 }
1284
1285 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ALSAAudioIODeviceType)
1286};
1287
1288}
1289
1290//==============================================================================
1291static inline AudioIODeviceType* createAudioIODeviceType_ALSA_Soundcards()
1292{
1293 return new ALSAAudioIODeviceType (true, "ALSA HW");
1294}
1295
1296static inline AudioIODeviceType* createAudioIODeviceType_ALSA_PCMDevices()
1297{
1298 return new ALSAAudioIODeviceType (false, "ALSA");
1299}
1300
1301} // namespace juce
static String fromUTF8(const char *utf8buffer, int bufferSizeBytes=-1)
Creates a String from a UTF-8 encoded buffer.
close
T count(T... args)
T data(T... args)
#define jassert(expression)
Platform-independent assertion macro.
#define JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(className)
This is a shorthand way of writing both a JUCE_DECLARE_NON_COPYABLE and JUCE_LEAK_DETECTOR macro for ...
#define jassertfalse
This will always cause an assertion failure.
T internal(T... args)
typedef int
JUCE Namespace.
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.
Type unalignedPointerCast(void *ptr) noexcept
Casts a pointer to another type via void*, which suppresses the cast-align warning which sometimes ar...
Definition juce_Memory.h:88
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.
Definition juce_Memory.h:28
open
sleep