LCOV - code coverage report
Current view: top level - foo/src/media/audio - audiolayer.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 50 171 29.2 %
Date: 2026-02-28 10:41:24 Functions: 13 34 38.2 %

          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 "audiolayer.h"
      19             : #include "audio/sound/tone.h"
      20             : #include "logger.h"
      21             : #include "manager.h"
      22             : #include "audio/ringbufferpool.h"
      23             : #include "audio/resampler.h"
      24             : #include "client/jami_signal.h"
      25             : 
      26             : #include "tracepoint.h"
      27             : #if HAVE_WEBRTC_AP
      28             : #include "audio-processing/webrtc.h"
      29             : #endif
      30             : #if HAVE_SPEEXDSP
      31             : #include "audio-processing/speex.h"
      32             : #endif
      33             : 
      34             : #include <ctime>
      35             : #include <algorithm>
      36             : 
      37             : namespace jami {
      38             : 
      39          34 : AudioLayer::AudioLayer(const AudioPreference& pref)
      40          68 :     : isCaptureMuted_(pref.getCaptureMuted())
      41          34 :     , isPlaybackMuted_(pref.getPlaybackMuted())
      42          68 :     , captureGain_(pref.getVolumemic())
      43          34 :     , playbackGain_(pref.getVolumespkr())
      44          34 :     , pref_(pref)
      45          34 :     , mainRingBuffer_(Manager::instance().getRingBufferPool().getRingBuffer(RingBufferPool::DEFAULT_ID))
      46          34 :     , audioFormat_(Manager::instance().getRingBufferPool().getInternalAudioFormat())
      47          34 :     , audioInputFormat_(Manager::instance().getRingBufferPool().getInternalAudioFormat())
      48          34 :     , urgentRingBuffer_("urgentRingBuffer_id", audioFormat_)
      49          34 :     , resampler_(new Resampler)
      50         204 :     , lastNotificationTime_()
      51             : {
      52          34 :     urgentRingBuffer_.createReadOffset(RingBufferPool::DEFAULT_ID);
      53             : 
      54         136 :     JAMI_LOG("[audiolayer] AGC: {:d}, noiseReduce: {:s}, VAD: {:d}, echoCancel: {:s}, audioProcessor: {:s}",
      55             :              pref_.isAGCEnabled(),
      56             :              pref.getNoiseReduce(),
      57             :              pref.getVadEnabled(),
      58             :              pref.getEchoCanceller(),
      59             :              pref.getAudioProcessor());
      60          34 : }
      61             : 
      62          34 : AudioLayer::~AudioLayer() {}
      63             : 
      64             : void
      65          84 : AudioLayer::hardwareFormatAvailable(AudioFormat playback, size_t bufSize)
      66             : {
      67         336 :     JAMI_LOG("Hardware audio format available: {:s} {}", playback.toString(), bufSize);
      68          84 :     audioFormat_ = Manager::instance().hardwareAudioFormatChanged(playback);
      69          84 :     audioInputFormat_.sampleFormat = audioFormat_.sampleFormat;
      70          84 :     urgentRingBuffer_.setFormat(audioFormat_);
      71          84 :     nativeFrameSize_ = bufSize;
      72          84 : }
      73             : 
      74             : void
      75           0 : AudioLayer::hardwareInputFormatAvailable(AudioFormat capture)
      76             : {
      77           0 :     JAMI_LOG("Hardware input audio format available: {:s}", capture.toString());
      78           0 : }
      79             : 
      80             : void
      81           0 : AudioLayer::devicesChanged()
      82             : {
      83           0 :     emitSignal<libjami::AudioSignal::DeviceEvent>();
      84           0 : }
      85             : 
      86             : void
      87           0 : AudioLayer::flushMain()
      88             : {
      89           0 :     Manager::instance().getRingBufferPool().flushAllBuffers();
      90           0 : }
      91             : 
      92             : void
      93         373 : AudioLayer::flushUrgent()
      94             : {
      95         373 :     urgentRingBuffer_.flushAll();
      96         373 : }
      97             : 
      98             : void
      99           0 : AudioLayer::flush()
     100             : {
     101           0 :     Manager::instance().getRingBufferPool().flushAllBuffers();
     102           0 :     urgentRingBuffer_.flushAll();
     103           0 : }
     104             : 
     105             : void
     106         188 : AudioLayer::playbackChanged(bool started)
     107             : {
     108         188 :     playbackStarted_ = started;
     109         188 : }
     110             : 
     111             : void
     112         188 : AudioLayer::recordChanged(bool started)
     113             : {
     114         188 :     std::lock_guard lock(audioProcessorMutex);
     115         188 :     if (started) {
     116             :         // create audio processor
     117           0 :         createAudioProcessor();
     118             :     } else {
     119             :         // destroy audio processor
     120         188 :         destroyAudioProcessor();
     121             :     }
     122         188 :     recordStarted_ = started;
     123         188 : }
     124             : 
     125             : // helper function
     126             : static inline bool
     127           0 : shouldUseAudioProcessorEchoCancel(bool hasNativeAEC, const std::string& echoCancellerPref)
     128             : {
     129             :     return
     130             :         // user doesn't care which and there is not a system AEC
     131           0 :         (echoCancellerPref == "auto" && !hasNativeAEC)
     132             :         // user specifically wants audioProcessor
     133           0 :         or (echoCancellerPref == "audioProcessor");
     134             : }
     135             : 
     136             : // helper function
     137             : static inline bool
     138           0 : shouldUseAudioProcessorNoiseSuppression(bool hasNativeNS, const std::string& noiseSuppressionPref)
     139             : {
     140             :     return
     141             :         // user doesn't care which and there is no system noise suppression
     142           0 :         (noiseSuppressionPref == "auto" && !hasNativeNS)
     143             :         // user specifically wants audioProcessor
     144           0 :         or (noiseSuppressionPref == "audioProcessor");
     145             : }
     146             : 
     147             : void
     148          31 : AudioLayer::setHasNativeAEC(bool hasNativeAEC)
     149             : {
     150          31 :     JAMI_INFO("[audiolayer] setHasNativeAEC: %d", hasNativeAEC);
     151          31 :     std::lock_guard lock(audioProcessorMutex);
     152          31 :     hasNativeAEC_ = hasNativeAEC;
     153             :     // if we have a current audio processor, tell it to enable/disable its own AEC
     154          31 :     if (audioProcessor) {
     155           0 :         audioProcessor->enableEchoCancel(shouldUseAudioProcessorEchoCancel(hasNativeAEC, pref_.getEchoCanceller()));
     156             :     }
     157          31 : }
     158             : 
     159             : void
     160          34 : AudioLayer::setHasNativeNS(bool hasNativeNS)
     161             : {
     162          34 :     JAMI_INFO("[audiolayer] setHasNativeNS: %d", hasNativeNS);
     163          34 :     std::lock_guard lock(audioProcessorMutex);
     164          34 :     hasNativeNS_ = hasNativeNS;
     165             :     // if we have a current audio processor, tell it to enable/disable its own noise suppression
     166          34 :     if (audioProcessor) {
     167           0 :         audioProcessor->enableNoiseSuppression(
     168           0 :             shouldUseAudioProcessorNoiseSuppression(hasNativeNS, pref_.getNoiseReduce()));
     169             :     }
     170          34 : }
     171             : 
     172             : // must acquire lock beforehand
     173             : void
     174           0 : AudioLayer::createAudioProcessor()
     175             : {
     176           0 :     auto nb_channels = std::max(audioFormat_.nb_channels, audioInputFormat_.nb_channels);
     177           0 :     auto sample_rate = std::max(audioFormat_.sample_rate, audioInputFormat_.sample_rate);
     178             : 
     179           0 :     sample_rate = std::clamp(sample_rate, 16000u, 48000u);
     180             : 
     181           0 :     AudioFormat formatForProcessor {sample_rate, nb_channels};
     182             : 
     183             :     unsigned int frame_size;
     184           0 :     if (pref_.getAudioProcessor() == "speex") {
     185             :         // TODO: maybe force this to be equivalent to 20ms? as expected by Speex
     186           0 :         frame_size = sample_rate / 50u;
     187             :     } else {
     188           0 :         frame_size = sample_rate / 100u;
     189             :     }
     190             : 
     191           0 :     JAMI_WARNING("Input {}", audioInputFormat_.toString());
     192           0 :     JAMI_WARNING("Output {}", audioFormat_.toString());
     193           0 :     JAMI_WARNING("Starting audio processor with: [{} Hz, {} channels, {} samples/frame]",
     194             :                  sample_rate,
     195             :                  nb_channels,
     196             :                  frame_size);
     197             : 
     198           0 :     if (pref_.getAudioProcessor() == "webrtc") {
     199             : #if HAVE_WEBRTC_AP
     200           0 :         JAMI_WARN("[audiolayer] using WebRTCAudioProcessor");
     201           0 :         audioProcessor.reset(new WebRTCAudioProcessor(formatForProcessor, frame_size));
     202             : #else
     203             :         JAMI_ERR("[audiolayer] audioProcessor preference is webrtc, but library not linked! "
     204             :                  "using null AudioProcessor instead");
     205             :         audioProcessor.reset();
     206             : #endif
     207           0 :     } else if (pref_.getAudioProcessor() == "speex") {
     208             : #if HAVE_SPEEXDSP
     209           0 :         JAMI_WARN("[audiolayer] using SpeexAudioProcessor");
     210           0 :         audioProcessor.reset(new SpeexAudioProcessor(formatForProcessor, frame_size));
     211             : #else
     212             :         JAMI_ERR("[audiolayer] audioProcessor preference is Speex, but library not linked! "
     213             :                  "using null AudioProcessor instead");
     214             :         audioProcessor.reset();
     215             : #endif
     216           0 :     } else if (pref_.getAudioProcessor() == "null") {
     217           0 :         JAMI_WARN("[audiolayer] using null AudioProcessor");
     218           0 :         audioProcessor.reset();
     219             :     } else {
     220           0 :         JAMI_ERR("[audiolayer] audioProcessor preference not recognized, using null AudioProcessor "
     221             :                  "instead");
     222           0 :         audioProcessor.reset();
     223             :     }
     224             : 
     225           0 :     if (audioProcessor) {
     226           0 :         audioProcessor->enableNoiseSuppression(
     227           0 :             shouldUseAudioProcessorNoiseSuppression(hasNativeNS_, pref_.getNoiseReduce()));
     228             : 
     229           0 :         audioProcessor->enableAutomaticGainControl(pref_.isAGCEnabled());
     230             : 
     231           0 :         audioProcessor->enableEchoCancel(shouldUseAudioProcessorEchoCancel(hasNativeAEC_, pref_.getEchoCanceller()));
     232             : 
     233           0 :         audioProcessor->enableVoiceActivityDetection(pref_.getVadEnabled());
     234             :     }
     235           0 : }
     236             : 
     237             : // must acquire lock beforehand
     238             : void
     239         188 : AudioLayer::destroyAudioProcessor()
     240             : {
     241             :     // delete it
     242         188 :     audioProcessor.reset();
     243         188 : }
     244             : 
     245             : void
     246           0 : AudioLayer::putUrgent(std::shared_ptr<AudioFrame> buffer)
     247             : {
     248           0 :     urgentRingBuffer_.put(std::move(buffer));
     249           0 : }
     250             : 
     251             : // Notify (with a beep) an incoming call when there is already a call in progress
     252             : void
     253           0 : AudioLayer::notifyIncomingCall()
     254             : {
     255           0 :     if (not playIncomingCallBeep_)
     256           0 :         return;
     257             : 
     258           0 :     auto now = std::chrono::system_clock::now();
     259             : 
     260             :     // Notify maximum once every 5 seconds
     261           0 :     if (now < lastNotificationTime_ + std::chrono::seconds(5))
     262           0 :         return;
     263             : 
     264           0 :     lastNotificationTime_ = now;
     265             : 
     266           0 :     Tone tone("440/160", getSampleRate(), audioFormat_.sampleFormat);
     267           0 :     size_t nbSample = tone.getSize();
     268             : 
     269             :     /* Put the data in the urgent ring buffer */
     270           0 :     urgentRingBuffer_.flushAll();
     271           0 :     urgentRingBuffer_.put(tone.getNext(nbSample));
     272           0 : }
     273             : 
     274             : std::shared_ptr<AudioFrame>
     275           0 : AudioLayer::getToRing(AudioFormat format, size_t writableSamples)
     276             : {
     277           0 :     if (auto fileToPlay = Manager::instance().getTelephoneFile()) {
     278           0 :         auto fileformat = fileToPlay->getFormat();
     279           0 :         bool resample = format != fileformat;
     280             : 
     281           0 :         size_t readableSamples = resample ? rational<size_t>(writableSamples * (size_t) fileformat.sample_rate,
     282           0 :                                                              format.sample_rate)
     283           0 :                                                 .real<size_t>()
     284           0 :                                           : writableSamples;
     285             : 
     286           0 :         return resampler_->resample(fileToPlay->getNext(readableSamples, isRingtoneMuted_), format);
     287           0 :     }
     288           0 :     return {};
     289             : }
     290             : 
     291             : std::shared_ptr<AudioFrame>
     292           0 : AudioLayer::getToPlay(AudioFormat format, size_t writableSamples)
     293             : {
     294           0 :     notifyIncomingCall();
     295           0 :     auto& bufferPool = Manager::instance().getRingBufferPool();
     296             : 
     297           0 :     if (not playbackQueue_)
     298           0 :         playbackQueue_.reset(new AudioFrameResizer(format, static_cast<int>(writableSamples)));
     299             :     else
     300           0 :         playbackQueue_->setFrameSize(static_cast<int>(writableSamples));
     301             : 
     302           0 :     std::shared_ptr<AudioFrame> playbackBuf {};
     303           0 :     while (!(playbackBuf = playbackQueue_->dequeue())) {
     304           0 :         std::shared_ptr<AudioFrame> resampled;
     305             : 
     306           0 :         if (auto urgentSamples = urgentRingBuffer_.get(RingBufferPool::DEFAULT_ID)) {
     307           0 :             bufferPool.discard(1, RingBufferPool::DEFAULT_ID);
     308           0 :             resampled = resampler_->resample(std::move(urgentSamples), format);
     309           0 :         } else if (auto toneToPlay = Manager::instance().getTelephoneTone()) {
     310           0 :             resampled = resampler_->resample(toneToPlay->getNext(), format);
     311           0 :         } else if (auto buf = bufferPool.getData(RingBufferPool::DEFAULT_ID)) {
     312           0 :             resampled = resampler_->resample(std::move(buf), format);
     313             :         } else {
     314           0 :             std::lock_guard lock(audioProcessorMutex);
     315           0 :             if (audioProcessor) {
     316           0 :                 auto silence = std::make_shared<AudioFrame>(format, writableSamples);
     317           0 :                 libav_utils::fillWithSilence(silence->pointer());
     318           0 :                 audioProcessor->putPlayback(silence);
     319           0 :             }
     320           0 :             break;
     321           0 :         }
     322             : 
     323           0 :         if (resampled) {
     324           0 :             std::lock_guard lock(audioProcessorMutex);
     325           0 :             if (audioProcessor) {
     326           0 :                 audioProcessor->putPlayback(resampled);
     327             :             }
     328           0 :             playbackQueue_->enqueue(std::move(resampled));
     329           0 :         } else
     330           0 :             break;
     331           0 :     }
     332             : 
     333             :     jami_tracepoint(audio_layer_get_to_play_end);
     334             : 
     335           0 :     return playbackBuf;
     336           0 : }
     337             : 
     338             : void
     339           0 : AudioLayer::putRecorded(std::shared_ptr<AudioFrame>&& frame)
     340             : {
     341           0 :     std::lock_guard lock(audioProcessorMutex);
     342           0 :     if (audioProcessor && playbackStarted_ && recordStarted_) {
     343           0 :         audioProcessor->putRecorded(std::move(frame));
     344           0 :         while (auto rec = audioProcessor->getProcessed()) {
     345           0 :             mainRingBuffer_->put(std::move(rec));
     346           0 :         }
     347             :     } else {
     348           0 :         mainRingBuffer_->put(std::move(frame));
     349             :     }
     350             : 
     351             :     jami_tracepoint(audio_layer_put_recorded_end, );
     352           0 : }
     353             : 
     354             : } // namespace jami

Generated by: LCOV version 1.14