LCOV - code coverage report
Current view: top level - src/media/audio/pulseaudio - pulselayer.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 8.4 % 486 41
Test Date: 2026-06-13 09:18:46 Functions: 9.7 % 113 11

            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           12 :     JAMI_LOG("[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            0 : }
      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           12 :         JAMI_LOG("Waiting…");
     138            3 :         break;
     139              : 
     140            0 :     case PA_CONTEXT_READY:
     141            0 :         JAMI_LOG("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           12 :         JAMI_ERROR("{}", 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_LOG("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_LOG("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_LOG("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_ERROR("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_ERROR("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_WARNING("Preferred device {} not found in device list, selecting default {} instead.",
     316              :                      name,
     317              :                      list.front().name);
     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_ERROR("Index {} 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_ERROR("Index {} 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_ERROR("Unexpected type");
     345            0 :         return "";
     346              :     }
     347              : }
     348              : 
     349              : void
     350            0 : PulseLayer::onStreamReady()
     351              : {
     352            0 :     if (--pendingStreams == 0) {
     353            0 :         JAMI_LOG("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_WARNING("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              :                                  mainloop_.get(),
     391              :                                  name,
     392              :                                  type,
     393              :                                  audioFormat_.sample_rate,
     394              :                                  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_ERROR("Capture stream drop failed: {}", 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_LOG("Unhandled event type 0x{:x}", (int) 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_WARNING("Playback devices changed, restarting streams.");
     696            0 :             stopStream(AudioDeviceType::PLAYBACK);
     697            0 :             startStream(AudioDeviceType::PLAYBACK);
     698              :         }
     699            0 :         if (recordDeviceChanged) {
     700            0 :             JAMI_WARNING("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_LOG("PulseAudio server info:\n    Server name: {}\n    Server version: {}\n    Default sink: {}\n    Default "
     714              :              "source: {}\n    Default sample specification: {}\n    Default channel map: {}",
     715              :              i->server_name,
     716              :              i->server_version,
     717              :              i->default_sink_name,
     718              :              i->default_source_name,
     719              :              pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec),
     720              :              pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map));
     721              : 
     722            0 :     PulseLayer* context = static_cast<PulseLayer*>(userdata);
     723            0 :     std::lock_guard lk(context->readyMtx_);
     724            0 :     context->defaultSink_ = {};
     725            0 :     context->defaultSource_ = {};
     726            0 :     context->defaultAudioFormat_ = {i->sample_spec.rate,
     727            0 :                                     i->sample_spec.channels,
     728            0 :                                     sampleFormatFromPulse(i->sample_spec.format)};
     729              :     {
     730            0 :         std::lock_guard lk(context->mutex_);
     731            0 :         context->hardwareFormatAvailable(context->defaultAudioFormat_);
     732            0 :     }
     733              :     /*if (not context->sinkList_.empty())
     734              :         context->sinkList_.front().channel_map.channels = std::min(i->sample_spec.channels,
     735              :                                                                    (uint8_t) 2);
     736              :     if (not context->sourceList_.empty())
     737              :         context->sourceList_.front().channel_map.channels = std::min(i->sample_spec.channels,
     738              :                                                                      (uint8_t) 2);*/
     739            0 :     context->gettingServerInfo_ = false;
     740            0 :     context->readyCv_.notify_all();
     741            0 : }
     742              : 
     743              : void
     744            0 : PulseLayer::source_input_info_callback(pa_context* c UNUSED, const pa_source_info* i, int eol, void* userdata)
     745              : {
     746            0 :     PulseLayer* context = static_cast<PulseLayer*>(userdata);
     747              : 
     748            0 :     if (eol) {
     749            0 :         std::lock_guard lk(context->readyMtx_);
     750            0 :         context->enumeratingSources_ = false;
     751            0 :         context->readyCv_.notify_all();
     752            0 :         return;
     753            0 :     }
     754              : #ifdef PA_LOG_SINK_SOURCES
     755              :     char s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
     756              :     JAMI_LOG("Source {}\n    Name: {}\n    Driver: {}\n    Description: {}\n    Sample Specification: {}\n    Channel "
     757              :              "Map: {}\n    Owner Module: {}\n    Volume: {}\n    Monitor if Sink: {}\n    Latency: {:0.0} usec\n    "
     758              :              "Flags: {}{}{}",
     759              :              i->index,
     760              :              i->name,
     761              :              i->driver,
     762              :              i->description,
     763              :              pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec),
     764              :              pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map),
     765              :              i->owner_module,
     766              :              i->mute ? "muted" : pa_cvolume_snprint(cv, sizeof(cv), &i->volume),
     767              :              i->monitor_of_sink,
     768              :              (double) i->latency,
     769              :              i->flags & PA_SOURCE_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "",
     770              :              i->flags & PA_SOURCE_LATENCY ? "LATENCY " : "",
     771              :              i->flags & PA_SOURCE_HARDWARE ? "HARDWARE" : "");
     772              : #endif
     773            0 :     if (not context->inSourceList(i->name)) {
     774            0 :         context->sourceList_.emplace_back(*i);
     775              :     }
     776              : }
     777              : 
     778              : void
     779            0 : PulseLayer::sink_input_info_callback(pa_context* c UNUSED, const pa_sink_info* i, int eol, void* userdata)
     780              : {
     781            0 :     PulseLayer* context = static_cast<PulseLayer*>(userdata);
     782            0 :     std::lock_guard lk(context->readyMtx_);
     783              : 
     784            0 :     if (eol) {
     785            0 :         context->enumeratingSinks_ = false;
     786            0 :         context->readyCv_.notify_all();
     787            0 :         return;
     788              :     }
     789              : #ifdef PA_LOG_SINK_SOURCES
     790              :     char s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX];
     791              :     JAMI_LOG(
     792              :         "Sink {}\n    Name: {}\n    Driver: {}\n    Description: {}\n    Sample Specification: {}\n    Channel Map: "
     793              :         "{}\n    Owner Module: {}\n    Volume: {}\n    Monitor Source: {}\n    Latency: {:0.0} usec\n    Flags: {}{}{}",
     794              :         i->index,
     795              :         i->name,
     796              :         i->driver,
     797              :         i->description,
     798              :         pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec),
     799              :         pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map),
     800              :         i->owner_module,
     801              :         i->mute ? "muted" : pa_cvolume_snprint(cv, sizeof(cv), &i->volume),
     802              :         i->monitor_source,
     803              :         static_cast<double>(i->latency),
     804              :         i->flags & PA_SINK_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "",
     805              :         i->flags & PA_SINK_LATENCY ? "LATENCY " : "",
     806              :         i->flags & PA_SINK_HARDWARE ? "HARDWARE" : "");
     807              : #endif
     808            0 :     if (not context->inSinkList(i->name)) {
     809            0 :         context->sinkList_.emplace_back(*i);
     810              :     }
     811            0 : }
     812              : 
     813              : void
     814            0 : PulseLayer::updatePreference(AudioPreference& preference, int index, AudioDeviceType type)
     815              : {
     816            0 :     const std::string devName(getAudioDeviceName(index, type));
     817              : 
     818            0 :     switch (type) {
     819            0 :     case AudioDeviceType::PLAYBACK:
     820            0 :         JAMI_LOG("setting {} for playback", devName);
     821            0 :         preference.setPulseDevicePlayback(devName);
     822            0 :         break;
     823              : 
     824            0 :     case AudioDeviceType::CAPTURE:
     825            0 :         JAMI_LOG("setting {} for capture", devName);
     826            0 :         preference.setPulseDeviceRecord(devName);
     827            0 :         break;
     828              : 
     829            0 :     case AudioDeviceType::RINGTONE:
     830            0 :         JAMI_LOG("setting {} for ringer", devName);
     831            0 :         preference.setPulseDeviceRingtone(devName);
     832            0 :         break;
     833              : 
     834            0 :     default:
     835            0 :         break;
     836              :     }
     837            0 : }
     838              : 
     839              : int
     840            0 : PulseLayer::getIndexCapture() const
     841              : {
     842            0 :     return getAudioDeviceIndexByName(preference_.getPulseDeviceRecord(), AudioDeviceType::CAPTURE);
     843              : }
     844              : 
     845              : int
     846            0 : PulseLayer::getIndexPlayback() const
     847              : {
     848            0 :     return getAudioDeviceIndexByName(preference_.getPulseDevicePlayback(), AudioDeviceType::PLAYBACK);
     849              : }
     850              : 
     851              : int
     852            0 : PulseLayer::getIndexRingtone() const
     853              : {
     854            0 :     return getAudioDeviceIndexByName(preference_.getPulseDeviceRingtone(), AudioDeviceType::RINGTONE);
     855              : }
     856              : 
     857              : std::string
     858            0 : PulseLayer::getPreferredPlaybackDevice() const
     859              : {
     860            0 :     const std::string& device(preference_.getPulseDevicePlayback());
     861            0 :     return stripEchoSufix(device.empty() ? defaultSink_ : device);
     862              : }
     863              : 
     864              : std::string
     865            0 : PulseLayer::getPreferredRingtoneDevice() const
     866              : {
     867            0 :     const std::string& device(preference_.getPulseDeviceRingtone());
     868            0 :     return stripEchoSufix(device.empty() ? defaultSink_ : device);
     869              : }
     870              : 
     871              : std::string
     872            0 : PulseLayer::getPreferredCaptureDevice() const
     873              : {
     874            0 :     const std::string& device(preference_.getPulseDeviceRecord());
     875            0 :     return stripEchoSufix(device.empty() ? defaultSource_ : device);
     876              : }
     877              : 
     878              : } // namespace jami
        

Generated by: LCOV version 2.0-1