Ring Daemon
Loading...
Searching...
No Matches
portaudiolayer.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2026 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 float* inputBuffer,
76 float* outputBuffer,
77 unsigned long framesPerBuffer,
80
82 const float* inputBuffer,
83 float* outputBuffer,
84 unsigned long framesPerBuffer,
87
89 const float* inputBuffer,
90 float* outputBuffer,
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 return pimpl_->getDeviceNameByType(index, type);
151}
152
153int
155{
156 return pimpl_->getIndexByType(AudioDeviceType::CAPTURE);
157}
158
159int
161{
162 auto index = pimpl_->getIndexByType(AudioDeviceType::PLAYBACK);
163 return index;
164}
165
166int
168{
169 return pimpl_->getIndexByType(AudioDeviceType::RINGTONE);
170}
171
172void
174{
175 if (!pimpl_->apiInitialised_) {
176 JAMI_WARN("PortAudioLayer API not initialised");
177 return;
178 }
179
180 auto startPlayback = [this](bool fullDuplexMode = false) -> bool {
181 std::unique_lock lock(mutex_);
182 if (status_.load() != Status::Idle)
183 return false;
184 bool ret {false};
185 if (fullDuplexMode)
186 ret = pimpl_->initFullDuplexStream(*this);
187 else
188 ret = pimpl_->initOutputStream(*this);
189 if (ret) {
191 startedCv_.notify_all();
192 lock.unlock();
193 flushUrgent();
194 flushMain();
195 }
196 return ret;
197 };
198
199 switch (stream) {
201 if (!startPlayback(true)) {
202 pimpl_->initInputStream(*this);
204 }
205 break;
207 pimpl_->initInputStream(*this);
208 break;
212 break;
213 }
214}
215
216void
218{
219 std::lock_guard lock(mutex_);
220 if (status_.load() != Status::Started)
221 return;
222
223 bool stopped = false;
224 bool outputStopped = false;
225 switch (stream) {
227 if (pimpl_->hasFullDuplexStream()) {
228 stopped = pimpl_->paStopStream(Direction::IO);
229 JAMI_DBG("PortAudioLayer full-duplex stream stopped");
230 if (stopped) {
231 playbackChanged(false);
232 recordChanged(false);
233 outputStopped = true;
234 }
235 } else {
236 if (pimpl_->paStopStream(Direction::Output)) {
237 playbackChanged(false);
238 stopped = true;
239 outputStopped = true;
240 JAMI_DBG("PortAudioLayer output stream stopped");
241 }
242 if (pimpl_->paStopStream(Direction::Input)) {
243 recordChanged(false);
244 stopped = true;
245 JAMI_DBG("PortAudioLayer input stream stopped");
246 }
247 }
248 break;
250 if (pimpl_->paStopStream(Direction::Input)) {
251 recordChanged(false);
252 JAMI_DBG("PortAudioLayer input stream stopped");
253 stopped = true;
254 }
255 break;
258 if (pimpl_->paStopStream(Direction::Output)) {
259 playbackChanged(false);
260 stopped = true;
261 outputStopped = true;
262 JAMI_DBG("PortAudioLayer output stream stopped");
263 }
264 break;
265 }
266
267 // Flush the ring buffers if any streams were stopped
268 if (stopped) {
269 JAMI_DBG("PortAudioLayer streams stopped, flushing buffers");
270 flushUrgent();
271 flushMain();
272 if (outputStopped) {
273 status_.store(Status::Idle);
274 startedCv_.notify_all();
275 }
276 }
277}
278
279void
281{
282 auto deviceName = pimpl_->getDeviceNameByType(index, type);
283 switch (type) {
285 preference.setPortAudioDevicePlayback(deviceName);
286 break;
288 preference.setPortAudioDeviceRecord(deviceName);
289 break;
291 preference.setPortAudioDeviceRingtone(deviceName);
292 break;
293 default:
294 break;
295 }
296}
297
298// ##################################################################################################
299
301 : deviceRecord_ {pref.getPortAudioDeviceRecord()}
302 , devicePlayback_ {pref.getPortAudioDevicePlayback()}
303 , deviceRingtone_ {pref.getPortAudioDeviceRingtone()}
304 , audioDeviceNotificationClient_ {new AudioDeviceNotificationClient}
305{
306 // Set up our callback to restart the layer on any device event
308 [this, &parent](const std::string& deviceName, const DeviceEventType event) {
309 JAMI_LOG("PortAudioLayer device event: {}, {}", deviceName.c_str(), to_string(event).c_str());
310 // Here we want to debounce the device events as a DefaultChanged could
311 // follow a DeviceAdded event and we don't want to restart the layer twice
312 if (!restartRequestPending_.exchange(true)) {
313 std::thread([] {
314 // First wait for the debounce period to pass, allowing for multiple events
315 // to be grouped together (e.g. DeviceAdded -> DefaultChanged).
316 std::this_thread::sleep_for(std::chrono::milliseconds(300));
319 }).detach();
320 }
321 });
322
323 init(parent);
324}
325
330
331void
333{
334 // convert out preference to an api index
335 auto apiIndex = getApiIndexByType(AudioDeviceType::CAPTURE);
336
337 // Pa_GetDefault[Comm]InputDevice returned paNoDevice or we already initialized the device
338 if (apiIndex == paNoDevice || inputInitialized_)
339 return;
340
342 if (!inputDeviceInfo) {
343 // this represents complete failure after attempting a fallback to default
344 JAMI_WARN("PortAudioLayer was unable to initialize input");
345 deviceRecord_.clear();
346 inputInitialized_ = true;
347 return;
348 }
349
350 // if the device index is somehow no longer a device of the correct type, reset the
351 // internal index to paNoDevice and reenter in an attempt to set the default
352 // communications device
353 if (inputDeviceInfo->maxInputChannels <= 0) {
354 JAMI_WARN("PortAudioLayer was unable to initialize input, falling back to default device");
355 deviceRecord_.clear();
356 return initInput(parent);
357 }
358
359 // at this point, the device is of the correct type and can be opened
360 parent.audioInputFormat_.sample_rate = inputDeviceInfo->defaultSampleRate;
361 parent.audioInputFormat_.nb_channels = inputDeviceInfo->maxInputChannels;
362 parent.audioInputFormat_.sampleFormat = AV_SAMPLE_FMT_FLTP;
363 parent.hardwareInputFormatAvailable(parent.audioInputFormat_);
364 JAMI_DBG("PortAudio input device: %s (native: %.0f Hz, using: %d Hz, %d channels)",
365 inputDeviceInfo->name,
366 inputDeviceInfo->defaultSampleRate,
367 parent.audioInputFormat_.sample_rate,
368 parent.audioInputFormat_.nb_channels);
369 inputInitialized_ = true;
370}
371
372void
374{
375 // convert out preference to an api index
376 auto apiIndex = getApiIndexByType(AudioDeviceType::PLAYBACK);
377
378 // Pa_GetDefault[Comm]OutputDevice returned paNoDevice or we already initialized the device
379 if (apiIndex == paNoDevice || outputInitialized_)
380 return;
381
383 if (!outputDeviceInfo) {
384 // this represents complete failure after attempting a fallback to default
385 JAMI_WARN("PortAudioLayer was unable to initialize output");
386 devicePlayback_.clear();
387 outputInitialized_ = true;
388 return;
389 }
390
391 // if the device index is somehow no longer a device of the correct type, reset the
392 // internal index to paNoDevice and reenter in an attempt to set the default
393 // communications device
394 if (outputDeviceInfo->maxOutputChannels <= 0) {
395 JAMI_WARN("PortAudioLayer was unable to initialize output, falling back to default device");
396 devicePlayback_.clear();
397 return initOutput(parent);
398 }
399
400 // at this point, the device is of the correct type and can be opened
401 parent.audioFormat_.sample_rate = outputDeviceInfo->defaultSampleRate;
402 parent.audioFormat_.nb_channels = outputDeviceInfo->maxOutputChannels;
403 parent.audioFormat_.sampleFormat = AV_SAMPLE_FMT_FLTP;
404 parent.hardwareFormatAvailable(parent.audioFormat_);
405 JAMI_DBG("PortAudio output device: %s (native: %.0f Hz, using: %d Hz, %d channels)",
406 outputDeviceInfo->name,
407 outputDeviceInfo->defaultSampleRate,
408 parent.audioFormat_.sample_rate,
409 parent.audioFormat_.nb_channels);
410 outputInitialized_ = true;
411}
412
413void
415{
416 JAMI_DBG("PortAudioLayer Init");
417 const auto err = Pa_Initialize();
420 if (err != paNoError || apiInfo == nullptr) {
421 JAMI_ERR("PortAudioLayer error: %s", Pa_GetErrorText(err));
422 terminate();
423 return;
424 }
425
426 apiInitialised_ = true;
427 JAMI_DBG() << "Portaudio initialized using: " << apiInfo->name;
428
429 initInput(parent);
430 initOutput(parent);
431
432 std::lock_guard lock(streamsMutex_);
433 std::fill(std::begin(streams_), std::end(streams_), nullptr);
434}
435
436std::vector<std::string>
438{
439 std::vector<std::string> devices;
441 if (numDevices < 0)
442 JAMI_ERR("PortAudioLayer error: %s", Pa_GetErrorText(numDevices));
443 else {
444 for (int i = 0; i < numDevices; i++) {
445 const auto deviceInfo = Pa_GetDeviceInfo(i);
446 if (type == AudioDeviceType::CAPTURE) {
447 if (deviceInfo->maxInputChannels > 0)
448 devices.push_back(deviceInfo->name);
449 } else if (deviceInfo->maxOutputChannels > 0)
450 devices.push_back(deviceInfo->name);
451 }
452 // add the default device aliases if requested and if there are any devices of this type
453 if (!devices.empty()) {
454 // default comm (index:0)
455 auto defaultDeviceName = getApiDefaultDeviceName(type, true);
456 devices.insert(devices.begin(), "{{Default}} - " + defaultDeviceName);
457 }
458 }
459 return devices;
460}
461
462int
464{
465 auto devices = getDevicesByType(type);
466 if (!devices.size()) {
467 return 0;
468 }
469 std::string_view toMatch = (type == AudioDeviceType::CAPTURE
470 ? deviceRecord_
471 : (type == AudioDeviceType::PLAYBACK ? devicePlayback_ : deviceRingtone_));
472 auto it = std::find_if(devices.cbegin(), devices.cend(), [&toMatch](const auto& deviceName) {
473 return deviceName == toMatch;
474 });
475 return it != devices.end() ? std::distance(devices.cbegin(), it) : 0;
476}
477
478std::string
480{
481 if (index == defaultIndex_)
482 return {};
483
484 auto devices = getDevicesByType(type);
485 if (!devices.size() || index >= devices.size())
486 return {};
487
488 return devices.at(index);
489}
490
493{
495 if (numDevices < 0) {
496 JAMI_ERR("PortAudioLayer error: %s", Pa_GetErrorText(numDevices));
497 return paNoDevice;
498 } else {
499 std::string_view toMatch = (type == AudioDeviceType::CAPTURE
500 ? deviceRecord_
501 : (type == AudioDeviceType::PLAYBACK ? devicePlayback_ : deviceRingtone_));
502 if (!toMatch.empty()) {
503 for (int i = 0; i < numDevices; ++i) {
504 if (const auto deviceInfo = Pa_GetDeviceInfo(i)) {
505 if (deviceInfo->name == toMatch)
506 return i;
507 }
508 }
509 }
510 }
511 // If nothing was found, return the default device
513}
514
515std::string
517{
518 std::string deviceName {};
520 if (type == AudioDeviceType::CAPTURE) {
522 } else {
524 }
525 if (const auto deviceInfo = Pa_GetDeviceInfo(deviceIndex)) {
526 deviceName = deviceInfo->name;
527 }
528 return deviceName;
529}
530
531void
533{
534 JAMI_DBG("PortAudioLayer terminate.");
535 auto err = Pa_Terminate();
536 if (err != paNoError)
537 JAMI_ERR("PortAudioLayer error: %s", Pa_GetErrorText(err));
538}
539
540// Unified PortAudio stream opener for input, output, or full-duplex
541static PaError
545 PaStreamCallback* callback,
546 void* user_data)
547{
548 const bool hasInput = inputDeviceIndex != paNoDevice;
549 const bool hasOutput = outputDeviceIndex != paNoDevice;
550
553
558
559 if (hasInput && inputInfo) {
561 inputParams.channelCount = inputInfo->maxInputChannels;
562 inputParams.sampleFormat = paFloat32 | paNonInterleaved;
563 inputParams.suggestedLatency = inputInfo->defaultLowInputLatency;
564 inputParams.hostApiSpecificStreamInfo = nullptr;
566 }
567 if (hasOutput && outputInfo) {
569 outputParams.channelCount = outputInfo->maxOutputChannels;
570 outputParams.sampleFormat = paFloat32 | paNonInterleaved;
571 outputParams.suggestedLatency = outputInfo->defaultLowOutputLatency;
572 outputParams.hostApiSpecificStreamInfo = nullptr;
574 }
575
576 // Choose a working sample rate
577 double sampleRate = 0.0;
579 sampleRate = std::min(inputInfo->defaultSampleRate, outputInfo->defaultSampleRate);
580 } else if (inputParamsPtr) {
581 sampleRate = inputInfo->defaultSampleRate;
582 } else if (outputParamsPtr) {
583 sampleRate = outputInfo->defaultSampleRate;
584 }
585
586 unsigned long framesPerBuffer = (unsigned long) std::max(1.0, sampleRate / 100.0); // ~10 ms
587 auto err = Pa_OpenStream(stream,
590 sampleRate,
592 paNoFlag,
593 callback,
594 user_data);
595 if (err != paNoError) {
596 JAMI_ERR("PortAudioLayer error: %s", Pa_GetErrorText(err));
597 }
598 return err;
599}
600
601static bool
603{
604 auto err = Pa_StartStream(stream);
605 if (err != paNoError) {
606 JAMI_ERR("PortAudioLayer error: %s", Pa_GetErrorText(err));
607 return false;
608 }
609 return true;
610}
611
612bool
614{
615 JAMI_DBG("Open PortAudio Input Stream");
616 std::lock_guard lock(streamsMutex_);
617 auto& stream = streams_[Direction::Input];
618 auto apiIndex = getApiIndexByType(AudioDeviceType::CAPTURE);
619 if (apiIndex != paNoDevice) {
620 auto err = openPaStream(
621 &stream,
622 apiIndex,
624 [](const void* inputBuffer,
625 void* outputBuffer,
626 unsigned long framesPerBuffer,
629 void* userData) -> int {
630 auto layer = static_cast<PortAudioLayer*>(userData);
631 return layer->pimpl_->paInputCallback(*layer,
632 static_cast<const float*>(inputBuffer),
633 static_cast<float*>(outputBuffer),
635 timeInfo,
637 },
638 &parent);
639 if (err != paNoError) {
640 return false;
641 }
642 } else {
643 JAMI_ERR("Error: No valid input device. There will be no mic.");
644 return false;
645 }
646
647 JAMI_DBG("Starting PortAudio Input Stream");
648 if (!startPaStream(stream))
649 return false;
650
651 parent.recordChanged(true);
652 return true;
653}
654
655bool
657{
658 JAMI_DBG("Open PortAudio Output Stream");
659 std::lock_guard lock(streamsMutex_);
660 auto& stream = streams_[Direction::Output];
661 auto apiIndex = getApiIndexByType(AudioDeviceType::PLAYBACK);
662 if (apiIndex != paNoDevice) {
663 auto err = openPaStream(
664 &stream,
666 apiIndex,
667 [](const void* inputBuffer,
668 void* outputBuffer,
669 unsigned long framesPerBuffer,
672 void* userData) -> int {
673 auto layer = static_cast<PortAudioLayer*>(userData);
674 return layer->pimpl_->paOutputCallback(*layer,
675 static_cast<const float*>(inputBuffer),
676 static_cast<float*>(outputBuffer),
678 timeInfo,
680 },
681 &parent);
682 if (err != paNoError) {
683 return false;
684 }
685 } else {
686 JAMI_ERR("Error: No valid output device. There will be no sound.");
687 return false;
688 }
689
690 JAMI_DBG("Starting PortAudio Output Stream");
691 if (!startPaStream(stream))
692 return false;
693
694 parent.playbackChanged(true);
695 return true;
696}
697
698bool
700{
701 auto apiIndexRecord = getApiIndexByType(AudioDeviceType::CAPTURE);
702 auto apiIndexPlayback = getApiIndexByType(AudioDeviceType::PLAYBACK);
704 JAMI_ERR("Error: Invalid input/output devices. There will be no audio.");
705 return false;
706 }
707
708 JAMI_DBG("Open PortAudio Full-duplex input/output stream");
709 std::lock_guard lock(streamsMutex_);
710 auto& stream = streams_[Direction::IO];
711 auto err = openPaStream(
712 &stream,
715 [](const void* inputBuffer,
716 void* outputBuffer,
717 unsigned long framesPerBuffer,
720 void* userData) -> int {
721 auto layer = static_cast<PortAudioLayer*>(userData);
722 return layer->pimpl_->paIOCallback(*layer,
723 static_cast<const float*>(inputBuffer),
724 static_cast<float*>(outputBuffer),
726 timeInfo,
728 },
729 &parent);
730 if (err != paNoError) {
731 return false;
732 }
733
734 JAMI_DBG("Start PortAudio I/O Streams");
735 if (!startPaStream(stream))
736 return false;
737
738 parent.recordChanged(true);
739 parent.playbackChanged(true);
740 return true;
741}
742
743bool
745{
746 std::lock_guard lock(streamsMutex_);
747 PaStream* paStream = streams_[streamDirection];
748 if (!paStream)
749 return false;
751 if (ret == 1) {
752 JAMI_DBG("PortAudioLayer stream %d already stopped", streamDirection);
753 return true;
754 } else if (ret < 0) {
755 JAMI_ERR("Pa_IsStreamStopped error: %s", Pa_GetErrorText(ret));
756 return false;
757 }
758 auto err = Pa_StopStream(paStream);
759 if (err != paNoError) {
760 JAMI_ERR("Pa_StopStream error: %s", Pa_GetErrorText(err));
761 return false;
762 }
764 if (err != paNoError) {
765 JAMI_ERR("Pa_CloseStream error: %s", Pa_GetErrorText(err));
766 return false;
767 }
768 JAMI_DBG("PortAudioLayer stream %d stopped", streamDirection);
769 streams_[streamDirection] = nullptr;
770 return true;
771};
772
773bool
775{
776 std::lock_guard lock(streamsMutex_);
777 return streams_[Direction::IO] != nullptr;
778}
779
780int
782 const float* inputBuffer,
783 float* outputBuffer,
784 unsigned long framesPerBuffer,
787{
788 // unused arguments
790 (void) timeInfo;
792
793 auto toPlay = parent.getPlayback(parent.audioFormat_, framesPerBuffer);
794 if (!toPlay) {
795 // Fill silence for all channels in planar format
796 float** outputChannels = (float**) outputBuffer;
797 for (unsigned i = 0; i < parent.audioFormat_.nb_channels; ++i) {
798 std::fill_n(outputChannels[i], framesPerBuffer, 0.0f);
799 }
800 return paContinue;
801 }
802
803 auto numSamples = toPlay->pointer()->nb_samples;
804 auto channels = std::min<size_t>(parent.audioFormat_.nb_channels, toPlay->pointer()->ch_layout.nb_channels);
805 float** outputChannels = (float**) outputBuffer;
806 for (size_t i = 0; i < channels; ++i) {
807 std::copy_n((float*) toPlay->pointer()->extended_data[i], numSamples, outputChannels[i]);
808 }
809 return paContinue;
810}
811
812int
814 const float* inputBuffer,
815 float* outputBuffer,
816 unsigned long framesPerBuffer,
819{
820 // unused arguments
822 (void) timeInfo;
824
825 if (framesPerBuffer == 0) {
826 JAMI_WARN("No frames for input.");
827 return paContinue;
828 }
829
830 auto inBuff = std::make_shared<AudioFrame>(parent.audioInputFormat_, framesPerBuffer);
831 if (parent.isCaptureMuted_ || inputBuffer == nullptr) {
833 } else {
834 auto channels = parent.audioInputFormat_.nb_channels;
835 float** inputChannels = (float**) inputBuffer;
836 for (size_t i = 0; i < channels; ++i) {
837 std::copy_n(inputChannels[i], framesPerBuffer, (float*) inBuff->pointer()->extended_data[i]);
838 }
839 }
840 parent.putRecorded(std::move(inBuff));
841 return paContinue;
842}
843
844int
856
857} // 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:296
std::atomic< Status > status_
Whether or not the audio layer's playback stream is started.
Definition audiolayer.h:273
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.
std::condition_variable startedCv_
Definition audiolayer.h:274
static LIBJAMI_TEST_EXPORT Manager & instance()
Definition manager.cpp:694
std::string getAudioManager() const
Get the audio manager.
Definition manager.cpp:2425
void setAudioPlugin(const std::string &audioPlugin)
Set input audio plugin.
Definition manager.cpp:2161
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
void startStream(AudioDeviceType stream) override
Start the capture stream and prepare the playback stream.
int getAudioDeviceIndex(const std::string &name, AudioDeviceType type) const override
std::vector< std::string > getCaptureDeviceList() const override
std::string getAudioDeviceName(int index, AudioDeviceType type) const override
#define JAMI_ERR(...)
Definition logger.h:230
#define JAMI_DBG(...)
Definition logger.h:228
#define JAMI_WARN(...)
Definition logger.h:229
#define JAMI_LOG(formatstr,...)
Definition logger.h:237
void fillWithSilence(AVFrame *frame)
void emitSignal(Args... args)
Definition jami_signal.h:64
std::string to_string(double value)
AudioDeviceType
Definition audiolayer.h:57
static PaError openPaStream(PaStream **stream, PaDeviceIndex inputDeviceIndex, PaDeviceIndex outputDeviceIndex, PaStreamCallback *callback, void *user_data)
static bool startPaStream(PaStream *stream)
std::vector< std::string > getDevicesByType(AudioDeviceType type) const
static constexpr const int defaultIndex_
int paIOCallback(PortAudioLayer &parent, const float *inputBuffer, float *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags)
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
int paOutputCallback(PortAudioLayer &parent, const float *inputBuffer, float *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags)
AudioDeviceNotificationClientPtr audioDeviceNotificationClient_
std::array< PaStream *, static_cast< int >(Direction::End)> streams_
int paInputCallback(PortAudioLayer &parent, const float *inputBuffer, float *outputBuffer, unsigned long framesPerBuffer, const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags)