/* * speex_aec.h — акустическое эхоподавление (AEC), C-обёртка над SpeexDSP mdf.c. * * Подавляет эхо дальнего конца (то, что играем в динамик) в сигнале микрофона * перед кодированием. Работает на int16 PCM, один канал, частота 48000 (можно * 8000/16000/32000/48000), кадр 20 мс (960 сэмплов @48 кГц). * * Модель использования (duplex-контур звонка): * - рендер (far-end, то что пошло в динамик) → speex_aec_feed_playback(); * - захват (near-end, микрофон) → speex_aec_process_capture(). * * Обёртка держит собственную линию задержки рендера на `delay_frames` кадров и * зовёт синхронный speex_echo_cancellation(rec, play_delayed, out). Это делает * выравнивание рендер↔захват предсказуемым и настраиваемым (встроенный буфер * SpeexDSP фиксирован в 2 кадра и для Android-задержки не годится). * * Дрейф двух независимых потоков (capture/play на Android) компенсируется * ограниченной глубиной линии: переполнение → отбрасываем старый рендер-кадр, * недозаполнение → passthrough без канселлера (с подробным логом категории "aec"). * * Зависимости: SpeexDSP (mdf.c/fftwrap.c/kiss_fft*) из lib/speexdsp, флаги * FLOATING_POINT + USE_KISS_FFT. Собирается всегда (без внешних зависимостей). * * Лицензия SpeexDSP: 3-clause BSD (Xiph) — см. lib/speexdsp/COPYING. */ #ifndef SPEEX_AEC_H #define SPEEX_AEC_H #include #ifdef __cplusplus extern "C" { #endif typedef struct speex_aec speex_aec_t; /** * Создать эхоканселлер. * sample_rate — 48000 (также 8000/16000/32000); * frame_samples — сэмплов в кадре (960 = 20 мс @48 кГц); * filter_samples— длина эхо-хвоста в сэмплах (14400 = 300 мс, кратно кадру); * delay_frames — задержка рендер→захват в кадрах (>=0; 0 = без линии задержки). * Возвращает NULL при ошибке (лог категории "aec"). */ speex_aec_t* speex_aec_create(int sample_rate, int frame_samples, int filter_samples, int delay_frames); /* Освободить канселлер. NULL безопасен. */ void speex_aec_destroy(speex_aec_t* aec); /* Сбросить адаптивный фильтр и линию задержки (смена устройства/роута). */ void speex_aec_reset(speex_aec_t* aec); /** * Подать рендер (дальний конец). Накопление до кадра внутри; полный кадр * кладётся в линию задержки. count может быть любым (обычно 480 или 960). */ void speex_aec_feed_playback(speex_aec_t* aec, const int16_t* pcm, int count); /** * Обработать захват (микрофон). Накопление до кадра внутри. Когда кадр собран — * подавляет эхо (с учётом линии задержки) и пишет результат в out. * Возвращает число сэмплов, записанных в out (frame_samples, если кадр готов; * 0 — кадр ещё накапливается). out не должен алиаситься с pcm. */ int speex_aec_process_capture(speex_aec_t* aec, const int16_t* pcm, int count, int16_t* out); /* Актуальная глубина линии задержки (кадров) — для диагностики дрейфа. */ int speex_aec_delay_fill(const speex_aec_t* aec); #ifdef __cplusplus } #endif #endif /* SPEEX_AEC_H */