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 488 8.6 %
Date: 2026-02-28 10:41:24 Functions: 5 67 7.5 %

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

Generated by: LCOV version 1.14