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