34static unsigned int g_pa_init_refcount = 0;
45static int duplex_callback(
const void *inputBuffer,
void *outputBuffer,
unsigned long framesPerBuffer,
46 const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags,
51 const float *input = (
const float *)inputBuffer;
52 float *output = (
float *)outputBuffer;
58 SAFE_MEMSET(output, num_samples *
sizeof(
float), 0, num_samples *
sizeof(
float));
64 if (statusFlags != 0) {
65 if (statusFlags & paOutputUnderflow) {
68 if (statusFlags & paInputOverflow) {
74 size_t samples_read = 0;
78 float buffer_latency_ms = (float)buffer_samples / 48.0f;
79 log_debug_every(500000,
"LATENCY: Playback buffer %.1fms (%zu samples)", buffer_latency_ms, buffer_samples);
82 if (samples_read == 0) {
83 SAFE_MEMSET(output, num_samples *
sizeof(
float), 0, num_samples *
sizeof(
float));
86 SAFE_MEMSET(output, num_samples *
sizeof(
float), 0, num_samples *
sizeof(
float));
92 float *processed = (
float *)alloca(num_samples *
sizeof(
float));
96 float *aec3_render = output;
97 float *peeked_audio = NULL;
101 peeked_audio = (
float *)alloca(num_samples *
sizeof(
float));
106 if (peeked < num_samples) {
107 SAFE_MEMSET(peeked_audio + peeked, (num_samples - peeked) *
sizeof(
float), 0,
108 (num_samples - peeked) *
sizeof(
float));
110 aec3_render = peeked_audio;
113 log_debug_every(1000000,
"AEC3: Using peeked audio (%zu samples) - jitter buffer filling", peeked);
119 float render_rms = 0.0f;
121 float sum_squares = 0.0f;
122 for (
size_t i = 0; i < num_samples; i++) {
123 sum_squares += aec3_render[i] * aec3_render[i];
125 render_rms = sqrtf(sum_squares / (
float)num_samples);
127 log_info_every(1000000,
"AEC3 RENDER SIGNAL: RMS=%.6f, samples_read=%zu, using_peeked=%s, buffer_available=%zu",
128 render_rms, samples_read, (peeked_audio != NULL) ?
"YES" :
"NO",
165void resample_linear(
const float *src,
size_t src_samples,
float *dst,
size_t dst_samples,
double src_rate,
167 if (src_samples == 0 || dst_samples == 0) {
168 SAFE_MEMSET(dst, dst_samples *
sizeof(
float), 0, dst_samples *
sizeof(
float));
172 double ratio = src_rate / dst_rate;
174 for (
size_t i = 0; i < dst_samples; i++) {
175 double src_pos = (double)i * ratio;
176 size_t idx0 = (size_t)src_pos;
177 size_t idx1 = idx0 + 1;
178 double frac = src_pos - (double)idx0;
181 if (idx0 >= src_samples)
182 idx0 = src_samples - 1;
183 if (idx1 >= src_samples)
184 idx1 = src_samples - 1;
187 dst[i] = (float)((1.0 - frac) * src[idx0] + frac * src[idx1]);
197static int output_callback(
const void *inputBuffer,
void *outputBuffer,
unsigned long framesPerBuffer,
198 const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags,
204 float *output = (
float *)outputBuffer;
210 SAFE_MEMSET(output, num_output_samples *
sizeof(
float), 0, num_output_samples *
sizeof(
float));
215 if (statusFlags & paOutputUnderflow) {
222 bool needs_resample =
225 if (needs_resample) {
228 size_t num_src_samples = (size_t)((
double)num_output_samples * ratio) + 2;
231 float *src_buffer = (
float *)alloca(num_src_samples *
sizeof(
float));
234 if (samples_read == 0) {
235 SAFE_MEMSET(output, num_output_samples *
sizeof(
float), 0, num_output_samples *
sizeof(
float));
250 if (samples_read == 0) {
251 SAFE_MEMSET(output, num_output_samples *
sizeof(
float), 0, num_output_samples *
sizeof(
float));
260 SAFE_MEMSET(output, num_output_samples *
sizeof(
float), 0, num_output_samples *
sizeof(
float));
272static int input_callback(
const void *inputBuffer,
void *outputBuffer,
unsigned long framesPerBuffer,
273 const PaStreamCallbackTimeInfo *timeInfo, PaStreamCallbackFlags statusFlags,
void *userData) {
278 const float *input = (
const float *)inputBuffer;
286 if (statusFlags & paInputOverflow) {
294 bool needs_resample =
297 float *render = (
float *)alloca(num_samples *
sizeof(
float));
301 static float last_render[960];
302 static size_t last_render_count = 0;
303 static bool last_render_valid =
false;
305 if (needs_resample) {
309 size_t num_render_samples = (size_t)((
double)num_samples * ratio) + 2;
311 float *render_raw = (
float *)alloca(num_render_samples *
sizeof(
float));
314 if (render_read == 0) {
317 if (render_read > 0) {
318 log_debug_every(1000000,
"AEC3 separate: Using peeked render (%zu samples)", render_read);
322 if (render_read == 0 && last_render_valid) {
324 size_t copy_count = (last_render_count < num_samples) ? last_render_count : num_samples;
325 SAFE_MEMCPY(render, copy_count *
sizeof(
float), last_render, copy_count *
sizeof(
float));
326 if (copy_count < num_samples) {
327 SAFE_MEMSET(render + copy_count, (num_samples - copy_count) *
sizeof(
float), 0,
328 (num_samples - copy_count) *
sizeof(
float));
330 log_debug_every(1000000,
"AEC3 separate: Using cached last_render (%zu samples)", copy_count);
331 }
else if (render_read == 0) {
332 SAFE_MEMSET(render, num_samples *
sizeof(
float), 0, num_samples *
sizeof(
float));
337 size_t cache_count = (num_samples < 960) ? num_samples : 960;
338 SAFE_MEMCPY(last_render, cache_count *
sizeof(
float), render, cache_count *
sizeof(
float));
339 last_render_count = cache_count;
340 last_render_valid =
true;
346 if (render_samples == 0) {
349 if (render_samples > 0) {
350 log_debug_every(1000000,
"AEC3 separate: Using peeked render (%zu samples)", render_samples);
354 if (render_samples == 0 && last_render_valid) {
356 size_t copy_count = (last_render_count < num_samples) ? last_render_count : num_samples;
357 SAFE_MEMCPY(render, copy_count *
sizeof(
float), last_render, copy_count *
sizeof(
float));
358 if (copy_count < num_samples) {
359 SAFE_MEMSET(render + copy_count, (num_samples - copy_count) *
sizeof(
float), 0,
360 (num_samples - copy_count) *
sizeof(
float));
362 log_debug_every(1000000,
"AEC3 separate: Using cached last_render (%zu samples)", copy_count);
363 }
else if (render_samples < num_samples) {
365 SAFE_MEMSET(render + render_samples, (num_samples - render_samples) *
sizeof(
float), 0,
366 (num_samples - render_samples) *
sizeof(
float));
370 if (render_samples > 0) {
371 size_t cache_count = (num_samples < 960) ? num_samples : 960;
372 SAFE_MEMCPY(last_render, cache_count *
sizeof(
float), render, cache_count *
sizeof(
float));
373 last_render_count = cache_count;
374 last_render_valid =
true;
379 float *processed = (
float *)alloca(num_samples *
sizeof(
float));
382 input, (
int)num_samples,
432 return audio_ring_buffer_create_internal(
true);
436 return audio_ring_buffer_create_internal(
false);
462 if (!rb || !data || samples <= 0)
474 unsigned int write_idx = atomic_load_explicit(&rb->
write_index, memory_order_relaxed);
475 unsigned int read_idx = atomic_load_explicit(&rb->
read_index, memory_order_acquire);
479 if (write_idx >= read_idx) {
480 buffer_level = (int)(write_idx - read_idx);
498 atomic_store_explicit(&rb->
read_index, new_read_idx, memory_order_release);
501 "Audio buffer high water mark exceeded: dropping %d OLD samples to reduce latency "
502 "(buffer was %d, target %d)",
506 read_idx = new_read_idx;
508 if (buffer_level < 0)
515 int samples_to_write = samples;
516 if (samples > available) {
518 int samples_dropped = samples - available;
519 samples_to_write = available;
525 if (samples_to_write > 0) {
528 if (samples_to_write <= remaining) {
530 SAFE_MEMCPY(&rb->
data[write_idx], samples_to_write *
sizeof(
float), data, samples_to_write *
sizeof(
float));
533 SAFE_MEMCPY(&rb->
data[write_idx], remaining *
sizeof(
float), data, remaining *
sizeof(
float));
534 SAFE_MEMCPY(&rb->
data[0], (samples_to_write - remaining) *
sizeof(
float), &data[remaining],
535 (samples_to_write - remaining) *
sizeof(
float));
541 atomic_store_explicit(&rb->
write_index, new_write_idx, memory_order_release);
550 if (!rb || !data || samples <= 0) {
558 unsigned int write_idx = atomic_load_explicit(&rb->
write_index, memory_order_acquire);
559 unsigned int read_idx = atomic_load_explicit(&rb->
read_index, memory_order_relaxed);
563 if (write_idx >= read_idx) {
564 available = write_idx - read_idx;
572 bool fade_in = atomic_load_explicit(&rb->
crossfade_fade_in, memory_order_acquire);
579 if (!fade_in && crossfade_remaining > 0) {
582 size_t fade_samples = (samples < (size_t)crossfade_remaining) ? samples : (size_t)crossfade_remaining;
584 for (
size_t i = 0; i < fade_samples; i++) {
586 data[i] = last * fade_factor;
589 for (
size_t i = fade_samples; i < samples; i++) {
594 memory_order_release);
595 if (crossfade_remaining - (
int)fade_samples <= 0) {
607 log_info(
"Jitter buffer filled (%zu samples), starting playback with fade-in", available);
609 jitter_filled =
true;
614 log_debug_every(1000000,
"Jitter buffer filling: %zu/%d samples (%.1f%%)", available,
621 static unsigned int health_log_counter = 0;
622 if (++health_log_counter % 250 == 0) {
623 unsigned int underruns = atomic_load_explicit(&rb->
underrun_count, memory_order_relaxed);
635 unsigned int underrun_count = atomic_fetch_add_explicit(&rb->
underrun_count, 1, memory_order_relaxed) + 1;
637 "Audio buffer low #%u: only %zu samples available (low water mark: %d), padding with silence",
642 size_t to_read = (samples > available) ? available : samples;
647 if (to_read <= remaining) {
649 SAFE_MEMCPY(data, to_read *
sizeof(
float), &rb->
data[read_idx], to_read *
sizeof(
float));
652 SAFE_MEMCPY(data, remaining *
sizeof(
float), &rb->
data[read_idx], remaining *
sizeof(
float));
653 SAFE_MEMCPY(&data[remaining], (to_read - remaining) *
sizeof(
float), &rb->
data[0],
654 (to_read - remaining) *
sizeof(
float));
660 atomic_store_explicit(&rb->
read_index, new_read_idx, memory_order_release);
663 if (fade_in && crossfade_remaining > 0) {
665 size_t fade_samples = (to_read < (size_t)crossfade_remaining) ? to_read : (size_t)crossfade_remaining;
667 for (
size_t i = 0; i < fade_samples; i++) {
669 data[i] *= fade_factor;
672 int new_crossfade_remaining = crossfade_remaining - (int)fade_samples;
674 if (new_crossfade_remaining <= 0) {
691 if (to_read < samples) {
692 size_t silence_samples = samples - to_read;
693 SAFE_MEMSET(data + to_read, silence_samples *
sizeof(
float), 0, silence_samples *
sizeof(
float));
711 if (!rb || !data || samples <= 0) {
716 unsigned int write_idx = atomic_load_explicit(&rb->
write_index, memory_order_acquire);
717 unsigned int read_idx = atomic_load_explicit(&rb->
read_index, memory_order_relaxed);
721 if (write_idx >= read_idx) {
722 available = write_idx - read_idx;
727 size_t to_peek = (samples > available) ? available : samples;
736 SAFE_MEMCPY(data, first_chunk *
sizeof(
float), rb->
data + read_idx, first_chunk *
sizeof(
float));
738 if (first_chunk < to_peek) {
740 size_t second_chunk = to_peek - first_chunk;
741 SAFE_MEMCPY(data + first_chunk, second_chunk *
sizeof(
float), rb->
data, second_chunk *
sizeof(
float));
754 unsigned int write_idx = atomic_load_explicit(&rb->
write_index, memory_order_acquire);
755 unsigned int read_idx = atomic_load_explicit(&rb->
read_index, memory_order_relaxed);
757 if (write_idx >= read_idx) {
758 return write_idx - read_idx;
785 static_mutex_lock(&g_pa_refcount_mutex);
786 if (g_pa_init_refcount == 0) {
789 int stderr_fd_backup = -1;
792 stderr_fd_backup = dup(STDERR_FILENO);
794 if (stderr_fd_backup >= 0 && devnull_fd >= 0) {
795 dup2(devnull_fd, STDERR_FILENO);
799 PaError err = Pa_Initialize();
803 if (stderr_fd_backup >= 0) {
804 dup2(stderr_fd_backup, STDERR_FILENO);
805 close(stderr_fd_backup);
807 if (devnull_fd >= 0) {
812 if (err != paNoError) {
813 static_mutex_unlock(&g_pa_refcount_mutex);
819 log_debug(
"PortAudio initialized successfully (probe warnings suppressed)");
821 g_pa_init_refcount++;
822 static_mutex_unlock(&g_pa_refcount_mutex);
825 int numDevices = Pa_GetDeviceCount();
826 const size_t max_device_info_size = 4096;
827 char device_names[max_device_info_size];
829 for (
int i = 0; i < numDevices && offset < (int)
sizeof(device_names) - 256; i++) {
830 const PaDeviceInfo *deviceInfo = Pa_GetDeviceInfo(i);
831 if (deviceInfo && deviceInfo->name) {
832 int remaining =
sizeof(device_names) - offset;
836 int len = snprintf(&device_names[offset], remaining,
837 "\n Device %d: %s (inputs=%d, outputs=%d, sample_rate=%.0f Hz)%s%s", i, deviceInfo->name,
838 deviceInfo->maxInputChannels, deviceInfo->maxOutputChannels, deviceInfo->defaultSampleRate,
839 (i == Pa_GetDefaultInputDevice()) ?
" [DEFAULT INPUT]" :
"",
840 (i == Pa_GetDefaultOutputDevice()) ?
" [DEFAULT OUTPUT]" :
"");
841 if (len > 0 && len < remaining) {
849 device_names[offset] =
'\0';
851 log_debug(
"PortAudio found %d audio devices:%s", numDevices, device_names);
853 log_warn(
"PortAudio found no audio devices");
860 static_mutex_lock(&g_pa_refcount_mutex);
861 if (g_pa_init_refcount > 0) {
862 g_pa_init_refcount--;
863 if (g_pa_init_refcount == 0) {
867 static_mutex_unlock(&g_pa_refcount_mutex);
876 static_mutex_lock(&g_pa_refcount_mutex);
877 if (g_pa_init_refcount > 0) {
878 g_pa_init_refcount--;
879 if (g_pa_init_refcount == 0) {
883 static_mutex_unlock(&g_pa_refcount_mutex);
890 log_info(
"Audio system initialized successfully");
910 static_mutex_lock(&g_pa_refcount_mutex);
911 if (g_pa_init_refcount > 0) {
912 g_pa_init_refcount--;
913 if (g_pa_init_refcount == 0) {
917 static_mutex_unlock(&g_pa_refcount_mutex);
947 PaStreamParameters inputParams;
949 inputParams.device =
GET_OPTION(microphone_index);
951 inputParams.device = Pa_GetDefaultInputDevice();
954 if (inputParams.device == paNoDevice) {
959 const PaDeviceInfo *inputInfo = Pa_GetDeviceInfo(inputParams.device);
966 inputParams.sampleFormat = paFloat32;
967 inputParams.suggestedLatency = inputInfo->defaultLowInputLatency;
968 inputParams.hostApiSpecificStreamInfo = NULL;
971 PaStreamParameters outputParams;
973 outputParams.device =
GET_OPTION(speakers_index);
975 outputParams.device = Pa_GetDefaultOutputDevice();
978 if (outputParams.device == paNoDevice) {
983 const PaDeviceInfo *outputInfo = Pa_GetDeviceInfo(outputParams.device);
990 outputParams.sampleFormat = paFloat32;
991 outputParams.suggestedLatency = outputInfo->defaultLowOutputLatency;
992 outputParams.hostApiSpecificStreamInfo = NULL;
999 log_info(
" Input: %s (%.0f Hz)", inputInfo->name, inputInfo->defaultSampleRate);
1000 log_info(
" Output: %s (%.0f Hz)", outputInfo->name, outputInfo->defaultSampleRate);
1003 bool rates_differ = (inputInfo->defaultSampleRate != outputInfo->defaultSampleRate);
1004 bool try_separate = rates_differ;
1005 PaError err = paNoError;
1007 if (!try_separate) {
1010 paClipOff, duplex_callback, ctx);
1012 if (err == paNoError) {
1014 if (err != paNoError) {
1017 log_warn(
"Full-duplex stream failed to start: %s", Pa_GetErrorText(err));
1018 try_separate =
true;
1021 log_warn(
"Full-duplex stream failed to open: %s", Pa_GetErrorText(err));
1022 try_separate =
true;
1028 log_info(
"Using separate input/output streams (sample rates differ: %.0f vs %.0f Hz)", inputInfo->defaultSampleRate,
1029 outputInfo->defaultSampleRate);
1031 outputInfo->defaultSampleRate);
1044 err = Pa_OpenStream(&ctx->
output_stream, NULL, &outputParams, outputInfo->defaultSampleRate,
1046 if (err != paNoError) {
1056 input_callback, ctx);
1057 if (err != paNoError) {
1068 if (err != paNoError) {
1080 if (err != paNoError) {
1093 log_info(
"Separate streams started successfully");
1096 log_info(
"Full-duplex stream started (single callback, perfect AEC3 timing)");
1157 if (!ctx || !ctx->
initialized || !buffer || num_samples <= 0) {
1168 if (!ctx || !ctx->
initialized || !buffer || num_samples <= 0) {
1186 if (!out_devices || !out_count) {
1190 *out_devices = NULL;
1194 static_mutex_lock(&g_pa_refcount_mutex);
1195 bool pa_was_initialized = (g_pa_init_refcount > 0);
1198 if (!pa_was_initialized) {
1201 int stderr_fd_backup = -1;
1202 int devnull_fd = -1;
1204 stderr_fd_backup = dup(STDERR_FILENO);
1206 if (stderr_fd_backup >= 0 && devnull_fd >= 0) {
1207 dup2(devnull_fd, STDERR_FILENO);
1211 PaError err = Pa_Initialize();
1215 if (stderr_fd_backup >= 0) {
1216 dup2(stderr_fd_backup, STDERR_FILENO);
1217 close(stderr_fd_backup);
1219 if (devnull_fd >= 0) {
1224 if (err != paNoError) {
1225 static_mutex_unlock(&g_pa_refcount_mutex);
1228 g_pa_init_refcount = 1;
1232 g_pa_init_refcount++;
1234 static_mutex_unlock(&g_pa_refcount_mutex);
1236 int num_devices = Pa_GetDeviceCount();
1237 if (num_devices < 0) {
1239 static_mutex_lock(&g_pa_refcount_mutex);
1240 if (g_pa_init_refcount > 0) {
1241 g_pa_init_refcount--;
1242 if (!pa_was_initialized && g_pa_init_refcount == 0) {
1246 static_mutex_unlock(&g_pa_refcount_mutex);
1250 if (num_devices == 0) {
1252 static_mutex_lock(&g_pa_refcount_mutex);
1253 if (g_pa_init_refcount > 0) {
1254 g_pa_init_refcount--;
1255 if (!pa_was_initialized && g_pa_init_refcount == 0) {
1259 static_mutex_unlock(&g_pa_refcount_mutex);
1264 PaDeviceIndex default_input = Pa_GetDefaultInputDevice();
1265 PaDeviceIndex default_output = Pa_GetDefaultOutputDevice();
1268 unsigned int device_count = 0;
1269 for (
int i = 0; i < num_devices; i++) {
1270 const PaDeviceInfo *info = Pa_GetDeviceInfo(i);
1272 bool matches = list_inputs ? (info->maxInputChannels > 0) : (info->maxOutputChannels > 0);
1279 if (device_count == 0) {
1281 static_mutex_lock(&g_pa_refcount_mutex);
1282 if (g_pa_init_refcount > 0) {
1283 g_pa_init_refcount--;
1284 if (!pa_was_initialized && g_pa_init_refcount == 0) {
1288 static_mutex_unlock(&g_pa_refcount_mutex);
1296 static_mutex_lock(&g_pa_refcount_mutex);
1297 if (g_pa_init_refcount > 0) {
1298 g_pa_init_refcount--;
1299 if (!pa_was_initialized && g_pa_init_refcount == 0) {
1303 static_mutex_unlock(&g_pa_refcount_mutex);
1308 unsigned int idx = 0;
1309 for (
int i = 0; i < num_devices && idx < device_count; i++) {
1310 const PaDeviceInfo *info = Pa_GetDeviceInfo(i);
1314 bool match = list_inputs ? (info->maxInputChannels > 0) : (info->maxOutputChannels > 0);
1318 devices[idx].
index = i;
1333 static_mutex_lock(&g_pa_refcount_mutex);
1334 if (g_pa_init_refcount > 0) {
1335 g_pa_init_refcount--;
1336 if (!pa_was_initialized && g_pa_init_refcount == 0) {
1340 static_mutex_unlock(&g_pa_refcount_mutex);
1342 *out_devices = devices;
1348 return audio_list_devices_internal(out_devices, out_count,
true);
1352 return audio_list_devices_internal(out_devices, out_count,
false);
1360 if (!samples_ptr || !out_samples || total_samples == 0) {
1364 for (
uint32_t i = 0; i < total_samples; i++) {
1367 memcpy(&network_sample, samples_ptr + i *
sizeof(
uint32_t),
sizeof(
uint32_t));
1369 out_samples[i] = (float)scaled / 2147483647.0f;
1379 log_info(
"✓ Audio thread real-time priority set successfully");
1457 static const uint32_t supported_rates[] = {
1468 const size_t rate_count =
sizeof(supported_rates) /
sizeof(supported_rates[0]);
1469 for (
size_t i = 0; i < rate_count; i++) {
1470 if (sample_rate == supported_rates[i]) {
⚠️‼️ Error and/or exit() when things go bad.
🗃️ Lock-Free Unified Memory Buffer Pool with Lazy Allocation
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.
Unified client-side audio processing pipeline.
🔄 Network byte order conversion helpers
#define NET_TO_HOST_U32(val)
size_t audio_ring_buffer_read(audio_ring_buffer_t *rb, float *data, size_t samples)
Read audio samples from ring buffer.
void audio_free_device_list(audio_device_info_t *devices)
Free device list allocated by audio_list_input_devices/audio_list_output_devices.
asciichat_error_t audio_ring_buffer_write(audio_ring_buffer_t *rb, const float *data, int samples)
Write audio samples to ring buffer.
#define AUDIO_SAMPLE_RATE
Audio sample rate (48kHz professional quality, Opus-compatible)
void audio_ring_buffer_destroy(audio_ring_buffer_t *rb)
Destroy an audio ring buffer.
asciichat_error_t audio_init(audio_context_t *ctx)
Initialize audio context and PortAudio.
asciichat_error_t audio_set_realtime_priority(void)
Request real-time priority for current thread.
audio_ring_buffer_t * audio_ring_buffer_create(void)
Create a new audio ring buffer (for playback with jitter buffering)
bool audio_is_supported_sample_rate(uint32_t sample_rate)
Check if a sample rate is a standard/supported rate.
#define AUDIO_FRAMES_PER_BUFFER
Audio frames per buffer (480 = 10ms at 48kHz, matches WebRTC AEC3 frame size)
size_t audio_ring_buffer_available_read(audio_ring_buffer_t *rb)
Get number of samples available for reading.
asciichat_error_t audio_start_duplex(audio_context_t *ctx)
Start full-duplex audio (simultaneous capture and playback)
asciichat_error_t audio_parse_batch_header(const void *data, size_t len, audio_batch_info_t *out_batch)
Parse an audio batch packet header from raw packet data.
void audio_destroy(audio_context_t *ctx)
Destroy audio context and clean up resources.
asciichat_error_t audio_stop_duplex(audio_context_t *ctx)
Stop full-duplex audio.
asciichat_error_t audio_write_samples(audio_context_t *ctx, const float *buffer, int num_samples)
Write audio samples to playback buffer.
#define AUDIO_CHANNELS
Number of audio channels (1 = mono)
void audio_set_pipeline(audio_context_t *ctx, void *pipeline)
Set audio pipeline for echo cancellation.
audio_ring_buffer_t * audio_ring_buffer_create_for_capture(void)
Create a new audio ring buffer for capture (without jitter buffering)
size_t audio_ring_buffer_peek(audio_ring_buffer_t *rb, float *data, size_t samples)
Peek at available samples without consuming them (for AEC3 render signal)
asciichat_error_t audio_validate_batch_params(const audio_batch_info_t *batch)
Validate audio batch parameters for sanity.
asciichat_error_t audio_list_input_devices(audio_device_info_t **out_devices, unsigned int *out_count)
List available audio input devices (microphones)
asciichat_error_t audio_dequantize_samples(const uint8_t *samples_ptr, uint32_t total_samples, float *out_samples)
Dequantize network audio samples from int32 to float.
asciichat_error_t audio_list_output_devices(audio_device_info_t **out_devices, unsigned int *out_count)
List available audio output devices (speakers)
void audio_ring_buffer_clear(audio_ring_buffer_t *rb)
Clear all audio samples from ring buffer.
asciichat_error_t audio_read_samples(audio_context_t *ctx, float *buffer, int num_samples)
Read captured audio samples from capture buffer.
void resample_linear(const float *src, size_t src_samples, float *dst, size_t dst_samples, double src_rate, double dst_rate)
Resample audio using linear interpolation.
size_t audio_ring_buffer_available_write(audio_ring_buffer_t *rb)
Get number of sample slots available for writing.
void buffer_pool_free(buffer_pool_t *pool, void *data, size_t size)
Free a buffer back to the pool (lock-free)
void * buffer_pool_alloc(buffer_pool_t *pool, size_t size)
Allocate a buffer from the pool (lock-free fast path)
#define SAFE_STRNCPY(dst, src, size)
#define SAFE_MEMSET(dest, dest_size, ch, count)
#define SAFE_CALLOC(count, size, cast)
#define SAFE_MEMCPY(dest, dest_size, src, count)
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
#define LOG_RATE_FAST
Log rate limit: 1 second (1,000,000 microseconds)
#define log_warn(...)
Log a WARN message.
#define log_info_every(interval_us, fmt,...)
Rate-limited INFO logging.
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
#define log_info(...)
Log an INFO message.
#define log_debug(...)
Log a DEBUG message.
#define log_warn_every(interval_us, fmt,...)
Rate-limited WARN logging.
uint32_t channels
Number of audio channels (1=mono, 2=stereo)
uint32_t batch_count
Number of audio chunks in this batch (usually AUDIO_BATCH_COUNT = 32)
uint32_t sample_rate
Sample rate in Hz (e.g., 44100, 48000)
uint32_t total_samples
Total audio samples across all chunks (typically 8192)
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
struct audio_ring_buffer audio_ring_buffer_t
Audio ring buffer for real-time audio streaming.
#define AUDIO_JITTER_HIGH_WATER_MARK
High water mark - drop OLD samples when buffer exceeds this.
#define AUDIO_JITTER_BUFFER_THRESHOLD
Jitter buffer threshold - samples needed before starting playback.
#define AUDIO_RING_BUFFER_SIZE
Audio ring buffer size in samples (192000 samples = 4 seconds @ 48kHz)
#define AUDIO_JITTER_LOW_WATER_MARK
Low water mark - warn when available drops below this.
#define AUDIO_JITTER_TARGET_LEVEL
Target buffer level after dropping old samples.
#define AUDIO_CROSSFADE_SAMPLES
Crossfade duration in samples for smooth underrun recovery.
Platform initialization and static synchronization helpers.
🔊 Audio Capture and Playback Interface for ascii-chat
#define AUDIO_DEVICE_NAME_MAX
Maximum length of audio device name.
📝 Logging API with multiple log levels and terminal output control
🔢 Mathematical Utility Functions
⚙️ Command-line options parsing and configuration management for ascii-chat
Packet protocol implementation with encryption and compression support.
Parsed audio batch packet header information.
uint32_t batch_count
Number of audio frames in this batch.
uint32_t channels
Number of channels (1=mono, 2=stereo)
uint32_t sample_rate
Sample rate in Hz (e.g., 48000)
uint32_t total_samples
Total number of samples across all frames.
Audio batch packet structure (Packet Type 28)
Audio context for full-duplex capture and playback.
bool initialized
True if context has been initialized.
double input_device_rate
Native sample rate of input device.
audio_ring_buffer_t * playback_buffer
Ring buffer for decoded audio from network.
PaStream * input_stream
Separate input stream (when full-duplex unavailable)
bool separate_streams
True if using separate input/output streams.
double sample_rate
Actual sample rate of streams (48kHz)
audio_ring_buffer_t * capture_buffer
Ring buffer for processed capture (after AEC3) for encoder thread.
void * audio_pipeline
Client audio pipeline for AEC3 echo cancellation (opaque pointer)
bool running
True if duplex stream is active.
audio_ring_buffer_t * render_buffer
Ring buffer for render reference (separate streams mode)
double output_device_rate
Native sample rate of output device.
mutex_t state_mutex
Mutex protecting context state.
_Atomic bool shutting_down
True when shutdown started - callback outputs silence.
PaStream * duplex_stream
PortAudio full-duplex stream (simultaneous input+output)
PaStream * output_stream
Separate output stream (when full-duplex unavailable)
Audio device information structure.
int max_output_channels
Maximum output channels (0 if input only)
int max_input_channels
Maximum input channels (0 if output only)
int index
PortAudio device index.
double default_sample_rate
Default sample rate in Hz.
bool is_default_output
True if this is the default output device.
bool is_default_input
True if this is the default input device.
Audio ring buffer for real-time audio streaming.
atomic_uint read_index
Read index (consumer position) - LOCK-FREE with atomic operations.
float last_sample
Last sample value for smooth fade-out during underrun - NOT atomic (only written by reader)
atomic_bool jitter_buffer_filled
True after initial jitter buffer fill.
atomic_uint underrun_count
Count of underrun events for diagnostics.
atomic_int crossfade_samples_remaining
Samples remaining in crossfade (0 = no crossfade active)
mutex_t mutex
Mutex for SLOW PATH only (clear/destroy operations, not regular read/write)
bool jitter_buffer_enabled
Whether jitter buffering is enabled (false for capture, true for playback)
atomic_uint write_index
Write index (producer position) - LOCK-FREE with atomic operations.
float data[192000]
Audio sample data buffer.
atomic_bool crossfade_fade_in
True if we're fading in (recovering from underrun)
Client audio pipeline state.
Static mutex structure for global mutexes requiring static initialization.