LCOV - code coverage report
Current view: top level - src/media/audio/pulseaudio - audiostream.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 0.0 % 125 0
Test Date: 2026-06-13 09:18:46 Functions: 0.0 % 41 0

            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 "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            0 :     pa_sample_spec sample_spec = {format, samplrate, infos.channel_map.channels};
      47              : 
      48            0 :     JAMI_DEBUG("{}: Creating stream with device {} ({}, {}Hz, {} channels)",
      49              :                desc,
      50              :                infos.name,
      51              :                pa_sample_format_to_string(sample_spec.format),
      52              :                samplrate,
      53              :                infos.channel_map.channels);
      54              : 
      55            0 :     assert(pa_sample_spec_valid(&sample_spec));
      56            0 :     assert(pa_channel_map_valid(&infos.channel_map));
      57              : 
      58            0 :     std::unique_ptr<pa_proplist, decltype(pa_proplist_free)&> pl(pa_proplist_new(), pa_proplist_free);
      59            0 :     pa_proplist_sets(pl.get(), PA_PROP_FILTER_WANT, "echo-cancel");
      60            0 :     pa_proplist_sets(pl.get(),
      61              :                      "filter.apply.echo-cancel.parameters", // needs pulseaudio >= 11.0
      62              :                      "use_volume_sharing=0"                 // share volume with master sink/source
      63              :                      " use_master_format=1"                 // use format/rate/channels from master sink/source
      64              :                      " aec_args=\""
      65              :                      "digital_gain_control=1"
      66              :                      " analog_gain_control=0"
      67              :                      " experimental_agc=1"
      68              :                      "\"");
      69              : 
      70            0 :     audiostream_ = pa_stream_new_with_proplist(c, desc, &sample_spec, &infos.channel_map, ec ? pl.get() : nullptr);
      71            0 :     if (!audiostream_) {
      72            0 :         JAMI_ERROR("{}: pa_stream_new() failed : {}", desc, pa_strerror(pa_context_errno(c)));
      73            0 :         throw std::runtime_error("Unable to create stream\n");
      74              :     }
      75              : 
      76              :     pa_buffer_attr attributes;
      77            0 :     attributes.maxlength = pa_usec_to_bytes(160 * PA_USEC_PER_MSEC, &sample_spec);
      78            0 :     attributes.tlength = pa_usec_to_bytes(80 * PA_USEC_PER_MSEC, &sample_spec);
      79            0 :     attributes.prebuf = 0;
      80            0 :     attributes.fragsize = pa_usec_to_bytes(80 * PA_USEC_PER_MSEC, &sample_spec);
      81            0 :     attributes.minreq = (uint32_t) -1;
      82              : 
      83            0 :     pa_stream_set_state_callback(
      84              :         audiostream_,
      85            0 :         [](pa_stream* s, void* user_data) { static_cast<AudioStream*>(user_data)->stateChanged(s); },
      86              :         this);
      87            0 :     pa_stream_set_moved_callback(
      88            0 :         audiostream_, [](pa_stream* s, void* user_data) { static_cast<AudioStream*>(user_data)->moved(s); }, this);
      89              : 
      90            0 :     constexpr pa_stream_flags_t flags = static_cast<pa_stream_flags_t>(
      91              :         PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_START_CORKED);
      92              : 
      93            0 :     if (type == AudioDeviceType::PLAYBACK || type == AudioDeviceType::RINGTONE) {
      94            0 :         pa_stream_set_write_callback(
      95              :             audiostream_,
      96            0 :             [](pa_stream* /*s*/, size_t bytes, void* userdata) { static_cast<AudioStream*>(userdata)->onData_(bytes); },
      97              :             this);
      98              : 
      99            0 :         pa_stream_connect_playback(audiostream_,
     100            0 :                                    infos.name.empty() ? nullptr : infos.name.c_str(),
     101              :                                    &attributes,
     102              :                                    flags,
     103              :                                    nullptr,
     104              :                                    nullptr);
     105            0 :     } else if (type == AudioDeviceType::CAPTURE) {
     106            0 :         pa_stream_set_read_callback(
     107              :             audiostream_,
     108            0 :             [](pa_stream* /*s*/, size_t bytes, void* userdata) { static_cast<AudioStream*>(userdata)->onData_(bytes); },
     109              :             this);
     110              : 
     111            0 :         pa_stream_connect_record(audiostream_, infos.name.empty() ? nullptr : infos.name.c_str(), &attributes, flags);
     112              :     }
     113            0 : }
     114              : 
     115              : void
     116            0 : disconnectStream(pa_stream* s)
     117              : {
     118              :     // make sure we don't get any further callback
     119            0 :     pa_stream_set_write_callback(s, nullptr, nullptr);
     120            0 :     pa_stream_set_read_callback(s, nullptr, nullptr);
     121            0 :     pa_stream_set_moved_callback(s, nullptr, nullptr);
     122            0 :     pa_stream_set_underflow_callback(s, nullptr, nullptr);
     123            0 :     pa_stream_set_overflow_callback(s, nullptr, nullptr);
     124            0 :     pa_stream_set_suspended_callback(s, nullptr, nullptr);
     125            0 :     pa_stream_set_started_callback(s, nullptr, nullptr);
     126            0 : }
     127              : 
     128              : void
     129            0 : destroyStream(pa_stream* s)
     130              : {
     131            0 :     pa_stream_disconnect(s);
     132            0 :     pa_stream_set_state_callback(s, nullptr, nullptr);
     133            0 :     disconnectStream(s);
     134            0 :     pa_stream_unref(s);
     135            0 : }
     136              : 
     137            0 : AudioStream::~AudioStream()
     138              : {
     139            0 :     stop();
     140            0 : }
     141              : 
     142              : void
     143            0 : AudioStream::start()
     144              : {
     145            0 :     pa_stream_cork(audiostream_, 0, nullptr, nullptr);
     146              : 
     147              :     // trigger echo cancel check
     148            0 :     moved(audiostream_);
     149            0 : }
     150              : 
     151              : void
     152            0 : AudioStream::stop()
     153              : {
     154            0 :     if (not audiostream_)
     155            0 :         return;
     156            0 :     const char* deviceName = pa_stream_get_device_name(audiostream_);
     157            0 :     JAMI_LOG("Destroying stream with device {}", deviceName ? deviceName : "(unknown)");
     158            0 :     if (pa_stream_get_state(audiostream_) == PA_STREAM_CREATING) {
     159            0 :         disconnectStream(audiostream_);
     160            0 :         pa_stream_set_state_callback(audiostream_, [](pa_stream* s, void*) { destroyStream(s); }, nullptr);
     161              :     } else {
     162            0 :         destroyStream(audiostream_);
     163              :     }
     164            0 :     audiostream_ = nullptr;
     165              : 
     166              :     // pa_operation_cancel calls the operation's state callback synchronously, so
     167              :     // copy ongoing_ops to avoid erasing its elements while iterating over it.
     168            0 :     auto ops = ongoing_ops;
     169            0 :     for (auto op : ops)
     170            0 :         pa_operation_cancel(op);
     171            0 : }
     172              : 
     173              : void
     174            0 : AudioStream::moved(pa_stream* s)
     175              : {
     176            0 :     audiostream_ = s;
     177            0 :     const char* name = pa_stream_get_device_name(s);
     178            0 :     JAMI_LOG("[audiostream] Stream moved: {:d}, {:s}", pa_stream_get_index(s), name ? name : "(unknown device)");
     179              : 
     180            0 :     if (audioType_ == AudioDeviceType::CAPTURE) {
     181              :         // check for echo cancel
     182            0 :         if (!name) {
     183            0 :             JAMI_ERROR("[audiostream] moved() unable to get audio stream device");
     184            0 :             return;
     185              :         }
     186              : 
     187            0 :         auto* op = pa_context_get_source_info_by_name(
     188              :             pa_stream_get_context(s),
     189              :             name,
     190            0 :             [](pa_context* /*c*/, const pa_source_info* i, int /*eol*/, void* userdata) {
     191            0 :                 AudioStream* thisPtr = (AudioStream*) userdata;
     192              :                 // this closure gets called twice by pulse for some reason
     193              :                 // the 2nd time, i is invalid
     194            0 :                 if (!i) {
     195              :                     // JAMI_ERROR("[audiostream] source info not found");
     196            0 :                     return;
     197              :                 }
     198            0 :                 if (!thisPtr) {
     199            0 :                     JAMI_ERROR("[audiostream] AudioStream pointer became invalid during pa_source_info_cb_t callback!");
     200            0 :                     return;
     201              :                 }
     202              : 
     203              :                 // string compare
     204            0 :                 bool usingEchoCancel = std::string_view(i->driver) == "module-echo-cancel.c"sv;
     205            0 :                 JAMI_WARNING("[audiostream] capture stream using pulse echo cancel module? {} ({})",
     206              :                              usingEchoCancel ? "yes" : "no",
     207              :                              i->name);
     208            0 :                 thisPtr->echoCancelCb(usingEchoCancel);
     209              :             },
     210            0 :             this);
     211              : 
     212            0 :         pa_operation_set_state_callback(
     213            0 :             op, [](pa_operation* op, void* userdata) { static_cast<AudioStream*>(userdata)->opEnded(op); }, this);
     214            0 :         ongoing_ops.emplace(op);
     215              :     }
     216              : }
     217              : 
     218              : void
     219            0 : AudioStream::opEnded(pa_operation* op)
     220              : {
     221            0 :     ongoing_ops.erase(op);
     222            0 :     pa_operation_unref(op);
     223            0 : }
     224              : 
     225              : void
     226            0 : AudioStream::stateChanged(pa_stream* s)
     227              : {
     228              :     // UNUSED char str[PA_SAMPLE_SPEC_SNPRINT_MAX];
     229              : 
     230            0 :     switch (pa_stream_get_state(s)) {
     231            0 :     case PA_STREAM_CREATING:
     232            0 :         JAMI_LOG("Stream is creating…");
     233            0 :         break;
     234              : 
     235            0 :     case PA_STREAM_TERMINATED:
     236            0 :         JAMI_LOG("Stream is terminating…");
     237            0 :         break;
     238              : 
     239            0 :     case PA_STREAM_READY: {
     240            0 :         const char* deviceName = pa_stream_get_device_name(s);
     241            0 :         JAMI_LOG("Stream successfully created, connected to {}", deviceName ? deviceName : "(unknown)");
     242              :         // JAMI_DEBUG("maxlength {}", pa_stream_get_buffer_attr(s)->maxlength);
     243              :         // JAMI_DEBUG("tlength {}", pa_stream_get_buffer_attr(s)->tlength);
     244              :         // JAMI_DEBUG("prebuf {}", pa_stream_get_buffer_attr(s)->prebuf);
     245              :         // JAMI_DEBUG("minreq {}", pa_stream_get_buffer_attr(s)->minreq);
     246              :         // JAMI_DEBUG("fragsize {}", pa_stream_get_buffer_attr(s)->fragsize);
     247              :         // JAMI_DEBUG("samplespec {}", pa_sample_spec_snprint(str, sizeof(str), pa_stream_get_sample_spec(s)));
     248            0 :         onReady_();
     249            0 :         break;
     250              :     }
     251              : 
     252            0 :     case PA_STREAM_UNCONNECTED:
     253            0 :         JAMI_LOG("Stream unconnected");
     254            0 :         break;
     255              : 
     256            0 :     case PA_STREAM_FAILED:
     257              :     default:
     258            0 :         JAMI_ERROR("Stream failure: {}", pa_strerror(pa_context_errno(pa_stream_get_context(s))));
     259            0 :         break;
     260              :     }
     261            0 : }
     262              : 
     263              : bool
     264            0 : AudioStream::isReady()
     265              : {
     266            0 :     if (!audiostream_)
     267            0 :         return false;
     268              : 
     269            0 :     return pa_stream_get_state(audiostream_) == PA_STREAM_READY;
     270              : }
     271              : 
     272              : } // namespace jami
        

Generated by: LCOV version 2.0-1