diff --git a/CMakeLists.txt b/CMakeLists.txt index e6f5ab86aaf0c14a18b783986cd5fb777ce00147..45586a0daa8a4f81f7227ed340ffda7ea5e0fb75 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 151f1eb846757628c2029aa150e2c21a63a3554a..a45cd4de226a7b38ca1b7990b15e90a890e24c5e 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 3946a3f34122e48750baa7c0023b0f434bcb8e7d..18f29692195f039d88c7dab087571cf4de91498f 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 095656d2a5205bc0b13c9c7ff9a884db61a1c49c..1ed0cf3915beefd10d1054480c6c9a9253314145 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 320115d938a113d14a12f067167b805d0c99a22c..264aeac2808ca653c2f961bec71c36b18d5f4f00 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 f6838572290d29d4ee34b7b4209757ddca507ce3..a6f71f24a679922c1732b238a32c02b336be9112 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 34138871c411cce5a457bb0e137561c3bd1061c3..2772e51c4a7476187bad8948910afc2aa622bfcb 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 c5a1ba49072299c52cb8e3b6a5a6901d4fbfec7b..927db4896d6171e5f73dff7e24483adf90a410d4 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 fa11a7581218c228aff01089da3c6e6c420bd662..ab0e5483da6cdff09fde0616a32658500aec6b83 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 0000000000000000000000000000000000000000..fec1af7d8f9cf8e5df463c637ab74cb28cb07e42 --- /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 0000000000000000000000000000000000000000..2222ce86a5758d60d9e3fb6007a6dbd679e65010 --- /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 0000000000000000000000000000000000000000..c159957f4866c0224189d798eaf8764ba1ddb1b7 --- /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 0000000000000000000000000000000000000000..a7b9f10cbbb6571df5e069a7867eaf0b84defc3b --- /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 0000000000000000000000000000000000000000..098f56eb2f9de2e02e3ac4936e3f52cb9ba47a9a --- /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 0000000000000000000000000000000000000000..aace775c12372fdb7776f59def00cfbf0fe2eb7c --- /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 0000000000000000000000000000000000000000..cbc2eb9ce8badb7a8a22e3ac154fa433a028b74a --- /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