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