Раздели аудиопотоки и привяжи AEC к фактическому общему выходу
This commit is contained in:
1 parent
d7fb0c83f9
commit
bd082a26bf
30 files changed
+1839
-412
No files matched your search
+21
-10
@@ -7565,6 +7565,7 @@ struct ma_context
|
||||
ma_proc pa_stream_get_buffer_attr;
|
||||
ma_proc pa_stream_set_buffer_attr;
|
||||
ma_proc pa_stream_get_device_name;
|
||||
ma_proc pa_stream_get_latency;
|
||||
ma_proc pa_stream_set_write_callback;
|
||||
ma_proc pa_stream_set_read_callback;
|
||||
ma_proc pa_stream_set_suspended_callback;
|
||||
@@ -30959,6 +30960,7 @@ typedef const ma_pa_sample_spec* (* ma_pa_stream_get_sample_spec_proc) (
|
||||
typedef const ma_pa_channel_map* (* ma_pa_stream_get_channel_map_proc) (ma_pa_stream* s);
|
||||
typedef const ma_pa_buffer_attr* (* ma_pa_stream_get_buffer_attr_proc) (ma_pa_stream* s);
|
||||
typedef ma_pa_operation* (* ma_pa_stream_set_buffer_attr_proc) (ma_pa_stream* s, const ma_pa_buffer_attr* attr, ma_pa_stream_success_cb_t cb, void* userdata);
|
||||
typedef int (* ma_pa_stream_get_latency_proc)(const ma_pa_stream* s, ma_uint64* usec, int* negative);
|
||||
typedef const char* (* ma_pa_stream_get_device_name_proc) (const ma_pa_stream* s);
|
||||
typedef void (* ma_pa_stream_set_write_callback_proc) (ma_pa_stream* s, ma_pa_stream_request_cb_t cb, void* userdata);
|
||||
typedef void (* ma_pa_stream_set_read_callback_proc) (ma_pa_stream* s, ma_pa_stream_request_cb_t cb, void* userdata);
|
||||
@@ -31840,6 +31842,9 @@ static void ma_device_on_read__pulse(ma_pa_stream* pStream, size_t byteCount, vo
|
||||
ma_silence_pcm_frames(silence, capacity, pDevice->capture.internalFormat, pDevice->capture.internalChannels);
|
||||
while (remaining > 0 && ma_device_get_state(pDevice) == ma_device_state_started) {
|
||||
ma_uint32 n = (ma_uint32)ma_min(remaining, capacity);
|
||||
#ifdef MA_AUDIO_CAPTURE_HOLE
|
||||
MA_AUDIO_CAPTURE_HOLE(framesMapped - remaining);
|
||||
#endif
|
||||
ma_device_handle_backend_data_callback(pDevice, NULL, silence, n);
|
||||
remaining -= n;
|
||||
}
|
||||
@@ -32144,7 +32149,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi
|
||||
if (pDescriptorCapture->sampleRate != 0) {
|
||||
ss.rate = pDescriptorCapture->sampleRate;
|
||||
}
|
||||
streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY;
|
||||
streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY | MA_PA_STREAM_AUTO_TIMING_UPDATE | MA_PA_STREAM_INTERPOLATE_TIMING;
|
||||
|
||||
if (ma_format_from_pulse(ss.format) == ma_format_unknown) {
|
||||
if (ma_is_little_endian()) {
|
||||
@@ -32303,7 +32308,7 @@ static ma_result ma_device_init__pulse(ma_device* pDevice, const ma_device_confi
|
||||
ss.rate = pDescriptorPlayback->sampleRate;
|
||||
}
|
||||
|
||||
streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY;
|
||||
streamFlags = MA_PA_STREAM_START_CORKED | MA_PA_STREAM_ADJUST_LATENCY | MA_PA_STREAM_AUTO_TIMING_UPDATE | MA_PA_STREAM_INTERPOLATE_TIMING;
|
||||
if (ma_format_from_pulse(ss.format) == ma_format_unknown) {
|
||||
if (ma_is_little_endian()) {
|
||||
ss.format = MA_PA_SAMPLE_FLOAT32LE;
|
||||
@@ -32729,6 +32734,7 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c
|
||||
pContext->pulse.pa_stream_get_channel_map = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_get_channel_map");
|
||||
pContext->pulse.pa_stream_get_buffer_attr = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_get_buffer_attr");
|
||||
pContext->pulse.pa_stream_set_buffer_attr = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_set_buffer_attr");
|
||||
pContext->pulse.pa_stream_get_latency = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_get_latency");
|
||||
pContext->pulse.pa_stream_get_device_name = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_get_device_name");
|
||||
pContext->pulse.pa_stream_set_write_callback = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_set_write_callback");
|
||||
pContext->pulse.pa_stream_set_read_callback = (ma_proc)ma_dlsym(ma_context_get_log(pContext), pContext->pulse.pulseSO, "pa_stream_set_read_callback");
|
||||
@@ -32795,6 +32801,7 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c
|
||||
ma_pa_stream_get_channel_map_proc _pa_stream_get_channel_map = pa_stream_get_channel_map;
|
||||
ma_pa_stream_get_buffer_attr_proc _pa_stream_get_buffer_attr = pa_stream_get_buffer_attr;
|
||||
ma_pa_stream_set_buffer_attr_proc _pa_stream_set_buffer_attr = pa_stream_set_buffer_attr;
|
||||
ma_pa_stream_get_latency_proc _pa_stream_get_latency = pa_stream_get_latency;
|
||||
ma_pa_stream_get_device_name_proc _pa_stream_get_device_name = pa_stream_get_device_name;
|
||||
ma_pa_stream_set_write_callback_proc _pa_stream_set_write_callback = pa_stream_set_write_callback;
|
||||
ma_pa_stream_set_read_callback_proc _pa_stream_set_read_callback = pa_stream_set_read_callback;
|
||||
@@ -32860,6 +32867,7 @@ static ma_result ma_context_init__pulse(ma_context* pContext, const ma_context_c
|
||||
pContext->pulse.pa_stream_get_channel_map = (ma_proc)_pa_stream_get_channel_map;
|
||||
pContext->pulse.pa_stream_get_buffer_attr = (ma_proc)_pa_stream_get_buffer_attr;
|
||||
pContext->pulse.pa_stream_set_buffer_attr = (ma_proc)_pa_stream_set_buffer_attr;
|
||||
pContext->pulse.pa_stream_get_latency = (ma_proc)_pa_stream_get_latency;
|
||||
pContext->pulse.pa_stream_get_device_name = (ma_proc)_pa_stream_get_device_name;
|
||||
pContext->pulse.pa_stream_set_write_callback = (ma_proc)_pa_stream_set_write_callback;
|
||||
pContext->pulse.pa_stream_set_read_callback = (ma_proc)_pa_stream_set_read_callback;
|
||||
@@ -44639,6 +44647,14 @@ MA_API ma_result ma_device_handle_backend_data_callback(ma_device* pDevice, void
|
||||
if (frameCount == 0) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
if (((pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_loopback) && pInput == NULL) ||
|
||||
(pDevice->type == ma_device_type_playback && pOutput == NULL)) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
#ifdef MA_AUDIO_TIMING_BEGIN
|
||||
MA_AUDIO_TIMING_BEGIN(pDevice, frameCount, pInput != NULL);
|
||||
#endif
|
||||
|
||||
if (pDevice->type == ma_device_type_duplex) {
|
||||
if (pInput != NULL) {
|
||||
@@ -44650,22 +44666,17 @@ MA_API ma_result ma_device_handle_backend_data_callback(ma_device* pDevice, void
|
||||
}
|
||||
} else {
|
||||
if (pDevice->type == ma_device_type_capture || pDevice->type == ma_device_type_loopback) {
|
||||
if (pInput == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
ma_device__send_frames_to_client(pDevice, frameCount, pInput);
|
||||
}
|
||||
|
||||
if (pDevice->type == ma_device_type_playback) {
|
||||
if (pOutput == NULL) {
|
||||
return MA_INVALID_ARGS;
|
||||
}
|
||||
|
||||
ma_device__read_frames_from_client(pDevice, frameCount, pOutput);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef MA_AUDIO_TIMING_END
|
||||
MA_AUDIO_TIMING_END();
|
||||
#endif
|
||||
return MA_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
+24
-14
@@ -24,6 +24,7 @@
|
||||
|
||||
struct speex_aec {
|
||||
SpeexEchoState* st;
|
||||
int mic_channels, render_channels;
|
||||
int frame_samples; /* сэмплов в кадре (960 @48кГц) */
|
||||
int depth; /* глубина линии задержки в кадрах (>=1) */
|
||||
int16_t* line; /* кольцо depth*frame_samples */
|
||||
@@ -42,17 +43,25 @@ static void aec_enqueue(speex_aec_t* a, const int16_t* frame) {
|
||||
a->count--;
|
||||
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));
|
||||
memcpy(a->line + (size_t)((a->head + a->count) % a->depth) * a->frame_samples * a->render_channels,
|
||||
frame, (size_t)a->frame_samples * a->render_channels * sizeof(int16_t));
|
||||
a->count++;
|
||||
}
|
||||
|
||||
speex_aec_t* speex_aec_create(int sample_rate, int frame_samples, int filter_samples, int delay_frames) {
|
||||
return speex_aec_create_mc(sample_rate, frame_samples, filter_samples, delay_frames, 1, 1);
|
||||
}
|
||||
|
||||
/* Независимые каналы референса: противофазное стерео не усредняется. */
|
||||
speex_aec_t* speex_aec_create_mc(int sample_rate, int frame_samples, int filter_samples, int delay_frames,
|
||||
int mic_channels, int render_channels) {
|
||||
speex_aec_t* a;
|
||||
int rate;
|
||||
|
||||
if (sample_rate <= 0 || frame_samples <= 0 || filter_samples < frame_samples ||
|
||||
(delay_frames > 0 && delay_frames > INT_MAX / frame_samples)) {
|
||||
mic_channels < 1 || mic_channels > 2 || render_channels < 1 || render_channels > 2 ||
|
||||
frame_samples > INT_MAX / render_channels || frame_samples > INT_MAX / mic_channels ||
|
||||
(delay_frames > 0 && delay_frames > INT_MAX / frame_samples / render_channels)) {
|
||||
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;
|
||||
@@ -64,7 +73,7 @@ speex_aec_t* speex_aec_create(int sample_rate, int frame_samples, int filter_sam
|
||||
return NULL;
|
||||
}
|
||||
|
||||
a->st = speex_echo_state_init(frame_samples, filter_samples);
|
||||
a->st = speex_echo_state_init_mc(frame_samples, filter_samples, mic_channels, render_channels);
|
||||
if (!a->st) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_AEC, "%s: echo state allocation failed", AEC_ID);
|
||||
u_free(a);
|
||||
@@ -78,10 +87,11 @@ speex_aec_t* speex_aec_create(int sample_rate, int frame_samples, int filter_sam
|
||||
return NULL;
|
||||
}
|
||||
|
||||
a->mic_channels = mic_channels; a->render_channels = render_channels;
|
||||
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->line = (int16_t*)u_calloc((uint32_t)(a->depth * frame_samples * render_channels), sizeof(int16_t));
|
||||
a->play_acc = (int16_t*)u_calloc((uint32_t)(frame_samples * render_channels), sizeof(int16_t));
|
||||
if (!a->line || !a->play_acc) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_AEC, "%s: OOM for buffers", AEC_ID);
|
||||
if (a->line) u_free(a->line);
|
||||
@@ -91,8 +101,8 @@ speex_aec_t* speex_aec_create(int sample_rate, int frame_samples, int filter_sam
|
||||
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);
|
||||
DEBUG_INFO(DEBUG_CATEGORY_AEC, "%s: created rate=%d frame=%d filter=%d delay=%d frames mic=%d render=%d",
|
||||
AEC_ID, sample_rate, frame_samples, filter_samples, a->depth, mic_channels, render_channels);
|
||||
return a;
|
||||
}
|
||||
|
||||
@@ -117,7 +127,7 @@ 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;
|
||||
fs = a->frame_samples * a->render_channels;
|
||||
|
||||
while (count > 0) {
|
||||
take = fs - a->play_len;
|
||||
@@ -139,23 +149,23 @@ int speex_aec_process_capture(speex_aec_t* a, const int16_t* pcm, int count, int
|
||||
if (!a || !pcm || !out || count <= 0) return 0;
|
||||
fs = a->frame_samples;
|
||||
|
||||
if (count != fs) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_AEC, "%s: capture requires one frame samples=%d expected=%d", AEC_ID, count, fs);
|
||||
if (count != fs * a->mic_channels) {
|
||||
DEBUG_ERROR(DEBUG_CATEGORY_AEC, "%s: capture requires one frame samples=%d expected=%d", AEC_ID, count, fs * a->mic_channels);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (a->count == a->depth) {
|
||||
const int16_t* ref = a->line + (size_t)a->head * fs;
|
||||
const int16_t* ref = a->line + (size_t)a->head * fs * a->render_channels;
|
||||
speex_echo_cancellation(a->st, pcm, ref, out);
|
||||
a->head = (a->head + 1) % a->depth;
|
||||
a->count--;
|
||||
} else {
|
||||
/* линия не наполнена (старт или рендер отстаёт): passthrough без канселлера */
|
||||
memcpy(out, pcm, (size_t)fs * sizeof(int16_t));
|
||||
memcpy(out, pcm, (size_t)count * sizeof(int16_t));
|
||||
a->underruns++;
|
||||
}
|
||||
|
||||
return fs;
|
||||
return count;
|
||||
}
|
||||
|
||||
int speex_aec_delay_fill(const speex_aec_t* a) {
|
||||
|
||||
+8
-3
@@ -2,7 +2,7 @@
|
||||
* speex_aec.h — акустическое эхоподавление (AEC), C-обёртка над SpeexDSP mdf.c.
|
||||
*
|
||||
* Подавляет эхо дальнего конца (то, что играем в динамик) в сигнале микрофона
|
||||
* перед кодированием. Работает на int16 PCM, один канал, частота 48000 (можно
|
||||
* перед кодированием. Работает на interleaved int16 PCM, 1/2 канала, частота 48000 (можно
|
||||
* 8000/16000/32000/48000), кадр 20 мс (960 сэмплов @48 кГц).
|
||||
*
|
||||
* Модель использования (duplex-контур звонка):
|
||||
@@ -44,6 +44,11 @@ typedef struct speex_aec speex_aec_t;
|
||||
*/
|
||||
speex_aec_t* speex_aec_create(int sample_rate, int frame_samples, int filter_samples, int delay_frames);
|
||||
|
||||
/* Многоканальный AEC: frame_samples на канал, count в feed/process — все interleaved отсчёты.
|
||||
* mic_channels и render_channels — 1/2; возвращаемый count захвата равен frame_samples*mic_channels. */
|
||||
speex_aec_t* speex_aec_create_mc(int sample_rate, int frame_samples, int filter_samples, int delay_frames,
|
||||
int mic_channels, int render_channels);
|
||||
|
||||
/* Освободить канселлер. NULL безопасен. */
|
||||
void speex_aec_destroy(speex_aec_t* aec);
|
||||
|
||||
@@ -57,8 +62,8 @@ void speex_aec_reset(speex_aec_t* aec);
|
||||
void speex_aec_feed_playback(speex_aec_t* aec, const int16_t* pcm, int count);
|
||||
|
||||
/**
|
||||
* Обработать ровно один кадр захвата (count == frame_samples).
|
||||
* Возвращает frame_samples при успехе, 0 при ошибке. out вмещает полный кадр
|
||||
* Обработать ровно один кадр захвата (count == frame_samples*mic_channels).
|
||||
* Возвращает count при успехе, 0 при ошибке. out вмещает полный interleaved кадр
|
||||
* и не должен алиаситься с pcm. Накопитель аппаратных чанков принадлежит I/O-движку.
|
||||
*/
|
||||
int speex_aec_process_capture(speex_aec_t* aec, const int16_t* pcm, int count, int16_t* out);
|
||||
|
||||
Reference in new issue
Block a user