1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
2 /*
3     bqaudioio
4 
5     Copyright 2007-2016 Particular Programs Ltd.
6 
7     Permission is hereby granted, free of charge, to any person
8     obtaining a copy of this software and associated documentation
9     files (the "Software"), to deal in the Software without
10     restriction, including without limitation the rights to use, copy,
11     modify, merge, publish, distribute, sublicense, and/or sell copies
12     of the Software, and to permit persons to whom the Software is
13     furnished to do so, subject to the following conditions:
14 
15     The above copyright notice and this permission notice shall be
16     included in all copies or substantial portions of the Software.
17 
18     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
19     EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
20     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
22     ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
23     CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
24     WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 
26     Except as contained in this notice, the names of Chris Cannam and
27     Particular Programs Ltd shall not be used in advertising or
28     otherwise to promote the sale, use or other dealings in this
29     Software without prior written authorization.
30 */
31 
32 #ifdef HAVE_PORTAUDIO
33 
34 #include "PortAudioIO.h"
35 #include "ApplicationPlaybackSource.h"
36 #include "ApplicationRecordTarget.h"
37 #include "Gains.h"
38 #include "Log.h"
39 
40 #include "bqvec/VectorOps.h"
41 #include "bqvec/Allocators.h"
42 
43 #include <iostream>
44 #include <sstream>
45 #include <cassert>
46 #include <cmath>
47 #include <climits>
48 #include <algorithm>
49 
50 #include <mutex>
51 
52 #ifndef __LINUX__
53 #ifndef _WIN32
54 #include <pthread.h>
55 #endif
56 #endif
57 
58 //#define DEBUG_AUDIO_PORT_AUDIO_IO 1
59 
60 using namespace std;
61 
62 namespace breakfastquay {
63 
log(string message)64 static void log(string message) {
65     Log::log("PortAudioIO: " + message);
66 }
67 
68 #ifdef __LINUX__
69 extern "C" {
70 void
71 PaAlsa_EnableRealtimeScheduling(PaStream *, int);
72 }
73 #endif
74 
75 static bool // true if "can attempt on this platform", not "succeeded"
enableRT(PaStream * stream)76 enableRT(PaStream *
77 #ifdef __LINUX__
78          stream
79 #endif
80     ) {
81 #ifdef __LINUX__
82     // This will link only if the PA ALSA host API is linked statically
83     PaAlsa_EnableRealtimeScheduling(stream, 1);
84     return true;
85 #else
86     return false;
87 #endif
88 }
89 
90 static bool // true if "can attempt on this platform", not "succeeded"
enableRT()91 enableRT() { // on current thread
92 #ifndef __LINUX__
93 #ifndef _WIN32
94     sched_param param;
95     param.sched_priority = 20;
96     if (pthread_setschedparam(pthread_self(), SCHED_RR, &param)) {
97         log("NOTE: couldn't set RT scheduling class");
98     } else {
99         log("NOTE: successfully set RT scheduling class");
100     }
101     return true;
102 #endif
103 #endif
104     return false;
105 }
106 
107 static bool paio_initialised = false;
108 static bool paio_working = false;
109 static mutex paio_init_mutex;
110 
initialise()111 static bool initialise() {
112 
113     lock_guard<mutex> guard(paio_init_mutex);
114 
115     if (!paio_initialised) {
116         PaError err = Pa_Initialize();
117         paio_initialised = true;
118         if (err != paNoError) {
119             log("ERROR: Failed to initialize PortAudio");
120             paio_working = false;
121         } else {
122             paio_working = true;
123         }
124     }
125 
126     return paio_working;
127 }
128 
deinitialise()129 static void deinitialise() {
130 
131     lock_guard<mutex> guard(paio_init_mutex);
132 
133     if (paio_initialised && paio_working) {
134 	Pa_Terminate();
135         paio_initialised = false;
136     }
137 }
138 
139 static
140 vector<string>
getDeviceNames(bool record)141 getDeviceNames(bool record)
142 {
143     if (!initialise()) {
144         return {};
145     }
146 
147     vector<string> names;
148     PaDeviceIndex count = Pa_GetDeviceCount();
149 
150     if (count < 0) {
151         // error
152         log(string("error in retrieving device list: ") + Pa_GetErrorText(count));
153         return names;
154     } else {
155         ostringstream os;
156         os << "have " << count << " device(s)";
157         log(os.str());
158     }
159 
160     for (int i = 0; i < count; ++i) {
161 
162         const PaDeviceInfo *info = Pa_GetDeviceInfo(i);
163 
164         ostringstream os;
165         os << "device " << i << " of " << count << ":" << endl;
166         os << "name = \"" << info->name << "\"" << endl;
167         os << "maxInputChannels = " << info->maxInputChannels << endl;
168         os << "maxOutputChannels = " << info->maxOutputChannels << endl;
169         os << "defaultSampleRate = " << info->defaultSampleRate;
170         log(os.str());
171 
172         if (record) {
173             if (info->maxInputChannels > 0) {
174                 names.push_back(info->name);
175             }
176         } else {
177             if (info->maxOutputChannels > 0) {
178                 names.push_back(info->name);
179             }
180         }
181     }
182     return names;
183 }
184 
185 static
186 PaDeviceIndex
getDeviceIndex(string name,bool record)187 getDeviceIndex(string name, bool record)
188 {
189     {
190         ostringstream os;
191         os << "getDeviceIndex: name = \"" << name << "\", record = " << record;
192         log(os.str());
193     }
194 
195     if (name != "") {
196         PaDeviceIndex count = Pa_GetDeviceCount();
197         if (count < 0) {
198             log(string("error in retrieving device index: ") + Pa_GetErrorText(count));
199         }
200         for (int i = 0; i < count; ++i) {
201             const PaDeviceInfo *info = Pa_GetDeviceInfo(i);
202             if (record) {
203                 if (info->maxInputChannels > 0) {
204                     if (name == info->name) {
205                         return i;
206                     }
207                 }
208             } else {
209                 if (info->maxOutputChannels > 0) {
210                     if (name == info->name) {
211                         return i;
212                     }
213                 }
214             }
215         }
216     }
217 
218     // no name supplied, or no match in device list
219     if (record) {
220         return Pa_GetDefaultInputDevice();
221     } else {
222         return Pa_GetDefaultOutputDevice();
223     }
224 }
225 
226 vector<string>
getRecordDeviceNames()227 PortAudioIO::getRecordDeviceNames()
228 {
229     return getDeviceNames(true);
230 }
231 
232 vector<string>
getPlaybackDeviceNames()233 PortAudioIO::getPlaybackDeviceNames()
234 {
235     return getDeviceNames(false);
236 }
237 
PortAudioIO(Mode mode,ApplicationRecordTarget * target,ApplicationPlaybackSource * source,string recordDevice,string playbackDevice)238 PortAudioIO::PortAudioIO(Mode mode,
239                          ApplicationRecordTarget *target,
240                          ApplicationPlaybackSource *source,
241                          string recordDevice,
242                          string playbackDevice) :
243     SystemAudioIO(target, source),
244     m_stream(0),
245     m_mode(mode),
246     m_bufferSize(0),
247     m_sampleRate(0),
248     m_inputLatency(0),
249     m_outputLatency(0),
250     m_prioritySet(false),
251     m_suspended(false),
252     m_buffers(0),
253     m_bufferChannels(0)
254 {
255     log("starting");
256 
257     if (!initialise()) return;
258 
259     // solely to debug-log the list of devices, so both arg
260     // and return value are irrelevant here:
261     (void)getDeviceNames(false);
262 
263     if (m_mode == Mode::Playback) {
264         m_target = 0;
265     }
266     if (m_mode == Mode::Record) {
267         m_source = 0;
268     }
269 
270     PaError err;
271 
272     m_bufferSize = 0;
273 
274     PaStreamParameters ip, op;
275     ip.device = getDeviceIndex(recordDevice, true);
276     op.device = getDeviceIndex(playbackDevice, false);
277 
278     {
279         ostringstream os;
280         os << "Obtained playback device index " << op.device
281            << " and record device index " << ip.device;
282         log(os.str());
283     }
284 
285     const PaDeviceInfo *inInfo = Pa_GetDeviceInfo(ip.device);
286     const PaDeviceInfo *outInfo = Pa_GetDeviceInfo(op.device);
287     if (outInfo) {
288         m_sampleRate = outInfo->defaultSampleRate;
289     }
290 
291     m_sourceChannels = 2;
292     m_targetChannels = 2;
293 
294     int sourceRate = 0;
295     int targetRate = 0;
296 
297     if (m_source) {
298         sourceRate = m_source->getApplicationSampleRate();
299         if (sourceRate != 0) {
300             m_sampleRate = sourceRate;
301         }
302         if (m_source->getApplicationChannelCount() != 0) {
303             m_sourceChannels = m_source->getApplicationChannelCount();
304         }
305     }
306     if (m_target) {
307         targetRate = m_target->getApplicationSampleRate();
308         if (targetRate != 0) {
309             if (sourceRate != 0 && sourceRate != targetRate) {
310                 ostringstream os;
311                 os << "WARNING: Source and target both provide sample rates, but different ones (source " << sourceRate << ", target " << targetRate << ") - using source rate";
312             } else {
313                 m_sampleRate = targetRate;
314             }
315         }
316         if (m_target->getApplicationChannelCount() != 0) {
317             m_targetChannels = m_target->getApplicationChannelCount();
318         }
319     }
320     if (m_sampleRate == 0) {
321         m_sampleRate = 44100;
322     }
323 
324     m_inputChannels = m_targetChannels;
325     m_outputChannels = m_sourceChannels;
326 
327     if (inInfo &&
328         m_inputChannels > inInfo->maxInputChannels &&
329         inInfo->maxInputChannels > 0) {
330         m_inputChannels = inInfo->maxInputChannels;
331     }
332 
333     if (outInfo &&
334         m_outputChannels > outInfo->maxOutputChannels &&
335         outInfo->maxOutputChannels > 0) {
336         m_outputChannels = outInfo->maxOutputChannels;
337     }
338 
339     ip.channelCount = m_inputChannels;
340     op.channelCount = m_outputChannels;
341     ip.sampleFormat = paFloat32;
342     op.sampleFormat = paFloat32;
343     ip.suggestedLatency = 0.2;
344     op.suggestedLatency = 0.2;
345     ip.hostApiSpecificStreamInfo = 0;
346     op.hostApiSpecificStreamInfo = 0;
347 
348     m_bufferSize = 0;
349     err = openStream(m_mode, &m_stream, &ip, &op, m_sampleRate,
350                      paFramesPerBufferUnspecified, this);
351 
352     if (err != paNoError) {
353 	m_bufferSize = 1024;
354         err = openStream(m_mode, &m_stream, &ip, &op, m_sampleRate,
355                          1024, this);
356     }
357 
358     if (err != paNoError) {
359         if (m_inputChannels != 2 || m_outputChannels != 2) {
360 
361             log(string("WARNING: Failed to open PortAudio stream: ") +
362                 Pa_GetErrorText(err) + ": trying again with 2x2 configuration");
363 
364             m_inputChannels = 2;
365             m_outputChannels = 2;
366             ip.channelCount = m_inputChannels;
367             op.channelCount = m_outputChannels;
368 
369             m_bufferSize = 0;
370             err = openStream(m_mode, &m_stream, &ip, &op, m_sampleRate,
371                              paFramesPerBufferUnspecified, this);
372 
373             m_bufferSize = 1024;
374             if (err != paNoError) {
375                 err = openStream(m_mode, &m_stream, &ip, &op, m_sampleRate,
376                                  1024, this);
377             }
378         }
379     }
380 
381     if (err != paNoError) {
382         m_startupError = "Failed to open PortAudio stream: ";
383         m_startupError += Pa_GetErrorText(err);
384 	log("ERROR: " + m_startupError);
385 	m_stream = 0;
386         deinitialise();
387 	return;
388     }
389 
390     const PaStreamInfo *info = Pa_GetStreamInfo(m_stream);
391     m_outputLatency = int(info->outputLatency * m_sampleRate + 0.001);
392     m_inputLatency = int(info->inputLatency * m_sampleRate + 0.001);
393     if (m_bufferSize == 0) m_bufferSize = m_outputLatency;
394     if (m_bufferSize == 0) m_bufferSize = m_inputLatency;
395 
396     if (enableRT(m_stream)) {
397         m_prioritySet = true;
398     }
399 
400     {
401         ostringstream os;
402         os << "block size " << m_bufferSize;
403         log(os.str());
404     }
405 
406     if (m_source) {
407 	m_source->setSystemPlaybackBlockSize(m_bufferSize);
408 	m_source->setSystemPlaybackSampleRate(int(round(m_sampleRate)));
409 	m_source->setSystemPlaybackLatency(m_outputLatency);
410         m_source->setSystemPlaybackChannelCount(m_outputChannels);
411     }
412 
413     if (m_target) {
414 	m_target->setSystemRecordBlockSize(m_bufferSize);
415 	m_target->setSystemRecordSampleRate(int(round(m_sampleRate)));
416 	m_target->setSystemRecordLatency(m_inputLatency);
417         m_target->setSystemRecordChannelCount(m_inputChannels);
418     }
419 
420     m_bufferChannels = std::max(std::max(m_sourceChannels, m_targetChannels),
421                                 std::max(m_inputChannels, m_outputChannels));
422     m_buffers = allocate_and_zero_channels<float>(m_bufferChannels, m_bufferSize);
423 
424     err = Pa_StartStream(m_stream);
425 
426     if (err != paNoError) {
427 	m_startupError = "Failed to start PortAudio stream: ";
428         m_startupError += Pa_GetErrorText(err);
429         log("ERROR: " + m_startupError);
430 	Pa_CloseStream(m_stream);
431 	m_stream = 0;
432         deinitialise();
433 	return;
434     }
435 
436     log("started successfully");
437 }
438 
~PortAudioIO()439 PortAudioIO::~PortAudioIO()
440 {
441     if (m_stream) {
442 	PaError err;
443         if (!m_suspended) {
444             err = Pa_StopStream(m_stream);
445             if (err != paNoError) {
446                 log("ERROR: Failed to stop PortAudio stream");
447                 err = Pa_AbortStream(m_stream);
448                 if (err != paNoError) {
449                     log("ERROR: Failed to abort PortAudio stream");
450                 }
451             }
452 	}
453 	err = Pa_CloseStream(m_stream);
454 	if (err != paNoError) {
455 	    log("ERROR: Failed to close PortAudio stream");
456 	}
457         deallocate_channels(m_buffers, m_bufferChannels);
458         deinitialise();
459         log("closed");
460     }
461 }
462 
463 PaError
openStream(Mode mode,PaStream ** stream,const PaStreamParameters * inputParameters,const PaStreamParameters * outputParameters,double sampleRate,unsigned long framesPerBuffer,void * data)464 PortAudioIO::openStream(Mode mode,
465                         PaStream **stream,
466                         const PaStreamParameters *inputParameters,
467                         const PaStreamParameters *outputParameters,
468                         double sampleRate,
469                         unsigned long framesPerBuffer,
470                         void *data)
471 {
472     switch (mode) {
473     case Mode::Playback:
474         return Pa_OpenStream(stream, 0, outputParameters, sampleRate,
475                              framesPerBuffer, paNoFlag, processStatic, data);
476     case Mode::Record:
477         return Pa_OpenStream(stream, inputParameters, 0, sampleRate,
478                              framesPerBuffer, paNoFlag, processStatic, data);
479     case Mode::Duplex:
480         return Pa_OpenStream(stream, inputParameters, outputParameters,
481                              sampleRate,
482                              framesPerBuffer, paNoFlag, processStatic, data);
483     };
484     return paNoError;
485 }
486 
487 bool
isSourceOK() const488 PortAudioIO::isSourceOK() const
489 {
490     if (m_mode == Mode::Playback) {
491         // record source is irrelevant in playback mode
492         return true;
493     } else {
494         return (m_stream != 0);
495     }
496 }
497 
498 bool
isTargetOK() const499 PortAudioIO::isTargetOK() const
500 {
501     if (m_mode == Mode::Record) {
502         // playback target is irrelevant in record mode
503         return true;
504     } else {
505         return (m_stream != 0);
506     }
507 }
508 
509 double
getCurrentTime() const510 PortAudioIO::getCurrentTime() const
511 {
512     if (!m_stream) return 0.0;
513     else return Pa_GetStreamTime(m_stream);
514 }
515 
516 void
suspend()517 PortAudioIO::suspend()
518 {
519     log("suspend called");
520 
521     if (m_suspended || !m_stream) return;
522     PaError err = Pa_StopStream(m_stream);
523     if (err != paNoError) {
524         log("ERROR: Failed to stop PortAudio stream");
525     }
526 
527     m_suspended = true;
528     log("suspended");
529 }
530 
531 void
resume()532 PortAudioIO::resume()
533 {
534     log("resume called");
535 
536     if (!m_suspended || !m_stream) return;
537     PaError err = Pa_StartStream(m_stream);
538     if (err != paNoError) {
539         cerr << "ERROR: Failed to restart PortAudio stream";
540     }
541 
542     m_suspended = false;
543     log("resumed");
544 }
545 
546 int
processStatic(const void * input,void * output,unsigned long nframes,const PaStreamCallbackTimeInfo * timeInfo,PaStreamCallbackFlags flags,void * data)547 PortAudioIO::processStatic(const void *input, void *output,
548                            unsigned long nframes,
549                            const PaStreamCallbackTimeInfo *timeInfo,
550                            PaStreamCallbackFlags flags, void *data)
551 {
552     return ((PortAudioIO *)data)->process(input, output,
553                                           nframes, timeInfo,
554                                           flags);
555 }
556 
557 int
process(const void * inputBuffer,void * outputBuffer,unsigned long pa_nframes,const PaStreamCallbackTimeInfo *,PaStreamCallbackFlags)558 PortAudioIO::process(const void *inputBuffer, void *outputBuffer,
559                      unsigned long pa_nframes,
560                      const PaStreamCallbackTimeInfo *,
561                      PaStreamCallbackFlags)
562 {
563 #ifdef DEBUG_AUDIO_PORT_AUDIO_IO
564     cout << "PortAudioIO::process(" << pa_nframes << ")" << endl;
565 #endif
566 
567     if (!m_prioritySet) {
568         enableRT();
569         m_prioritySet = true;
570     }
571 
572     if (!m_source && !m_target) return 0;
573     if (!m_stream) return 0;
574 
575     if (pa_nframes > INT_MAX) pa_nframes = 0;
576 
577     int nframes = int(pa_nframes);
578 
579     if (nframes > m_bufferSize) {
580         m_buffers = reallocate_and_zero_extend_channels
581             (m_buffers,
582              m_bufferChannels, m_bufferSize,
583              m_bufferChannels, nframes);
584         m_bufferSize = nframes;
585     }
586 
587     const float *input = (const float *)inputBuffer;
588     float *output = (float *)outputBuffer;
589 
590     float peakLeft, peakRight;
591 
592     if (m_target && input) {
593 
594         v_deinterleave
595             (m_buffers, input, m_inputChannels, nframes);
596 
597         v_reconfigure_channels_inplace
598             (m_buffers, m_targetChannels, m_inputChannels, nframes);
599 
600         peakLeft = 0.0, peakRight = 0.0;
601 
602         for (int c = 0; c < m_targetChannels && c < 2; ++c) {
603             float peak = 0.f;
604             for (int i = 0; i < nframes; ++i) {
605                 if (m_buffers[c][i] > peak) {
606                     peak = m_buffers[c][i];
607                 }
608             }
609             if (c == 0) peakLeft = peak;
610             if (c > 0 || m_targetChannels == 1) peakRight = peak;
611         }
612 
613         m_target->putSamples(m_buffers, m_targetChannels, nframes);
614         m_target->setInputLevels(peakLeft, peakRight);
615     }
616 
617     if (m_source && output) {
618 
619         int received = m_source->getSourceSamples(m_buffers, m_sourceChannels, nframes);
620 
621         if (received < nframes) {
622             for (int c = 0; c < m_sourceChannels; ++c) {
623                 v_zero(m_buffers[c] + received, nframes - received);
624             }
625         }
626 
627         v_reconfigure_channels_inplace
628             (m_buffers, m_outputChannels, m_sourceChannels, nframes);
629 
630         auto gain = Gains::gainsFor(m_outputGain, m_outputBalance, m_outputChannels);
631         for (int c = 0; c < m_outputChannels; ++c) {
632             v_scale(m_buffers[c], gain[c], nframes);
633         }
634 
635         peakLeft = 0.0, peakRight = 0.0;
636         for (int c = 0; c < m_outputChannels && c < 2; ++c) {
637             float peak = 0.f;
638             for (int i = 0; i < nframes; ++i) {
639                 if (m_buffers[c][i] > peak) {
640                     peak = m_buffers[c][i];
641                 }
642             }
643             if (c == 0) peakLeft = peak;
644             if (c == 1 || m_outputChannels == 1) peakRight = peak;
645         }
646 
647         v_interleave
648             (output, m_buffers, m_outputChannels, nframes);
649 
650         m_source->setOutputLevels(peakLeft, peakRight);
651 
652     } else if (m_outputChannels > 0) {
653 
654         v_zero(output, m_outputChannels * nframes);
655     }
656 
657     return 0;
658 }
659 
660 }
661 
662 #endif /* HAVE_PORTAUDIO */
663 
664