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