Line data Source code
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 "compiler_intrinsics.h"
19 : #include "audiostream.h"
20 : #include "pulselayer.h"
21 : #include "audio/ringbufferpool.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 :
40 : namespace jami {
41 :
42 : static const std::regex PA_EC_SUFFIX {"\\.echo-cancel(?:\\..+)?$"};
43 :
44 3 : PulseMainLoopLock::PulseMainLoopLock(pa_threaded_mainloop* loop)
45 3 : : loop_(loop)
46 : {
47 3 : pa_threaded_mainloop_lock(loop_);
48 3 : }
49 :
50 3 : PulseMainLoopLock::~PulseMainLoopLock()
51 : {
52 3 : pa_threaded_mainloop_unlock(loop_);
53 3 : }
54 :
55 3 : PulseLayer::PulseLayer(AudioPreference& pref)
56 : : AudioLayer(pref)
57 3 : , playback_()
58 3 : , record_()
59 3 : , ringtone_()
60 3 : , mainloop_(pa_threaded_mainloop_new(), pa_threaded_mainloop_free)
61 9 : , preference_(pref)
62 : {
63 3 : JAMI_INFO("[audiolayer] Created PulseAudio layer");
64 3 : if (!mainloop_)
65 0 : throw std::runtime_error("Unable to create PulseAudio mainloop");
66 :
67 3 : if (pa_threaded_mainloop_start(mainloop_.get()) < 0)
68 0 : throw std::runtime_error("Failed to start PulseAudio mainloop");
69 :
70 3 : setHasNativeNS(false);
71 :
72 3 : PulseMainLoopLock lock(mainloop_.get());
73 :
74 3 : std::unique_ptr<pa_proplist, decltype(pa_proplist_free)&> pl(pa_proplist_new(), pa_proplist_free);
75 3 : pa_proplist_sets(pl.get(), PA_PROP_MEDIA_ROLE, "phone");
76 :
77 3 : context_ = pa_context_new_with_proplist(pa_threaded_mainloop_get_api(mainloop_.get()), PACKAGE_NAME, pl.get());
78 3 : if (!context_)
79 0 : throw std::runtime_error("Unable to create PulseAudio context");
80 :
81 3 : pa_context_set_state_callback(context_, context_state_callback, this);
82 :
83 3 : if (pa_context_connect(context_, nullptr, PA_CONTEXT_NOFLAGS, nullptr) < 0)
84 3 : throw std::runtime_error("Unable to connect PulseAudio context to the server");
85 :
86 : // wait until context is ready
87 : for (;;) {
88 0 : pa_context_state_t context_state = pa_context_get_state(context_);
89 0 : if (not PA_CONTEXT_IS_GOOD(context_state))
90 0 : throw std::runtime_error("Pulse audio context is bad");
91 0 : if (context_state == PA_CONTEXT_READY)
92 0 : break;
93 0 : pa_threaded_mainloop_wait(mainloop_.get());
94 0 : }
95 39 : }
96 :
97 0 : PulseLayer::~PulseLayer()
98 : {
99 0 : if (streamStarter_.joinable())
100 0 : streamStarter_.join();
101 :
102 0 : disconnectAudioStream();
103 :
104 : {
105 0 : PulseMainLoopLock lock(mainloop_.get());
106 0 : pa_context_set_state_callback(context_, NULL, NULL);
107 0 : pa_context_set_subscribe_callback(context_, NULL, NULL);
108 0 : pa_context_disconnect(context_);
109 0 : pa_context_unref(context_);
110 0 : }
111 :
112 0 : if (subscribeOp_)
113 0 : pa_operation_unref(subscribeOp_);
114 :
115 0 : playbackChanged(false);
116 0 : recordChanged(false);
117 0 : }
118 :
119 : void
120 6 : PulseLayer::context_state_callback(pa_context* c, void* user_data)
121 : {
122 6 : PulseLayer* pulse = static_cast<PulseLayer*>(user_data);
123 6 : if (c and pulse)
124 6 : pulse->contextStateChanged(c);
125 6 : }
126 :
127 : void
128 6 : PulseLayer::contextStateChanged(pa_context* c)
129 : {
130 6 : const pa_subscription_mask_t mask = (pa_subscription_mask_t) (PA_SUBSCRIPTION_MASK_SINK
131 : | PA_SUBSCRIPTION_MASK_SOURCE);
132 :
133 6 : switch (pa_context_get_state(c)) {
134 3 : case PA_CONTEXT_CONNECTING:
135 : case PA_CONTEXT_AUTHORIZING:
136 : case PA_CONTEXT_SETTING_NAME:
137 3 : JAMI_DBG("Waiting…");
138 3 : break;
139 :
140 0 : case PA_CONTEXT_READY:
141 0 : JAMI_DBG("Connection to PulseAudio server established");
142 0 : pa_threaded_mainloop_signal(mainloop_.get(), 0);
143 0 : subscribeOp_ = pa_context_subscribe(c, mask, nullptr, this);
144 0 : pa_context_set_subscribe_callback(c, context_changed_callback, this);
145 0 : updateSinkList();
146 0 : updateSourceList();
147 0 : updateServerInfo();
148 0 : waitForDeviceList();
149 0 : break;
150 :
151 0 : case PA_CONTEXT_TERMINATED:
152 0 : if (subscribeOp_) {
153 0 : pa_operation_unref(subscribeOp_);
154 0 : subscribeOp_ = nullptr;
155 : }
156 0 : break;
157 :
158 3 : case PA_CONTEXT_FAILED:
159 : default:
160 3 : JAMI_ERR("%s", pa_strerror(pa_context_errno(c)));
161 3 : pa_threaded_mainloop_signal(mainloop_.get(), 0);
162 3 : break;
163 : }
164 6 : }
165 :
166 : void
167 0 : PulseLayer::updateSinkList()
168 : {
169 0 : std::unique_lock lk(readyMtx_);
170 0 : if (not enumeratingSinks_) {
171 0 : JAMI_DBG("Updating PulseAudio sink list");
172 0 : enumeratingSinks_ = true;
173 0 : sinkList_.clear();
174 0 : sinkList_.emplace_back();
175 0 : sinkList_.front().channel_map.channels = std::min(defaultAudioFormat_.nb_channels, 2u);
176 0 : if (auto op = pa_context_get_sink_info_list(context_, sink_input_info_callback, this))
177 0 : pa_operation_unref(op);
178 : else
179 0 : enumeratingSinks_ = false;
180 : }
181 0 : }
182 :
183 : void
184 0 : PulseLayer::updateSourceList()
185 : {
186 0 : std::unique_lock lk(readyMtx_);
187 0 : if (not enumeratingSources_) {
188 0 : JAMI_DBG("Updating PulseAudio source list");
189 0 : enumeratingSources_ = true;
190 0 : sourceList_.clear();
191 0 : sourceList_.emplace_back();
192 0 : sourceList_.front().channel_map.channels = std::min(defaultAudioFormat_.nb_channels, 2u);
193 0 : if (auto op = pa_context_get_source_info_list(context_, source_input_info_callback, this))
194 0 : pa_operation_unref(op);
195 : else
196 0 : enumeratingSources_ = false;
197 : }
198 0 : }
199 :
200 : void
201 0 : PulseLayer::updateServerInfo()
202 : {
203 0 : std::unique_lock lk(readyMtx_);
204 0 : if (not gettingServerInfo_) {
205 0 : JAMI_DBG("Updating PulseAudio server info");
206 0 : gettingServerInfo_ = true;
207 0 : if (auto op = pa_context_get_server_info(context_, server_info_callback, this))
208 0 : pa_operation_unref(op);
209 : else
210 0 : gettingServerInfo_ = false;
211 : }
212 0 : }
213 :
214 : bool
215 0 : PulseLayer::inSinkList(const std::string& deviceName)
216 : {
217 0 : return std::find_if(sinkList_.begin(), sinkList_.end(), PaDeviceInfos::NameComparator(deviceName))
218 0 : != sinkList_.end();
219 : }
220 :
221 : bool
222 0 : PulseLayer::inSourceList(const std::string& deviceName)
223 : {
224 0 : return std::find_if(sourceList_.begin(), sourceList_.end(), PaDeviceInfos::NameComparator(deviceName))
225 0 : != sourceList_.end();
226 : }
227 :
228 : std::vector<std::string>
229 0 : PulseLayer::getCaptureDeviceList() const
230 : {
231 0 : std::vector<std::string> names;
232 0 : names.reserve(sourceList_.size());
233 0 : for (const auto& s : sourceList_)
234 0 : names.emplace_back(s.description);
235 0 : return names;
236 0 : }
237 :
238 : std::vector<std::string>
239 0 : PulseLayer::getPlaybackDeviceList() const
240 : {
241 0 : std::vector<std::string> names;
242 0 : names.reserve(sinkList_.size());
243 0 : for (const auto& s : sinkList_)
244 0 : names.emplace_back(s.description);
245 0 : return names;
246 0 : }
247 :
248 : int
249 0 : PulseLayer::getAudioDeviceIndex(const std::string& descr, AudioDeviceType type) const
250 : {
251 0 : switch (type) {
252 0 : case AudioDeviceType::PLAYBACK:
253 : case AudioDeviceType::RINGTONE:
254 0 : return std::distance(sinkList_.begin(),
255 : std::find_if(sinkList_.begin(),
256 : sinkList_.end(),
257 0 : PaDeviceInfos::DescriptionComparator(descr)));
258 0 : case AudioDeviceType::CAPTURE:
259 0 : return std::distance(sourceList_.begin(),
260 : std::find_if(sourceList_.begin(),
261 : sourceList_.end(),
262 0 : PaDeviceInfos::DescriptionComparator(descr)));
263 0 : default:
264 0 : JAMI_ERR("Unexpected device type");
265 0 : return 0;
266 : }
267 : }
268 :
269 : int
270 0 : PulseLayer::getAudioDeviceIndexByName(const std::string& name, AudioDeviceType type) const
271 : {
272 0 : if (name.empty())
273 0 : return 0;
274 0 : switch (type) {
275 0 : case AudioDeviceType::PLAYBACK:
276 : case AudioDeviceType::RINGTONE:
277 0 : return std::distance(sinkList_.begin(),
278 0 : std::find_if(sinkList_.begin(), sinkList_.end(), PaDeviceInfos::NameComparator(name)));
279 0 : case AudioDeviceType::CAPTURE:
280 0 : return std::distance(sourceList_.begin(),
281 0 : std::find_if(sourceList_.begin(), sourceList_.end(), PaDeviceInfos::NameComparator(name)));
282 0 : default:
283 0 : JAMI_ERR("Unexpected device type");
284 0 : return 0;
285 : }
286 : }
287 :
288 : bool
289 0 : endsWith(const std::string& str, const std::string& ending)
290 : {
291 0 : if (ending.size() >= str.size())
292 0 : return false;
293 0 : return std::equal(ending.rbegin(), ending.rend(), str.rbegin());
294 : }
295 :
296 : /**
297 : * Find default device for PulseAudio to open, filter monitors and EC.
298 : */
299 : const PaDeviceInfos*
300 0 : findBest(const std::vector<PaDeviceInfos>& list)
301 : {
302 0 : if (list.empty())
303 0 : return nullptr;
304 0 : for (const auto& info : list)
305 0 : if (info.monitor_of == PA_INVALID_INDEX)
306 0 : return &info;
307 0 : return &list[0];
308 : }
309 :
310 : const PaDeviceInfos*
311 0 : PulseLayer::getDeviceInfos(const std::vector<PaDeviceInfos>& list, const std::string& name) const
312 : {
313 0 : auto dev_info = std::find_if(list.begin(), list.end(), PaDeviceInfos::NameComparator(name));
314 0 : if (dev_info == list.end()) {
315 0 : 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 0 : return &list.front();
319 : }
320 0 : return &(*dev_info);
321 : }
322 :
323 : std::string
324 0 : PulseLayer::getAudioDeviceName(int index, AudioDeviceType type) const
325 : {
326 0 : switch (type) {
327 0 : case AudioDeviceType::PLAYBACK:
328 : case AudioDeviceType::RINGTONE:
329 0 : if (index < 0 or static_cast<size_t>(index) >= sinkList_.size()) {
330 0 : JAMI_ERR("Index %d out of range", index);
331 0 : return "";
332 : }
333 0 : return sinkList_[index].name;
334 :
335 0 : case AudioDeviceType::CAPTURE:
336 0 : if (index < 0 or static_cast<size_t>(index) >= sourceList_.size()) {
337 0 : JAMI_ERR("Index %d out of range", index);
338 0 : return "";
339 : }
340 0 : return sourceList_[index].name;
341 :
342 0 : default:
343 : // Should never happen
344 0 : JAMI_ERR("Unexpected type");
345 0 : return "";
346 : }
347 : }
348 :
349 : void
350 0 : PulseLayer::onStreamReady()
351 : {
352 0 : if (--pendingStreams == 0) {
353 0 : 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 0 : flushUrgent();
357 0 : flushMain();
358 0 : if (playback_) {
359 0 : playback_->start();
360 0 : playbackChanged(true);
361 : }
362 0 : if (ringtone_) {
363 0 : ringtone_->start();
364 : }
365 0 : if (record_) {
366 0 : record_->start();
367 0 : recordChanged(true);
368 : }
369 : }
370 0 : }
371 :
372 : void
373 0 : PulseLayer::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 0 : if (stream) {
380 0 : JAMI_WARN("Stream already exists");
381 0 : return;
382 : }
383 0 : pendingStreams++;
384 0 : const char* name = type == AudioDeviceType::PLAYBACK
385 0 : ? "Playback"
386 : : (type == AudioDeviceType::CAPTURE
387 0 : ? "Record"
388 0 : : (type == AudioDeviceType::RINGTONE ? "Ringtone" : "?"));
389 0 : stream.reset(new AudioStream(context_,
390 0 : mainloop_.get(),
391 : name,
392 : type,
393 : audioFormat_.sample_rate,
394 0 : pulseSampleFormatFromAv(audioFormat_.sampleFormat),
395 : dev_infos,
396 : ec,
397 0 : std::bind(&PulseLayer::onStreamReady, this),
398 0 : std::move(onData)));
399 : }
400 :
401 : void
402 0 : PulseLayer::disconnectAudioStream()
403 : {
404 0 : PulseMainLoopLock lock(mainloop_.get());
405 0 : playback_.reset();
406 0 : ringtone_.reset();
407 0 : record_.reset();
408 0 : playbackChanged(false);
409 0 : recordChanged(false);
410 0 : pendingStreams = 0;
411 0 : status_ = Status::Idle;
412 0 : startedCv_.notify_all();
413 0 : }
414 :
415 : void
416 0 : PulseLayer::startStream(AudioDeviceType type)
417 : {
418 0 : waitForDevices();
419 0 : PulseMainLoopLock lock(mainloop_.get());
420 0 : bool ec = preference_.getEchoCanceller() == "system" || preference_.getEchoCanceller() == "auto";
421 :
422 : // Create Streams
423 0 : if (type == AudioDeviceType::PLAYBACK) {
424 0 : if (auto dev_infos = getDeviceInfos(sinkList_, getPreferredPlaybackDevice())) {
425 0 : createStream(playback_, type, *dev_infos, ec, std::bind(&PulseLayer::writeToSpeaker, this));
426 : }
427 0 : } else if (type == AudioDeviceType::RINGTONE) {
428 0 : if (auto dev_infos = getDeviceInfos(sinkList_, getPreferredRingtoneDevice()))
429 0 : createStream(ringtone_, type, *dev_infos, false, std::bind(&PulseLayer::ringtoneToSpeaker, this));
430 0 : } else if (type == AudioDeviceType::CAPTURE) {
431 0 : if (auto dev_infos = getDeviceInfos(sourceList_, getPreferredCaptureDevice())) {
432 0 : createStream(record_, type, *dev_infos, ec, std::bind(&PulseLayer::readFromMic, this));
433 :
434 : // whenever the stream is moved, it will call this cb
435 0 : record_->setEchoCancelCb([this](bool echoCancel) { setHasNativeAEC(echoCancel); });
436 : }
437 : }
438 0 : pa_threaded_mainloop_signal(mainloop_.get(), 0);
439 :
440 0 : std::lock_guard lk(mutex_);
441 0 : status_ = Status::Started;
442 0 : startedCv_.notify_all();
443 0 : }
444 :
445 : void
446 0 : PulseLayer::stopStream(AudioDeviceType type)
447 : {
448 0 : waitForDevices();
449 0 : PulseMainLoopLock lock(mainloop_.get());
450 0 : auto& stream(getStream(type));
451 0 : if (not stream)
452 0 : return;
453 :
454 0 : if (not stream->isReady())
455 0 : pendingStreams--;
456 0 : stream->stop();
457 0 : stream.reset();
458 :
459 0 : if (type == AudioDeviceType::PLAYBACK || type == AudioDeviceType::ALL)
460 0 : playbackChanged(false);
461 :
462 0 : std::lock_guard lk(mutex_);
463 0 : if (not playback_ and not ringtone_ and not record_) {
464 0 : pendingStreams = 0;
465 0 : status_ = Status::Idle;
466 0 : startedCv_.notify_all();
467 : }
468 0 : }
469 :
470 : void
471 0 : PulseLayer::writeToSpeaker()
472 : {
473 0 : if (!playback_ or !playback_->isReady())
474 0 : return;
475 :
476 : // available bytes to be written in PulseAudio internal buffer
477 0 : void* data = nullptr;
478 0 : size_t writableBytes = (size_t) -1;
479 0 : int ret = pa_stream_begin_write(playback_->stream(), &data, &writableBytes);
480 0 : if (ret == 0 and data and writableBytes != 0) {
481 0 : writableBytes = std::min(pa_stream_writable_size(playback_->stream()), writableBytes);
482 0 : const auto& buff = getToPlay(playback_->format(), writableBytes / playback_->frameSize());
483 0 : if (not buff or isPlaybackMuted_)
484 0 : memset(data, 0, writableBytes);
485 : else
486 0 : std::memcpy(data, buff->pointer()->data[0], buff->pointer()->nb_samples * playback_->frameSize());
487 0 : pa_stream_write(playback_->stream(), data, writableBytes, nullptr, 0, PA_SEEK_RELATIVE);
488 0 : }
489 : }
490 :
491 : void
492 0 : PulseLayer::readFromMic()
493 : {
494 0 : if (!record_ or !record_->isReady())
495 0 : return;
496 :
497 0 : const char* data = nullptr;
498 : size_t bytes;
499 0 : if (pa_stream_peek(record_->stream(), (const void**) &data, &bytes) < 0 or !data)
500 0 : return;
501 :
502 0 : if (bytes == 0)
503 0 : return;
504 :
505 0 : size_t sample_size = record_->frameSize();
506 0 : const size_t samples = bytes / sample_size;
507 :
508 0 : auto out = std::make_shared<AudioFrame>(record_->format(), samples);
509 0 : if (isCaptureMuted_)
510 0 : libav_utils::fillWithSilence(out->pointer());
511 : else
512 0 : std::memcpy(out->pointer()->data[0], data, bytes);
513 :
514 0 : if (pa_stream_drop(record_->stream()) < 0)
515 0 : JAMI_ERR("Capture stream drop failed: %s", pa_strerror(pa_context_errno(context_)));
516 :
517 0 : putRecorded(std::move(out));
518 0 : }
519 :
520 : void
521 0 : PulseLayer::ringtoneToSpeaker()
522 : {
523 0 : if (!ringtone_ or !ringtone_->isReady())
524 0 : return;
525 :
526 0 : void* data = nullptr;
527 0 : size_t writableBytes = (size_t) -1;
528 0 : int ret = pa_stream_begin_write(ringtone_->stream(), &data, &writableBytes);
529 0 : if (ret == 0 and data and writableBytes != 0) {
530 0 : writableBytes = std::min(pa_stream_writable_size(ringtone_->stream()), writableBytes);
531 0 : const auto& buff = getToRing(ringtone_->format(), writableBytes / ringtone_->frameSize());
532 0 : if (not buff or isRingtoneMuted_)
533 0 : memset(data, 0, writableBytes);
534 : else
535 0 : std::memcpy(data, buff->pointer()->data[0], buff->pointer()->nb_samples * ringtone_->frameSize());
536 0 : pa_stream_write(ringtone_->stream(), data, writableBytes, nullptr, 0, PA_SEEK_RELATIVE);
537 0 : }
538 : }
539 :
540 : std::string
541 0 : stripEchoSufix(const std::string& deviceName)
542 : {
543 0 : return std::regex_replace(deviceName, PA_EC_SUFFIX, "");
544 : }
545 :
546 : void
547 0 : PulseLayer::context_changed_callback(pa_context* c, pa_subscription_event_type_t type, uint32_t idx, void* userdata)
548 : {
549 0 : static_cast<PulseLayer*>(userdata)->contextChanged(c, type, idx);
550 0 : }
551 :
552 : void
553 0 : PulseLayer::contextChanged(pa_context* c UNUSED, pa_subscription_event_type_t type, uint32_t idx UNUSED)
554 : {
555 0 : bool reset = false;
556 :
557 0 : switch (type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
558 0 : case PA_SUBSCRIPTION_EVENT_SINK:
559 0 : switch (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) {
560 0 : case PA_SUBSCRIPTION_EVENT_NEW:
561 : case PA_SUBSCRIPTION_EVENT_REMOVE:
562 0 : updateSinkList();
563 0 : reset = true;
564 0 : default:
565 0 : break;
566 : }
567 :
568 0 : break;
569 :
570 0 : case PA_SUBSCRIPTION_EVENT_SOURCE:
571 0 : switch (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) {
572 0 : case PA_SUBSCRIPTION_EVENT_NEW:
573 : case PA_SUBSCRIPTION_EVENT_REMOVE:
574 0 : updateSourceList();
575 0 : reset = true;
576 0 : default:
577 0 : break;
578 : }
579 :
580 0 : break;
581 :
582 0 : default:
583 0 : JAMI_DBG("Unhandled event type 0x%x", type);
584 0 : break;
585 : }
586 :
587 0 : if (reset) {
588 0 : updateServerInfo();
589 0 : waitForDeviceList();
590 : }
591 0 : }
592 :
593 : void
594 0 : PulseLayer::waitForDevices()
595 : {
596 0 : std::unique_lock lk(readyMtx_);
597 0 : readyCv_.wait(lk, [this] { return !(enumeratingSinks_ or enumeratingSources_ or gettingServerInfo_); });
598 0 : }
599 :
600 : void
601 0 : PulseLayer::waitForDeviceList()
602 : {
603 0 : std::unique_lock lock(readyMtx_);
604 0 : if (waitingDeviceList_.exchange(true))
605 0 : return;
606 0 : if (streamStarter_.joinable())
607 0 : streamStarter_.join();
608 0 : streamStarter_ = std::thread([this]() mutable {
609 : bool playbackDeviceChanged, recordDeviceChanged;
610 :
611 0 : waitForDevices();
612 0 : waitingDeviceList_ = false;
613 :
614 : // If a current device changed, restart streams
615 0 : devicesChanged();
616 0 : auto playbackInfo = getDeviceInfos(sinkList_, getPreferredPlaybackDevice());
617 0 : playbackDeviceChanged = playback_
618 0 : and (!playbackInfo->name.empty()
619 0 : and playbackInfo->name != stripEchoSufix(playback_->getDeviceName()));
620 :
621 0 : auto recordInfo = getDeviceInfos(sourceList_, getPreferredCaptureDevice());
622 0 : recordDeviceChanged = record_
623 0 : and (!recordInfo->name.empty()
624 0 : and recordInfo->name != stripEchoSufix(record_->getDeviceName()));
625 :
626 0 : if (status_ != Status::Started)
627 0 : return;
628 0 : if (playbackDeviceChanged) {
629 0 : JAMI_WARN("Playback devices changed, restarting streams.");
630 0 : stopStream(AudioDeviceType::PLAYBACK);
631 0 : startStream(AudioDeviceType::PLAYBACK);
632 : }
633 0 : if (recordDeviceChanged) {
634 0 : JAMI_WARN("Record devices changed, restarting streams.");
635 0 : stopStream(AudioDeviceType::CAPTURE);
636 0 : startStream(AudioDeviceType::CAPTURE);
637 : }
638 0 : });
639 0 : }
640 :
641 : void
642 0 : PulseLayer::server_info_callback(pa_context*, const pa_server_info* i, void* userdata)
643 : {
644 0 : if (!i)
645 0 : return;
646 : char s[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
647 0 : JAMI_DBG("PulseAudio server info:"
648 : "\n Server name: %s"
649 : "\n Server version: %s"
650 : "\n Default sink: %s"
651 : "\n Default source: %s"
652 : "\n Default sample specification: %s"
653 : "\n Default channel map: %s",
654 : i->server_name,
655 : i->server_version,
656 : i->default_sink_name,
657 : i->default_source_name,
658 : pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec),
659 : pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map));
660 :
661 0 : PulseLayer* context = static_cast<PulseLayer*>(userdata);
662 0 : std::lock_guard lk(context->readyMtx_);
663 0 : context->defaultSink_ = {};
664 0 : context->defaultSource_ = {};
665 0 : context->defaultAudioFormat_ = {i->sample_spec.rate,
666 0 : i->sample_spec.channels,
667 0 : sampleFormatFromPulse(i->sample_spec.format)};
668 : {
669 0 : std::lock_guard lk(context->mutex_);
670 0 : context->hardwareFormatAvailable(context->defaultAudioFormat_);
671 0 : }
672 : /*if (not context->sinkList_.empty())
673 : context->sinkList_.front().channel_map.channels = std::min(i->sample_spec.channels,
674 : (uint8_t) 2);
675 : if (not context->sourceList_.empty())
676 : context->sourceList_.front().channel_map.channels = std::min(i->sample_spec.channels,
677 : (uint8_t) 2);*/
678 0 : context->gettingServerInfo_ = false;
679 0 : context->readyCv_.notify_all();
680 0 : }
681 :
682 : void
683 0 : PulseLayer::source_input_info_callback(pa_context* c UNUSED, const pa_source_info* i, int eol, void* userdata)
684 : {
685 0 : PulseLayer* context = static_cast<PulseLayer*>(userdata);
686 :
687 0 : if (eol) {
688 0 : std::lock_guard lk(context->readyMtx_);
689 0 : context->enumeratingSources_ = false;
690 0 : context->readyCv_.notify_all();
691 0 : return;
692 0 : }
693 : #ifdef PA_LOG_SINK_SOURCES
694 : char s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
695 : JAMI_DBG("Source %u\n"
696 : " Name: %s\n"
697 : " Driver: %s\n"
698 : " Description: %s\n"
699 : " Sample Specification: %s\n"
700 : " Channel Map: %s\n"
701 : " Owner Module: %u\n"
702 : " Volume: %s\n"
703 : " Monitor if Sink: %u\n"
704 : " Latency: %0.0f usec\n"
705 : " Flags: %s%s%s\n",
706 : i->index,
707 : i->name,
708 : i->driver,
709 : i->description,
710 : pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec),
711 : pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map),
712 : i->owner_module,
713 : i->mute ? "muted" : pa_cvolume_snprint(cv, sizeof(cv), &i->volume),
714 : i->monitor_of_sink,
715 : (double) i->latency,
716 : i->flags & PA_SOURCE_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "",
717 : i->flags & PA_SOURCE_LATENCY ? "LATENCY " : "",
718 : i->flags & PA_SOURCE_HARDWARE ? "HARDWARE" : "");
719 : #endif
720 0 : if (not context->inSourceList(i->name)) {
721 0 : context->sourceList_.emplace_back(*i);
722 : }
723 : }
724 :
725 : void
726 0 : PulseLayer::sink_input_info_callback(pa_context* c UNUSED, const pa_sink_info* i, int eol, void* userdata)
727 : {
728 0 : PulseLayer* context = static_cast<PulseLayer*>(userdata);
729 0 : std::lock_guard lk(context->readyMtx_);
730 :
731 0 : if (eol) {
732 0 : context->enumeratingSinks_ = false;
733 0 : context->readyCv_.notify_all();
734 0 : return;
735 : }
736 : #ifdef PA_LOG_SINK_SOURCES
737 : char s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
738 : JAMI_DBG("Sink %u\n"
739 : " Name: %s\n"
740 : " Driver: %s\n"
741 : " Description: %s\n"
742 : " Sample Specification: %s\n"
743 : " Channel Map: %s\n"
744 : " Owner Module: %u\n"
745 : " Volume: %s\n"
746 : " Monitor Source: %u\n"
747 : " Latency: %0.0f usec\n"
748 : " Flags: %s%s%s\n",
749 : i->index,
750 : i->name,
751 : i->driver,
752 : i->description,
753 : pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec),
754 : pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map),
755 : i->owner_module,
756 : i->mute ? "muted" : pa_cvolume_snprint(cv, sizeof(cv), &i->volume),
757 : i->monitor_source,
758 : static_cast<double>(i->latency),
759 : i->flags & PA_SINK_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "",
760 : i->flags & PA_SINK_LATENCY ? "LATENCY " : "",
761 : i->flags & PA_SINK_HARDWARE ? "HARDWARE" : "");
762 : #endif
763 0 : if (not context->inSinkList(i->name)) {
764 0 : context->sinkList_.emplace_back(*i);
765 : }
766 0 : }
767 :
768 : void
769 0 : PulseLayer::updatePreference(AudioPreference& preference, int index, AudioDeviceType type)
770 : {
771 0 : const std::string devName(getAudioDeviceName(index, type));
772 :
773 0 : switch (type) {
774 0 : case AudioDeviceType::PLAYBACK:
775 0 : JAMI_DBG("setting %s for playback", devName.c_str());
776 0 : preference.setPulseDevicePlayback(devName);
777 0 : break;
778 :
779 0 : case AudioDeviceType::CAPTURE:
780 0 : JAMI_DBG("setting %s for capture", devName.c_str());
781 0 : preference.setPulseDeviceRecord(devName);
782 0 : break;
783 :
784 0 : case AudioDeviceType::RINGTONE:
785 0 : JAMI_DBG("setting %s for ringer", devName.c_str());
786 0 : preference.setPulseDeviceRingtone(devName);
787 0 : break;
788 :
789 0 : default:
790 0 : break;
791 : }
792 0 : }
793 :
794 : int
795 0 : PulseLayer::getIndexCapture() const
796 : {
797 0 : return getAudioDeviceIndexByName(preference_.getPulseDeviceRecord(), AudioDeviceType::CAPTURE);
798 : }
799 :
800 : int
801 0 : PulseLayer::getIndexPlayback() const
802 : {
803 0 : return getAudioDeviceIndexByName(preference_.getPulseDevicePlayback(), AudioDeviceType::PLAYBACK);
804 : }
805 :
806 : int
807 0 : PulseLayer::getIndexRingtone() const
808 : {
809 0 : return getAudioDeviceIndexByName(preference_.getPulseDeviceRingtone(), AudioDeviceType::RINGTONE);
810 : }
811 :
812 : std::string
813 0 : PulseLayer::getPreferredPlaybackDevice() const
814 : {
815 0 : const std::string& device(preference_.getPulseDevicePlayback());
816 0 : return stripEchoSufix(device.empty() ? defaultSink_ : device);
817 : }
818 :
819 : std::string
820 0 : PulseLayer::getPreferredRingtoneDevice() const
821 : {
822 0 : const std::string& device(preference_.getPulseDeviceRingtone());
823 0 : return stripEchoSufix(device.empty() ? defaultSink_ : device);
824 : }
825 :
826 : std::string
827 0 : PulseLayer::getPreferredCaptureDevice() const
828 : {
829 0 : const std::string& device(preference_.getPulseDeviceRecord());
830 0 : return stripEchoSufix(device.empty() ? defaultSource_ : device);
831 : }
832 :
833 : } // namespace jami
|