Раздели аудиопотоки и привяжи AEC к фактическому общему выходу
This commit is contained in:
1 parent
d7fb0c83f9
commit
bd082a26bf
30 files changed
+1839
-412
No files matched your search
+38
-11
@@ -44,6 +44,7 @@ static struct call_jitter* g_vj = NULL;
|
||||
static struct call_tones* g_tones = NULL;
|
||||
static speex_aec_t* g_aec = NULL;
|
||||
static int g_aec_delay_frames = CALL_AEC_DELAY_FRAMES;
|
||||
static int g_prepared_capture = 0;
|
||||
static int g_aec_enabled = 1; /* по умолчанию: настройка aec_enabled */
|
||||
static struct audio_compressor* g_compressor = NULL;
|
||||
static uint64_t g_tx_generation;
|
||||
@@ -124,7 +125,7 @@ void call_audio_on_media(struct UTUN_INSTANCE* inst, uint64_t call_id,
|
||||
|
||||
/* ── GUI-поток: lifecycle ── */
|
||||
|
||||
int call_audio_start(uint64_t call_id) {
|
||||
static int call_audio_start_impl(uint64_t call_id, int prepared) {
|
||||
if (call_id == 0) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: start bad call_id=0", CALL_AUDIO_ID);
|
||||
return -1;
|
||||
@@ -132,8 +133,10 @@ int call_audio_start(uint64_t call_id) {
|
||||
|
||||
pthread_mutex_lock(&g_mtx);
|
||||
if (g_active && g_call_id == call_id) {
|
||||
int same_mode = g_prepared_capture == prepared;
|
||||
pthread_mutex_unlock(&g_mtx);
|
||||
return 0;
|
||||
if (!same_mode) DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: capture policy cannot change within active call", CALL_AUDIO_ID);
|
||||
return same_mode ? 0 : -1;
|
||||
}
|
||||
if (g_active) {
|
||||
pthread_mutex_unlock(&g_mtx);
|
||||
@@ -179,7 +182,7 @@ int call_audio_start(uint64_t call_id) {
|
||||
|
||||
g_aec_enabled = g_inst ? chat_setting_get_int(g_inst, "aec_enabled", 1) : 1;
|
||||
speex_aec_t* aec = NULL;
|
||||
if (g_aec_enabled) {
|
||||
if (g_aec_enabled && !prepared) {
|
||||
aec = call_audio_aec_create();
|
||||
if (!aec) {
|
||||
g_aec_enabled = 0;
|
||||
@@ -210,7 +213,7 @@ int call_audio_start(uint64_t call_id) {
|
||||
g_decoder = dec;
|
||||
g_vj = j;
|
||||
g_tones = tones;
|
||||
g_aec = aec;
|
||||
g_aec = aec; g_prepared_capture = prepared;
|
||||
g_compressor = ac;
|
||||
g_call_id = call_id;
|
||||
g_active = 1;
|
||||
@@ -231,6 +234,10 @@ int call_audio_start(uint64_t call_id) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Платформа с собственным голосовым захватом явно передаёт PCM после AEC. */
|
||||
int call_audio_start_prepared(uint64_t call_id) { return call_audio_start_impl(call_id, 1); }
|
||||
int call_audio_start(uint64_t call_id) { return call_audio_start_impl(call_id, 0); }
|
||||
|
||||
void call_audio_begin_end(uint64_t call_id) {
|
||||
pthread_mutex_lock(&g_mtx);
|
||||
if (g_active && call_id == g_call_id && !g_ending) {
|
||||
@@ -292,7 +299,7 @@ void call_audio_reset_io(uint64_t call_id, int playback) {
|
||||
pthread_mutex_lock(&g_mtx);
|
||||
if (g_active && g_call_id == call_id) {
|
||||
if (g_aec) speex_aec_reset(g_aec);
|
||||
if (g_compressor) audio_compressor_reset(g_compressor);
|
||||
if (!playback && g_compressor) audio_compressor_reset(g_compressor);
|
||||
if (playback) {
|
||||
call_jitter_reset(g_vj);
|
||||
g_gap_samples = 0;
|
||||
@@ -317,7 +324,7 @@ static speex_aec_t* call_audio_aec_create(void) {
|
||||
void call_audio_set_aec_enabled(int enabled) {
|
||||
pthread_mutex_lock(&g_mtx);
|
||||
g_aec_enabled = enabled ? 1 : 0;
|
||||
if (g_active) {
|
||||
if (g_active && !g_prepared_capture) {
|
||||
if (g_aec_enabled && !g_aec) {
|
||||
g_aec = call_audio_aec_create();
|
||||
if (!g_aec) DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: AEC create failed (enabled)", CALL_AUDIO_ID);
|
||||
@@ -335,7 +342,7 @@ void call_audio_set_aec_delay_frames(int frames) {
|
||||
if (frames < 1) frames = 1;
|
||||
pthread_mutex_lock(&g_mtx);
|
||||
g_aec_delay_frames = frames;
|
||||
if (g_active && g_aec_enabled && g_aec) {
|
||||
if (g_active && !g_prepared_capture && g_aec_enabled && g_aec) {
|
||||
speex_aec_destroy(g_aec);
|
||||
g_aec = call_audio_aec_create();
|
||||
if (!g_aec) DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: AEC recreate failed (delay=%d)", CALL_AUDIO_ID, frames);
|
||||
@@ -346,8 +353,11 @@ void call_audio_set_aec_delay_frames(int frames) {
|
||||
|
||||
/* ── аудио-поток: TX / RX ── */
|
||||
|
||||
int call_audio_feed_pcm_muted(uint64_t call_id, const int16_t* pcm, int count, int muted) {
|
||||
if (!pcm || count != CALL_AUDIO_FRAME_SAMPLES) return -1;
|
||||
static int call_audio_feed_impl(uint64_t call_id, const int16_t* pcm, int count, int muted, const uint8_t* mute_mask, int prepared) {
|
||||
if (!pcm || count != CALL_AUDIO_FRAME_SAMPLES || (prepared && !mute_mask)) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: invalid capture frame count=%d prepared=%d", CALL_AUDIO_ID, count, prepared);
|
||||
return -1;
|
||||
}
|
||||
|
||||
uint8_t opus[256];
|
||||
int16_t cleaned[CALL_AUDIO_FRAME_SAMPLES];
|
||||
@@ -360,6 +370,11 @@ int call_audio_feed_pcm_muted(uint64_t call_id, const int16_t* pcm, int count, i
|
||||
pthread_mutex_unlock(&g_mtx);
|
||||
return -1;
|
||||
}
|
||||
if (g_prepared_capture != prepared) {
|
||||
pthread_mutex_unlock(&g_mtx);
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_CALL, "%s: capture policy mismatch prepared=%d", CALL_AUDIO_ID, prepared);
|
||||
return -1;
|
||||
}
|
||||
const int16_t* tx = pcm;
|
||||
if (g_aec) {
|
||||
if (speex_aec_process_capture(g_aec, pcm, count, cleaned) == count)
|
||||
@@ -374,7 +389,11 @@ int call_audio_feed_pcm_muted(uint64_t call_id, const int16_t* pcm, int count, i
|
||||
}
|
||||
tx = amplified;
|
||||
}
|
||||
if (muted) { memset(amplified, 0, sizeof(amplified)); tx = amplified; }
|
||||
if (mute_mask) {
|
||||
if (tx != amplified) memcpy(amplified, tx, sizeof(amplified));
|
||||
for (int i = 0; i < count; ++i) if (mute_mask[i]) amplified[i] = 0;
|
||||
tx = amplified;
|
||||
} else if (muted) { memset(amplified, 0, sizeof(amplified)); tx = amplified; }
|
||||
len = opus_codec_encode(g_encoder, tx, count, opus, (int)sizeof(opus));
|
||||
ua = g_inst ? g_inst->ua : NULL;
|
||||
inst = g_inst;
|
||||
@@ -405,9 +424,17 @@ int call_audio_feed_pcm_muted(uint64_t call_id, const int16_t* pcm, int count, i
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Маска соответствует границам mute в том же упорядоченном потоке, что и PCM. */
|
||||
int call_audio_feed_prepared_pcm(uint64_t call_id, const int16_t* pcm, const uint8_t* mute_mask) {
|
||||
return call_audio_feed_impl(call_id, pcm, CALL_AUDIO_FRAME_SAMPLES, 0, mute_mask, 1);
|
||||
}
|
||||
int call_audio_feed_pcm_muted(uint64_t call_id, const int16_t* pcm, int count, int muted) {
|
||||
return call_audio_feed_impl(call_id, pcm, count, muted, NULL, 0);
|
||||
}
|
||||
|
||||
/* Незамьюченный захват для платформ без программного mute. */
|
||||
int call_audio_feed_pcm(uint64_t call_id, const int16_t* pcm, int count) {
|
||||
return call_audio_feed_pcm_muted(call_id, pcm, count, 0);
|
||||
return call_audio_feed_impl(call_id, pcm, count, 0, NULL, 0);
|
||||
}
|
||||
|
||||
int call_audio_pull_pcm(uint64_t call_id, int16_t* out, int max_samples) {
|
||||
|
||||
@@ -35,8 +35,11 @@ extern "C" {
|
||||
int call_audio_init(struct UTUN_INSTANCE* inst);
|
||||
void call_audio_destroy(struct UTUN_INSTANCE* inst);
|
||||
|
||||
/* GUI-поток: создать codec + jitter + tones для звонка. 0 = успех. */
|
||||
/* GUI-поток: создать codec + jitter + tones. Старый API принимает raw PCM и использует core AEC.
|
||||
* Для desktop нужен start_prepared: AEC и stereo reference принадлежат VoiceAudioIo. 0 = успех. */
|
||||
int call_audio_start(uint64_t call_id);
|
||||
/* AEC принадлежит платформенному захвату; pull больше не подаёт собственный референс. */
|
||||
int call_audio_start_prepared(uint64_t call_id);
|
||||
|
||||
/* GUI-поток: по CALL_ENDED — начать тон завершения (pull_pcm выдаст 450мс тона). */
|
||||
void call_audio_begin_end(uint64_t call_id);
|
||||
@@ -50,6 +53,8 @@ void call_audio_stop(void);
|
||||
/* аудио-поток: закодировать PCM-кадр (ровно CALL_AUDIO_FRAME_SAMPLES) и отправить пиру. */
|
||||
int call_audio_feed_pcm(uint64_t call_id, const int16_t* pcm, int count);
|
||||
/* Mute после AEC/AGC: фильтр получает настоящий микрофон, Opus — нулевой PCM. */
|
||||
/* Подготовленный PCM и маска mute (960 элементов); AEC уже выполнен до этого API. */
|
||||
int call_audio_feed_prepared_pcm(uint64_t call_id, const int16_t* pcm, const uint8_t* mute_mask);
|
||||
int call_audio_feed_pcm_muted(uint64_t call_id, const int16_t* pcm, int count, int muted);
|
||||
|
||||
/* аудио-поток: выдать до max_samples финального PCM (jitter + stretch + тоны).
|
||||
@@ -70,12 +75,12 @@ int call_audio_get_stats(uint64_t call_id, int* buffer_ms, int* tempo_x100,
|
||||
* Release допустим только после остановки обоих. */
|
||||
void call_audio_reset_io(uint64_t call_id, int playback);
|
||||
|
||||
/* Включить/выключить AEC (SpeexDSP) в рантайме. desktop — по настройке GUI,
|
||||
* Android — по спикерфону (route == SPEAKER). Работает и в активном звонке. */
|
||||
/* Core AEC для raw capture: Android — по спикерфону (route == SPEAKER).
|
||||
* Prepared capture не создаёт core AEC; desktop передаёт настройку в VoiceAudioIo. */
|
||||
void call_audio_set_aec_enabled(int enabled);
|
||||
|
||||
/* Задать задержку рендер→захват AEC в кадрах (20 мс). Android измеряет через
|
||||
* AudioTrack/AudioRecord getTimestamp, desktop использует дефолт. Если AEC уже
|
||||
* AudioTrack/AudioRecord getTimestamp. Prepared desktop выравнивает референс по времени. Если core AEC уже
|
||||
* активен — пересоздаётся с новой задержкой. */
|
||||
void call_audio_set_aec_delay_frames(int frames);
|
||||
|
||||
|
||||
Reference in new issue
Block a user