Ring Daemon 16.0.0
Loading...
Searching...
No Matches
portaudiolayer.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2025 Savoir-faire Linux Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18#include "portaudiolayer.h"
19
20#include "audio/resampler.h"
22#include "manager.h"
23#include "preferences.h"
24
25#include <portaudio.h>
26
27#include <windows.h>
28
29#include <algorithm>
30
31namespace jami {
32
33enum Direction { Input = 0, Output = 1, IO = 2, End = 3 };
34
36{
39
40 void init(PortAudioLayer&);
43 void terminate() const;
47 bool apiInitialised_ {false};
48
49 std::vector<std::string> getDevicesByType(AudioDeviceType type) const;
51 std::string getDeviceNameByType(const int index, AudioDeviceType type);
53 std::string getApiDefaultDeviceName(AudioDeviceType type, bool commDevice) const;
54
55 std::string deviceRecord_ {};
56 std::string devicePlayback_ {};
57 std::string deviceRingtone_ {};
58
59 static constexpr const int defaultIndex_ {0};
60
61 bool inputInitialized_ {false};
62 bool outputInitialized_ {false};
63
64 std::array<PaStream*, static_cast<int>(Direction::End)> streams_;
65 mutable std::mutex streamsMutex_;
67 bool hasFullDuplexStream() const;
68
70 // The following flag used to debounce the device state changes,
71 // as default-device change events often follow plug/unplug events.
72 std::atomic<bool> restartRequestPending_ = false;
73
75 const int16_t* inputBuffer,
77 unsigned long framesPerBuffer,
80
82 const int16_t* inputBuffer,
84 unsigned long framesPerBuffer,
87
89 const int16_t* inputBuffer,
91 unsigned long framesPerBuffer,
94};
95
96// ##################################################################################################
97
100 , pimpl_ {new PortAudioLayerImpl(*this, pref)}
101{
102 setHasNativeAEC(false);
103 setHasNativeNS(false);
104
106 if (numDevices < 0) {
107 JAMI_ERR("Pa_CountDevices returned 0x%x", numDevices);
108 return;
109 }
111 for (auto i = 0; i < numDevices; i++) {
113 JAMI_DBG("PortAudio device: %d, %s", i, deviceInfo->name);
114 }
115
116 // Notify of device changes in case this layer was reset based on a hotplug event.
118}
119
124
125std::vector<std::string>
127{
128 return pimpl_->getDevicesByType(AudioDeviceType::CAPTURE);
129}
130
131std::vector<std::string>
133{
134 return pimpl_->getDevicesByType(AudioDeviceType::PLAYBACK);
135}
136
137int
138PortAudioLayer::getAudioDeviceIndex(const std::string& name, AudioDeviceType type) const
139{
140 auto devices = pimpl_->getDevicesByType(type);
141 auto it = std::find_if(devices.cbegin(), devices.cend(), [&name](const auto& deviceName) {
142 return deviceName == name;
143 });
144 return it != devices.end() ? std::distance(devices.cbegin(), it) : -1;
145}
146
147std::string
149{
150 (void) index;
151 (void) type;
152 return {};
153}
154
155int
157{
158 return pimpl_->getIndexByType(AudioDeviceType::CAPTURE);
159}
160
161int
163{
164 auto index = pimpl_->getIndexByType(AudioDeviceType::PLAYBACK);
165 return index;
166}
167
168int
170{
171 return pimpl_->getIndexByType(AudioDeviceType::RINGTONE);
172}
173
174void
176{
177 if (!pimpl_->apiInitialised_) {
178 JAMI_WARN("PortAudioLayer API not initialised");
179 return;
180 }
181
182 auto startPlayback = [this](bool fullDuplexMode = false) -> bool {
183 std::unique_lock lock(mutex_);
184 if (status_.load() != Status::Idle)
185 return false;
186 bool ret {false};
187 if (fullDuplexMode)
188 ret = pimpl_->initFullDuplexStream(*this);
189 else
190 ret = pimpl_->initOutputStream(*this);
191 if (ret) {
193 lock.unlock();
194 flushUrgent();
195 flushMain();
196 }
197 return ret;
198 };
199
200 switch (stream) {
202 if (!startPlayback(true)) {
203 pimpl_->initInputStream(*this);
205 }
206 break;
208 pimpl_->initInputStream(*this);
209 break;
213 break;
214 }
215}
216
217void
219{
220 std::lock_guard lock(mutex_);
221 if (status_.load() != Status::Started)
222 return;
223
224 bool stopped = false;
225 switch (stream) {
227 if (pimpl_->hasFullDuplexStream()) {
228 stopped = pimpl_->paStopStream(Direction::IO);
229 JAMI_DBG("PortAudioLayer full-duplex stream stopped");
230 } else {
231 if (pimpl_->paStopStream(Direction::Output)) {
232 playbackChanged(false);
233 stopped = true;
234 JAMI_DBG("PortAudioLayer output stream stopped");
235 }
236 if (pimpl_->paStopStream(Direction::Input)) {
237 recordChanged(false);
238 stopped = true;
239 JAMI_DBG("PortAudioLayer input stream stopped");
240 }
241 }
242 break;
244 if (pimpl_->paStopStream(Direction::Input)) {
245 recordChanged(false);
246 JAMI_DBG("PortAudioLayer input stream stopped");
247 stopped = true;
248 }
249 break;
252 if (pimpl_->paStopStream(Direction::Output)) {
253 playbackChanged(false);
254 stopped = true;
255 JAMI_DBG("PortAudioLayer output stream stopped");
256 }
257 break;
258 }
259
260 // Flush the ring buffers if any streams were stopped
261 if (stopped) {
262 JAMI_DBG("PortAudioLayer streams stopped, flushing buffers");
263 flushUrgent();
264 flushMain();
265 }
266}
267
268void
270{
271 auto deviceName = pimpl_->getDeviceNameByType(index, type);
272 switch (type) {
274 preference.setPortAudioDevicePlayback(deviceName);
275 break;
277 preference.setPortAudioDeviceRecord(deviceName);
278 break;
280 preference.setPortAudioDeviceRingtone(deviceName);
281 break;
282 default:
283 break;
284 }
285}
286
287// ##################################################################################################
288
290 const AudioPreference& pref)
291 : deviceRecord_ {pref.getPortAudioDeviceRecord()}
292 , devicePlayback_ {pref.getPortAudioDevicePlayback()}
293 , deviceRingtone_ {pref.getPortAudioDeviceRingtone()}
294 , audioDeviceNotificationClient_ {new AudioDeviceNotificationClient}
295{
296 // Set up our callback to restart the layer on any device event
298 [this, &parent](const std::string& deviceName, const DeviceEventType event) {
299 JAMI_LOG("PortAudioLayer device event: {}, {}",
300 deviceName.c_str(),
302 // Here we want to debounce the device events as a DefaultChanged could
303 // follow a DeviceAdded event and we don't want to restart the layer twice
304 if (!restartRequestPending_.exchange(true)) {
305 std::thread([] {
306 // First wait for the debounce period to pass, allowing for multiple events
307 // to be grouped together (e.g. DeviceAdded -> DefaultChanged).
308 std::this_thread::sleep_for(std::chrono::milliseconds(300));
311 }).detach();
312 }
313 });
314
315 init(parent);
316}
317
322
323void
325{
326 // convert out preference to an api index
327 auto apiIndex = getApiIndexByType(AudioDeviceType::CAPTURE);
328
329 // Pa_GetDefault[Comm]InputDevice returned paNoDevice or we already initialized the device
330 if (apiIndex == paNoDevice || inputInitialized_)
331 return;
332
334 if (!inputDeviceInfo) {
335 // this represents complete failure after attempting a fallback to default
336 JAMI_WARN("PortAudioLayer was unable to initialize input");
337 deviceRecord_.clear();
338 inputInitialized_ = true;
339 return;
340 }
341
342 // if the device index is somehow no longer a device of the correct type, reset the
343 // internal index to paNoDevice and reenter in an attempt to set the default
344 // communications device
345 if (inputDeviceInfo->maxInputChannels <= 0) {
346 JAMI_WARN("PortAudioLayer was unable to initialize input, falling back to default device");
347 deviceRecord_.clear();
348 return initInput(parent);
349 }
350
351 // at this point, the device is of the correct type and can be opened
352 parent.audioInputFormat_.sample_rate = inputDeviceInfo->defaultSampleRate;
353 parent.audioInputFormat_.nb_channels = inputDeviceInfo->maxInputChannels;
354 parent.hardwareInputFormatAvailable(parent.audioInputFormat_);
355 JAMI_DBG("PortAudioLayer initialized input: %s {%d Hz, %d channels}",
356 inputDeviceInfo->name,
357 parent.audioInputFormat_.sample_rate,
358 parent.audioInputFormat_.nb_channels);
359 inputInitialized_ = true;
360}
361
362void
364{
365 // convert out preference to an api index
366 auto apiIndex = getApiIndexByType(AudioDeviceType::PLAYBACK);
367
368 // Pa_GetDefault[Comm]OutputDevice returned paNoDevice or we already initialized the device
369 if (apiIndex == paNoDevice || outputInitialized_)
370 return;
371
373 if (!outputDeviceInfo) {
374 // this represents complete failure after attempting a fallback to default
375 JAMI_WARN("PortAudioLayer was unable to initialize output");
376 devicePlayback_.clear();
377 outputInitialized_ = true;
378 return;
379 }
380
381 // if the device index is somehow no longer a device of the correct type, reset the
382 // internal index to paNoDevice and reenter in an attempt to set the default
383 // communications device
384 if (outputDeviceInfo->maxOutputChannels <= 0) {
385 JAMI_WARN("PortAudioLayer was unable to initialize output, falling back to default device");
386 devicePlayback_.clear();
387 return initOutput(parent);
388 }
389
390 // at this point, the device is of the correct type and can be opened
391 parent.audioFormat_.sample_rate = outputDeviceInfo->defaultSampleRate;
392 parent.audioFormat_.nb_channels = outputDeviceInfo->maxOutputChannels;
393 parent.hardwareFormatAvailable(parent.audioFormat_);
394 JAMI_DBG("PortAudioLayer initialized output: %s {%d Hz, %d channels}",
395 outputDeviceInfo->name,
396 parent.audioFormat_.sample_rate,
397 parent.audioFormat_.nb_channels);
398 outputInitialized_ = true;
399}
400
401void
403{
404 JAMI_DBG("PortAudioLayer Init");
405 const auto err = Pa_Initialize();
408 if (err != paNoError || apiInfo == nullptr) {
409 JAMI_ERR("PortAudioLayer error: %s", Pa_GetErrorText(err));
410 terminate();
411 return;
412 }
413
414 apiInitialised_ = true;
415 JAMI_DBG() << "Portaudio initialized using: " << apiInfo->name;
416
417 initInput(parent);
418 initOutput(parent);
419
420 std::lock_guard lock(streamsMutex_);
421 std::fill(std::begin(streams_), std::end(streams_), nullptr);
422}
423
424std::vector<std::string>
426{
427 std::vector<std::string> devices;
429 if (numDevices < 0)
430 JAMI_ERR("PortAudioLayer error: %s", Pa_GetErrorText(numDevices));
431 else {
432 for (int i = 0; i < numDevices; i++) {
433 const auto deviceInfo = Pa_GetDeviceInfo(i);
434 if (type == AudioDeviceType::CAPTURE) {
435 if (deviceInfo->maxInputChannels > 0)
436 devices.push_back(deviceInfo->name);
437 } else if (deviceInfo->maxOutputChannels > 0)
438 devices.push_back(deviceInfo->name);
439 }
440 // add the default device aliases if requested and if there are any devices of this type
441 if (!devices.empty()) {
442 // default comm (index:0)
443 auto defaultDeviceName = getApiDefaultDeviceName(type, true);
444 devices.insert(devices.begin(), "{{Default}} - " + defaultDeviceName);
445 }
446 }
447 return devices;
448}
449
450int
452{
453 auto devices = getDevicesByType(type);
454 if (!devices.size()) {
455 return 0;
456 }
457 std::string_view toMatch = (type == AudioDeviceType::CAPTURE
458 ? deviceRecord_
459 : (type == AudioDeviceType::PLAYBACK ? devicePlayback_
460 : deviceRingtone_));
461 auto it = std::find_if(devices.cbegin(), devices.cend(), [&toMatch](const auto& deviceName) {
462 return deviceName == toMatch;
463 });
464 return it != devices.end() ? std::distance(devices.cbegin(), it) : 0;
465}
466
467std::string
469{
470 if (index == defaultIndex_)
471 return {};
472
473 auto devices = getDevicesByType(type);
474 if (!devices.size() || index >= devices.size())
475 return {};
476
477 return devices.at(index);
478}
479
482{
484 if (numDevices < 0) {
485 JAMI_ERR("PortAudioLayer error: %s", Pa_GetErrorText(numDevices));
486 return paNoDevice;
487 } else {
488 std::string_view toMatch = (type == AudioDeviceType::CAPTURE
489 ? deviceRecord_
490 : (type == AudioDeviceType::PLAYBACK ? devicePlayback_
491 : deviceRingtone_));
492 if (!toMatch.empty()) {
493 for (int i = 0; i < numDevices; ++i) {
494 if (const auto deviceInfo = Pa_GetDeviceInfo(i)) {
495 if (deviceInfo->name == toMatch)
496 return i;
497 }
498 }
499 }
500 }
501 // If nothing was found, return the default device
504}
505
506std::string
508 bool commDevice) const
509{
510 std::string deviceName {};
512 if (type == AudioDeviceType::CAPTURE) {
514 } else {
516 }
517 if (const auto deviceInfo = Pa_GetDeviceInfo(deviceIndex)) {
518 deviceName = deviceInfo->name;
519 }
520 return deviceName;
521}
522
523void
525{
526 JAMI_DBG("PortAudioLayer terminate.");
527 auto err = Pa_Terminate();
528 if (err != paNoError)
529 JAMI_ERR("PortAudioLayer error: %s", Pa_GetErrorText(err));
530}
531
532static void
534 PaDeviceIndex device,
536 PaStreamCallback* callback,
537 void* user_data)
538{
540 auto device_info = Pa_GetDeviceInfo(device);
541
543 params.device = device;
544 params.channelCount = is_out ? device_info->maxOutputChannels : device_info->maxInputChannels;
545 params.sampleFormat = paInt16;
546 params.suggestedLatency = is_out ? device_info->defaultLowOutputLatency
547 : device_info->defaultLowInputLatency;
548 params.hostApiSpecificStreamInfo = nullptr;
549
550 auto err = Pa_OpenStream(stream,
551 is_out ? nullptr : &params,
552 is_out ? &params : nullptr,
553 device_info->defaultSampleRate,
555 paNoFlag,
556 callback,
557 user_data);
558
559 if (err != paNoError)
560 JAMI_ERR("PortAudioLayer error: %s", Pa_GetErrorText(err));
561}
562
563static void
567 PaStreamCallback* callback,
568 void* user_data)
569{
572
575 inputParams.channelCount = input_device_info->maxInputChannels;
576 inputParams.sampleFormat = paInt16;
577 inputParams.suggestedLatency = input_device_info->defaultLowInputLatency;
578 inputParams.hostApiSpecificStreamInfo = nullptr;
579
582 outputParams.channelCount = output_device_info->maxOutputChannels;
583 outputParams.sampleFormat = paInt16;
584 outputParams.suggestedLatency = output_device_info->defaultLowOutputLatency;
585 outputParams.hostApiSpecificStreamInfo = nullptr;
586
587 auto err = Pa_OpenStream(stream,
590 std::min(input_device_info->defaultSampleRate,
591 input_device_info->defaultSampleRate),
593 paNoFlag,
594 callback,
595 user_data);
596
597 if (err != paNoError)
598 JAMI_ERR("PortAudioLayer error: %s", Pa_GetErrorText(err));
599}
600
601bool
603{
604 JAMI_DBG("Open PortAudio Input Stream");
605 std::lock_guard lock(streamsMutex_);
606 auto& stream = streams_[Direction::Input];
607 auto apiIndex = getApiIndexByType(AudioDeviceType::CAPTURE);
608 if (apiIndex != paNoDevice) {
610 &stream,
611 apiIndex,
613 [](const void* inputBuffer,
614 void* outputBuffer,
615 unsigned long framesPerBuffer,
618 void* userData) -> int {
619 auto layer = static_cast<PortAudioLayer*>(userData);
620 return layer->pimpl_->paInputCallback(*layer,
621 static_cast<const int16_t*>(inputBuffer),
622 static_cast<int16_t*>(outputBuffer),
624 timeInfo,
626 },
627 &parent);
628 } else {
629 JAMI_ERR("Error: No valid input device. There will be no mic.");
630 return false;
631 }
632
633 JAMI_DBG("Starting PortAudio Input Stream");
634 auto err = Pa_StartStream(stream);
635 if (err != paNoError) {
636 JAMI_ERR("PortAudioLayer error: %s", Pa_GetErrorText(err));
637 return false;
638 }
639
640 parent.recordChanged(true);
641 return true;
642}
643
644bool
646{
647 JAMI_DBG("Open PortAudio Output Stream");
648 std::lock_guard lock(streamsMutex_);
649 auto& stream = streams_[Direction::Output];
650 auto apiIndex = getApiIndexByType(AudioDeviceType::PLAYBACK);
651 if (apiIndex != paNoDevice) {
653 &stream,
654 apiIndex,
656 [](const void* inputBuffer,
657 void* outputBuffer,
658 unsigned long framesPerBuffer,
661 void* userData) -> int {
662 auto layer = static_cast<PortAudioLayer*>(userData);
663 return layer->pimpl_->paOutputCallback(*layer,
664 static_cast<const int16_t*>(inputBuffer),
665 static_cast<int16_t*>(outputBuffer),
667 timeInfo,
669 },
670 &parent);
671 } else {
672 JAMI_ERR("Error: No valid output device. There will be no sound.");
673 return false;
674 }
675
676 JAMI_DBG("Starting PortAudio Output Stream");
677 auto err = Pa_StartStream(stream);
678 if (err != paNoError) {
679 JAMI_ERR("PortAudioLayer error: %s", Pa_GetErrorText(err));
680 return false;
681 }
682
683 parent.playbackChanged(true);
684 return true;
685}
686
687bool
689{
690 auto apiIndexRecord = getApiIndexByType(AudioDeviceType::CAPTURE);
691 auto apiIndexPlayback = getApiIndexByType(AudioDeviceType::PLAYBACK);
693 JAMI_ERR("Error: Invalid input/output devices. There will be no audio.");
694 return false;
695 }
696
697 JAMI_DBG("Open PortAudio Full-duplex input/output stream");
698 std::lock_guard lock(streamsMutex_);
699 auto& stream = streams_[Direction::IO];
701 &stream,
704 [](const void* inputBuffer,
705 void* outputBuffer,
706 unsigned long framesPerBuffer,
709 void* userData) -> int {
710 auto layer = static_cast<PortAudioLayer*>(userData);
711 return layer->pimpl_->paIOCallback(*layer,
712 static_cast<const int16_t*>(inputBuffer),
713 static_cast<int16_t*>(outputBuffer),
715 timeInfo,
717 },
718 &parent);
719
720 JAMI_DBG("Start PortAudio I/O Streams");
721 auto err = Pa_StartStream(stream);
722 if (err != paNoError) {
723 JAMI_ERR("PortAudioLayer error: %s", Pa_GetErrorText(err));
724 return false;
725 }
726
727 parent.recordChanged(true);
728 parent.playbackChanged(true);
729 return true;
730}
731
732bool
734{
735 std::lock_guard lock(streamsMutex_);
736 PaStream* paStream = streams_[streamDirection];
737 if (!paStream)
738 return false;
740 if (ret == 1) {
741 JAMI_DBG("PortAudioLayer stream %d already stopped", streamDirection);
742 return true;
743 } else if (ret < 0) {
744 JAMI_ERR("Pa_IsStreamStopped error: %s", Pa_GetErrorText(ret));
745 return false;
746 }
747 auto err = Pa_StopStream(paStream);
748 if (err != paNoError) {
749 JAMI_ERR("Pa_StopStream error: %s", Pa_GetErrorText(err));
750 return false;
751 }
753 if (err != paNoError) {
754 JAMI_ERR("Pa_CloseStream error: %s", Pa_GetErrorText(err));
755 return false;
756 }
757 JAMI_DBG("PortAudioLayer stream %d stopped", streamDirection);
758 streams_[streamDirection] = nullptr;
759 return true;
760};
761
763{
764 std::lock_guard lock(streamsMutex_);
765 return streams_[Direction::IO] != nullptr;
766}
767
768int
770 const int16_t* inputBuffer,
772 unsigned long framesPerBuffer,
775{
776 // unused arguments
778 (void) timeInfo;
780
781 auto toPlay = parent.getPlayback(parent.audioFormat_, framesPerBuffer);
782 if (!toPlay) {
783 std::fill_n(outputBuffer, framesPerBuffer * parent.audioFormat_.nb_channels, 0);
784 return paContinue;
785 }
786
787 auto nFrames = toPlay->pointer()->nb_samples * toPlay->pointer()->ch_layout.nb_channels;
788 std::copy_n((int16_t*) toPlay->pointer()->extended_data[0], nFrames, outputBuffer);
789 return paContinue;
790}
791
792int
794 const int16_t* inputBuffer,
796 unsigned long framesPerBuffer,
799{
800 // unused arguments
802 (void) timeInfo;
804
805 if (framesPerBuffer == 0) {
806 JAMI_WARN("No frames for input.");
807 return paContinue;
808 }
809
810 auto inBuff = std::make_shared<AudioFrame>(parent.audioInputFormat_, framesPerBuffer);
811 auto nFrames = framesPerBuffer * parent.audioInputFormat_.nb_channels;
812 if (parent.isCaptureMuted_)
814 else
815 std::copy_n(inputBuffer, nFrames, (int16_t*) inBuff->pointer()->extended_data[0]);
816 parent.putRecorded(std::move(inBuff));
817 return paContinue;
818}
819
820int
832
833} // namespace jami
std::unique_ptr< AudioDeviceNotificationClient, AudioDeviceNotificationClient_deleter > AudioDeviceNotificationClientPtr
void setDeviceEventCallback(DeviceEventCallback callback)
std::mutex mutex_
Lock for the entire audio layer.
Definition audiolayer.h:283
std::atomic< Status > status_
Whether or not the audio layer's playback stream is started.
Definition audiolayer.h:260
void playbackChanged(bool started)
void setHasNativeNS(bool hasNS)
void flushUrgent()
Flush urgent buffer.
void setHasNativeAEC(bool hasEAC)
void recordChanged(bool started)
void flushMain()
Flush main buffer.
static LIBJAMI_TEST_EXPORT Manager & instance()
Definition manager.cpp:676
std::string getAudioManager() const
Get the audio manager.
Definition manager.cpp:2415
void setAudioPlugin(const std::string &audioPlugin)
Set input audio plugin.
Definition manager.cpp:2164
int getIndexCapture() const override
void updatePreference(AudioPreference &pref, int index, AudioDeviceType type) override
int getIndexPlayback() const override
PortAudioLayer(const AudioPreference &pref)
void stopStream(AudioDeviceType stream=AudioDeviceType::ALL) override
Stop the playback and capture streams.
int getIndexRingtone() const override
std::vector< std::string > getPlaybackDeviceList() const override
int getAudioDeviceIndex(const std::string &name, AudioDeviceType type) const override
std::vector< std::string > getCaptureDeviceList() const override
void startStream(AudioDeviceType stream=AudioDeviceType::ALL) override
Start the capture stream and prepare the playback stream.
std::string getAudioDeviceName(int index, AudioDeviceType type) const override
direction
0: outgoing, 1: incoming
#define JAMI_ERR(...)
Definition logger.h:218
#define JAMI_DBG(...)
Definition logger.h:216
#define JAMI_WARN(...)
Definition logger.h:217
#define JAMI_LOG(formatstr,...)
Definition logger.h:225
void fillWithSilence(AVFrame *frame)
void emitSignal(Args... args)
Definition ring_signal.h:64
static void openStreamDevice(PaStream **stream, PaDeviceIndex device, Direction direction, PaStreamCallback *callback, void *user_data)
std::string to_string(double value)
AudioDeviceType
Definition audiolayer.h:58
static void openFullDuplexStream(PaStream **stream, PaDeviceIndex inputDeviceIndex, PaDeviceIndex ouputDeviceIndex, PaStreamCallback *callback, void *user_data)
int paIOCallback(PortAudioLayer &parent, const int16_t *inputBuffer, int16_t *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags)
int paInputCallback(PortAudioLayer &parent, const int16_t *inputBuffer, int16_t *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags)
int paOutputCallback(PortAudioLayer &parent, const int16_t *inputBuffer, int16_t *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags)
std::vector< std::string > getDevicesByType(AudioDeviceType type) const
static constexpr const int defaultIndex_
std::string getDeviceNameByType(const int index, AudioDeviceType type)
PaDeviceIndex getApiIndexByType(AudioDeviceType type)
bool paStopStream(Direction streamDirection)
PortAudioLayerImpl(PortAudioLayer &, const AudioPreference &)
std::string getApiDefaultDeviceName(AudioDeviceType type, bool commDevice) const
AudioDeviceNotificationClientPtr audioDeviceNotificationClient_
std::array< PaStream *, static_cast< int >(Direction::End)> streams_