LCOV - code coverage report
Current view: top level - src/media/audio/pulseaudio - pulselayer.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 43 461 9.3 %
Date: 2024-11-15 09:04:49 Functions: 5 46 10.9 %

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

Generated by: LCOV version 1.14