LCOV - code coverage report
Current view: top level - foo/src/media/audio/pulseaudio - pulselayer.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 42 448 9.4 %
Date: 2025-12-18 10:07:43 Functions: 5 46 10.9 %

          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

Generated by: LCOV version 1.14