From e787de4ea59315ae9c507b5ef6a272b08f731e51 Mon Sep 17 00:00:00 2001 From: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> Date: Wed, 24 Feb 2021 17:11:33 -0500 Subject: [PATCH] aec: move aec implementations into EchoCanceller Hide speexdsp's echo cancellation implementation details behind an EchoCanceller derived class. An AudioLayer may now instantiate implementations of the EchoCanceller. This may be platform specific compile-time or swapped out at runtime. Introduces a null echo canceller for testing. As the speexdsp aec is not currently functional, the null echo canceller is replaced as the default implementation. GitLab: #454 Change-Id: I169f1e9758afbed884fc42d9d78a69ce28d12fe2 --- CMakeLists.txt | 2 + compat/msvc/config.h | 2 +- configure.ac | 1 + src/CMakeLists.txt | 1 + src/media/CMakeLists.txt | 1 + src/media/audio/CMakeLists.txt | 5 +- src/media/audio/Makefile.am | 5 +- src/media/audio/audiolayer.cpp | 98 ++-------------- src/media/audio/audiolayer.h | 6 +- src/media/audio/echo-cancel/CMakeLists.txt | 12 ++ src/media/audio/echo-cancel/Makefile.am | 18 +++ src/media/audio/echo-cancel/echo_canceller.h | 63 ++++++++++ .../audio/echo-cancel/null_echo_canceller.cpp | 73 ++++++++++++ .../audio/echo-cancel/null_echo_canceller.h | 39 +++++++ .../echo-cancel/speex_echo_canceller.cpp | 109 ++++++++++++++++++ .../audio/echo-cancel/speex_echo_canceller.h | 51 ++++++++ 16 files changed, 391 insertions(+), 95 deletions(-) create mode 100644 src/media/audio/echo-cancel/CMakeLists.txt create mode 100644 src/media/audio/echo-cancel/Makefile.am create mode 100644 src/media/audio/echo-cancel/echo_canceller.h create mode 100644 src/media/audio/echo-cancel/null_echo_canceller.cpp create mode 100644 src/media/audio/echo-cancel/null_echo_canceller.h create mode 100644 src/media/audio/echo-cancel/speex_echo_canceller.cpp create mode 100644 src/media/audio/echo-cancel/speex_echo_canceller.h diff --git a/CMakeLists.txt b/CMakeLists.txt index e6f5ab86aa..45586a0daa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ source_group("Source Files\\jamidht\\eth\\libdevcore" FILES ${Source_Files__jami source_group("Source Files\\jamidht\\eth\\libdevcrypto" FILES ${Source_Files__jamidht__eth__libdevcrypto}) source_group("Source Files\\media" FILES ${Source_Files__media}) source_group("Source Files\\media\\audio" FILES ${Source_Files__media__audio}) +source_group("Source Files\\media\\audio\\echo-cancel" FILES ${Source_Files__media__audio__echo_cancel}) source_group("Source Files\\media\\audio\\sound" FILES ${Source_Files__media__audio__sound}) source_group("Source Files\\media\\video" FILES ${Source_Files__media__video}) source_group("Source Files\\plugin" FILES ${Source_Files__plugin}) @@ -54,6 +55,7 @@ list (APPEND ALL_FILES ${Source_Files__media} ${Source_Files__media__audio} ${Source_Files__media__audio__sound} + ${Source_Files__media__audio__echo_cancel} ${Source_Files__media__video} ${Source_Files__security} ${Source_Files__sip} diff --git a/compat/msvc/config.h b/compat/msvc/config.h index 151f1eb846..a45cd4de22 100644 --- a/compat/msvc/config.h +++ b/compat/msvc/config.h @@ -98,7 +98,7 @@ systems. This function is required for `alloca.c' support on those systems. #define HAVE_SPEEX 0 /* Define if you have libspeexdsp */ -#define HAVE_SPEEXDSP 0 +#define HAVE_SPEEXDSP 1 /* Define to 1 if stdbool.h conforms to C99. */ #define HAVE_STDBOOL_H 1 diff --git a/configure.ac b/configure.ac index 3946a3f341..18f2969219 100644 --- a/configure.ac +++ b/configure.ac @@ -641,6 +641,7 @@ AC_CONFIG_FILES([Makefile \ src/media/audio/coreaudio/Makefile \ src/media/audio/portaudio/Makefile \ src/media/audio/sound/Makefile \ + src/media/audio/echo-cancel/Makefile \ src/config/Makefile \ src/client/Makefile \ src/media/video/Makefile \ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 095656d2a5..1ed0cf3915 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -95,6 +95,7 @@ set (Source_Files__jamidht__eth__libdevcrypto ${Source_Files__jamidht__eth__libd set (Source_Files__media ${Source_Files__media} PARENT_SCOPE) set (Source_Files__media__audio ${Source_Files__media__audio} PARENT_SCOPE) set (Source_Files__media__audio__sound ${Source_Files__media__audio__sound} PARENT_SCOPE) +set (Source_Files__media__audio__echo_cancel ${Source_Files__media__audio__echo_cancel} PARENT_SCOPE) set (Source_Files__media__video ${Source_Files__media__video} PARENT_SCOPE) if(MSVC) if(WINDOWS_STORE) diff --git a/src/media/CMakeLists.txt b/src/media/CMakeLists.txt index 320115d938..264aeac280 100644 --- a/src/media/CMakeLists.txt +++ b/src/media/CMakeLists.txt @@ -54,6 +54,7 @@ if(MSVC) set (Source_Files__media__audio__portaudio ${Source_Files__media__audio__portaudio} PARENT_SCOPE) endif() set (Source_Files__media__audio__sound ${Source_Files__media__audio__sound} PARENT_SCOPE) +set (Source_Files__media__audio__echo_cancel ${Source_Files__media__audio__echo_cancel} PARENT_SCOPE) set (Source_Files__media__video ${Source_Files__media__video} PARENT_SCOPE) if(MSVC) diff --git a/src/media/audio/CMakeLists.txt b/src/media/audio/CMakeLists.txt index f683857229..a6f71f24a6 100644 --- a/src/media/audio/CMakeLists.txt +++ b/src/media/audio/CMakeLists.txt @@ -48,4 +48,7 @@ if(MSVC) endif() add_subdirectory(sound) -set (Source_Files__media__audio__sound ${Source_Files__media__audio__sound} PARENT_SCOPE) \ No newline at end of file +set (Source_Files__media__audio__sound ${Source_Files__media__audio__sound} PARENT_SCOPE) + +add_subdirectory(echo-cancel) +set (Source_Files__media__audio__echo_cancel ${Source_Files__media__audio__echo_cancel} PARENT_SCOPE) \ No newline at end of file diff --git a/src/media/audio/Makefile.am b/src/media/audio/Makefile.am index 34138871c4..2772e51c4a 100644 --- a/src/media/audio/Makefile.am +++ b/src/media/audio/Makefile.am @@ -2,7 +2,7 @@ include $(top_srcdir)/globals.mk noinst_LTLIBRARIES = libaudio.la -SUBDIRS = sound +SUBDIRS = sound echo-cancel if BUILD_OPENSL SUBDIRS += opensl @@ -78,7 +78,8 @@ noinst_HEADERS = \ tonecontrol.h libaudio_la_LIBADD = \ - ./sound/libsound.la + ./sound/libsound.la \ + ./echo-cancel/libecho-cancel.la if BUILD_PULSE libaudio_la_LIBADD += ./pulseaudio/libpulselayer.la diff --git a/src/media/audio/audiolayer.cpp b/src/media/audio/audiolayer.cpp index c5a1ba4907..927db4896d 100644 --- a/src/media/audio/audiolayer.cpp +++ b/src/media/audio/audiolayer.cpp @@ -27,87 +27,13 @@ #include "audio/resampler.h" #include "tonecontrol.h" #include "client/ring_signal.h" - -extern "C" { -#include <speex/speex_echo.h> -} +#include "echo-cancel/null_echo_canceller.h" #include <ctime> #include <algorithm> namespace jami { -struct AudioLayer::EchoState -{ - EchoState(AudioFormat format, unsigned frameSize, unsigned) - : state(speex_echo_state_init_mc(frameSize, - frameSize * 16, - format.nb_channels, - format.nb_channels), - &speex_echo_state_destroy) - , playbackQueue(format, frameSize) - , recordQueue(format, frameSize) - { - int sr = format.sample_rate; - speex_echo_ctl(state.get(), SPEEX_ECHO_SET_SAMPLING_RATE, &sr); - } - - void putRecorded(std::shared_ptr<AudioFrame>&& in) - { - // JAMI_DBG("putRecorded %s %d", in->getFormat().toString().c_str(), in->getFrameSize()); - recordQueue.enqueue(std::move(in)); - } - - void putPlayback(const std::shared_ptr<AudioFrame>& in) - { - // JAMI_DBG("putPlayback %s %d", in->getFormat().toString().c_str(), in->getFrameSize()); - auto c = in; - playbackQueue.enqueue(std::move(c)); - } - - std::shared_ptr<AudioFrame> getRecorded() - { - if (playbackQueue.samples() < playbackQueue.frameSize() - or recordQueue.samples() < recordQueue.frameSize()) { - JAMI_DBG("getRecorded underflow %d / %d, %d / %d", - playbackQueue.samples(), - playbackQueue.frameSize(), - recordQueue.samples(), - recordQueue.frameSize()); - return {}; - } - if (recordQueue.samples() > 2 * recordQueue.frameSize() && playbackQueue.samples() == 0) { - JAMI_DBG("getRecorded PLAYBACK underflow"); - return recordQueue.dequeue(); - } - while (playbackQueue.samples() > 10 * playbackQueue.frameSize()) { - JAMI_DBG("getRecorded record underflow"); - playbackQueue.dequeue(); - } - while (recordQueue.samples() > 4 * recordQueue.frameSize()) { - JAMI_DBG("getRecorded playback underflow"); - recordQueue.dequeue(); - } - auto playback = playbackQueue.dequeue(); - auto record = recordQueue.dequeue(); - if (playback and record) { - auto ret = std::make_shared<AudioFrame>(record->getFormat(), record->getFrameSize()); - speex_echo_cancellation(state.get(), - (const int16_t*) record->pointer()->data[0], - (const int16_t*) playback->pointer()->data[0], - (int16_t*) ret->pointer()->data[0]); - return ret; - } - return {}; - } - -private: - using SpeexEchoStatePtr = std::unique_ptr<SpeexEchoState, void (*)(SpeexEchoState*)>; - SpeexEchoStatePtr state; - AudioFrameResizer playbackQueue; - AudioFrameResizer recordQueue; -}; - AudioLayer::AudioLayer(const AudioPreference& pref) : isCaptureMuted_(pref.getCaptureMuted()) , isPlaybackMuted_(pref.getPlaybackMuted()) @@ -194,13 +120,12 @@ AudioLayer::checkAEC() { bool shouldSoftAEC = not hasNativeAEC_ and playbackStarted_ and recordStarted_; - if (not echoState_ and shouldSoftAEC) { + if (not echoCanceller_ and shouldSoftAEC) { JAMI_WARN("Starting AEC"); - echoState_.reset( - new EchoState(audioFormat_, nativeFrameSize_, audioFormat_.sample_rate / 4)); - } else if (echoState_ and not shouldSoftAEC) { + echoCanceller_.reset(new NullEchoCanceller(audioFormat_, audioFormat_.sample_rate / 100)); + } else if (echoCanceller_ and not shouldSoftAEC) { JAMI_WARN("Stopping AEC"); - echoState_.reset(); + echoCanceller_.reset(); } } @@ -288,8 +213,8 @@ AudioLayer::getToPlay(AudioFormat format, size_t writableSamples) } if (resampled) { - if (echoState_) { - echoState_->putPlayback(resampled); + if (echoCanceller_) { + echoCanceller_->putPlayback(resampled); } playbackQueue_->enqueue(std::move(resampled)); } else @@ -302,12 +227,9 @@ AudioLayer::getToPlay(AudioFormat format, size_t writableSamples) void AudioLayer::putRecorded(std::shared_ptr<AudioFrame>&& frame) { - // if (isCaptureMuted_) - // libav_utils::fillWithSilence(frame->pointer()); - - if (echoState_) { - echoState_->putRecorded(std::move(frame)); - while (auto rec = echoState_->getRecorded()) + if (echoCanceller_) { + echoCanceller_->putRecorded(std::move(frame)); + while (auto rec = echoCanceller_->getProcessed()) mainRingBuffer_->put(std::move(rec)); } else { mainRingBuffer_->put(std::move(frame)); diff --git a/src/media/audio/audiolayer.h b/src/media/audio/audiolayer.h index fa11a75812..ab0e5483da 100644 --- a/src/media/audio/audiolayer.h +++ b/src/media/audio/audiolayer.h @@ -26,6 +26,7 @@ #include "dcblocker.h" #include "noncopyable.h" #include "audio_frame_resizer.h" +#include "echo-cancel/echo_canceller.h" #include <chrono> #include <mutex> @@ -52,7 +53,7 @@ typedef struct SpeexEchoState_ SpeexEchoState; #define COREAUDIO_API_STR "coreaudio" #define PORTAUDIO_API_STR "portaudio" -#define PCM_DEFAULT "default" // Default ALSA plugin +#define PCM_DEFAULT "default" // Default ALSA plugin #define PCM_DSNOOP "plug:dsnoop" // Alsa plugin for microphone sharing #define PCM_DMIX_DSNOOP "dmix/dsnoop" // Audio profile using Alsa dmix/dsnoop @@ -288,8 +289,7 @@ protected: */ std::unique_ptr<Resampler> resampler_; - struct EchoState; - std::unique_ptr<EchoState> echoState_; + std::unique_ptr<EchoCanceller> echoCanceller_; private: void checkAEC(); diff --git a/src/media/audio/echo-cancel/CMakeLists.txt b/src/media/audio/echo-cancel/CMakeLists.txt new file mode 100644 index 0000000000..fec1af7d8f --- /dev/null +++ b/src/media/audio/echo-cancel/CMakeLists.txt @@ -0,0 +1,12 @@ +################################################################################ +# Source groups - echo-cancel +################################################################################ +list (APPEND Source_Files__media__audio__echo_cancel + "${CMAKE_CURRENT_SOURCE_DIR}/echo_canceller.h" + "${CMAKE_CURRENT_SOURCE_DIR}/null_echo_canceller.h" + "${CMAKE_CURRENT_SOURCE_DIR}/null_echo_canceller.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/speex_echo_canceller.h" + "${CMAKE_CURRENT_SOURCE_DIR}/speex_echo_canceller.cpp" +) + +set (Source_Files__media__audio__echo_cancel ${Source_Files__media__audio__echo_cancel} PARENT_SCOPE) \ No newline at end of file diff --git a/src/media/audio/echo-cancel/Makefile.am b/src/media/audio/echo-cancel/Makefile.am new file mode 100644 index 0000000000..2222ce86a5 --- /dev/null +++ b/src/media/audio/echo-cancel/Makefile.am @@ -0,0 +1,18 @@ +include $(top_srcdir)/globals.mk + +noinst_LTLIBRARIES = libecho-cancel.la + +EC_SRC = null_echo_canceller.cpp +EC_HDR = null_echo_canceller.h + +if BUILD_SPEEXDSP +EC_SRC += speex_echo_canceller.cpp +EC_HDR += speex_echo_canceller.h +endif + +libecho_cancel_la_SOURCES = \ + $(EC_SRC) + +noinst_HEADERS = \ + echo_canceller.h \ + $(EC_HDR) diff --git a/src/media/audio/echo-cancel/echo_canceller.h b/src/media/audio/echo-cancel/echo_canceller.h new file mode 100644 index 0000000000..c159957f48 --- /dev/null +++ b/src/media/audio/echo-cancel/echo_canceller.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2021 Savoir-faire Linux Inc. + * + * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include "noncopyable.h" +#include "audio/audio_frame_resizer.h" +#include "audio/audiobuffer.h" +#include "libav_deps.h" + +namespace jami { + +class EchoCanceller +{ +private: + NON_COPYABLE(EchoCanceller); + +public: + EchoCanceller(AudioFormat format, unsigned frameSize) + : playbackQueue_(format, frameSize) + , recordQueue_(format, frameSize) + , sampleRate_(format.sample_rate) + , frameSize_(frameSize) + {} + virtual ~EchoCanceller() = default; + + virtual void putRecorded(std::shared_ptr<AudioFrame>&& buf) + { + recordQueue_.enqueue(std::move(buf)); + }; + virtual void putPlayback(const std::shared_ptr<AudioFrame>& buf) + { + auto c = buf; + playbackQueue_.enqueue(std::move(c)); + }; + virtual std::shared_ptr<AudioFrame> getProcessed() = 0; + virtual void done() = 0; + +protected: + AudioFrameResizer playbackQueue_; + AudioFrameResizer recordQueue_; + unsigned sampleRate_; + unsigned frameSize_; +}; + +} // namespace jami diff --git a/src/media/audio/echo-cancel/null_echo_canceller.cpp b/src/media/audio/echo-cancel/null_echo_canceller.cpp new file mode 100644 index 0000000000..a7b9f10cbb --- /dev/null +++ b/src/media/audio/echo-cancel/null_echo_canceller.cpp @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2021 Savoir-faire Linux Inc. + * + * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "null_echo_canceller.h" + +#include <cassert> + +namespace jami { + +NullEchoCanceller::NullEchoCanceller(AudioFormat format, unsigned frameSize) + : EchoCanceller(format, frameSize) +{} + +void +NullEchoCanceller::putRecorded(std::shared_ptr<AudioFrame>&& buf) +{ + EchoCanceller::putRecorded(std::move(buf)); +}; + +void +NullEchoCanceller::putPlayback(const std::shared_ptr<AudioFrame>& buf) +{ + EchoCanceller::putPlayback(buf); +}; + +std::shared_ptr<AudioFrame> +NullEchoCanceller::getProcessed() +{ + while (recordQueue_.samples() > recordQueue_.frameSize() * 10) { + JAMI_DBG("record overflow %d / %d", recordQueue_.samples(), frameSize_); + recordQueue_.dequeue(); + } + while (playbackQueue_.samples() > playbackQueue_.frameSize() * 10) { + JAMI_DBG("playback overflow %d / %d", playbackQueue_.samples(), frameSize_); + playbackQueue_.dequeue(); + } + if (recordQueue_.samples() < recordQueue_.frameSize() + || playbackQueue_.samples() < playbackQueue_.frameSize()) { + JAMI_DBG("underflow rec: %d, play: %d fs: %d", + recordQueue_.samples(), + playbackQueue_.samples(), + frameSize_); + return {}; + } + + JAMI_WARN("Processing %d samples, rec: %d, play: %d ", + frameSize_, + recordQueue_.samples(), + playbackQueue_.samples()); + playbackQueue_.dequeue(); + return recordQueue_.dequeue(); +}; + +void NullEchoCanceller::done() {}; + +} // namespace jami diff --git a/src/media/audio/echo-cancel/null_echo_canceller.h b/src/media/audio/echo-cancel/null_echo_canceller.h new file mode 100644 index 0000000000..098f56eb2f --- /dev/null +++ b/src/media/audio/echo-cancel/null_echo_canceller.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2021 Savoir-faire Linux Inc. + * + * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include "echo_canceller.h" + +namespace jami { + +class NullEchoCanceller final : public EchoCanceller +{ +public: + NullEchoCanceller(AudioFormat format, unsigned frameSize); + ~NullEchoCanceller() = default; + + void putRecorded(std::shared_ptr<AudioFrame>&& buf) override; + void putPlayback(const std::shared_ptr<AudioFrame>& buf) override; + std::shared_ptr<AudioFrame> getProcessed() override; + void done() override; +}; + +} // namespace jami diff --git a/src/media/audio/echo-cancel/speex_echo_canceller.cpp b/src/media/audio/echo-cancel/speex_echo_canceller.cpp new file mode 100644 index 0000000000..aace775c12 --- /dev/null +++ b/src/media/audio/echo-cancel/speex_echo_canceller.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2021 Savoir-faire Linux Inc. + * + * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "speex_echo_canceller.h" + +#include "audio/audiolayer.h" + +extern "C" { +#include <speex/speex_echo.h> +#include <speex/speex_preprocess.h> +} + +namespace jami { + +struct SpeexEchoCanceller::SpeexEchoStateImpl +{ + using SpeexEchoStatePtr = std::unique_ptr<SpeexEchoState, void (*)(SpeexEchoState*)>; + SpeexEchoStatePtr state; + + SpeexEchoStateImpl(AudioFormat format, unsigned frameSize) + : state(speex_echo_state_init_mc(frameSize, + frameSize * 16, + format.nb_channels, + format.nb_channels), + &speex_echo_state_destroy) + { + int sr = format.sample_rate; + speex_echo_ctl(state.get(), SPEEX_ECHO_SET_SAMPLING_RATE, &sr); + } +}; + +SpeexEchoCanceller::SpeexEchoCanceller(AudioFormat format, unsigned frameSize) + : EchoCanceller(format, frameSize) + , pimpl_(std::make_unique<SpeexEchoStateImpl>(format, frameSize)) +{ + speex_echo_ctl(pimpl_->state.get(), SPEEX_ECHO_SET_SAMPLING_RATE, &sampleRate_); +} + +void +SpeexEchoCanceller::putRecorded(std::shared_ptr<AudioFrame>&& buf) +{ + EchoCanceller::putRecorded(std::move(buf)); +} + +void +SpeexEchoCanceller::putPlayback(const std::shared_ptr<AudioFrame>& buf) +{ + EchoCanceller::putPlayback(buf); +} + +std::shared_ptr<AudioFrame> +SpeexEchoCanceller::getProcessed() +{ + if (playbackQueue_.samples() < playbackQueue_.frameSize() + or recordQueue_.samples() < recordQueue_.frameSize()) { + JAMI_DBG("getRecorded underflow %d / %d, %d / %d", + playbackQueue_.samples(), + playbackQueue_.frameSize(), + recordQueue_.samples(), + recordQueue_.frameSize()); + return {}; + } + if (recordQueue_.samples() > 2 * recordQueue_.frameSize() && playbackQueue_.samples() == 0) { + JAMI_DBG("getRecorded PLAYBACK underflow"); + return recordQueue_.dequeue(); + } + while (playbackQueue_.samples() > 10 * playbackQueue_.frameSize()) { + JAMI_DBG("getRecorded record underflow"); + playbackQueue_.dequeue(); + } + while (recordQueue_.samples() > 4 * recordQueue_.frameSize()) { + JAMI_DBG("getRecorded playback underflow"); + recordQueue_.dequeue(); + } + auto playback = playbackQueue_.dequeue(); + auto record = recordQueue_.dequeue(); + if (playback and record) { + auto ret = std::make_shared<AudioFrame>(record->getFormat(), record->getFrameSize()); + speex_echo_cancellation(pimpl_->state.get(), + (const int16_t*) record->pointer()->data[0], + (const int16_t*) playback->pointer()->data[0], + (int16_t*) ret->pointer()->data[0]); + return ret; + } + return {}; +} + +void +SpeexEchoCanceller::done() +{} + +} // namespace jami diff --git a/src/media/audio/echo-cancel/speex_echo_canceller.h b/src/media/audio/echo-cancel/speex_echo_canceller.h new file mode 100644 index 0000000000..cbc2eb9ce8 --- /dev/null +++ b/src/media/audio/echo-cancel/speex_echo_canceller.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2021 Savoir-faire Linux Inc. + * + * Author: Andreas Traczyk <andreas.traczyk@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#pragma once + +#include "audio/echo-cancel/echo_canceller.h" +#include "audio/audio_frame_resizer.h" + +extern "C" { +struct SpeexEchoState_; +typedef struct SpeexEchoState_ SpeexEchoState; +} + +#include <memory> + +namespace jami { + +class SpeexEchoCanceller final : public EchoCanceller +{ +public: + SpeexEchoCanceller(AudioFormat format, unsigned frameSize); + ~SpeexEchoCanceller() = default; + + // Inherited via EchoCanceller + void putRecorded(std::shared_ptr<AudioFrame>&& buf) override; + void putPlayback(const std::shared_ptr<AudioFrame>& buf) override; + std::shared_ptr<AudioFrame> getProcessed() override; + void done() override; + +private: + struct SpeexEchoStateImpl; + std::unique_ptr<SpeexEchoStateImpl> pimpl_; +}; +} // namespace jami -- GitLab