75#include <ascii-chat/audio/analysis.h>
79#include <ascii-chat/util/fps.h>
80#include <ascii-chat/util/thread.h>
81#include <ascii-chat/util/time.h>
83#include <ascii-chat/audio/audio.h>
84#include <ascii-chat/audio/client_audio_pipeline.h>
85#include <ascii-chat/audio/wav_writer.h>
86#include <ascii-chat/common.h>
87#include <ascii-chat/options/options.h>
88#include <ascii-chat/options/rcu.h>
89#include <ascii-chat/platform/system.h>
95#include <ascii-chat/platform/abstraction.h>
96#include <ascii-chat/platform/init.h>
97#include <ascii-chat/thread_pool.h>
112static audio_context_t g_audio_context = {0};
126static client_audio_pipeline_t *g_audio_pipeline = NULL;
133static wav_writer_t *g_wav_capture_raw = NULL;
136static wav_writer_t *g_wav_capture_processed = NULL;
139static wav_writer_t *g_wav_playback_received = NULL;
154static asciichat_thread_t g_audio_capture_thread;
164static bool g_audio_capture_thread_created =
false;
174static atomic_bool g_audio_capture_thread_exited =
false;
187 uint8_t data[8 * 4000];
189 uint16_t frame_sizes[8];
194#define AUDIO_SEND_QUEUE_SIZE 32
196static int g_audio_send_queue_head = 0;
197static int g_audio_send_queue_tail = 0;
198static mutex_t g_audio_send_queue_mutex;
199static cond_t g_audio_send_queue_cond;
200static bool g_audio_send_queue_initialized =
false;
201static static_mutex_t g_audio_send_queue_init_mutex = STATIC_MUTEX_INIT;
204static bool g_audio_sender_thread_created =
false;
205static atomic_bool g_audio_sender_should_exit =
false;
218static int audio_queue_packet(
const uint8_t *opus_data,
size_t opus_size,
const uint16_t *frame_sizes,
220 if (!g_audio_send_queue_initialized || !opus_data || opus_size == 0) {
224 mutex_lock(&g_audio_send_queue_mutex);
228 if (next_head == g_audio_send_queue_tail) {
229 mutex_unlock(&g_audio_send_queue_mutex);
230 log_warn_every(LOG_RATE_FAST,
"Audio send queue full, dropping packet");
236 if (opus_size <=
sizeof(packet->
data)) {
237 memcpy(packet->
data, opus_data, opus_size);
238 packet->
size = opus_size;
240 for (
int i = 0; i < frame_count && i < 8; i++) {
243 g_audio_send_queue_head = next_head;
247 cond_signal(&g_audio_send_queue_cond);
248 mutex_unlock(&g_audio_send_queue_mutex);
259static void *audio_sender_thread_func(
void *arg) {
261 log_debug(
"Audio sender thread started");
268 static int send_count = 0;
270 while (!atomic_load(&g_audio_sender_should_exit)) {
271 mutex_lock(&g_audio_send_queue_mutex);
274 while (g_audio_send_queue_head == g_audio_send_queue_tail && !atomic_load(&g_audio_sender_should_exit)) {
275 cond_wait(&g_audio_send_queue_cond, &g_audio_send_queue_mutex);
278 if (atomic_load(&g_audio_sender_should_exit)) {
279 mutex_unlock(&g_audio_send_queue_mutex);
287 mutex_unlock(&g_audio_send_queue_mutex);
290 START_TIMER(
"network_send_audio");
291 asciichat_error_t send_result =
293 double send_time_ns = STOP_TIMER(
"network_send_audio");
296 if (send_result < 0) {
297 log_debug_every(LOG_RATE_VERY_FAST,
"Failed to send audio packet");
298 }
else if (send_count % 50 == 0) {
299 char duration_str[32];
301 log_debug(
"Audio network send #%d: %zu bytes (%d frames) in %s", send_count, packet.
size, packet.
frame_count,
306 log_debug(
"Audio sender thread exiting");
320static void audio_sender_init(
void) {
321 static_mutex_lock(&g_audio_send_queue_init_mutex);
324 if (g_audio_send_queue_initialized) {
325 static_mutex_unlock(&g_audio_send_queue_init_mutex);
331 cond_init(&g_audio_send_queue_cond);
332 g_audio_send_queue_head = 0;
333 g_audio_send_queue_tail = 0;
334 g_audio_send_queue_initialized =
true;
335 atomic_store(&g_audio_sender_should_exit,
false);
337 static_mutex_unlock(&g_audio_send_queue_init_mutex);
341 g_audio_sender_thread_created =
true;
342 log_debug(
"Audio sender thread created");
344 log_error(
"Failed to spawn audio sender thread in worker pool");
345 LOG_ERRNO_IF_SET(
"Audio sender thread creation failed");
352static void audio_sender_cleanup(
void) {
353 if (!g_audio_send_queue_initialized) {
358 atomic_store(&g_audio_sender_should_exit,
true);
359 mutex_lock(&g_audio_send_queue_mutex);
360 cond_signal(&g_audio_send_queue_cond);
361 mutex_unlock(&g_audio_send_queue_mutex);
364 if (THREAD_IS_CREATED(g_audio_sender_thread_created)) {
365 g_audio_sender_thread_created =
false;
366 log_debug(
"Audio sender thread will be joined by thread pool");
370 cond_destroy(&g_audio_send_queue_cond);
371 g_audio_send_queue_initialized =
false;
379#define AUDIO_VOLUME_BOOST 1.0f
401 if (!samples || num_samples <= 0) {
402 SET_ERRNO(ERROR_INVALID_PARAM,
"Invalid audio samples: samples=%p, num_samples=%d", (
void *)samples, num_samples);
406 if (!GET_OPTION(audio_enabled)) {
407 log_warn_every(NS_PER_MS_INT,
"Received audio samples but audio is disabled");
412 if (num_samples > AUDIO_BATCH_SAMPLES) {
413 log_warn(
"Audio packet too large: %d samples (max %d)", num_samples, AUDIO_BATCH_SAMPLES);
418 float sum_squares = 0.0f;
419 for (
int i = 0; i < num_samples; i++) {
420 sum_squares += samples[i] * samples[i];
422 float received_rms = sqrtf(sum_squares / num_samples);
425 if (g_wav_playback_received) {
430 if (GET_OPTION(audio_analysis_enabled)) {
431 for (
int i = 0; i < num_samples; i++) {
437 float audio_buffer[AUDIO_BATCH_SAMPLES];
438 memcpy(audio_buffer, samples, (
size_t)num_samples *
sizeof(
float));
441 static int recv_count = 0;
443 if (recv_count <= 10 || recv_count % 50 == 0) {
445 for (
int i = 0; i < num_samples; i++) {
446 float abs_val = fabsf(samples[i]);
450 log_debug(
"CLIENT AUDIO RECV #%d: %d samples, RMS=%.6f, Peak=%.6f, first4=[%.4f,%.4f,%.4f,%.4f]", recv_count,
451 num_samples, received_rms, peak, num_samples > 0 ? samples[0] : 0.0f, num_samples > 1 ? samples[1] : 0.0f,
452 num_samples > 2 ? samples[2] : 0.0f, num_samples > 3 ? samples[3] : 0.0f);
462 if (g_audio_context.playback_buffer) {
464 float buffer_latency_ms = (float)buffer_samples / 48.0f;
465 log_dev_every(500 * US_PER_MS_INT,
"LATENCY: Client playback buffer after recv: %.1fms (%zu samples)",
466 buffer_latency_ms, buffer_samples);
470 log_debug(
"Processed %d received audio samples", num_samples);
499static void *audio_capture_thread_func(
void *arg) {
502 log_debug(
"Audio capture thread started");
510 static fps_t fps_tracker = {0};
511 static bool fps_tracker_initialized =
false;
512 if (!fps_tracker_initialized) {
513 fps_init(&fps_tracker, 50,
"AUDIO_TX");
514 fps_tracker_initialized =
true;
518 static double total_loop_ns = 0;
519 static double total_read_ns = 0;
520 static double total_encode_ns = 0;
521 static double total_queue_ns = 0;
522 static double max_loop_ns = 0;
523 static double max_read_ns = 0;
524 static double max_encode_ns = 0;
525 static double max_queue_ns = 0;
526 static uint64_t timing_loop_count = 0;
529#define OPUS_FRAME_SAMPLES 960
530#define OPUS_MAX_PACKET_SIZE 500
535#define CAPTURE_READ_SIZE (OPUS_FRAME_SAMPLES * 4)
538 static bool wav_dumpers_initialized =
false;
542 g_wav_capture_raw =
wav_writer_open(
"/tmp/audio_capture_raw.wav", AUDIO_SAMPLE_RATE, 1);
543 g_wav_capture_processed =
wav_writer_open(
"/tmp/audio_capture_processed.wav", AUDIO_SAMPLE_RATE, 1);
544 log_debug(
"Audio debugging enabled: dumping to /tmp/audio_capture_*.wav");
545 wav_dumpers_initialized =
true;
550 int opus_frame_samples_collected = 0;
553#define MAX_BATCH_FRAMES 8
554#define BATCH_TIMEOUT_NS (40LL * NS_PER_MS_INT)
557 static int batch_frame_count = 0;
558 static size_t batch_total_size = 0;
559 static uint64_t batch_start_time_ns = 0;
560 static bool batch_has_data =
false;
563 START_TIMER(
"audio_capture_loop_iteration");
567 STOP_TIMER(
"audio_capture_loop_iteration");
573 if (!g_audio_pipeline) {
574 STOP_TIMER(
"audio_capture_loop_iteration");
581 if (available <= 0) {
583 if (batch_has_data && batch_frame_count > 0) {
588 long elapsed_ms = (long)time_ns_to_ms(elapsed_ns);
589 log_debug_every(LOG_RATE_FAST,
"Idle timeout flush: %d frames (%zu bytes) after %ld ms", batch_frame_count,
590 batch_total_size, elapsed_ms);
591 (void)audio_queue_packet(batch_buffer, batch_total_size, batch_frame_sizes, batch_frame_count);
592 batch_frame_count = 0;
593 batch_total_size = 0;
594 batch_has_data =
false;
601 STOP_TIMER(
"audio_capture_loop_iteration");
610 START_TIMER(
"audio_read_samples");
611 asciichat_error_t read_result =
audio_read_samples(&g_audio_context, audio_buffer, to_read);
612 double read_time_ns = STOP_TIMER(
"audio_read_samples");
614 total_read_ns += read_time_ns;
615 if (read_time_ns > max_read_ns)
616 max_read_ns = read_time_ns;
618 if (read_result != ASCIICHAT_OK) {
619 log_error(
"Failed to read audio samples from ring buffer");
620 STOP_TIMER(
"audio_capture_loop_iteration");
625 int samples_read = to_read;
628 static int total_reads = 0;
630 if (total_reads % 10 == 0) {
631 log_debug(
"Audio capture loop iteration #%d: available=%d, samples_read=%d", total_reads, available,
635 if (samples_read > 0) {
639 for (
int i = 0; i < samples_read; i++) {
640 float abs_val = fabsf(audio_buffer[i]);
648 float gain = 0.99f / peak;
649 for (
int i = 0; i < samples_read; i++) {
650 audio_buffer[i] *= gain;
652 static int norm_count = 0;
654 if (norm_count <= 5 || norm_count % 100 == 0) {
655 log_debug(
"Input normalization #%d: peak=%.4f, gain=%.4f", norm_count, peak, gain);
660 if (g_wav_capture_raw) {
665 static int read_count = 0;
667 float sum_squares = 0.0f;
668 for (
int i = 0; i < samples_read && i < 10; i++) {
669 sum_squares += audio_buffer[i] * audio_buffer[i];
671 float rms = sqrtf(sum_squares / (samples_read > 10 ? 10 : samples_read));
672 if (read_count <= 5 || read_count % 20 == 0) {
673 log_debug(
"Audio capture read #%d: available=%d, samples_read=%d, first=[%.6f,%.6f,%.6f], RMS=%.6f", read_count,
674 available, samples_read, samples_read > 0 ? audio_buffer[0] : 0.0f,
675 samples_read > 1 ? audio_buffer[1] : 0.0f, samples_read > 2 ? audio_buffer[2] : 0.0f, rms);
679 if (GET_OPTION(audio_analysis_enabled)) {
680 for (
int i = 0; i < samples_read; i++) {
686 int samples_to_process = samples_read;
687 int sample_offset = 0;
689 while (samples_to_process > 0) {
692 int samples_to_copy = (samples_to_process < space_in_frame) ? samples_to_process : space_in_frame;
695 memcpy(&opus_frame_buffer[opus_frame_samples_collected], &audio_buffer[sample_offset],
696 (
size_t)samples_to_copy *
sizeof(
float));
698 opus_frame_samples_collected += samples_to_copy;
699 sample_offset += samples_to_copy;
700 samples_to_process -= samples_to_copy;
707 START_TIMER(
"opus_encode");
711 static int encode_count = 0;
713 double opus_elapsed_ns = STOP_TIMER(
"opus_encode");
714 if (encode_count % 50 == 0) {
715 if (opus_elapsed_ns >= 0.0) {
716 char _duration_str[32];
718 log_dev(
"Opus encode #%d: %d samples -> %d bytes in %s", encode_count,
OPUS_FRAME_SAMPLES, opus_len,
723 double encode_time_ns = 0;
725 total_encode_ns += encode_time_ns;
726 if (encode_time_ns > max_encode_ns)
727 max_encode_ns = encode_time_ns;
731 log_debug_every(LOG_RATE_VERY_FAST,
"Pipeline encoded: %d samples -> %d bytes (compression: %.1fx)",
736 if (batch_frame_count <
MAX_BATCH_FRAMES && batch_total_size + (
size_t)opus_len <=
sizeof(batch_buffer)) {
738 if (batch_frame_count == 0) {
740 batch_has_data =
true;
743 memcpy(batch_buffer + batch_total_size, opus_packet, (
size_t)opus_len);
744 batch_frame_sizes[batch_frame_count] = (uint16_t)opus_len;
745 batch_total_size += (size_t)opus_len;
748 if (GET_OPTION(audio_analysis_enabled)) {
752 }
else if (opus_len == 0) {
754 log_debug_every(LOG_RATE_VERY_FAST,
"Pipeline DTX frame (silence detected)");
758 opus_frame_samples_collected = 0;
763 if (batch_frame_count > 0) {
764 static int batch_send_count = 0;
767 START_TIMER(
"audio_queue_packet");
768 int queue_result = audio_queue_packet(batch_buffer, batch_total_size, batch_frame_sizes, batch_frame_count);
769 double queue_time_ns = STOP_TIMER(
"audio_queue_packet");
771 total_queue_ns += queue_time_ns;
772 if (queue_time_ns > max_queue_ns)
773 max_queue_ns = queue_time_ns;
775 if (queue_result < 0) {
776 log_debug_every(LOG_RATE_VERY_FAST,
"Failed to queue audio batch (queue full)");
778 if (batch_send_count <= 10 || batch_send_count % 50 == 0) {
779 char queue_duration_str[32];
781 log_debug(
"CLIENT: Queued Opus batch #%d (%d frames, %zu bytes) in %s", batch_send_count, batch_frame_count,
782 batch_total_size, queue_duration_str);
789 batch_frame_count = 0;
790 batch_total_size = 0;
791 batch_has_data =
false;
795 double loop_time_ns = STOP_TIMER(
"audio_capture_loop_iteration");
796 total_loop_ns += loop_time_ns;
797 if (loop_time_ns > max_loop_ns)
798 max_loop_ns = loop_time_ns;
801 if (timing_loop_count % 100 == 0) {
802 char avg_loop_str[32], max_loop_str[32];
803 char avg_read_str[32], max_read_str[32];
804 char avg_encode_str[32], max_encode_str[32];
805 char avg_queue_str[32], max_queue_str[32];
807 format_duration_ns(total_loop_ns / timing_loop_count, avg_loop_str,
sizeof(avg_loop_str));
809 format_duration_ns(total_read_ns / timing_loop_count, avg_read_str,
sizeof(avg_read_str));
811 format_duration_ns(total_encode_ns / timing_loop_count, avg_encode_str,
sizeof(avg_encode_str));
813 format_duration_ns(total_queue_ns / timing_loop_count, avg_queue_str,
sizeof(avg_queue_str));
816 log_debug(
"CAPTURE TIMING #%lu: loop avg=%s max=%s, read avg=%s max=%s", timing_loop_count, avg_loop_str,
817 max_loop_str, avg_read_str, max_read_str);
818 log_info(
" encode avg=%s max=%s, queue avg=%s max=%s", avg_encode_str, max_encode_str, avg_queue_str,
824 if (batch_has_data && batch_frame_count > 0) {
829 static int timeout_flush_count = 0;
830 timeout_flush_count++;
832 long elapsed_ms = (long)time_ns_to_ms(elapsed_ns);
833 log_debug_every(LOG_RATE_FAST,
"Timeout flush #%d: %d frames (%zu bytes) after %ld ms", timeout_flush_count,
834 batch_frame_count, batch_total_size, elapsed_ms);
837 int queue_result = audio_queue_packet(batch_buffer, batch_total_size, batch_frame_sizes, batch_frame_count);
838 if (queue_result == 0) {
844 batch_frame_count = 0;
845 batch_total_size = 0;
846 batch_has_data =
false;
856 double loop_time_ns = STOP_TIMER(
"audio_capture_loop_iteration");
857 total_loop_ns += loop_time_ns;
858 if (loop_time_ns > max_loop_ns)
859 max_loop_ns = loop_time_ns;
862 if (batch_has_data && batch_frame_count > 0) {
867 long elapsed_ms = (long)time_ns_to_ms(elapsed_ns);
868 log_debug_every(LOG_RATE_FAST,
"Error path timeout flush: %d frames (%zu bytes) after %ld ms",
869 batch_frame_count, batch_total_size, elapsed_ms);
870 (void)audio_queue_packet(batch_buffer, batch_total_size, batch_frame_sizes, batch_frame_count);
871 batch_frame_count = 0;
872 batch_total_size = 0;
873 batch_has_data =
false;
881 log_debug(
"Audio capture thread stopped");
882 atomic_store(&g_audio_capture_thread_exited,
true);
905 if (!GET_OPTION(audio_enabled)) {
911 g_wav_playback_received =
wav_writer_open(
"/tmp/audio_playback_received.wav", AUDIO_SAMPLE_RATE, 1);
912 if (g_wav_playback_received) {
913 log_debug(
"Audio debugging enabled: dumping received audio to /tmp/audio_playback_received.wav");
918 log_debug(
"DEBUG: About to call audio_init()...");
919 if (
audio_init(&g_audio_context) != ASCIICHAT_OK) {
920 log_error(
"Failed to initialize audio system");
922 if (g_wav_playback_received) {
924 g_wav_playback_received = NULL;
928 log_debug(
"DEBUG: audio_init() completed successfully");
932 pipeline_config.opus_bitrate = 128000;
936 pipeline_config.flags.echo_cancel =
true;
937 pipeline_config.flags.jitter_buffer =
true;
938 pipeline_config.flags.noise_suppress =
false;
939 pipeline_config.flags.agc =
true;
940 pipeline_config.flags.vad =
false;
941 pipeline_config.flags.compressor =
true;
942 pipeline_config.flags.noise_gate =
false;
943 pipeline_config.flags.highpass =
true;
944 pipeline_config.flags.lowpass =
false;
949 pipeline_config.jitter_margin_ns = 100;
951 log_debug(
"DEBUG: About to create audio pipeline...");
953 log_debug(
"DEBUG: client_audio_pipeline_create() returned");
954 if (!g_audio_pipeline) {
955 log_error(
"Failed to create audio pipeline");
958 if (g_wav_playback_received) {
960 g_wav_playback_received = NULL;
965 log_debug(
"Audio pipeline created: %d Hz sample rate, %d bps bitrate", pipeline_config.sample_rate,
966 pipeline_config.opus_bitrate);
975 log_error(
"Failed to start full-duplex audio");
977 g_audio_pipeline = NULL;
980 if (g_wav_playback_received) {
982 g_wav_playback_received = NULL;
1004 log_debug(
"audio_start_thread called: audio_enabled=%d", GET_OPTION(audio_enabled));
1006 if (!GET_OPTION(audio_enabled)) {
1007 log_debug(
"Audio is disabled, skipping audio capture thread creation");
1012 if (g_audio_capture_thread_created && !atomic_load(&g_audio_capture_thread_exited)) {
1013 log_warn(
"Audio capture thread already running");
1018 if (g_audio_capture_thread_created && atomic_load(&g_audio_capture_thread_exited)) {
1019 log_debug(
"Previous audio capture thread exited, recreating");
1021 int join_result = asciichat_thread_join_timeout(&g_audio_capture_thread, NULL, 5000 * NS_PER_MS_INT);
1022 if (join_result != 0) {
1023 log_warn(
"Audio capture thread join timed out after 5s - thread may be deadlocked, "
1024 "forcing thread handle reset (stuck thread resources will not be cleaned up)");
1028 g_audio_capture_thread_created =
false;
1034 log_error(
"Failed to send audio stream start packet");
1039 atomic_store(&g_audio_capture_thread_exited,
false);
1041 log_error(
"Failed to spawn audio capture thread in worker pool");
1042 LOG_ERRNO_IF_SET(
"Audio capture thread creation failed");
1046 g_audio_capture_thread_created =
true;
1064 if (g_audio_send_queue_initialized) {
1065 log_debug(
"Signaling audio sender thread to exit");
1066 atomic_store(&g_audio_sender_should_exit,
true);
1067 mutex_lock(&g_audio_send_queue_mutex);
1068 cond_signal(&g_audio_send_queue_cond);
1069 mutex_unlock(&g_audio_send_queue_mutex);
1072 if (!THREAD_IS_CREATED(g_audio_capture_thread_created)) {
1081 while (wait_count < 20 && !atomic_load(&g_audio_capture_thread_exited)) {
1086 if (!atomic_load(&g_audio_capture_thread_exited)) {
1087 log_warn(
"Audio capture thread not responding - will be joined by thread pool");
1091 g_audio_capture_thread_created =
false;
1093 log_debug(
"Audio capture thread stopped");
1104 return atomic_load(&g_audio_capture_thread_exited);
1116 if (!GET_OPTION(audio_enabled)) {
1124 audio_sender_cleanup();
1133 if (g_audio_context.initialized) {
1150 if (g_audio_pipeline) {
1152 g_audio_pipeline = NULL;
1153 log_debug(
"Audio pipeline destroyed");
1157 if (g_wav_capture_raw) {
1159 g_wav_capture_raw = NULL;
1160 log_debug(
"Closed audio capture raw dump");
1162 if (g_wav_capture_processed) {
1164 g_wav_capture_processed = NULL;
1165 log_debug(
"Closed audio capture processed dump");
1167 if (g_wav_playback_received) {
1169 g_wav_playback_received = NULL;
1170 log_debug(
"Closed audio playback received dump");
1174 if (g_audio_context.initialized) {
1186 return g_audio_pipeline;
1200 if (!g_audio_pipeline || !output || max_samples <= 0) {
1214 return &g_audio_context;
void audio_analysis_track_received_sample(float sample)
void audio_analysis_track_sent_sample(float sample)
void audio_analysis_track_sent_packet(size_t size)
void asciichat_errno_destroy(void)
thread_pool_t * g_client_worker_pool
Global client worker thread pool.
int client_audio_pipeline_playback(client_audio_pipeline_t *pipeline, const uint8_t *opus_in, int opus_len, float *output, int num_samples)
client_audio_pipeline_t * client_audio_pipeline_create(const client_audio_pipeline_config_t *config)
Create and initialize a client audio pipeline.
client_audio_pipeline_config_t client_audio_pipeline_default_config(void)
int client_audio_pipeline_capture(client_audio_pipeline_t *pipeline, const float *input, int num_samples, uint8_t *opus_out, int max_opus_len)
void client_audio_pipeline_destroy(client_audio_pipeline_t *pipeline)
void fps_frame_ns(fps_t *tracker, uint64_t current_time_ns, const char *context)
void fps_init(fps_t *tracker, int expected_fps, const char *name)
void audio_process_received_samples(const float *samples, int num_samples)
Process received audio samples from server.
void audio_stop_thread()
Stop audio capture thread.
int audio_client_init()
Initialize audio subsystem.
client_audio_pipeline_t * audio_get_pipeline(void)
Get the audio pipeline (for advanced usage)
void audio_cleanup()
Cleanup audio subsystem.
int audio_decode_opus(const uint8_t *opus_data, size_t opus_len, float *output, int max_samples)
Decode Opus packet using the audio pipeline.
bool audio_thread_exited()
Check if audio capture thread has exited.
audio_context_t * audio_get_context(void)
Get the global audio context for use by other subsystems.
int audio_start_thread()
Start audio capture thread.
bool server_connection_is_active()
Check if server connection is currently active.
bool server_connection_is_lost()
Check if connection loss has been detected.
asciichat_error_t threaded_send_audio_opus_batch(const uint8_t *opus_data, size_t opus_size, const uint16_t *frame_sizes, int frame_count)
Thread-safe Opus audio batch packet transmission.
asciichat_error_t threaded_send_stream_start_packet(uint32_t stream_type)
Thread-safe stream start packet transmission.
asciichat_error_t audio_init(audio_context_t *ctx)
size_t audio_ring_buffer_available_read(audio_ring_buffer_t *rb)
asciichat_error_t audio_start_duplex(audio_context_t *ctx)
void audio_destroy(audio_context_t *ctx)
asciichat_error_t audio_stop_duplex(audio_context_t *ctx)
asciichat_error_t audio_write_samples(audio_context_t *ctx, const float *buffer, int num_samples)
void audio_terminate_portaudio_final(void)
Terminate PortAudio and free all device resources.
void audio_set_pipeline(audio_context_t *ctx, void *pipeline)
asciichat_error_t audio_read_samples(audio_context_t *ctx, float *buffer, int num_samples)
ascii-chat Server Mode Entry Point Header
#define OPUS_FRAME_SAMPLES
#define AUDIO_SEND_QUEUE_SIZE
#define CAPTURE_READ_SIZE
#define OPUS_MAX_PACKET_SIZE
ascii-chat Client Audio Processing Management Interface
Audio packet for async sending.
asciichat_error_t thread_pool_spawn(thread_pool_t *pool, void *(*thread_func)(void *), void *thread_arg, int stop_id, const char *thread_name)
int mutex_init(mutex_t *mutex)
int mutex_destroy(mutex_t *mutex)
uint64_t time_get_ns(void)
int format_duration_ns(double nanoseconds, char *buffer, size_t buffer_size)
uint64_t time_elapsed_ns(uint64_t start_ns, uint64_t end_ns)
bool timer_is_initialized(void)
bool timer_system_init(void)
bool wav_dump_enabled(void)
wav_writer_t * wav_writer_open(const char *filepath, int sample_rate, int channels)
int wav_writer_write(wav_writer_t *writer, const float *samples, int num_samples)
void wav_writer_close(wav_writer_t *writer)