20#define WEBRTC_APM_DEBUG_DUMP 0
21#define WEBRTC_MODULE_AUDIO_PROCESSING 1
23#if defined(__unix__) || defined(__APPLE__)
29#pragma clang diagnostic push
30#pragma clang diagnostic ignored "-Wdeprecated-builtins"
31#pragma clang diagnostic ignored "-Wunused-parameter"
35#include "api/echo_canceller3_factory.h"
36#include "api/echo_control.h"
38#include "audio_processing/audio_buffer.h"
40#pragma clang diagnostic pop
66#define __STDC_NO_ATOMICS__ 1
79 std::unique_ptr<webrtc::EchoControl>
aec3;
87static std::atomic<float> g_max_render_rms{0.0f};
90static std::atomic<int> g_render_frames_fed{0};
104 .opus_bitrate = 24000,
106 .echo_filter_ms = 250,
108 .noise_suppress_db = -25,
115 .jitter_margin_ms = 20,
118 .highpass_hz = 150.0f,
119 .lowpass_hz = 8000.0f,
123 .comp_threshold_db = -6.0f,
125 .comp_attack_ms = 5.0f,
126 .comp_release_ms = 150.0f,
127 .comp_makeup_db = 2.0f,
131 .gate_threshold = 0.08f,
132 .gate_attack_ms = 0.5f,
133 .gate_release_ms = 30.0f,
134 .gate_hysteresis = 0.3f,
156 log_error(
"Failed to allocate client audio pipeline");
175 if (!p->
encoder || opus_error != OPUS_OK) {
176 log_error(
"Failed to create Opus encoder: %d", opus_error);
183 opus_encoder_ctl(p->
encoder, OPUS_SET_DTX(0));
187 if (!p->
decoder || opus_error != OPUS_OK) {
188 log_error(
"Failed to create Opus decoder: %d", opus_error);
200 webrtc::EchoCanceller3Config aec3_config;
205 aec3_config.filter.main.length_blocks = 50;
206 aec3_config.filter.shadow.length_blocks = 50;
207 aec3_config.filter.main_initial.length_blocks = 25;
208 aec3_config.filter.shadow_initial.length_blocks = 25;
212 aec3_config.echo_audibility.audibility_threshold_lf = 5;
215 auto factory = webrtc::EchoCanceller3Factory(aec3_config);
217 std::unique_ptr<webrtc::EchoControl> echo_control = factory.Create(
static_cast<int>(p->
config.
sample_rate),
223 log_warn(
"Failed to create WebRTC AEC3 instance - echo cancellation unavailable");
228 wrapper->aec3 = std::move(echo_control);
229 wrapper->config = aec3_config;
232 log_info(
"✓ WebRTC AEC3 initialized (67ms filter for bass, adaptive delay)");
242 float *
const *render_ch = render_buf->channels();
243 float *
const *capture_ch = capture_buf->channels();
244 if (render_ch && render_ch[0]) {
245 memset(render_ch[0], 0, 480 *
sizeof(
float));
247 if (capture_ch && capture_ch[0]) {
248 memset(capture_ch[0], 0, 480 *
sizeof(
float));
252 render_buf->SplitIntoFrequencyBands();
253 render_buf->MergeFrequencyBands();
254 capture_buf->SplitIntoFrequencyBands();
255 capture_buf->MergeFrequencyBands();
257 log_info(
" - AudioBuffer filterbank state initialized");
260 for (
int warmup = 0; warmup < 10; warmup++) {
261 memset(render_ch[0], 0, 480 *
sizeof(
float));
262 memset(capture_ch[0], 0, 480 *
sizeof(
float));
264 render_buf->SplitIntoFrequencyBands();
265 wrapper->aec3->AnalyzeRender(render_buf);
266 render_buf->MergeFrequencyBands();
268 wrapper->aec3->AnalyzeCapture(capture_buf);
269 capture_buf->SplitIntoFrequencyBands();
270 wrapper->aec3->SetAudioBufferDelay(0);
271 wrapper->aec3->ProcessCapture(capture_buf,
false);
272 capture_buf->MergeFrequencyBands();
274 log_info(
" - AEC3 warmed up with 10 silent frames");
275 log_info(
" - Persistent AudioBuffer instances created");
287 log_info(
"Debug: Recording AEC3 input to /tmp/aec3_input.wav");
290 log_info(
"Debug: Recording AEC3 output to /tmp/aec3_output.wav");
293 log_info(
"✓ AEC3 echo cancellation enabled (full-duplex mode, no ring buffer delay)");
324 log_info(
"✓ Playback noise gate: threshold=0.002 (-54dB)");
350 opus_encoder_destroy(p->
encoder);
352 opus_decoder_destroy(p->
decoder);
382 opus_encoder_destroy(pipeline->
encoder);
386 opus_decoder_destroy(pipeline->
decoder);
411 pipeline->
flags = flags;
418 return pipeline->
flags;
432 uint8_t *opus_out,
int max_opus_len) {
433 if (!pipeline || !input || !opus_out || num_samples != pipeline->
frame_size) {
439 int opus_len = opus_encode_float(pipeline->
encoder, input, num_samples, opus_out, max_opus_len);
442 log_error(
"Opus encoding failed: %d", opus_len);
453 float *output,
int num_samples) {
454 if (!pipeline || !opus_in || !output) {
461 int decoded_samples = opus_decode_float(pipeline->
decoder, opus_in, opus_len, output, num_samples, 0);
463 if (decoded_samples < 0) {
464 log_error(
"Opus decoding failed: %d", decoded_samples);
469 if (decoded_samples > 0) {
476 return decoded_samples;
483 if (!pipeline || !output) {
488 memset(output, 0, num_samples *
sizeof(
float));
510 int render_count,
const float *capture_samples,
int capture_count,
511 float *processed_output) {
512 if (!pipeline || !processed_output)
516 if (capture_samples && capture_count > 0) {
517 memcpy(processed_output, capture_samples, capture_count *
sizeof(
float));
519 memset(processed_output, 0, capture_count *
sizeof(
float));
524 static int bypass_aec3 = -1;
525 if (bypass_aec3 == -1) {
527 bypass_aec3 = (env && (strcmp(env,
"1") == 0 || strcmp(env,
"true") == 0)) ? 1 : 0;
529 log_warn(
"AEC3 BYPASSED (full-duplex mode) via BYPASS_AEC3=1");
544 processed_output[i] *= gain;
552 if (wrapper && wrapper->aec3) {
553 const int webrtc_frame_size = 480;
558 if (render_buf && capture_buf) {
559 float *
const *render_channels = render_buf->channels();
560 float *
const *capture_channels = capture_buf->channels();
562 if (render_channels && render_channels[0] && capture_channels && capture_channels[0]) {
564 if (!render_samples && render_count > 0) {
565 log_warn_every(1000000,
"AEC3: render_samples is NULL but render_count=%d", render_count);
570 int render_offset = 0;
571 int capture_offset = 0;
573 while (capture_offset < capture_count || render_offset < render_count) {
576 if (render_samples && render_offset < render_count) {
577 int render_chunk = (render_offset + webrtc_frame_size <= render_count) ? webrtc_frame_size
578 : (render_count - render_offset);
579 if (render_chunk == webrtc_frame_size) {
581 copy_buffer_with_gain(&render_samples[render_offset], render_channels[0], webrtc_frame_size, 32768.0f);
582 render_buf->SplitIntoFrequencyBands();
583 wrapper->aec3->AnalyzeRender(render_buf);
584 render_buf->MergeFrequencyBands();
585 g_render_frames_fed.fetch_add(1, std::memory_order_relaxed);
587 render_offset += render_chunk;
591 if (capture_offset < capture_count) {
592 int capture_chunk = (capture_offset + webrtc_frame_size <= capture_count)
594 : (capture_count - capture_offset);
595 if (capture_chunk == webrtc_frame_size) {
601 wrapper->aec3->AnalyzeCapture(capture_buf);
602 capture_buf->SplitIntoFrequencyBands();
608 wrapper->aec3->ProcessCapture(capture_buf,
false);
609 capture_buf->MergeFrequencyBands();
613 for (
int j = 0; j < webrtc_frame_size; j++) {
614 float sample = capture_channels[0][j] / 32768.0f;
615 processed_output[capture_offset + j] =
soft_clip(sample, 0.6f, 2.5f);
619 static int duplex_log_count = 0;
620 if (++duplex_log_count % 100 == 1) {
621 webrtc::EchoControl::Metrics metrics = wrapper->aec3->GetMetrics();
622 log_info(
"AEC3 DUPLEX: ERL=%.1f ERLE=%.1f delay=%dms", metrics.echo_return_loss,
623 metrics.echo_return_loss_enhancement, metrics.delay_ms);
628 capture_offset += capture_chunk;
652 for (
int i = 0; i < capture_count; i++) {
654 processed_output[i] *= gain;
678 g_render_frames_fed.store(0, std::memory_order_relaxed);
679 g_max_render_rms.store(0.0f, std::memory_order_relaxed);
🔌 Cross-platform abstraction layer umbrella header for ascii-chat
void audio_analysis_set_aec3_metrics(double echo_return_loss, double echo_return_loss_enhancement, int delay_ms)
Set AEC3 echo cancellation metrics.
Audio Analysis and Debugging Interface.
int client_audio_pipeline_playback(client_audio_pipeline_t *pipeline, const uint8_t *opus_in, int opus_len, float *output, int num_samples)
Decode Opus packet and process for playback.
client_audio_pipeline_t * client_audio_pipeline_create(const client_audio_pipeline_config_t *config)
Create and initialize a client audio pipeline.
void client_audio_pipeline_process_duplex(client_audio_pipeline_t *pipeline, const float *render_samples, int render_count, const float *capture_samples, int capture_count, float *processed_output)
Process AEC3 inline in full-duplex callback.
client_audio_pipeline_flags_t client_audio_pipeline_get_flags(client_audio_pipeline_t *pipeline)
Get current component enable flags.
int client_audio_pipeline_jitter_margin(client_audio_pipeline_t *pipeline)
Get jitter buffer margin (buffered time in ms)
int client_audio_pipeline_get_playback_frame(client_audio_pipeline_t *pipeline, float *output, int num_samples)
Get audio frame from jitter buffer for playback callback.
client_audio_pipeline_config_t client_audio_pipeline_default_config(void)
Get default configuration.
void client_audio_pipeline_set_flags(client_audio_pipeline_t *pipeline, client_audio_pipeline_flags_t flags)
Set component enable flags.
int client_audio_pipeline_capture(client_audio_pipeline_t *pipeline, const float *input, int num_samples, uint8_t *opus_out, int max_opus_len)
Process captured audio and encode to Opus.
void client_audio_pipeline_destroy(client_audio_pipeline_t *pipeline)
Destroy a client audio pipeline.
void client_audio_pipeline_reset(client_audio_pipeline_t *pipeline)
Reset pipeline state.
Unified client-side audio processing pipeline.
#define CLIENT_AUDIO_PIPELINE_FRAME_MS
#define CLIENT_AUDIO_PIPELINE_FLAGS_MINIMAL
Minimal flags for testing (only codec, no processing)
#define CLIENT_AUDIO_PIPELINE_FLAGS_ALL
Default flags with all processing enabled.
#define CLIENT_AUDIO_PIPELINE_SAMPLE_RATE
void copy_buffer_with_gain(const float *src, float *dst, int count, float gain)
Copy buffer with gain scaling.
void noise_gate_process_buffer(noise_gate_t *gate, float *buffer, int num_samples)
Process a buffer of samples through noise gate.
void soft_clip_buffer(float *buffer, int num_samples, float threshold, float steepness)
Apply soft clipping to a buffer.
void noise_gate_init(noise_gate_t *gate, float sample_rate)
Initialize a noise gate.
float soft_clip(float sample, float threshold, float steepness)
Apply soft clipping to a sample.
void compressor_set_params(compressor_t *comp, float threshold_dB, float ratio, float attack_ms, float release_ms, float makeup_dB)
Set compressor parameters.
void compressor_init(compressor_t *comp, float sample_rate)
Initialize a compressor.
void highpass_filter_init(highpass_filter_t *filter, float cutoff_hz, float sample_rate)
Initialize a high-pass filter.
float smoothstep(float t)
Compute smoothstep interpolation.
void lowpass_filter_init(lowpass_filter_t *filter, float cutoff_hz, float sample_rate)
Initialize a low-pass filter.
void lowpass_filter_process_buffer(lowpass_filter_t *filter, float *buffer, int num_samples)
Process a buffer of samples through low-pass filter.
void highpass_filter_process_buffer(highpass_filter_t *filter, float *buffer, int num_samples)
Process a buffer of samples through high-pass filter.
void noise_gate_set_params(noise_gate_t *gate, float threshold, float attack_ms, float release_ms, float hysteresis)
Set noise gate parameters.
float compressor_process_sample(compressor_t *comp, float sidechain)
Process a single sample through compressor.
@ OPUS_APPLICATION_VOIP
Voice over IP (optimized for speech)
#define SAFE_CALLOC(count, size, cast)
#define log_warn(...)
Log a WARN message.
#define log_error(...)
Log an ERROR message.
#define log_info(...)
Log an INFO message.
#define log_warn_every(interval_us, fmt,...)
Rate-limited WARN logging.
📝 Logging API with multiple log levels and terminal output control
🔢 Mathematical Utility Functions
Multi-Source Audio Mixing and Processing System.
C++ wrapper for WebRTC AEC3 (opaque to C code)
~WebRTCAec3Wrapper()=default
webrtc::EchoCanceller3Config config
WebRTCAec3Wrapper()=default
std::unique_ptr< webrtc::EchoControl > aec3
Pipeline configuration parameters.
client_audio_pipeline_flags_t flags
Component enable/disable flags.
Client audio pipeline state.
client_audio_pipeline_config_t config
int capture_fadein_remaining
void * aec3_render_buffer
void * debug_wav_aec3_out
client_audio_pipeline_flags_t flags
noise_gate_t playback_noise_gate
highpass_filter_t highpass
void * aec3_capture_buffer
wav_writer_t * wav_writer_open(const char *filepath, int sample_rate, int channels)
Open WAV file for writing.
int wav_writer_write(wav_writer_t *writer, const float *samples, int num_samples)
Write audio samples to WAV file.
void wav_writer_close(wav_writer_t *writer)
Close WAV file and finalize header.
Simple WAV file writer for audio debugging.