Line data Source code
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"
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 42 : }
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::startCaptureStream(const std::string& id)
447 : {
448 0 : if (loopbackCapture_.isRunning()) {
449 0 : JAMI_WARNING("[pulselayer] Loopback capture already running");
450 0 : return;
451 : }
452 :
453 0 : auto& rbPool = Manager::instance().getRingBufferPool();
454 0 : auto ringBuffer = rbPool.createRingBuffer(id);
455 0 : if (!ringBuffer) {
456 0 : JAMI_ERROR("[pulselayer] Failed to get ring buffer for id {}", id);
457 0 : return;
458 : }
459 :
460 0 : JAMI_DEBUG("[pulselayer] Starting loopback capture for ID {}", id);
461 :
462 0 : const auto rate = loopbackCapture_.sampleRate();
463 0 : const auto channels = loopbackCapture_.channels();
464 :
465 0 : auto started = loopbackCapture_.startCaptureAsync([ringBuffer, rate, channels](const void* data, size_t length) {
466 0 : if (!data || length == 0) {
467 0 : JAMI_WARNING("[pulselayer] No audio data captured");
468 0 : return;
469 : }
470 :
471 0 : const size_t frameSizeBytes = channels * sizeof(int16_t);
472 0 : if (frameSizeBytes == 0) {
473 0 : JAMI_ERROR("[pulselayer] Invalid frame size for captured audio");
474 0 : return;
475 : }
476 :
477 0 : const size_t samples = length / frameSizeBytes;
478 0 : if (samples == 0) {
479 0 : JAMI_WARNING("[pulselayer] Ignoring empty capture buffer");
480 0 : return;
481 : }
482 :
483 0 : auto capturedFrame = std::make_shared<AudioFrame>(AudioFormat {rate, channels, AV_SAMPLE_FMT_S16}, samples);
484 :
485 0 : std::memcpy(capturedFrame->pointer()->data[0], data, length);
486 0 : ringBuffer->put(std::move(capturedFrame));
487 0 : });
488 :
489 0 : if (!started) {
490 0 : JAMI_ERROR("[pulselayer] Failed to start loopback capture");
491 0 : rbPool.unBindAll(id);
492 : }
493 0 : }
494 :
495 : void
496 0 : PulseLayer::stopCaptureStream(const std::string& id)
497 : {
498 0 : if (!loopbackCapture_.isRunning()) {
499 0 : JAMI_WARNING("[pulselayer] Loopback capture is not running");
500 0 : return;
501 : }
502 :
503 0 : JAMI_DEBUG("[pulselayer] Stopping loopback capture for ID {}", id);
504 :
505 0 : loopbackCapture_.stopCapture();
506 :
507 0 : auto& rbPool = Manager::instance().getRingBufferPool();
508 0 : rbPool.unBindAll(id);
509 : }
510 :
511 : void
512 0 : PulseLayer::stopStream(AudioDeviceType type)
513 : {
514 0 : waitForDevices();
515 0 : PulseMainLoopLock lock(mainloop_.get());
516 0 : auto& stream(getStream(type));
517 0 : if (not stream)
518 0 : return;
519 :
520 0 : if (not stream->isReady())
521 0 : pendingStreams--;
522 0 : stream->stop();
523 0 : stream.reset();
524 :
525 0 : if (type == AudioDeviceType::PLAYBACK || type == AudioDeviceType::ALL)
526 0 : playbackChanged(false);
527 :
528 0 : std::lock_guard lk(mutex_);
529 0 : if (not playback_ and not ringtone_ and not record_) {
530 0 : pendingStreams = 0;
531 0 : status_ = Status::Idle;
532 0 : startedCv_.notify_all();
533 : }
534 0 : }
535 :
536 : void
537 0 : PulseLayer::writeToSpeaker()
538 : {
539 0 : if (!playback_ or !playback_->isReady())
540 0 : return;
541 :
542 : // available bytes to be written in PulseAudio internal buffer
543 0 : void* data = nullptr;
544 0 : size_t writableBytes = (size_t) -1;
545 0 : int ret = pa_stream_begin_write(playback_->stream(), &data, &writableBytes);
546 0 : if (ret == 0 and data and writableBytes != 0) {
547 0 : writableBytes = std::min(pa_stream_writable_size(playback_->stream()), writableBytes);
548 0 : const auto& buff = getToPlay(playback_->format(), writableBytes / playback_->frameSize());
549 0 : if (not buff or isPlaybackMuted_)
550 0 : memset(data, 0, writableBytes);
551 : else
552 0 : std::memcpy(data, buff->pointer()->data[0], buff->pointer()->nb_samples * playback_->frameSize());
553 0 : pa_stream_write(playback_->stream(), data, writableBytes, nullptr, 0, PA_SEEK_RELATIVE);
554 0 : }
555 : }
556 :
557 : void
558 0 : PulseLayer::readFromMic()
559 : {
560 0 : if (!record_ or !record_->isReady())
561 0 : return;
562 :
563 0 : const char* data = nullptr;
564 : size_t bytes;
565 0 : if (pa_stream_peek(record_->stream(), (const void**) &data, &bytes) < 0 or !data)
566 0 : return;
567 :
568 0 : if (bytes == 0)
569 0 : return;
570 :
571 0 : size_t sample_size = record_->frameSize();
572 0 : const size_t samples = bytes / sample_size;
573 :
574 0 : auto out = std::make_shared<AudioFrame>(record_->format(), samples);
575 0 : if (isCaptureMuted_)
576 0 : libav_utils::fillWithSilence(out->pointer());
577 : else
578 0 : std::memcpy(out->pointer()->data[0], data, bytes);
579 :
580 0 : if (pa_stream_drop(record_->stream()) < 0)
581 0 : JAMI_ERR("Capture stream drop failed: %s", pa_strerror(pa_context_errno(context_)));
582 :
583 0 : putRecorded(std::move(out));
584 0 : }
585 :
586 : void
587 0 : PulseLayer::ringtoneToSpeaker()
588 : {
589 0 : if (!ringtone_ or !ringtone_->isReady())
590 0 : return;
591 :
592 0 : void* data = nullptr;
593 0 : size_t writableBytes = (size_t) -1;
594 0 : int ret = pa_stream_begin_write(ringtone_->stream(), &data, &writableBytes);
595 0 : if (ret == 0 and data and writableBytes != 0) {
596 0 : writableBytes = std::min(pa_stream_writable_size(ringtone_->stream()), writableBytes);
597 0 : const auto& buff = getToRing(ringtone_->format(), writableBytes / ringtone_->frameSize());
598 0 : if (not buff or isRingtoneMuted_)
599 0 : memset(data, 0, writableBytes);
600 : else
601 0 : std::memcpy(data, buff->pointer()->data[0], buff->pointer()->nb_samples * ringtone_->frameSize());
602 0 : pa_stream_write(ringtone_->stream(), data, writableBytes, nullptr, 0, PA_SEEK_RELATIVE);
603 0 : }
604 : }
605 :
606 : std::string
607 0 : stripEchoSufix(const std::string& deviceName)
608 : {
609 0 : return std::regex_replace(deviceName, PA_EC_SUFFIX, "");
610 : }
611 :
612 : void
613 0 : PulseLayer::context_changed_callback(pa_context* c, pa_subscription_event_type_t type, uint32_t idx, void* userdata)
614 : {
615 0 : static_cast<PulseLayer*>(userdata)->contextChanged(c, type, idx);
616 0 : }
617 :
618 : void
619 0 : PulseLayer::contextChanged(pa_context* c UNUSED, pa_subscription_event_type_t type, uint32_t idx UNUSED)
620 : {
621 0 : bool reset = false;
622 :
623 0 : switch (type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) {
624 0 : case PA_SUBSCRIPTION_EVENT_SINK:
625 0 : switch (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) {
626 0 : case PA_SUBSCRIPTION_EVENT_NEW:
627 : case PA_SUBSCRIPTION_EVENT_REMOVE:
628 0 : updateSinkList();
629 0 : reset = true;
630 0 : default:
631 0 : break;
632 : }
633 :
634 0 : break;
635 :
636 0 : case PA_SUBSCRIPTION_EVENT_SOURCE:
637 0 : switch (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) {
638 0 : case PA_SUBSCRIPTION_EVENT_NEW:
639 : case PA_SUBSCRIPTION_EVENT_REMOVE:
640 0 : updateSourceList();
641 0 : reset = true;
642 0 : default:
643 0 : break;
644 : }
645 :
646 0 : break;
647 :
648 0 : default:
649 0 : JAMI_DBG("Unhandled event type 0x%x", type);
650 0 : break;
651 : }
652 :
653 0 : if (reset) {
654 0 : updateServerInfo();
655 0 : waitForDeviceList();
656 : }
657 0 : }
658 :
659 : void
660 0 : PulseLayer::waitForDevices()
661 : {
662 0 : std::unique_lock lk(readyMtx_);
663 0 : readyCv_.wait(lk, [this] { return !(enumeratingSinks_ or enumeratingSources_ or gettingServerInfo_); });
664 0 : }
665 :
666 : void
667 0 : PulseLayer::waitForDeviceList()
668 : {
669 0 : std::unique_lock lock(readyMtx_);
670 0 : if (waitingDeviceList_.exchange(true))
671 0 : return;
672 0 : if (streamStarter_.joinable())
673 0 : streamStarter_.join();
674 0 : streamStarter_ = std::thread([this]() mutable {
675 : bool playbackDeviceChanged, recordDeviceChanged;
676 :
677 0 : waitForDevices();
678 0 : waitingDeviceList_ = false;
679 :
680 : // If a current device changed, restart streams
681 0 : devicesChanged();
682 0 : auto playbackInfo = getDeviceInfos(sinkList_, getPreferredPlaybackDevice());
683 0 : playbackDeviceChanged = playback_
684 0 : and (!playbackInfo->name.empty()
685 0 : and playbackInfo->name != stripEchoSufix(playback_->getDeviceName()));
686 :
687 0 : auto recordInfo = getDeviceInfos(sourceList_, getPreferredCaptureDevice());
688 0 : recordDeviceChanged = record_
689 0 : and (!recordInfo->name.empty()
690 0 : and recordInfo->name != stripEchoSufix(record_->getDeviceName()));
691 :
692 0 : if (status_ != Status::Started)
693 0 : return;
694 0 : if (playbackDeviceChanged) {
695 0 : JAMI_WARN("Playback devices changed, restarting streams.");
696 0 : stopStream(AudioDeviceType::PLAYBACK);
697 0 : startStream(AudioDeviceType::PLAYBACK);
698 : }
699 0 : if (recordDeviceChanged) {
700 0 : JAMI_WARN("Record devices changed, restarting streams.");
701 0 : stopStream(AudioDeviceType::CAPTURE);
702 0 : startStream(AudioDeviceType::CAPTURE);
703 : }
704 0 : });
705 0 : }
706 :
707 : void
708 0 : PulseLayer::server_info_callback(pa_context*, const pa_server_info* i, void* userdata)
709 : {
710 0 : if (!i)
711 0 : return;
712 : char s[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
713 0 : 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 0 : PulseLayer* context = static_cast<PulseLayer*>(userdata);
728 0 : std::lock_guard lk(context->readyMtx_);
729 0 : context->defaultSink_ = {};
730 0 : context->defaultSource_ = {};
731 0 : context->defaultAudioFormat_ = {i->sample_spec.rate,
732 0 : i->sample_spec.channels,
733 0 : sampleFormatFromPulse(i->sample_spec.format)};
734 : {
735 0 : std::lock_guard lk(context->mutex_);
736 0 : context->hardwareFormatAvailable(context->defaultAudioFormat_);
737 0 : }
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 0 : context->gettingServerInfo_ = false;
745 0 : context->readyCv_.notify_all();
746 0 : }
747 :
748 : void
749 0 : PulseLayer::source_input_info_callback(pa_context* c UNUSED, const pa_source_info* i, int eol, void* userdata)
750 : {
751 0 : PulseLayer* context = static_cast<PulseLayer*>(userdata);
752 :
753 0 : if (eol) {
754 0 : std::lock_guard lk(context->readyMtx_);
755 0 : context->enumeratingSources_ = false;
756 0 : context->readyCv_.notify_all();
757 0 : return;
758 0 : }
759 : #ifdef PA_LOG_SINK_SOURCES
760 : char s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
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),
780 : i->monitor_of_sink,
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 0 : if (not context->inSourceList(i->name)) {
787 0 : context->sourceList_.emplace_back(*i);
788 : }
789 : }
790 :
791 : void
792 0 : PulseLayer::sink_input_info_callback(pa_context* c UNUSED, const pa_sink_info* i, int eol, void* userdata)
793 : {
794 0 : PulseLayer* context = static_cast<PulseLayer*>(userdata);
795 0 : std::lock_guard lk(context->readyMtx_);
796 :
797 0 : if (eol) {
798 0 : context->enumeratingSinks_ = false;
799 0 : context->readyCv_.notify_all();
800 0 : return;
801 : }
802 : #ifdef PA_LOG_SINK_SOURCES
803 : char s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
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),
823 : i->monitor_source,
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 0 : if (not context->inSinkList(i->name)) {
830 0 : context->sinkList_.emplace_back(*i);
831 : }
832 0 : }
833 :
834 : void
835 0 : PulseLayer::updatePreference(AudioPreference& preference, int index, AudioDeviceType type)
836 : {
837 0 : const std::string devName(getAudioDeviceName(index, type));
838 :
839 0 : switch (type) {
840 0 : case AudioDeviceType::PLAYBACK:
841 0 : JAMI_DBG("setting %s for playback", devName.c_str());
842 0 : preference.setPulseDevicePlayback(devName);
843 0 : break;
844 :
845 0 : case AudioDeviceType::CAPTURE:
846 0 : JAMI_DBG("setting %s for capture", devName.c_str());
847 0 : preference.setPulseDeviceRecord(devName);
848 0 : break;
849 :
850 0 : case AudioDeviceType::RINGTONE:
851 0 : JAMI_DBG("setting %s for ringer", devName.c_str());
852 0 : preference.setPulseDeviceRingtone(devName);
853 0 : break;
854 :
855 0 : default:
856 0 : break;
857 : }
858 0 : }
859 :
860 : int
861 0 : PulseLayer::getIndexCapture() const
862 : {
863 0 : return getAudioDeviceIndexByName(preference_.getPulseDeviceRecord(), AudioDeviceType::CAPTURE);
864 : }
865 :
866 : int
867 0 : PulseLayer::getIndexPlayback() const
868 : {
869 0 : return getAudioDeviceIndexByName(preference_.getPulseDevicePlayback(), AudioDeviceType::PLAYBACK);
870 : }
871 :
872 : int
873 0 : PulseLayer::getIndexRingtone() const
874 : {
875 0 : return getAudioDeviceIndexByName(preference_.getPulseDeviceRingtone(), AudioDeviceType::RINGTONE);
876 : }
877 :
878 : std::string
879 0 : PulseLayer::getPreferredPlaybackDevice() const
880 : {
881 0 : const std::string& device(preference_.getPulseDevicePlayback());
882 0 : return stripEchoSufix(device.empty() ? defaultSink_ : device);
883 : }
884 :
885 : std::string
886 0 : PulseLayer::getPreferredRingtoneDevice() const
887 : {
888 0 : const std::string& device(preference_.getPulseDeviceRingtone());
889 0 : return stripEchoSufix(device.empty() ? defaultSink_ : device);
890 : }
891 :
892 : std::string
893 0 : PulseLayer::getPreferredCaptureDevice() const
894 : {
895 0 : const std::string& device(preference_.getPulseDeviceRecord());
896 0 : return stripEchoSufix(device.empty() ? defaultSource_ : device);
897 : }
898 :
899 : } // namespace jami
|