You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
169 lines
6.7 KiB
169 lines
6.7 KiB
// speex_aec.c — C-обёртка над SpeexDSP mdf.c (акустическое эхоподавление). |
|
// |
|
// API см. в speex_aec.h. Ключевые детали реализации: |
|
// - собственная линия задержки рендера на depth кадров (SpeexDSP даёт только |
|
// фиксированные 2 кадра — PLAYBACK_DELAY в mdf.c); |
|
// - синхронный speex_echo_cancellation(rec, play_delayed, out) с выравниванием |
|
// рендер↔захват по линии задержки; |
|
// - дрейф независимых потоков: переполнение линии → отбрасываем старый кадр, |
|
// недозаполнение (старт/underflow) → passthrough без канселлера. |
|
// |
|
// Захват и рендер приходят чанками разного размера (480/960), поэтому обе |
|
// стороны накапливаются до полного кадра (frame_samples) внутри обёртки. |
|
|
|
#include "speex_aec.h" |
|
#include "debug_config.h" |
|
#include "mem.h" |
|
|
|
#include <string.h> |
|
|
|
#include <speex/speex_echo.h> |
|
|
|
#define AEC_ID "speex_aec" |
|
|
|
struct speex_aec { |
|
SpeexEchoState* st; |
|
int frame_samples; /* сэмплов в кадре (960 @48кГц) */ |
|
int depth; /* глубина линии задержки в кадрах (>=1) */ |
|
int16_t* line; /* кольцо depth*frame_samples */ |
|
int head; /* индекс самого старого кадра в line */ |
|
int count; /* кадров в линии (0..depth) */ |
|
int16_t* play_acc; /* аккумулятор рендера до кадра */ |
|
int play_len; |
|
int16_t* cap_acc; /* аккумулятор захвата до кадра */ |
|
int cap_len; |
|
uint32_t overruns; /* отброшено рендер-кадров (дрейф: рендер быстрее) */ |
|
uint32_t underruns; /* passthrough-кадров (дрейф: рендер медленнее/старт) */ |
|
}; |
|
|
|
/* Положить полный рендер-кадр в линию задержки (вытесняя старейший при переполнении). */ |
|
static void aec_enqueue(speex_aec_t* a, const int16_t* frame) { |
|
if (a->count == a->depth) { |
|
a->head = (a->head + 1) % a->depth; |
|
a->count--; |
|
a->overruns++; |
|
if (a->overruns == 1 || (a->overruns & 0xff) == 0) { |
|
DEBUG_WARN(DEBUG_CATEGORY_AEC, |
|
"%s: delay line overflow (render faster than capture) overruns=%u", AEC_ID, a->overruns); |
|
} |
|
} |
|
memcpy(a->line + (size_t)((a->head + a->count) % a->depth) * a->frame_samples, |
|
frame, (size_t)a->frame_samples * sizeof(int16_t)); |
|
a->count++; |
|
} |
|
|
|
speex_aec_t* speex_aec_create(int sample_rate, int frame_samples, int filter_samples, int delay_frames) { |
|
speex_aec_t* a; |
|
int rate; |
|
|
|
if (sample_rate <= 0 || frame_samples <= 0 || filter_samples < frame_samples) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_AEC, "%s: bad args rate=%d frame=%d filter=%d delay=%d", |
|
AEC_ID, sample_rate, frame_samples, filter_samples, delay_frames); |
|
return NULL; |
|
} |
|
|
|
a = (speex_aec_t*)u_calloc(1, sizeof(*a)); |
|
if (!a) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_AEC, "%s: OOM for state", AEC_ID); |
|
return NULL; |
|
} |
|
|
|
a->st = speex_echo_state_init(frame_samples, filter_samples); |
|
rate = sample_rate; |
|
if (speex_echo_ctl(a->st, SPEEX_ECHO_SET_SAMPLING_RATE, &rate) != 0) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_AEC, "%s: SET_SAMPLING_RATE(%d) failed", AEC_ID, sample_rate); |
|
speex_echo_state_destroy(a->st); |
|
u_free(a); |
|
return NULL; |
|
} |
|
|
|
a->frame_samples = frame_samples; |
|
a->depth = delay_frames > 0 ? delay_frames : 1; |
|
a->line = (int16_t*)u_calloc((uint32_t)(a->depth * frame_samples), sizeof(int16_t)); |
|
a->play_acc = (int16_t*)u_calloc((uint32_t)frame_samples, sizeof(int16_t)); |
|
a->cap_acc = (int16_t*)u_calloc((uint32_t)frame_samples, sizeof(int16_t)); |
|
if (!a->line || !a->play_acc || !a->cap_acc) { |
|
DEBUG_ERROR(DEBUG_CATEGORY_AEC, "%s: OOM for buffers", AEC_ID); |
|
if (a->line) u_free(a->line); |
|
if (a->play_acc) u_free(a->play_acc); |
|
if (a->cap_acc) u_free(a->cap_acc); |
|
speex_echo_state_destroy(a->st); |
|
u_free(a); |
|
return NULL; |
|
} |
|
|
|
DEBUG_INFO(DEBUG_CATEGORY_AEC, "%s: created rate=%d frame=%d filter=%d delay=%d frames", |
|
AEC_ID, sample_rate, frame_samples, filter_samples, a->depth); |
|
return a; |
|
} |
|
|
|
void speex_aec_destroy(speex_aec_t* a) { |
|
if (!a) return; |
|
speex_echo_state_destroy(a->st); |
|
u_free(a->line); |
|
u_free(a->play_acc); |
|
u_free(a->cap_acc); |
|
u_free(a); |
|
} |
|
|
|
void speex_aec_reset(speex_aec_t* a) { |
|
if (!a) return; |
|
speex_echo_state_reset(a->st); |
|
a->head = 0; |
|
a->count = 0; |
|
a->play_len = 0; |
|
a->cap_len = 0; |
|
DEBUG_INFO(DEBUG_CATEGORY_AEC, "%s: reset (filter + delay line)", AEC_ID); |
|
} |
|
|
|
void speex_aec_feed_playback(speex_aec_t* a, const int16_t* pcm, int count) { |
|
int fs, take; |
|
|
|
if (!a || !pcm || count <= 0) return; |
|
fs = a->frame_samples; |
|
|
|
take = count; |
|
if (a->play_len + count > fs) take = fs - a->play_len; |
|
memcpy(a->play_acc + a->play_len, pcm, (size_t)take * sizeof(int16_t)); |
|
a->play_len += take; |
|
if (a->play_len < fs) return; |
|
|
|
aec_enqueue(a, a->play_acc); |
|
a->play_len = 0; |
|
} |
|
|
|
int speex_aec_process_capture(speex_aec_t* a, const int16_t* pcm, int count, int16_t* out) { |
|
int fs, take; |
|
|
|
if (!a || !pcm || !out || count <= 0) return 0; |
|
fs = a->frame_samples; |
|
|
|
take = count; |
|
if (a->cap_len + count > fs) take = fs - a->cap_len; |
|
memcpy(a->cap_acc + a->cap_len, pcm, (size_t)take * sizeof(int16_t)); |
|
a->cap_len += take; |
|
if (a->cap_len < fs) return 0; |
|
|
|
if (a->count == a->depth) { |
|
const int16_t* ref = a->line + (size_t)a->head * fs; |
|
speex_echo_cancellation(a->st, a->cap_acc, ref, out); |
|
a->head = (a->head + 1) % a->depth; |
|
a->count--; |
|
} else { |
|
/* линия не наполнена (старт или рендер отстаёт): passthrough без канселлера */ |
|
if (a->underruns == 0 || (a->underruns & 0xff) == 0) { |
|
DEBUG_WARN(DEBUG_CATEGORY_AEC, |
|
"%s: delay line underflow (render slower than capture) underruns=%u fill=%d/%d", |
|
AEC_ID, a->underruns, a->count, a->depth); |
|
} |
|
memcpy(out, a->cap_acc, (size_t)fs * sizeof(int16_t)); |
|
a->underruns++; |
|
} |
|
|
|
a->cap_len = 0; |
|
return fs; |
|
} |
|
|
|
int speex_aec_delay_fill(const speex_aec_t* a) { |
|
return a ? a->count : 0; |
|
}
|
|
|