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-12-21 08:56:24 Functions: 0 26 0.0 %

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

Generated by: LCOV version 1.14