LCOV - code coverage report
Current view: top level - src/media/audio - audiolayer.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 51 173 29.5 %
Date: 2024-12-21 08:56:24 Functions: 13 34 38.2 %

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

Generated by: LCOV version 1.14