LCOV - code coverage report
Current view: top level - foo/src/media/audio/audio-processing - speex.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 0 81 0.0 %
Date: 2025-12-18 10:07:43 Functions: 0 6 0.0 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2025 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 "speex.h"
      19             : 
      20             : #include "audio/audiolayer.h"
      21             : 
      22             : #ifndef _MSC_VER
      23             : #if __has_include(<speex/speexdsp_config_types.h>)
      24             : #include <speex/speexdsp_config_types.h>
      25             : #else
      26             : #include <speex/speex_config_types.h>
      27             : #endif
      28             : #endif
      29             : extern "C" {
      30             : #include <speex/speex_echo.h>
      31             : #include <speex/speex_preprocess.h>
      32             : }
      33             : 
      34             : #include <cstdint>
      35             : #include <memory>
      36             : #include <vector>
      37             : 
      38             : namespace jami {
      39             : 
      40             : inline AudioFormat
      41             : audioFormatToSampleFormat(AudioFormat format)
      42             : {
      43             :     return {format.sample_rate, format.nb_channels, AV_SAMPLE_FMT_S16};
      44             : }
      45             : 
      46           0 : SpeexAudioProcessor::SpeexAudioProcessor(AudioFormat format, unsigned frameSize)
      47             :     : AudioProcessor(format.withSampleFormat(AV_SAMPLE_FMT_S16), frameSize)
      48           0 :     , echoState(speex_echo_state_init_mc((int) frameSize,
      49           0 :                                          (int) frameSize * 16,
      50           0 :                                          (int) format_.nb_channels,
      51           0 :                                          (int) format_.nb_channels),
      52           0 :                 &speex_echo_state_destroy)
      53           0 :     , procBuffer(std::make_unique<AudioFrame>(format.withSampleFormat(AV_SAMPLE_FMT_S16P), frameSize_))
      54             : {
      55           0 :     JAMI_DBG("[speex-dsp] SpeexAudioProcessor, frame size = %d (=%d ms), channels = %d",
      56             :              frameSize,
      57             :              frameDurationMs_,
      58             :              format_.nb_channels);
      59             :     // set up speex echo state
      60           0 :     speex_echo_ctl(echoState.get(), SPEEX_ECHO_SET_SAMPLING_RATE, &format_.sample_rate);
      61             : 
      62             :     // speex specific value to turn feature on (need to pass a pointer to it)
      63           0 :     spx_int32_t speexOn = 1;
      64             : 
      65             :     // probability integers, i.e. 50 means 50%
      66             :     // vad will be true if speex's raw probability calculation is higher than this in any case
      67           0 :     spx_int32_t probStart = 99;
      68             : 
      69             :     // vad will be true if voice was active last frame
      70             :     //     AND speex's raw probability calculation is higher than this
      71           0 :     spx_int32_t probContinue = 90;
      72             : 
      73             :     // maximum noise suppression in dB (negative)
      74           0 :     spx_int32_t maxNoiseSuppress = -50;
      75             : 
      76             :     // set up speex preprocess states, one for each channel
      77             :     // note that they are not enabled here, but rather in the enable* functions
      78           0 :     for (unsigned int i = 0; i < format_.nb_channels; i++) {
      79             :         auto channelPreprocessorState = SpeexPreprocessStatePtr(speex_preprocess_state_init((int) frameSize,
      80           0 :                                                                                             (int) format_.sample_rate),
      81           0 :                                                                 &speex_preprocess_state_destroy);
      82             : 
      83             :         // set max noise suppression level
      84           0 :         speex_preprocess_ctl(channelPreprocessorState.get(), SPEEX_PREPROCESS_SET_NOISE_SUPPRESS, &maxNoiseSuppress);
      85             : 
      86             :         // set up voice activity values
      87           0 :         speex_preprocess_ctl(channelPreprocessorState.get(), SPEEX_PREPROCESS_SET_VAD, &speexOn);
      88           0 :         speex_preprocess_ctl(channelPreprocessorState.get(), SPEEX_PREPROCESS_SET_PROB_START, &probStart);
      89           0 :         speex_preprocess_ctl(channelPreprocessorState.get(), SPEEX_PREPROCESS_SET_PROB_CONTINUE, &probContinue);
      90             : 
      91             :         // keep track of this channel's preprocessor state
      92           0 :         preprocessorStates.push_back(std::move(channelPreprocessorState));
      93           0 :     }
      94             : 
      95           0 :     JAMI_INFO("[speex-dsp] Done initializing");
      96           0 : }
      97             : 
      98             : void
      99           0 : SpeexAudioProcessor::enableEchoCancel(bool enabled)
     100             : {
     101           0 :     JAMI_DBG("[speex-dsp] enableEchoCancel %d", enabled);
     102             :     // need to set member variable so we know to do it in getProcessed
     103           0 :     shouldAEC = enabled;
     104             : 
     105           0 :     if (enabled) {
     106             :         // reset the echo canceller
     107           0 :         speex_echo_state_reset(echoState.get());
     108             : 
     109           0 :         for (auto& channelPreprocessorState : preprocessorStates) {
     110             :             // attach our already-created echo canceller
     111           0 :             speex_preprocess_ctl(channelPreprocessorState.get(), SPEEX_PREPROCESS_SET_ECHO_STATE, echoState.get());
     112             :         }
     113             :     } else {
     114           0 :         for (auto& channelPreprocessorState : preprocessorStates) {
     115             :             // detach echo canceller (set it to NULL)
     116             :             // don't destroy it though, we will reset it when necessary
     117           0 :             speex_preprocess_ctl(channelPreprocessorState.get(), SPEEX_PREPROCESS_SET_ECHO_STATE, NULL);
     118             :         }
     119             :     }
     120           0 : }
     121             : 
     122             : void
     123           0 : SpeexAudioProcessor::enableNoiseSuppression(bool enabled)
     124             : {
     125           0 :     JAMI_DBG("[speex-dsp] enableNoiseSuppression %d", enabled);
     126           0 :     spx_int32_t speexSetValue = (spx_int32_t) enabled;
     127             : 
     128             :     // for each preprocessor
     129           0 :     for (auto& channelPreprocessorState : preprocessorStates) {
     130             :         // set denoise status
     131           0 :         speex_preprocess_ctl(channelPreprocessorState.get(), SPEEX_PREPROCESS_SET_DENOISE, &speexSetValue);
     132             :         // set de-reverb status
     133           0 :         speex_preprocess_ctl(channelPreprocessorState.get(), SPEEX_PREPROCESS_SET_DEREVERB, &speexSetValue);
     134             :     }
     135           0 : }
     136             : 
     137             : void
     138           0 : SpeexAudioProcessor::enableAutomaticGainControl(bool enabled)
     139             : {
     140           0 :     JAMI_DBG("[speex-dsp] enableAutomaticGainControl %d", enabled);
     141           0 :     spx_int32_t speexSetValue = (spx_int32_t) enabled;
     142             : 
     143             :     // for each preprocessor
     144           0 :     for (auto& channelPreprocessorState : preprocessorStates) {
     145             :         // set AGC status
     146           0 :         speex_preprocess_ctl(channelPreprocessorState.get(), SPEEX_PREPROCESS_SET_AGC, &speexSetValue);
     147             :     }
     148           0 : }
     149             : 
     150             : void
     151           0 : SpeexAudioProcessor::enableVoiceActivityDetection(bool enabled)
     152             : {
     153           0 :     JAMI_DBG("[speex-dsp] enableVoiceActivityDetection %d", enabled);
     154             : 
     155           0 :     shouldDetectVoice = enabled;
     156             : 
     157           0 :     spx_int32_t speexSetValue = (spx_int32_t) enabled;
     158           0 :     for (auto& channelPreprocessorState : preprocessorStates) {
     159           0 :         speex_preprocess_ctl(channelPreprocessorState.get(), SPEEX_PREPROCESS_SET_VAD, &speexSetValue);
     160             :     }
     161           0 : }
     162             : 
     163             : std::shared_ptr<AudioFrame>
     164           0 : SpeexAudioProcessor::getProcessed()
     165             : {
     166           0 :     if (tidyQueues()) {
     167           0 :         return {};
     168             :     }
     169             : 
     170           0 :     auto playback = playbackQueue_.dequeue();
     171           0 :     auto record = recordQueue_.dequeue();
     172             : 
     173           0 :     if (!playback || !record) {
     174           0 :         return {};
     175             :     }
     176             : 
     177           0 :     std::shared_ptr<AudioFrame> processed;
     178           0 :     if (shouldAEC) {
     179             :         // we want to echo cancel
     180             :         // multichannel, output into processed
     181           0 :         processed = std::make_shared<AudioFrame>(record->getFormat(), record->getFrameSize());
     182           0 :         speex_echo_cancellation(echoState.get(),
     183           0 :                                 (int16_t*) record->pointer()->data[0],
     184           0 :                                 (int16_t*) playback->pointer()->data[0],
     185           0 :                                 (int16_t*) processed->pointer()->data[0]);
     186             :     } else {
     187             :         // don't want to echo cancel, so just use record frame instead
     188           0 :         processed = record;
     189             :     }
     190             : 
     191           0 :     deinterleaveResampler.resample(processed->pointer(), procBuffer->pointer());
     192             : 
     193             :     // overall voice activity
     194           0 :     bool overallVad = false;
     195             :     // current channel voice activity
     196             :     int channelVad;
     197             : 
     198             :     // run preprocess on each channel
     199           0 :     int channel = 0;
     200           0 :     for (auto& channelPreprocessorState : preprocessorStates) {
     201             :         // preprocesses in place, returns voice activity boolean
     202           0 :         channelVad = speex_preprocess_run(channelPreprocessorState.get(),
     203           0 :                                           (int16_t*) procBuffer->pointer()->data[channel]);
     204             : 
     205             :         // boolean OR
     206           0 :         overallVad |= channelVad;
     207             : 
     208           0 :         channel += 1;
     209             :     }
     210             : 
     211           0 :     interleaveResampler.resample(procBuffer->pointer(), processed->pointer());
     212             : 
     213             :     // add stabilized voice activity to the AudioFrame
     214           0 :     processed->has_voice = shouldDetectVoice && getStabilizedVoiceActivity(overallVad);
     215           0 :     return processed;
     216           0 : }
     217             : 
     218             : } // namespace jami

Generated by: LCOV version 1.14