Ring Daemon
Loading...
Searching...
No Matches
pulselayer.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 "compiler_intrinsics.h"
19#include "audiostream.h"
20#include "pulselayer.h"
22#include "audio/ringbuffer.h"
23#include "libav_utils.h"
24#include "logger.h"
25#include "manager.h"
26
27#include <algorithm> // for std::find
28#include <stdexcept>
29
30#include <unistd.h>
31#include <cstdlib>
32#include <fstream>
33#include <cstring>
34
35#include <regex>
36
37// uncomment to log PulseAudio sink and sources
38// #define PA_LOG_SINK_SOURCES
39
40namespace jami {
41
42static const std::regex PA_EC_SUFFIX {"\\.echo-cancel(?:\\..+)?$"};
43
49
54
57 , playback_()
58 , record_()
59 , ringtone_()
61 , preference_(pref)
62{
63 JAMI_INFO("[audiolayer] Created PulseAudio layer");
64 if (!mainloop_)
65 throw std::runtime_error("Unable to create PulseAudio mainloop");
66
67 if (pa_threaded_mainloop_start(mainloop_.get()) < 0)
68 throw std::runtime_error("Failed to start PulseAudio mainloop");
69
70 setHasNativeNS(false);
71
72 PulseMainLoopLock lock(mainloop_.get());
73
74 std::unique_ptr<pa_proplist, decltype(pa_proplist_free)&> pl(pa_proplist_new(), pa_proplist_free);
75 pa_proplist_sets(pl.get(), PA_PROP_MEDIA_ROLE, "phone");
76
78 if (!context_)
79 throw std::runtime_error("Unable to create PulseAudio context");
80
81 pa_context_set_state_callback(context_, context_state_callback, this);
82
83 if (pa_context_connect(context_, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0)
84 throw std::runtime_error("Unable to connect PulseAudio context to the server");
85
86 // wait until context is ready
87 for (;;) {
90 throw std::runtime_error("Pulse audio context is bad");
92 break;
93 pa_threaded_mainloop_wait(mainloop_.get());
94 }
95}
96
98{
99 if (streamStarter_.joinable())
100 streamStarter_.join();
101
102 disconnectAudioStream();
103
104 {
105 PulseMainLoopLock lock(mainloop_.get());
108 pa_context_disconnect(context_);
109 pa_context_unref(context_);
110 }
111
112 if (subscribeOp_)
113 pa_operation_unref(subscribeOp_);
114
115 playbackChanged(false);
116 recordChanged(false);
117}
118
119void
120PulseLayer::context_state_callback(pa_context* c, void* user_data)
121{
122 PulseLayer* pulse = static_cast<PulseLayer*>(user_data);
123 if (c and pulse)
124 pulse->contextStateChanged(c);
125}
126
127void
128PulseLayer::contextStateChanged(pa_context* c)
129{
132
133 switch (pa_context_get_state(c)) {
137 JAMI_DBG("Waiting…");
138 break;
139
140 case PA_CONTEXT_READY:
141 JAMI_DBG("Connection to PulseAudio server established");
142 pa_threaded_mainloop_signal(mainloop_.get(), 0);
143 subscribeOp_ = pa_context_subscribe(c, mask, nullptr, this);
144 pa_context_set_subscribe_callback(c, context_changed_callback, this);
148 waitForDeviceList();
149 break;
150
152 if (subscribeOp_) {
153 pa_operation_unref(subscribeOp_);
154 subscribeOp_ = nullptr;
155 }
156 break;
157
159 default:
161 pa_threaded_mainloop_signal(mainloop_.get(), 0);
162 break;
163 }
164}
165
166void
168{
169 std::unique_lock lk(readyMtx_);
170 if (not enumeratingSinks_) {
171 JAMI_DBG("Updating PulseAudio sink list");
172 enumeratingSinks_ = true;
173 sinkList_.clear();
174 sinkList_.emplace_back();
175 sinkList_.front().channel_map.channels = std::min(defaultAudioFormat_.nb_channels, 2u);
176 if (auto op = pa_context_get_sink_info_list(context_, sink_input_info_callback, this))
178 else
179 enumeratingSinks_ = false;
180 }
181}
182
183void
185{
186 std::unique_lock lk(readyMtx_);
187 if (not enumeratingSources_) {
188 JAMI_DBG("Updating PulseAudio source list");
189 enumeratingSources_ = true;
190 sourceList_.clear();
191 sourceList_.emplace_back();
192 sourceList_.front().channel_map.channels = std::min(defaultAudioFormat_.nb_channels, 2u);
193 if (auto op = pa_context_get_source_info_list(context_, source_input_info_callback, this))
195 else
196 enumeratingSources_ = false;
197 }
198}
199
200void
202{
203 std::unique_lock lk(readyMtx_);
204 if (not gettingServerInfo_) {
205 JAMI_DBG("Updating PulseAudio server info");
206 gettingServerInfo_ = true;
207 if (auto op = pa_context_get_server_info(context_, server_info_callback, this))
209 else
210 gettingServerInfo_ = false;
211 }
212}
213
214bool
215PulseLayer::inSinkList(const std::string& deviceName)
216{
217 return std::find_if(sinkList_.begin(), sinkList_.end(), PaDeviceInfos::NameComparator(deviceName))
218 != sinkList_.end();
219}
220
221bool
222PulseLayer::inSourceList(const std::string& deviceName)
223{
224 return std::find_if(sourceList_.begin(), sourceList_.end(), PaDeviceInfos::NameComparator(deviceName))
225 != sourceList_.end();
226}
227
228std::vector<std::string>
230{
231 std::vector<std::string> names;
232 names.reserve(sourceList_.size());
233 for (const auto& s : sourceList_)
234 names.emplace_back(s.description);
235 return names;
236}
237
238std::vector<std::string>
240{
241 std::vector<std::string> names;
242 names.reserve(sinkList_.size());
243 for (const auto& s : sinkList_)
244 names.emplace_back(s.description);
245 return names;
246}
247
248int
250{
251 switch (type) {
254 return std::distance(sinkList_.begin(),
255 std::find_if(sinkList_.begin(),
256 sinkList_.end(),
259 return std::distance(sourceList_.begin(),
260 std::find_if(sourceList_.begin(),
261 sourceList_.end(),
263 default:
264 JAMI_ERR("Unexpected device type");
265 return 0;
266 }
267}
268
269int
270PulseLayer::getAudioDeviceIndexByName(const std::string& name, AudioDeviceType type) const
271{
272 if (name.empty())
273 return 0;
274 switch (type) {
277 return std::distance(sinkList_.begin(),
278 std::find_if(sinkList_.begin(), sinkList_.end(), PaDeviceInfos::NameComparator(name)));
280 return std::distance(sourceList_.begin(),
281 std::find_if(sourceList_.begin(), sourceList_.end(), PaDeviceInfos::NameComparator(name)));
282 default:
283 JAMI_ERR("Unexpected device type");
284 return 0;
285 }
286}
287
288bool
289endsWith(const std::string& str, const std::string& ending)
290{
291 if (ending.size() >= str.size())
292 return false;
293 return std::equal(ending.rbegin(), ending.rend(), str.rbegin());
294}
295
299const PaDeviceInfos*
300findBest(const std::vector<PaDeviceInfos>& list)
301{
302 if (list.empty())
303 return nullptr;
304 for (const auto& info : list)
305 if (info.monitor_of == PA_INVALID_INDEX)
306 return &info;
307 return &list[0];
308}
309
310const PaDeviceInfos*
311PulseLayer::getDeviceInfos(const std::vector<PaDeviceInfos>& list, const std::string& name) const
312{
313 auto dev_info = std::find_if(list.begin(), list.end(), PaDeviceInfos::NameComparator(name));
314 if (dev_info == list.end()) {
315 JAMI_WARN("Preferred device %s not found in device list, selecting default %s instead.",
316 name.c_str(),
317 list.front().name.c_str());
318 return &list.front();
319 }
320 return &(*dev_info);
321}
322
323std::string
325{
326 switch (type) {
329 if (index < 0 or static_cast<size_t>(index) >= sinkList_.size()) {
330 JAMI_ERR("Index %d out of range", index);
331 return "";
332 }
333 return sinkList_[index].name;
334
336 if (index < 0 or static_cast<size_t>(index) >= sourceList_.size()) {
337 JAMI_ERR("Index %d out of range", index);
338 return "";
339 }
340 return sourceList_[index].name;
341
342 default:
343 // Should never happen
344 JAMI_ERR("Unexpected type");
345 return "";
346 }
347}
348
349void
350PulseLayer::onStreamReady()
351{
352 if (--pendingStreams == 0) {
353 JAMI_DBG("All streams ready, starting audio");
354 // Flush outside the if statement: every time start stream is
355 // called is to notify a new event
356 flushUrgent();
357 flushMain();
358 if (playback_) {
359 playback_->start();
360 playbackChanged(true);
361 }
362 if (ringtone_) {
363 ringtone_->start();
364 }
365 if (record_) {
366 record_->start();
367 recordChanged(true);
368 }
369 }
370}
371
372void
373PulseLayer::createStream(std::unique_ptr<AudioStream>& stream,
374 AudioDeviceType type,
375 const PaDeviceInfos& dev_infos,
376 bool ec,
377 std::function<void(size_t)>&& onData)
378{
379 if (stream) {
380 JAMI_WARN("Stream already exists");
381 return;
382 }
383 pendingStreams++;
384 const char* name = type == AudioDeviceType::PLAYBACK
385 ? "Playback"
386 : (type == AudioDeviceType::CAPTURE
387 ? "Record"
388 : (type == AudioDeviceType::RINGTONE ? "Ringtone" : "?"));
389 stream.reset(new AudioStream(context_,
390 mainloop_.get(),
391 name,
392 type,
395 dev_infos,
396 ec,
397 std::bind(&PulseLayer::onStreamReady, this),
398 std::move(onData)));
399}
400
401void
402PulseLayer::disconnectAudioStream()
403{
404 PulseMainLoopLock lock(mainloop_.get());
405 playback_.reset();
406 ringtone_.reset();
407 record_.reset();
408 playbackChanged(false);
409 recordChanged(false);
410 pendingStreams = 0;
412 startedCv_.notify_all();
413}
414
415void
417{
418 waitForDevices();
419 PulseMainLoopLock lock(mainloop_.get());
420 bool ec = preference_.getEchoCanceller() == "system" || preference_.getEchoCanceller() == "auto";
421
422 // Create Streams
423 if (type == AudioDeviceType::PLAYBACK) {
424 if (auto dev_infos = getDeviceInfos(sinkList_, getPreferredPlaybackDevice())) {
425 createStream(playback_, type, *dev_infos, ec, std::bind(&PulseLayer::writeToSpeaker, this));
426 }
427 } else if (type == AudioDeviceType::RINGTONE) {
428 if (auto dev_infos = getDeviceInfos(sinkList_, getPreferredRingtoneDevice()))
429 createStream(ringtone_, type, *dev_infos, false, std::bind(&PulseLayer::ringtoneToSpeaker, this));
430 } else if (type == AudioDeviceType::CAPTURE) {
431 if (auto dev_infos = getDeviceInfos(sourceList_, getPreferredCaptureDevice())) {
432 createStream(record_, type, *dev_infos, ec, std::bind(&PulseLayer::readFromMic, this));
433
434 // whenever the stream is moved, it will call this cb
435 record_->setEchoCancelCb([this](bool echoCancel) { setHasNativeAEC(echoCancel); });
436 }
437 }
438 pa_threaded_mainloop_signal(mainloop_.get(), 0);
439
440 std::lock_guard lk(mutex_);
442 startedCv_.notify_all();
443}
444
445void
446PulseLayer::startCaptureStream(const std::string& id)
447{
448 if (loopbackCapture_.isRunning()) {
449 JAMI_WARNING("[pulselayer] Loopback capture already running");
450 return;
451 }
452
455 if (!ringBuffer) {
456 JAMI_ERROR("[pulselayer] Failed to get ring buffer for id {}", id);
457 return;
458 }
459
460 JAMI_DEBUG("[pulselayer] Starting loopback capture for ID {}", id);
461
462 const auto rate = loopbackCapture_.sampleRate();
463 const auto channels = loopbackCapture_.channels();
464
465 auto started = loopbackCapture_.startCaptureAsync([ringBuffer, rate, channels](const void* data, size_t length) {
466 if (!data || length == 0) {
467 JAMI_WARNING("[pulselayer] No audio data captured");
468 return;
469 }
470
471 const size_t frameSizeBytes = channels * sizeof(int16_t);
472 if (frameSizeBytes == 0) {
473 JAMI_ERROR("[pulselayer] Invalid frame size for captured audio");
474 return;
475 }
476
477 const size_t samples = length / frameSizeBytes;
478 if (samples == 0) {
479 JAMI_WARNING("[pulselayer] Ignoring empty capture buffer");
480 return;
481 }
482
483 auto capturedFrame = std::make_shared<AudioFrame>(AudioFormat {rate, channels, AV_SAMPLE_FMT_S16}, samples);
484
485 std::memcpy(capturedFrame->pointer()->data[0], data, length);
486 ringBuffer->put(std::move(capturedFrame));
487 });
488
489 if (!started) {
490 JAMI_ERROR("[pulselayer] Failed to start loopback capture");
491 rbPool.unBindAll(id);
492 }
493}
494
495void
496PulseLayer::stopCaptureStream(const std::string& id)
497{
498 if (!loopbackCapture_.isRunning()) {
499 JAMI_WARNING("[pulselayer] Loopback capture is not running");
500 return;
501 }
502
503 JAMI_DEBUG("[pulselayer] Stopping loopback capture for ID {}", id);
504
505 loopbackCapture_.stopCapture();
506
508 rbPool.unBindAll(id);
509}
510
511void
513{
514 waitForDevices();
515 PulseMainLoopLock lock(mainloop_.get());
516 auto& stream(getStream(type));
517 if (not stream)
518 return;
519
520 if (not stream->isReady())
521 pendingStreams--;
522 stream->stop();
523 stream.reset();
524
525 if (type == AudioDeviceType::PLAYBACK || type == AudioDeviceType::ALL)
526 playbackChanged(false);
527
528 std::lock_guard lk(mutex_);
529 if (not playback_ and not ringtone_ and not record_) {
530 pendingStreams = 0;
532 startedCv_.notify_all();
533 }
534}
535
536void
538{
539 if (!playback_ or !playback_->isReady())
540 return;
541
542 // available bytes to be written in PulseAudio internal buffer
543 void* data = nullptr;
544 size_t writableBytes = (size_t) -1;
545 int ret = pa_stream_begin_write(playback_->stream(), &data, &writableBytes);
546 if (ret == 0 and data and writableBytes != 0) {
547 writableBytes = std::min(pa_stream_writable_size(playback_->stream()), writableBytes);
548 const auto& buff = getToPlay(playback_->format(), writableBytes / playback_->frameSize());
550 memset(data, 0, writableBytes);
551 else
552 std::memcpy(data, buff->pointer()->data[0], buff->pointer()->nb_samples * playback_->frameSize());
553 pa_stream_write(playback_->stream(), data, writableBytes, nullptr, 0, PA_SEEK_RELATIVE);
554 }
555}
556
557void
559{
560 if (!record_ or !record_->isReady())
561 return;
562
563 const char* data = nullptr;
564 size_t bytes;
565 if (pa_stream_peek(record_->stream(), (const void**) &data, &bytes) < 0 or !data)
566 return;
567
568 if (bytes == 0)
569 return;
570
571 size_t sample_size = record_->frameSize();
572 const size_t samples = bytes / sample_size;
573
574 auto out = std::make_shared<AudioFrame>(record_->format(), samples);
575 if (isCaptureMuted_)
577 else
578 std::memcpy(out->pointer()->data[0], data, bytes);
579
580 if (pa_stream_drop(record_->stream()) < 0)
581 JAMI_ERR("Capture stream drop failed: %s", pa_strerror(pa_context_errno(context_)));
582
583 putRecorded(std::move(out));
584}
585
586void
588{
589 if (!ringtone_ or !ringtone_->isReady())
590 return;
591
592 void* data = nullptr;
593 size_t writableBytes = (size_t) -1;
594 int ret = pa_stream_begin_write(ringtone_->stream(), &data, &writableBytes);
595 if (ret == 0 and data and writableBytes != 0) {
596 writableBytes = std::min(pa_stream_writable_size(ringtone_->stream()), writableBytes);
597 const auto& buff = getToRing(ringtone_->format(), writableBytes / ringtone_->frameSize());
599 memset(data, 0, writableBytes);
600 else
601 std::memcpy(data, buff->pointer()->data[0], buff->pointer()->nb_samples * ringtone_->frameSize());
602 pa_stream_write(ringtone_->stream(), data, writableBytes, nullptr, 0, PA_SEEK_RELATIVE);
603 }
604}
605
606std::string
607stripEchoSufix(const std::string& deviceName)
608{
609 return std::regex_replace(deviceName, PA_EC_SUFFIX, "");
610}
611
612void
613PulseLayer::context_changed_callback(pa_context* c, pa_subscription_event_type_t type, uint32_t idx, void* userdata)
614{
615 static_cast<PulseLayer*>(userdata)->contextChanged(c, type, idx);
616}
617
618void
619PulseLayer::contextChanged(pa_context* c UNUSED, pa_subscription_event_type_t type, uint32_t idx UNUSED)
620{
621 bool reset = false;
622
625 switch (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) {
629 reset = true;
630 default:
631 break;
632 }
633
634 break;
635
637 switch (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) {
641 reset = true;
642 default:
643 break;
644 }
645
646 break;
647
648 default:
649 JAMI_DBG("Unhandled event type 0x%x", type);
650 break;
651 }
652
653 if (reset) {
655 waitForDeviceList();
656 }
657}
658
659void
660PulseLayer::waitForDevices()
661{
662 std::unique_lock lk(readyMtx_);
663 readyCv_.wait(lk, [this] { return !(enumeratingSinks_ or enumeratingSources_ or gettingServerInfo_); });
664}
665
666void
667PulseLayer::waitForDeviceList()
668{
669 std::unique_lock lock(readyMtx_);
670 if (waitingDeviceList_.exchange(true))
671 return;
672 if (streamStarter_.joinable())
673 streamStarter_.join();
674 streamStarter_ = std::thread([this]() mutable {
676
677 waitForDevices();
678 waitingDeviceList_ = false;
679
680 // If a current device changed, restart streams
682 auto playbackInfo = getDeviceInfos(sinkList_, getPreferredPlaybackDevice());
683 playbackDeviceChanged = playback_
684 and (!playbackInfo->name.empty()
685 and playbackInfo->name != stripEchoSufix(playback_->getDeviceName()));
686
687 auto recordInfo = getDeviceInfos(sourceList_, getPreferredCaptureDevice());
688 recordDeviceChanged = record_
689 and (!recordInfo->name.empty()
690 and recordInfo->name != stripEchoSufix(record_->getDeviceName()));
691
693 return;
695 JAMI_WARN("Playback devices changed, restarting streams.");
698 }
700 JAMI_WARN("Record devices changed, restarting streams.");
703 }
704 });
705}
706
707void
708PulseLayer::server_info_callback(pa_context*, const pa_server_info* i, void* userdata)
709{
710 if (!i)
711 return;
713 JAMI_DBG("PulseAudio server info:"
714 "\n Server name: %s"
715 "\n Server version: %s"
716 "\n Default sink: %s"
717 "\n Default source: %s"
718 "\n Default sample specification: %s"
719 "\n Default channel map: %s",
720 i->server_name,
721 i->server_version,
722 i->default_sink_name,
723 i->default_source_name,
724 pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec),
725 pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map));
726
727 PulseLayer* context = static_cast<PulseLayer*>(userdata);
728 std::lock_guard lk(context->readyMtx_);
729 context->defaultSink_ = {};
730 context->defaultSource_ = {};
731 context->defaultAudioFormat_ = {i->sample_spec.rate,
732 i->sample_spec.channels,
733 sampleFormatFromPulse(i->sample_spec.format)};
734 {
735 std::lock_guard lk(context->mutex_);
736 context->hardwareFormatAvailable(context->defaultAudioFormat_);
737 }
738 /*if (not context->sinkList_.empty())
739 context->sinkList_.front().channel_map.channels = std::min(i->sample_spec.channels,
740 (uint8_t) 2);
741 if (not context->sourceList_.empty())
742 context->sourceList_.front().channel_map.channels = std::min(i->sample_spec.channels,
743 (uint8_t) 2);*/
744 context->gettingServerInfo_ = false;
745 context->readyCv_.notify_all();
746}
747
748void
749PulseLayer::source_input_info_callback(pa_context* c UNUSED, const pa_source_info* i, int eol, void* userdata)
750{
751 PulseLayer* context = static_cast<PulseLayer*>(userdata);
752
753 if (eol) {
754 std::lock_guard lk(context->readyMtx_);
755 context->enumeratingSources_ = false;
756 context->readyCv_.notify_all();
757 return;
758 }
759#ifdef PA_LOG_SINK_SOURCES
761 JAMI_DBG("Source %u\n"
762 " Name: %s\n"
763 " Driver: %s\n"
764 " Description: %s\n"
765 " Sample Specification: %s\n"
766 " Channel Map: %s\n"
767 " Owner Module: %u\n"
768 " Volume: %s\n"
769 " Monitor if Sink: %u\n"
770 " Latency: %0.0f usec\n"
771 " Flags: %s%s%s\n",
772 i->index,
773 i->name,
774 i->driver,
775 i->description,
776 pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec),
777 pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map),
778 i->owner_module,
779 i->mute ? "muted" : pa_cvolume_snprint(cv, sizeof(cv), &i->volume),
781 (double) i->latency,
782 i->flags & PA_SOURCE_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "",
783 i->flags & PA_SOURCE_LATENCY ? "LATENCY " : "",
784 i->flags & PA_SOURCE_HARDWARE ? "HARDWARE" : "");
785#endif
786 if (not context->inSourceList(i->name)) {
787 context->sourceList_.emplace_back(*i);
788 }
789}
790
791void
792PulseLayer::sink_input_info_callback(pa_context* c UNUSED, const pa_sink_info* i, int eol, void* userdata)
793{
794 PulseLayer* context = static_cast<PulseLayer*>(userdata);
795 std::lock_guard lk(context->readyMtx_);
796
797 if (eol) {
798 context->enumeratingSinks_ = false;
799 context->readyCv_.notify_all();
800 return;
801 }
802#ifdef PA_LOG_SINK_SOURCES
804 JAMI_DBG("Sink %u\n"
805 " Name: %s\n"
806 " Driver: %s\n"
807 " Description: %s\n"
808 " Sample Specification: %s\n"
809 " Channel Map: %s\n"
810 " Owner Module: %u\n"
811 " Volume: %s\n"
812 " Monitor Source: %u\n"
813 " Latency: %0.0f usec\n"
814 " Flags: %s%s%s\n",
815 i->index,
816 i->name,
817 i->driver,
818 i->description,
819 pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec),
820 pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map),
821 i->owner_module,
822 i->mute ? "muted" : pa_cvolume_snprint(cv, sizeof(cv), &i->volume),
824 static_cast<double>(i->latency),
825 i->flags & PA_SINK_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "",
826 i->flags & PA_SINK_LATENCY ? "LATENCY " : "",
827 i->flags & PA_SINK_HARDWARE ? "HARDWARE" : "");
828#endif
829 if (not context->inSinkList(i->name)) {
830 context->sinkList_.emplace_back(*i);
831 }
832}
833
834void
835PulseLayer::updatePreference(AudioPreference& preference, int index, AudioDeviceType type)
836{
837 const std::string devName(getAudioDeviceName(index, type));
838
839 switch (type) {
841 JAMI_DBG("setting %s for playback", devName.c_str());
842 preference.setPulseDevicePlayback(devName);
843 break;
844
846 JAMI_DBG("setting %s for capture", devName.c_str());
847 preference.setPulseDeviceRecord(devName);
848 break;
849
851 JAMI_DBG("setting %s for ringer", devName.c_str());
852 preference.setPulseDeviceRingtone(devName);
853 break;
854
855 default:
856 break;
857 }
858}
859
860int
861PulseLayer::getIndexCapture() const
862{
864}
865
866int
867PulseLayer::getIndexPlayback() const
868{
870}
871
872int
873PulseLayer::getIndexRingtone() const
874{
876}
877
878std::string
879PulseLayer::getPreferredPlaybackDevice() const
880{
881 const std::string& device(preference_.getPulseDevicePlayback());
882 return stripEchoSufix(device.empty() ? defaultSink_ : device);
883}
884
885std::string
886PulseLayer::getPreferredRingtoneDevice() const
887{
888 const std::string& device(preference_.getPulseDeviceRingtone());
889 return stripEchoSufix(device.empty() ? defaultSink_ : device);
890}
891
892std::string
893PulseLayer::getPreferredCaptureDevice() const
894{
895 const std::string& device(preference_.getPulseDeviceRecord());
896 return stripEchoSufix(device.empty() ? defaultSource_ : device);
897}
898
899} // namespace jami
uint32_t sampleRate() const
bool startCaptureAsync(AudioFrameCallback callback)
bool isCaptureMuted_
True if capture is not to be used.
Definition audiolayer.h:234
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)
bool isPlaybackMuted_
True if playback is not to be used.
Definition audiolayer.h:239
std::shared_ptr< AudioFrame > getToRing(AudioFormat format, size_t writableSamples)
void flushUrgent()
Flush urgent buffer.
void setHasNativeAEC(bool hasEAC)
std::shared_ptr< AudioFrame > getToPlay(AudioFormat format, size_t writableSamples)
bool isRingtoneMuted_
True if ringtone should be muted.
Definition audiolayer.h:244
void recordChanged(bool started)
AudioFormat audioFormat_
Sample Rate that should be sent to the sound card.
Definition audiolayer.h:279
void flushMain()
Flush main buffer.
std::condition_variable startedCv_
Definition audiolayer.h:274
void putRecorded(std::shared_ptr< AudioFrame > &&frame)
const std::string & getPulseDeviceRecord() const
const std::string & getPulseDeviceRingtone() const
const std::string & getEchoCanceller() const
const std::string & getPulseDevicePlayback() const
static LIBJAMI_TEST_EXPORT Manager & instance()
Definition manager.cpp:694
RingBufferPool & getRingBufferPool()
Return a pointer to the instance of the RingBufferPool.
Definition manager.cpp:3197
Unary function to search for a device by name in a list using std functions.
Definition pulselayer.h:74
virtual void stopStream(AudioDeviceType stream=AudioDeviceType::ALL)
Stop the playback and capture streams.
std::string getAudioDeviceName(int index, AudioDeviceType type) const
virtual void startStream(AudioDeviceType stream)
Start the capture stream and prepare the playback stream.
virtual std::vector< std::string > getPlaybackDeviceList() const
PulseLayer(AudioPreference &pref)
virtual std::vector< std::string > getCaptureDeviceList() const
bool inSourceList(const std::string &deviceName)
virtual void stopCaptureStream(const std::string &id) override
Stop an ongoing capture stream on the given device.
void readFromMic()
Write data from the ring buffer to the hardware and read data from the hardware.
virtual void startCaptureStream(const std::string &id) override
Start a capture stream on the given device (eg.
int getAudioDeviceIndex(const std::string &descr, AudioDeviceType type) const
int getAudioDeviceIndexByName(const std::string &name, AudioDeviceType type) const
bool inSinkList(const std::string &deviceName)
PulseMainLoopLock(pa_threaded_mainloop *loop)
std::shared_ptr< RingBuffer > createRingBuffer(const std::string &id)
Create a new ringbuffer with a default readoffset.
void unBindAll(const std::string &ringbufferId)
#define UNUSED
#define JAMI_ERR(...)
Definition logger.h:230
#define JAMI_ERROR(formatstr,...)
Definition logger.h:243
#define JAMI_DBG(...)
Definition logger.h:228
#define JAMI_DEBUG(formatstr,...)
Definition logger.h:238
#define JAMI_WARN(...)
Definition logger.h:229
#define JAMI_WARNING(formatstr,...)
Definition logger.h:242
#define JAMI_INFO(...)
Definition logger.h:227
void fillWithSilence(AVFrame *frame)
AVSampleFormat sampleFormatFromPulse(pa_sample_format_t format)
Definition audiostream.h:28
const PaDeviceInfos * findBest(const std::vector< PaDeviceInfos > &list)
Find default device for PulseAudio to open, filter monitors and EC.
std::string stripEchoSufix(const std::string &deviceName)
void emitSignal(Args... args)
Definition jami_signal.h:64
static const std::regex PA_EC_SUFFIX
pa_sample_format_t pulseSampleFormatFromAv(AVSampleFormat format)
Definition audiostream.h:46
AudioDeviceType
Definition audiolayer.h:57
bool endsWith(const std::string &str, const std::string &ending)
Structure to hold sample rate and channel number associated with audio data.
AVSampleFormat sampleFormat