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, ¶m)) {
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