mirror of
https://github.com/overte-org/overte.git
synced 2025-04-08 08:14:48 +02:00
separates echo cancellation from loopback test (intermediate commit)
This commit is contained in:
parent
755208aaf7
commit
651dc19427
4 changed files with 195 additions and 190 deletions
|
@ -45,47 +45,44 @@
|
|||
# SPEEXDSP_FOUND - True if SPEEXDSP found.
|
||||
#
|
||||
|
||||
if (SPEEXDSP_INCLUDE_DIRS)
|
||||
# Already in cache, be silent
|
||||
set(SPEEXDSP_FIND_QUIETLY TRUE)
|
||||
if (SPEEXDSP_INCLUDE_DIRS AND SPEEXDSP_LIBRARIES)
|
||||
set(SPEEXDSP_FOUND TRUE)
|
||||
else (SPEEXDSP_INCLUDE_DIRS)
|
||||
|
||||
find_path(SPEEXDSP_INCLUDE_DIRS speex/speex.h
|
||||
/usr/include
|
||||
/usr/local/include
|
||||
${SPEEX_ROOT_DIR}/include
|
||||
)
|
||||
|
||||
set(SPEEXDSP_NAMES speexdsp)
|
||||
find_library(SPEEXDSP_LIBRARY NAMES ${SPEEXDSP_NAMES} PATHS /usr/lib usr/local/lib)
|
||||
if (NOT SPEEXDSP_LIBRARY AND APPLE)
|
||||
find_library(SPEEXDSP_LIBRARY NAMES ${SPEEXDSP_NAMES} PATHS ${SPEEX_ROOT_DIR}/lib/MacOS)
|
||||
elseif (WIN32)
|
||||
find_library(SPEEXDSP_LIBRARY NAMES ${SPEEXDSP_NAMES} PATHS ${SPEEX_ROOT_DIR}/lib/Win32)
|
||||
endif ()
|
||||
|
||||
if (SPEEXDSP_INCLUDE_DIRS AND SPEEXDSP_LIBRARY)
|
||||
set(SPEEXDSP_FOUND TRUE)
|
||||
set(SPEEXDSP_LIBRARIES ${SPEEXDSP_LIBRARY})
|
||||
else (SPEEXDSP_INCLUDE_DIRS AND SPEEXDSP_LIBRARY)
|
||||
set(SPEEXDSP_FOUND FALSE)
|
||||
set(SPEEXDSP_LIBRARIES)
|
||||
endif (SPEEXDSP_INCLUDE_DIRS AND SPEEXDSP_LIBRARY)
|
||||
|
||||
if (SPEEXDSP_FOUND)
|
||||
message(STATUS "Found SpeexDSP: ${SPEEXDSP_LIBRARY}")
|
||||
else (SPEEXDSP_FOUND)
|
||||
if (SPEEXDSP_FIND_REQUIRED)
|
||||
message(STATUS "Looked for SpeexDSP libraries named ${SPEEXDSP_NAMES}.")
|
||||
message(STATUS "Include file detected: [${SPEEXDSP_INCLUDE_DIRS}].")
|
||||
message(STATUS "Lib file detected: [${SPEEXDSP_LIBRARY}].")
|
||||
message(FATAL_ERROR "=========> Could NOT find SpeexDSP library")
|
||||
endif (SPEEXDSP_FIND_REQUIRED)
|
||||
endif (SPEEXDSP_FOUND)
|
||||
|
||||
mark_as_advanced(SPEEXDSP_INCLUDE_DIRS SPEEXDSP_LIBRARIES)
|
||||
|
||||
endif (SPEEXDSP_INCLUDE_DIRS)
|
||||
|
||||
find_path(SPEEXDSP_INCLUDE_DIRS speex/speex.h
|
||||
/usr/include
|
||||
/usr/local/include
|
||||
${SPEEX_ROOT_DIR}/include
|
||||
)
|
||||
|
||||
set(SPEEXDSP_NAMES speexdsp)
|
||||
find_library(SPEEXDSP_LIBRARY NAMES ${SPEEXDSP_NAMES} PATHS /usr/lib usr/local/lib)
|
||||
if (NOT SPEEXDSP_LIBRARY AND APPLE)
|
||||
find_library(SPEEXDSP_LIBRARY NAMES ${SPEEXDSP_NAMES} PATHS ${SPEEX_ROOT_DIR}/lib/MacOS)
|
||||
elseif (WIN32)
|
||||
find_library(SPEEXDSP_LIBRARY NAMES ${SPEEXDSP_NAMES} PATHS ${SPEEX_ROOT_DIR}/lib/Win32)
|
||||
endif ()
|
||||
|
||||
if (SPEEXDSP_INCLUDE_DIRS AND SPEEXDSP_LIBRARY)
|
||||
set(SPEEXDSP_FOUND TRUE)
|
||||
set( SPEEXDSP_LIBRARIES ${SPEEXDSP_LIBRARY} )
|
||||
else (SPEEXDSP_INCLUDE_DIRS AND SPEEXDSP_LIBRARY)
|
||||
set(SPEEXDSP_FOUND FALSE)
|
||||
set(SPEEXDSP_LIBRARIES)
|
||||
endif (SPEEXDSP_INCLUDE_DIRS AND SPEEXDSP_LIBRARY)
|
||||
|
||||
if (SPEEXDSP_FOUND)
|
||||
if (NOT SPEEXDSP_FIND_QUIETLY)
|
||||
message(STATUS "Found SpeexDSP: ${SPEEXDSP_LIBRARY}")
|
||||
endif (NOT SPEEXDSP_FIND_QUIETLY)
|
||||
else (SPEEXDSP_FOUND)
|
||||
if (SPEEXDSP_FIND_REQUIRED)
|
||||
message(STATUS "Looked for SpeexDSP libraries named ${SPEEXDSP_NAMES}.")
|
||||
message(STATUS "Include file detected: [${SPEEXDSP_INCLUDE_DIRS}].")
|
||||
message(STATUS "Lib file detected: [${SPEEXDSP_LIBRARY}].")
|
||||
message(FATAL_ERROR "=========> Could NOT find SpeexDSP library")
|
||||
endif (SPEEXDSP_FIND_REQUIRED)
|
||||
endif (SPEEXDSP_FOUND)
|
||||
|
||||
mark_as_advanced(
|
||||
SPEEXDSP_LIBRARY
|
||||
SPEEXDSP_INCLUDE_DIRS
|
||||
)
|
||||
|
|
|
@ -503,7 +503,7 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
break;
|
||||
|
||||
case Qt::Key_Semicolon:
|
||||
_audio.testPing();
|
||||
_audio.ping();
|
||||
break;
|
||||
case Qt::Key_Apostrophe:
|
||||
_audioScope.inputPaused = !_audioScope.inputPaused;
|
||||
|
@ -1647,7 +1647,7 @@ void Application::update(float deltaTime) {
|
|||
#ifndef _WIN32
|
||||
_audio.setLastAcceleration(_myAvatar.getThrust());
|
||||
_audio.setLastVelocity(_myAvatar.getVelocity());
|
||||
_audio.eventuallyCalibrateEchoCancellation();
|
||||
_audio.eventuallyAnalyzePing();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
#include "Util.h"
|
||||
#include "Log.h"
|
||||
|
||||
#define DEBUG_ECHO_CANCELLATION
|
||||
#define VISUALIZE_ECHO_CANCELLATION
|
||||
|
||||
static const int NUM_AUDIO_CHANNELS = 2;
|
||||
|
||||
|
@ -73,15 +73,16 @@ static const int AEC_AGC_MAX_INC = 6;
|
|||
static const int AEC_AGC_MAX_DEC = 40; // Max decrease in db/s
|
||||
static const bool AEC_USE_VAD = false; // Voice activity determination
|
||||
|
||||
// Delay test (performed before using speex)
|
||||
static const float AEC_PING_PITCH = 16.f; // Ping wavelength, # samples / radian
|
||||
static const float AEC_PING_VOLUME = 32000.f; // Ping peak amplitude
|
||||
static const int AEC_PING_RETRY = 3; // Number of retries for EC calibration
|
||||
static const int AEC_PING_MIN_AMPLI = 225; // Minimum amplitude for EC calibration
|
||||
static const int AEC_PING_MAX_PERIOD_DIFFERENCE = 15; // Maximum # samples from expected period
|
||||
static const int AEC_PING_PERIOD = int(Radians::twicePi() * AEC_PING_PITCH); // Sine period based on the given pitch
|
||||
static const int AEC_PING_HALF_PERIOD = int(Radians::pi() * AEC_PING_PITCH); // Distance between extrema
|
||||
static const int AEC_PING_BUFFER_OFFSET = BUFFER_LENGTH_SAMPLES_PER_CHANNEL - AEC_PING_PERIOD * 2.0f; // Signal start
|
||||
// Ping test configuration
|
||||
static const float PING_PITCH = 16.f; // Ping wavelength, # samples / radian
|
||||
static const float PING_VOLUME = 32000.f; // Ping peak amplitude
|
||||
static const int PING_MIN_AMPLI = 225; // Minimum amplitude
|
||||
static const int PING_MAX_PERIOD_DIFFERENCE = 15; // Maximum # samples from expected period
|
||||
static const int PING_PERIOD = int(Radians::twicePi() * PING_PITCH); // Sine period based on the given pitch
|
||||
static const int PING_HALF_PERIOD = int(Radians::pi() * PING_PITCH); // Distance between extrema
|
||||
static const int PING_FRAMES_TO_RECORD = AEC_BUFFERED_FRAMES; // Frames to record for analysis
|
||||
static const int PING_SAMPLES_TO_ANALYZE = AEC_BUFFERED_SAMPLES_PER_CHANNEL; // Samples to analyze (reusing AEC buffer)
|
||||
static const int PING_BUFFER_OFFSET = BUFFER_LENGTH_SAMPLES_PER_CHANNEL - PING_PERIOD * 2.0f; // Signal start
|
||||
|
||||
|
||||
inline void Audio::performIO(int16_t* inputLeft, int16_t* outputLeft, int16_t* outputRight) {
|
||||
|
@ -107,9 +108,14 @@ inline void Audio::performIO(int16_t* inputLeft, int16_t* outputLeft, int16_t* o
|
|||
_lastInputLoudness = loudness;
|
||||
|
||||
// add input (@microphone) data to the scope
|
||||
#ifndef DEBUG_ECHO_CANCELLATION
|
||||
_scope->addSamples(0, inputLeft, BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
|
||||
#ifdef VISUALIZE_ECHO_CANCELLATION
|
||||
if (! _isCancellingEcho || _pingFramesToRecord != 0 || ! _speexPreprocessState) {
|
||||
#endif
|
||||
_scope->addSamples(0, inputLeft, BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
|
||||
#ifdef VISUALIZE_ECHO_CANCELLATION
|
||||
}
|
||||
#endif
|
||||
|
||||
Agent* audioMixer = agentList->soloAgentOfType(AGENT_TYPE_AUDIO_MIXER);
|
||||
|
||||
if (audioMixer) {
|
||||
|
@ -252,9 +258,13 @@ inline void Audio::performIO(int16_t* inputLeft, int16_t* outputLeft, int16_t* o
|
|||
|
||||
|
||||
// add output (@speakers) data just written to the scope
|
||||
#ifndef DEBUG_ECHO_CANCELLATION
|
||||
#ifdef VISUALIZE_ECHO_CANCELLATION
|
||||
if (! _isCancellingEcho || _pingFramesToRecord != 0 || ! _speexPreprocessState) {
|
||||
#endif
|
||||
_scope->addSamples(1, outputLeft, BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
|
||||
_scope->addSamples(2, outputRight, BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
|
||||
#ifdef VISUALIZE_ECHO_CANCELLATION
|
||||
}
|
||||
#endif
|
||||
|
||||
gettimeofday(&_lastCallbackTime, NULL);
|
||||
|
@ -291,8 +301,6 @@ static void outputPortAudioError(PaError error) {
|
|||
|
||||
Audio::Audio(Oscilloscope* scope) :
|
||||
_stream(NULL),
|
||||
_speexEchoState(NULL),
|
||||
_speexPreprocessState(NULL),
|
||||
_ringBuffer(true),
|
||||
_scope(scope),
|
||||
_averagedLatency(0.0),
|
||||
|
@ -309,9 +317,13 @@ Audio::Audio(Oscilloscope* scope) :
|
|||
_firstPlaybackTime(),
|
||||
_packetsReceivedThisPlayback(0),
|
||||
_isCancellingEcho(false),
|
||||
_echoDelay(BUFFER_LENGTH_SAMPLES_PER_CHANNEL * 2),
|
||||
_echoSamplesLeft(0l),
|
||||
_speexEchoState(NULL),
|
||||
_speexPreprocessState(NULL),
|
||||
_isSendingEchoPing(false),
|
||||
_echoAnalysisPending(false),
|
||||
_echoInputFramesToRecord(0),
|
||||
_pingAnalysisPending(false),
|
||||
_pingFramesToRecord(0),
|
||||
_samplesLeftForFlange(0),
|
||||
_lastYawMeasuredMaximum(0),
|
||||
_flangeIntensity(0.0f),
|
||||
|
@ -528,15 +540,12 @@ void Audio::addProceduralSounds(int16_t* inputBuffer, int numSamples) {
|
|||
}
|
||||
}
|
||||
|
||||
static inline void subScaled(int16_t* dst, const int16_t* src, unsigned n, int scale16fixpt) {
|
||||
|
||||
for (int16_t* dstEnd = dst + n; dst != dstEnd; ++src, ++dst) {
|
||||
*dst -= int16_t((*src * scale16fixpt) >> 16);
|
||||
}
|
||||
}
|
||||
// -----------------------------
|
||||
// Speex-based echo cancellation
|
||||
// -----------------------------
|
||||
|
||||
inline void Audio::eventuallyCancelEcho(int16_t* inputLeft) {
|
||||
if (! _isCancellingEcho) {
|
||||
if (! _isCancellingEcho || _pingFramesToRecord != 0 || ! _speexPreprocessState) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -557,7 +566,7 @@ inline void Audio::eventuallyCancelEcho(int16_t* inputLeft) {
|
|||
memcpy(playBufferRight, _echoSamplesRight + readPos, n * sizeof(int16_t));
|
||||
memcpy(playBufferRight + n, _echoSamplesLeft, n2 * sizeof(int16_t));
|
||||
|
||||
#ifdef DEBUG_ECHO_CANCELLATION
|
||||
#ifdef VISUALIZE_ECHO_CANCELLATION
|
||||
// Visualize the input
|
||||
_scope->addSamples(0, inputLeft, BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
|
||||
_scope->addSamples(1, playBufferLeft, BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
|
||||
|
@ -568,14 +577,14 @@ inline void Audio::eventuallyCancelEcho(int16_t* inputLeft) {
|
|||
memcpy(inputLeft, _speexTmpBuf, BUFFER_LENGTH_BYTES_PER_CHANNEL);
|
||||
speex_preprocess_run(_speexPreprocessState, inputLeft);
|
||||
|
||||
#ifdef DEBUG_ECHO_CANCELLATION
|
||||
#ifdef VISUALIZE_ECHO_CANCELLATION
|
||||
// Visualize the result
|
||||
_scope->addSamples(2, inputLeft, BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void Audio::eventuallyRecordEcho(int16_t* outputLeft, int16_t* outputRight) {
|
||||
if (! _isCancellingEcho) {
|
||||
if (! _isCancellingEcho || _pingFramesToRecord != 0 || ! _speexPreprocessState) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -594,75 +603,65 @@ inline void Audio::eventuallyRecordEcho(int16_t* outputLeft, int16_t* outputRigh
|
|||
_echoWritePos = writeEnd;
|
||||
}
|
||||
|
||||
void Audio::setIsCancellingEcho(bool enabled) {
|
||||
// -----------------------------------------------------------
|
||||
// Accoustic ping (audio system round trip time determination)
|
||||
// -----------------------------------------------------------
|
||||
|
||||
_isCancellingEcho = false;
|
||||
void Audio::ping() {
|
||||
|
||||
if (enabled) {
|
||||
|
||||
// Request recalibration
|
||||
_echoPingRetries = AEC_PING_RETRY;
|
||||
_echoInputFramesToRecord = AEC_BUFFERED_FRAMES;
|
||||
_isSendingEchoPing = true;
|
||||
|
||||
// _scope->setDownsampleRatio(8); // DEBUG
|
||||
// _scope->inputPaused = false; // DEBUG
|
||||
}
|
||||
}
|
||||
|
||||
void Audio::testPing() {
|
||||
|
||||
_echoInputFramesToRecord = 0;
|
||||
_pingFramesToRecord = PING_FRAMES_TO_RECORD;
|
||||
_isSendingEchoPing = true;
|
||||
_scope->setDownsampleRatio(8);
|
||||
_scope->inputPaused = false;
|
||||
}
|
||||
|
||||
inline void Audio::eventuallySendRecvPing(int16_t* inputLeft, int16_t* outputLeft, int16_t* outputRight) {
|
||||
/*
|
||||
// Artificial, local echo hack
|
||||
if (Application::getInstance()->shouldEchoAudio()) {
|
||||
|
||||
enum { bufs = 32 };
|
||||
static int16_t buf[bufs][BUFFER_LENGTH_SAMPLES_PER_CHANNEL];
|
||||
static int bufIdx = 0;
|
||||
|
||||
int wBuf = bufIdx;
|
||||
bufIdx = (bufIdx + 1) % bufs;
|
||||
memcpy(buf[wBuf], inputLeft, BUFFER_LENGTH_BYTES_PER_CHANNEL);
|
||||
subScaled(outputLeft, buf[bufIdx], BUFFER_LENGTH_SAMPLES_PER_CHANNEL, -0x7000);
|
||||
}
|
||||
*/
|
||||
// Calibration of echo cancellation
|
||||
if (_isSendingEchoPing) {
|
||||
|
||||
// Overwrite output with ping signal
|
||||
memset(outputLeft, 0, AEC_PING_BUFFER_OFFSET * sizeof(int16_t));
|
||||
outputLeft += AEC_PING_BUFFER_OFFSET;
|
||||
memset(outputRight, 0, AEC_PING_BUFFER_OFFSET * sizeof(int16_t));
|
||||
outputRight += AEC_PING_BUFFER_OFFSET;
|
||||
for (int s = -AEC_PING_PERIOD; s < AEC_PING_PERIOD; ++s) {
|
||||
float t = float(s) / AEC_PING_PITCH;
|
||||
// Use signed variant of sinc
|
||||
// speaker-reproducible with a unique characteristic point in time
|
||||
*outputLeft++ = *outputRight++ = int16_t(AEC_PING_VOLUME *
|
||||
// Overwrite output with ping signal.
|
||||
//
|
||||
// Using a signed variant of sinc because it's speaker-reproducible
|
||||
// with a unique, characteristic point in time (its center), aligned
|
||||
// to the right of the output buffer.
|
||||
//
|
||||
// |
|
||||
// | |
|
||||
// ...--- t --------+-+-+-+-+------->
|
||||
// | | :
|
||||
// | :
|
||||
// buffer :<- start of next buffer
|
||||
// : : :
|
||||
// :---: sine period
|
||||
// :-: half sine period
|
||||
//
|
||||
memset(outputLeft, 0, PING_BUFFER_OFFSET * sizeof(int16_t));
|
||||
outputLeft += PING_BUFFER_OFFSET;
|
||||
memset(outputRight, 0, PING_BUFFER_OFFSET * sizeof(int16_t));
|
||||
outputRight += PING_BUFFER_OFFSET;
|
||||
for (int s = -PING_PERIOD; s < PING_PERIOD; ++s) {
|
||||
float t = float(s) / PING_PITCH;
|
||||
*outputLeft++ = *outputRight++ = int16_t(PING_VOLUME *
|
||||
sinf(t) / fmaxf(1.0f, pow((abs(t)-1.5f) / 1.5f, 1.2f)));
|
||||
}
|
||||
|
||||
// As of the next frame, we'll be recoding _echoInputFramesToRecord from the mic
|
||||
// As of the next frame, we'll be recoding PING_FRAMES_TO_RECORD from
|
||||
// the mic (pointless to start now as we can't record unsent audio).
|
||||
_isSendingEchoPing = false;
|
||||
printLog("Send audio ping\n");
|
||||
|
||||
} else if (_echoInputFramesToRecord > 0) {
|
||||
} else if (_pingFramesToRecord > 0) {
|
||||
|
||||
// Store input samples
|
||||
int offset = BUFFER_LENGTH_SAMPLES_PER_CHANNEL * (
|
||||
AEC_BUFFERED_FRAMES - _echoInputFramesToRecord);
|
||||
PING_FRAMES_TO_RECORD - _pingFramesToRecord);
|
||||
memcpy(_echoSamplesLeft + offset,
|
||||
inputLeft, BUFFER_LENGTH_SAMPLES_PER_CHANNEL * sizeof(int16_t));
|
||||
|
||||
--_echoInputFramesToRecord;
|
||||
--_pingFramesToRecord;
|
||||
|
||||
if (_echoInputFramesToRecord == 0) {
|
||||
_echoAnalysisPending = true;
|
||||
if (_pingFramesToRecord == 0) {
|
||||
_pingAnalysisPending = true;
|
||||
printLog("Received ping echo\n");
|
||||
}
|
||||
}
|
||||
|
@ -671,7 +670,7 @@ inline void Audio::eventuallySendRecvPing(int16_t* inputLeft, int16_t* outputLef
|
|||
static int findExtremum(int16_t const* samples, int length, int sign) {
|
||||
|
||||
int x0 = -1;
|
||||
int y0 = -AEC_PING_VOLUME;
|
||||
int y0 = -PING_VOLUME;
|
||||
for (int x = 0; x < length; ++samples, ++x) {
|
||||
int y = *samples * sign;
|
||||
if (y > y0) {
|
||||
|
@ -682,73 +681,85 @@ static int findExtremum(int16_t const* samples, int length, int sign) {
|
|||
return x0;
|
||||
}
|
||||
|
||||
bool Audio::calibrateEchoCancellation() {
|
||||
inline void Audio::analyzePing() {
|
||||
|
||||
// Analyze received signal
|
||||
int botAt = findExtremum(_echoSamplesLeft, AEC_BUFFERED_SAMPLES_PER_CHANNEL, -1);
|
||||
// Determine extrema
|
||||
int botAt = findExtremum(_echoSamplesLeft, PING_SAMPLES_TO_ANALYZE, -1);
|
||||
if (botAt == -1) {
|
||||
printLog("AEC: Minimum not found.\n");
|
||||
return false;
|
||||
printLog("Audio Ping: Minimum not found.\n");
|
||||
return;
|
||||
}
|
||||
int topAt = findExtremum(_echoSamplesLeft, AEC_BUFFERED_SAMPLES_PER_CHANNEL, 1);
|
||||
int topAt = findExtremum(_echoSamplesLeft, PING_SAMPLES_TO_ANALYZE, 1);
|
||||
if (topAt == -1) {
|
||||
printLog("AEC: Maximum not found.\n");
|
||||
return false;
|
||||
printLog("Audio Ping: Maximum not found.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine peak amplitude
|
||||
// Determine peak amplitude - warn if low
|
||||
int ampli = (_echoSamplesLeft[topAt] - _echoSamplesLeft[botAt]) / 2;
|
||||
if (ampli < AEC_PING_MIN_AMPLI) {
|
||||
// We can't reliably calibrate and probably won't hear it, anyways.
|
||||
printLog("AEC: Amplitude too low %d.\n", ampli);
|
||||
return false;
|
||||
if (ampli < PING_MIN_AMPLI) {
|
||||
printLog("Audio Ping unreliable - low amplitude %d.\n", ampli);
|
||||
}
|
||||
|
||||
// Determine period
|
||||
// Determine period - warn if doesn't look like our signal
|
||||
int halfPeriod = topAt - botAt;
|
||||
if (halfPeriod < 0) {
|
||||
printLog("AEC: Min/max inverted.\n");
|
||||
halfPeriod = -halfPeriod;
|
||||
topAt -= AEC_PING_PERIOD;
|
||||
ampli = -ampli;
|
||||
}
|
||||
if (abs(halfPeriod-AEC_PING_HALF_PERIOD) > AEC_PING_MAX_PERIOD_DIFFERENCE) {
|
||||
// Probably not our signal
|
||||
printLog("AEC: Unexpected period %d vs. %d\n", halfPeriod, AEC_PING_HALF_PERIOD);
|
||||
return false;
|
||||
if (abs(halfPeriod-PING_HALF_PERIOD) > PING_MAX_PERIOD_DIFFERENCE) {
|
||||
printLog("Audio Ping unreliable - peak distance %d vs. %d\n", halfPeriod, PING_HALF_PERIOD);
|
||||
}
|
||||
|
||||
// Determine delay based on the characteristic center of the signal we found
|
||||
// (this value is too small by one packet minus ping length and it's good that
|
||||
// way as the initial movement will be before the peak)
|
||||
_echoDelay = (botAt + topAt) / 2;
|
||||
// Ping is sent:
|
||||
//
|
||||
// ---[ record ]--[ play ]--- audio in space/time --->
|
||||
// : : :
|
||||
// : : ping: ->X<-
|
||||
// : : :
|
||||
// : : |+| (buffer end - signal center = t1-t0)
|
||||
// : |<----------+
|
||||
// : : : :
|
||||
// : ->X<- (corresponding input buffer position t0)
|
||||
// : : : :
|
||||
// : : : :
|
||||
// : : : :
|
||||
// Next frame (we're recording from now on):
|
||||
// : : :
|
||||
// : - - --[ record ]--[ play ]------------------>
|
||||
// : : : :
|
||||
// : : |<-- (start of recording t1)
|
||||
// : : :
|
||||
// : : :
|
||||
// At some frame, the signal is picked up:
|
||||
// : : : :
|
||||
// : : : :
|
||||
// : : : V
|
||||
// : : : - - --[ record ]--[ play ]---------->
|
||||
// : V : :
|
||||
// : |<--------->|
|
||||
// |+|<------->| period + measured samples
|
||||
//
|
||||
// If we could pick up the signal at t0 we'd have zero round trip
|
||||
// time - in this case we had recorded the output buffer instantly
|
||||
// in its entirety (we can't - but there's the proper reference
|
||||
// point). We know the number of samples from t1 and, knowing that
|
||||
// data is streaming continuously, we know that t1-t0 is the distance
|
||||
// of the characterisic point from the end of the buffer.
|
||||
|
||||
printLog("AEC:\ndelay = %d\namp = %d\ntopAt = %d\nbotAt = %d\n", _echoDelay, ampli, topAt, botAt);
|
||||
return true;
|
||||
int delay = (botAt + topAt) / 2 + PING_PERIOD;
|
||||
|
||||
printLog("| Audio Ping results:\n"
|
||||
"+----- ---- --- - - - - -\n"
|
||||
"\n"
|
||||
" Delay = %d samples (%d ms)\n"
|
||||
" Peak amplitude = %d\n\n", delay, delay * 1000 / SAMPLE_RATE, ampli);
|
||||
}
|
||||
|
||||
bool Audio::eventuallyCalibrateEchoCancellation() {
|
||||
bool Audio::eventuallyAnalyzePing() {
|
||||
|
||||
// Pending request -> process it
|
||||
if (! _echoAnalysisPending) {
|
||||
if (! _pingAnalysisPending) {
|
||||
return false;
|
||||
}
|
||||
_echoAnalysisPending = false;
|
||||
|
||||
if (calibrateEchoCancellation()) {
|
||||
// Success! Enable echo cancellation.
|
||||
_echoWritePos = 0;
|
||||
memset(_echoSamplesLeft, 0, AEC_BUFFERED_SAMPLES * sizeof(int16_t));
|
||||
_isCancellingEcho = true;
|
||||
}
|
||||
else if (--_echoPingRetries >= 0) {
|
||||
// Retry - better luck next time.
|
||||
_isSendingEchoPing = true;
|
||||
_echoInputFramesToRecord = AEC_BUFFERED_FRAMES;
|
||||
// _scope->inputPaused = false; // DEBUG
|
||||
return false;
|
||||
}
|
||||
// _scope->inputPaused = true; // DEBUG
|
||||
_scope->inputPaused = true;
|
||||
analyzePing();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,26 +35,18 @@ public:
|
|||
void setLastAcceleration(glm::vec3 lastAcceleration) { _lastAcceleration = lastAcceleration; };
|
||||
void setLastVelocity(glm::vec3 lastVelocity) { _lastVelocity = lastVelocity; };
|
||||
|
||||
// Enable/disable audio echo cancellation.
|
||||
// Will request calibration when called with an argument of 'true'.
|
||||
// Echo cancellation will be enabled when it can be calibrated successfully.
|
||||
void setIsCancellingEcho(bool enabled);
|
||||
|
||||
void setIsCancellingEcho(bool enabled) { _isCancellingEcho = enabled; }
|
||||
bool isCancellingEcho() const { return _isCancellingEcho; }
|
||||
|
||||
// Call periodically to eventually recalibrate audio echo cancellation.
|
||||
// A return value of 'true' indicates that a calibration request has been processed.
|
||||
// In this case a subsequent call to 'isCancellingEcho' will report whether the
|
||||
// calibration was successful.
|
||||
bool eventuallyCalibrateEchoCancellation();
|
||||
void ping();
|
||||
|
||||
void testPing();
|
||||
// Call periodically to eventually perform round trip time analysis,
|
||||
// in which case 'true' is returned - otherwise the return value is 'false'.
|
||||
// The results of the analysis are written to the log.
|
||||
bool eventuallyAnalyzePing();
|
||||
|
||||
private:
|
||||
PaStream* _stream;
|
||||
SpeexEchoState* _speexEchoState;
|
||||
SpeexPreprocessState* _speexPreprocessState;
|
||||
int16_t* _speexTmpBuf;
|
||||
AudioRingBuffer _ringBuffer;
|
||||
Oscilloscope* _scope;
|
||||
StDev _stdev;
|
||||
|
@ -72,16 +64,19 @@ private:
|
|||
int _totalPacketsReceived;
|
||||
timeval _firstPlaybackTime;
|
||||
int _packetsReceivedThisPlayback;
|
||||
// Echo Analysis
|
||||
// Echo cancellation
|
||||
volatile bool _isCancellingEcho;
|
||||
volatile bool _isSendingEchoPing;
|
||||
volatile bool _echoAnalysisPending;
|
||||
int _echoPingRetries;
|
||||
unsigned _echoWritePos;
|
||||
unsigned _echoDelay;
|
||||
int _echoInputFramesToRecord;
|
||||
int16_t* _echoSamplesLeft;
|
||||
int16_t* _echoSamplesRight;
|
||||
int16_t* _speexTmpBuf;
|
||||
SpeexEchoState* _speexEchoState;
|
||||
SpeexPreprocessState* _speexPreprocessState;
|
||||
// Ping analysis
|
||||
volatile bool _isSendingEchoPing;
|
||||
volatile bool _pingAnalysisPending;
|
||||
int _pingFramesToRecord;
|
||||
// Flange effect
|
||||
int _samplesLeftForFlange;
|
||||
int _lastYawMeasuredMaximum;
|
||||
|
@ -98,11 +93,13 @@ private:
|
|||
// When EC is enabled, record output samples.
|
||||
// Called from 'performIO' after the output has been generated.
|
||||
inline void eventuallyRecordEcho(int16_t* outputLeft, int16_t* outputRight);
|
||||
// When requested, performs sends/receives a signal for EC calibration.
|
||||
|
||||
// When requested, sends/receives a signal for round trip time determination.
|
||||
// Called from 'performIO'.
|
||||
inline void eventuallySendRecvPing(int16_t* inputLeft, int16_t* outputLeft, int16_t* outputRight);
|
||||
// Analyses the calibration signal and determines delay/amplitude for EC.
|
||||
// Called from (public) 'eventuallyCalibrateEchoCancellation'.
|
||||
inline bool calibrateEchoCancellation();
|
||||
|
||||
// Determines round trip time of the audio system. Called from 'eventuallyAnalyzePing'.
|
||||
inline void analyzePing();
|
||||
|
||||
void addProceduralSounds(int16_t* inputBuffer, int numSamples);
|
||||
|
||||
|
|
Loading…
Reference in a new issue