LCOV - code coverage report
Current view: top level - src/media/audio/audio-processing - speex.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 0.0 % 81 0
Test Date: 2026-06-13 09:18:46 Functions: 0.0 % 18 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 "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_LOG("[speex-dsp] SpeexAudioProcessor, frame size = {} (={} ms), channels = {}",
      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_LOG("[speex-dsp] Done initializing");
      96            0 : }
      97              : 
      98              : void
      99            0 : SpeexAudioProcessor::enableEchoCancel(bool enabled)
     100              : {
     101            0 :     JAMI_LOG("[speex-dsp] enableEchoCancel {}", 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_LOG("[speex-dsp] enableNoiseSuppression {}", 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_LOG("[speex-dsp] enableAutomaticGainControl {}", 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_LOG("[speex-dsp] enableVoiceActivityDetection {}", 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 2.0-1