LCOV - code coverage report
Current view: top level - src/media/audio/pulseaudio - audiostream.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 0 136 0.0 %
Date: 2024-04-25 08:05:53 Functions: 0 26 0.0 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2024 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com>
       5             :  *  Author: Adrien Beraud <adrien.beraud@savoirfairelinux.com>
       6             :  *
       7             :  *  This program is free software; you can redistribute it and/or modify
       8             :  *  it under the terms of the GNU General Public License as published by
       9             :  *  the Free Software Foundation; either version 3 of the License, or
      10             :  *  (at your option) any later version.
      11             :  *
      12             :  *  This program is distributed in the hope that it will be useful,
      13             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      14             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      15             :  *  GNU General Public License for more details.
      16             :  *
      17             :  *  You should have received a copy of the GNU General Public License
      18             :  *  along with this program; if not, write to the Free Software
      19             :  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA.
      20             :  */
      21             : 
      22             : #include "audiostream.h"
      23             : #include "pulselayer.h"
      24             : #include "logger.h"
      25             : #include "compiler_intrinsics.h"
      26             : 
      27             : #include <string_view>
      28             : #include <stdexcept>
      29             : 
      30             : using namespace std::literals;
      31             : 
      32             : namespace jami {
      33             : 
      34           0 : AudioStream::AudioStream(pa_context* c,
      35             :                          pa_threaded_mainloop* m,
      36             :                          const char* desc,
      37             :                          AudioDeviceType type,
      38             :                          unsigned samplrate,
      39             :                          pa_sample_format_t format,
      40             :                          const PaDeviceInfos& infos,
      41             :                          bool ec,
      42             :                          OnReady onReady,
      43           0 :                          OnData onData)
      44           0 :     : onReady_(std::move(onReady))
      45           0 :     , onData_(std::move(onData))
      46           0 :     , audiostream_(nullptr)
      47           0 :     , mainloop_(m)
      48           0 :     , audioType_(type)
      49             : {
      50             :     pa_sample_spec sample_spec = {format,
      51             :                                   samplrate,
      52           0 :                                   infos.channel_map.channels};
      53             : 
      54           0 :     JAMI_DEBUG("{}: Creating stream with device {} ({}, {}Hz, {} channels)",
      55             :              desc,
      56             :              infos.name,
      57             :              pa_sample_format_to_string(sample_spec.format),
      58             :              samplrate,
      59             :              infos.channel_map.channels);
      60             : 
      61           0 :     assert(pa_sample_spec_valid(&sample_spec));
      62           0 :     assert(pa_channel_map_valid(&infos.channel_map));
      63             : 
      64             :     std::unique_ptr<pa_proplist, decltype(pa_proplist_free)&> pl(pa_proplist_new(),
      65           0 :                                                                  pa_proplist_free);
      66           0 :     pa_proplist_sets(pl.get(), PA_PROP_FILTER_WANT, "echo-cancel");
      67           0 :     pa_proplist_sets(
      68             :         pl.get(), "filter.apply.echo-cancel.parameters", // needs pulseaudio >= 11.0
      69             :         "use_volume_sharing=0"  // share volume with master sink/source
      70             :         " use_master_format=1"  // use format/rate/channels from master sink/source
      71             :         " aec_args=\""
      72             :             "digital_gain_control=1"
      73             :             " analog_gain_control=0"
      74             :             " experimental_agc=1"
      75             :         "\"");
      76             : 
      77           0 :     audiostream_ = pa_stream_new_with_proplist(c,
      78             :                                                desc,
      79             :                                                &sample_spec,
      80             :                                                &infos.channel_map,
      81           0 :                                                ec ? pl.get() : nullptr);
      82           0 :     if (!audiostream_) {
      83           0 :         JAMI_ERR("%s: pa_stream_new() failed : %s", desc, pa_strerror(pa_context_errno(c)));
      84           0 :         throw std::runtime_error("Could not create stream\n");
      85             :     }
      86             : 
      87             :     pa_buffer_attr attributes;
      88           0 :     attributes.maxlength = pa_usec_to_bytes(160 * PA_USEC_PER_MSEC, &sample_spec);
      89           0 :     attributes.tlength = pa_usec_to_bytes(80 * PA_USEC_PER_MSEC, &sample_spec);
      90           0 :     attributes.prebuf = 0;
      91           0 :     attributes.fragsize = pa_usec_to_bytes(80 * PA_USEC_PER_MSEC, &sample_spec);
      92           0 :     attributes.minreq = (uint32_t) -1;
      93             : 
      94           0 :     pa_stream_set_state_callback(
      95             :         audiostream_,
      96           0 :         [](pa_stream* s, void* user_data) { static_cast<AudioStream*>(user_data)->stateChanged(s); },
      97             :         this);
      98           0 :     pa_stream_set_moved_callback(
      99             :         audiostream_,
     100           0 :         [](pa_stream* s, void* user_data) { static_cast<AudioStream*>(user_data)->moved(s); },
     101             :         this);
     102             : 
     103           0 :     constexpr pa_stream_flags_t flags = static_cast<pa_stream_flags_t>(
     104             :         PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_START_CORKED);
     105             : 
     106           0 :     if (type == AudioDeviceType::PLAYBACK || type == AudioDeviceType::RINGTONE) {
     107           0 :         pa_stream_set_write_callback(
     108             :             audiostream_,
     109           0 :             [](pa_stream* /*s*/, size_t bytes, void* userdata) {
     110           0 :                 static_cast<AudioStream*>(userdata)->onData_(bytes);
     111           0 :             },
     112             :             this);
     113             : 
     114           0 :         pa_stream_connect_playback(audiostream_,
     115           0 :                                    infos.name.empty() ? nullptr : infos.name.c_str(),
     116             :                                    &attributes,
     117             :                                    flags,
     118             :                                    nullptr,
     119             :                                    nullptr);
     120           0 :     } else if (type == AudioDeviceType::CAPTURE) {
     121           0 :         pa_stream_set_read_callback(
     122             :             audiostream_,
     123           0 :             [](pa_stream* /*s*/, size_t bytes, void* userdata) {
     124           0 :                 static_cast<AudioStream*>(userdata)->onData_(bytes);
     125           0 :             },
     126             :             this);
     127             : 
     128           0 :         pa_stream_connect_record(audiostream_,
     129           0 :                                  infos.name.empty() ? nullptr : infos.name.c_str(),
     130             :                                  &attributes,
     131             :                                  flags);
     132             :     }
     133           0 : }
     134             : 
     135             : void
     136           0 : disconnectStream(pa_stream* s)
     137             : {
     138             :     // make sure we don't get any further callback
     139           0 :     pa_stream_set_write_callback(s, nullptr, nullptr);
     140           0 :     pa_stream_set_read_callback(s, nullptr, nullptr);
     141           0 :     pa_stream_set_moved_callback(s, nullptr, nullptr);
     142           0 :     pa_stream_set_underflow_callback(s, nullptr, nullptr);
     143           0 :     pa_stream_set_overflow_callback(s, nullptr, nullptr);
     144           0 :     pa_stream_set_suspended_callback(s, nullptr, nullptr);
     145           0 :     pa_stream_set_started_callback(s, nullptr, nullptr);
     146           0 : }
     147             : 
     148             : void
     149           0 : destroyStream(pa_stream* s)
     150             : {
     151           0 :     pa_stream_disconnect(s);
     152           0 :     pa_stream_set_state_callback(s, nullptr, nullptr);
     153           0 :     disconnectStream(s);
     154           0 :     pa_stream_unref(s);
     155           0 : }
     156             : 
     157           0 : AudioStream::~AudioStream()
     158             : {
     159           0 :     stop();
     160           0 : }
     161             : 
     162             : void
     163           0 : AudioStream::start()
     164             : {
     165           0 :     pa_stream_cork(audiostream_, 0, nullptr, nullptr);
     166             : 
     167             :     // trigger echo cancel check
     168           0 :     moved(audiostream_);
     169           0 : }
     170             : 
     171             : void
     172           0 : AudioStream::stop()
     173             : {
     174           0 :     if (not audiostream_)
     175           0 :         return;
     176           0 :     JAMI_DBG("Destroying stream with device %s", pa_stream_get_device_name(audiostream_));
     177           0 :     if (pa_stream_get_state(audiostream_) == PA_STREAM_CREATING) {
     178           0 :         disconnectStream(audiostream_);
     179           0 :         pa_stream_set_state_callback(
     180           0 :             audiostream_, [](pa_stream* s, void*) { destroyStream(s); }, nullptr);
     181             :     } else {
     182           0 :         destroyStream(audiostream_);
     183             :     }
     184           0 :     audiostream_ = nullptr;
     185             : 
     186           0 :     std::unique_lock lock(mutex_);
     187           0 :     for (auto op : ongoing_ops)
     188           0 :         pa_operation_cancel(op);
     189             :     // wait for all operations to end
     190           0 :     cond_.wait(lock, [this]{ return ongoing_ops.empty(); });
     191           0 : }
     192             : 
     193             : void
     194           0 : AudioStream::moved(pa_stream* s)
     195             : {
     196           0 :     audiostream_ = s;
     197           0 :     JAMI_LOG("[audiostream] Stream moved: {:d}, {:s}",
     198             :              pa_stream_get_index(s),
     199             :              pa_stream_get_device_name(s));
     200             : 
     201           0 :     if (audioType_ == AudioDeviceType::CAPTURE) {
     202             :         // check for echo cancel
     203           0 :         const char* name = pa_stream_get_device_name(s);
     204           0 :         if (!name) {
     205           0 :             JAMI_ERR("[audiostream] moved() unable to get audio stream device");
     206           0 :             return;
     207             :         }
     208             : 
     209           0 :         auto* op = pa_context_get_source_info_by_name(
     210             :             pa_stream_get_context(s),
     211             :             name,
     212           0 :             [](pa_context* /*c*/, const pa_source_info* i, int /*eol*/, void* userdata) {
     213           0 :                 AudioStream* thisPtr = (AudioStream*) userdata;
     214             :                 // this closure gets called twice by pulse for some reason
     215             :                 // the 2nd time, i is invalid
     216           0 :                 if (!i) {
     217             :                     // JAMI_ERROR("[audiostream] source info not found");
     218           0 :                     return;
     219             :                 }
     220           0 :                 if (!thisPtr) {
     221           0 :                     JAMI_ERROR("[audiostream] AudioStream pointer became invalid during pa_source_info_cb_t callback!");
     222           0 :                     return;
     223             :                 }
     224             : 
     225             :                 // string compare
     226           0 :                 bool usingEchoCancel = std::string_view(i->driver) == "module-echo-cancel.c"sv;
     227           0 :                 JAMI_WARNING("[audiostream] capture stream using pulse echo cancel module? {} ({})",
     228             :                           usingEchoCancel ? "yes" : "no",
     229             :                           i->name);
     230           0 :                 thisPtr->echoCancelCb(usingEchoCancel);
     231             :             },
     232           0 :             this);
     233             : 
     234           0 :         std::lock_guard lock(mutex_);
     235           0 :         pa_operation_set_state_callback(op, [](pa_operation *op, void *userdata){
     236           0 :             static_cast<AudioStream*>(userdata)->opEnded(op);
     237           0 :         }, this);
     238           0 :         ongoing_ops.emplace(op);
     239           0 :     }
     240             : }
     241             : 
     242             : void
     243           0 : AudioStream::opEnded(pa_operation* op)
     244             : {
     245           0 :     std::lock_guard lock(mutex_);
     246           0 :     ongoing_ops.erase(op);
     247           0 :     pa_operation_unref(op);
     248           0 :     cond_.notify_all();
     249           0 : }
     250             : 
     251             : void
     252           0 : AudioStream::stateChanged(pa_stream* s)
     253             : {
     254             :     // UNUSED char str[PA_SAMPLE_SPEC_SNPRINT_MAX];
     255             : 
     256           0 :     switch (pa_stream_get_state(s)) {
     257           0 :     case PA_STREAM_CREATING:
     258           0 :         JAMI_DBG("Stream is creating...");
     259           0 :         break;
     260             : 
     261           0 :     case PA_STREAM_TERMINATED:
     262           0 :         JAMI_DBG("Stream is terminating...");
     263           0 :         break;
     264             : 
     265           0 :     case PA_STREAM_READY:
     266           0 :         JAMI_DBG("Stream successfully created, connected to %s", pa_stream_get_device_name(s));
     267             :         // JAMI_DBG("maxlength %u", pa_stream_get_buffer_attr(s)->maxlength);
     268             :         // JAMI_DBG("tlength %u", pa_stream_get_buffer_attr(s)->tlength);
     269             :         // JAMI_DBG("prebuf %u", pa_stream_get_buffer_attr(s)->prebuf);
     270             :         // JAMI_DBG("minreq %u", pa_stream_get_buffer_attr(s)->minreq);
     271             :         // JAMI_DBG("fragsize %u", pa_stream_get_buffer_attr(s)->fragsize);
     272             :         // JAMI_DBG("samplespec %s", pa_sample_spec_snprint(str, sizeof(str), pa_stream_get_sample_spec(s)));
     273           0 :         onReady_();
     274           0 :         break;
     275             : 
     276           0 :     case PA_STREAM_UNCONNECTED:
     277           0 :         JAMI_DBG("Stream unconnected");
     278           0 :         break;
     279             : 
     280           0 :     case PA_STREAM_FAILED:
     281             :     default:
     282           0 :         JAMI_ERR("Stream failure: %s", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
     283           0 :         break;
     284             :     }
     285           0 : }
     286             : 
     287             : bool
     288           0 : AudioStream::isReady()
     289             : {
     290           0 :     if (!audiostream_)
     291           0 :         return false;
     292             : 
     293           0 :     return pa_stream_get_state(audiostream_) == PA_STREAM_READY;
     294             : }
     295             : 
     296             : } // namespace jami

Generated by: LCOV version 1.14