LCOV - code coverage report
Current view: top level - src/media/audio - audiolayer.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 29.1 % 172 50
Test Date: 2026-08-23 08:52:56 Functions: 40.9 % 22 9

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

Generated by: LCOV version 2.0-1