151#include <stdatomic.h>
362 log_error(
"NULL client pointer in video render thread");
371 log_debug(
"Video render thread: client_id=%u", thread_client_id);
374 log_error(
"Invalid socket in video render thread for client %u", thread_client_id);
383 if (has_caps && desired_fps > 0) {
384 client_fps = desired_fps;
385 log_debug(
"Client %u requested FPS: %d (has_caps=%d, desired_fps=%d)", thread_client_id, client_fps, has_caps,
388 log_debug(
"Client %u using default FPS: %d (has_caps=%d, desired_fps=%d)", thread_client_id, client_fps, has_caps,
392 int base_frame_interval_ms = 1000 / client_fps;
393 log_debug(
"Client %u render interval: %dms (%d FPS)", thread_client_id, base_frame_interval_ms, client_fps);
394 struct timespec last_render_time;
395 (void)clock_gettime(CLOCK_MONOTONIC, &last_render_time);
398 fps_t video_fps_tracker = {0};
399 fps_init(&video_fps_tracker, client_fps,
"SERVER VIDEO");
401 log_info(
"Video render loop STARTING for client %u", thread_client_id);
403 bool should_continue =
true;
409 log_debug(
"Video render thread stopping for client %u (g_server_should_exit)", thread_client_id);
414 bool active = atomic_load(&client->
active);
417 should_continue = video_running && active && !shutting_down;
419 if (!should_continue) {
420 log_debug(
"Video render thread stopping for client %u (should_continue=false: video_running=%d, active=%d, "
422 thread_client_id, video_running, active, shutting_down);
427 struct timespec current_time;
428 (void)clock_gettime(CLOCK_MONOTONIC, ¤t_time);
431 int64_t elapsed_us = ((int64_t)(current_time.tv_sec - last_render_time.tv_sec) * 1000000LL) +
432 ((int64_t)(current_time.tv_nsec - last_render_time.tv_nsec) / 1000);
433 int64_t base_frame_interval_us = (int64_t)base_frame_interval_ms * 1000;
435 if (elapsed_us < base_frame_interval_us) {
436 long sleep_us = (long)(base_frame_interval_us - elapsed_us);
438 const long max_sleep_chunk = 5000;
440 long chunk = sleep_us > max_sleep_chunk ? max_sleep_chunk : sleep_us;
458 if (!should_continue) {
467 unsigned short width_snapshot = atomic_load(&client->
width);
468 unsigned short height_snapshot = atomic_load(&client->
height);
469 bool active_snapshot = atomic_load(&client->
active);
472 if (!active_snapshot) {
477 size_t frame_size = 0;
481 log_debug(
"Video render iteration for client %u: has_video_sources=%d", thread_client_id, has_video_sources);
483 if (!has_video_sources) {
488 log_debug(
"Skipping frame generation for client %u (no video sources)", thread_client_id);
489 goto skip_frame_generation;
492 int sources_count = 0;
494 log_debug(
"About to call create_mixed_ascii_frame_for_client for client %u", thread_client_id);
496 &frame_size, NULL, &sources_count);
497 log_debug(
"create_mixed_ascii_frame_for_client returned: ascii_frame=%p, frame_size=%zu, sources_count=%d",
498 (
void *)ascii_frame, frame_size, sources_count);
501 if (ascii_frame && frame_size > 0) {
502 log_debug(
"Buffering frame for client %u (size=%zu)", thread_client_id, frame_size);
515 if (write_frame->
data && frame_size <= vfb_snapshot->allocated_buffer_size) {
516 memcpy(write_frame->
data, ascii_frame, frame_size);
517 write_frame->
size = frame_size;
519 (
uint64_t)current_time.tv_sec * 1000000 + (
uint64_t)current_time.tv_nsec / 1000;
525 char pretty_size[64];
533 fps_frame(&video_fps_tracker, ¤t_time,
"frame rendered");
544 skip_frame_generation:
549 last_render_time = current_time;
553 log_debug(
"Video render thread stopped for client %u", thread_client_id);
677 log_error(
"Invalid client info in audio render thread");
684 char thread_display_name[64];
693 log_debug(
"Audio render thread started for client %u (%s)", thread_client_id, thread_display_name);
699 float mix_buffer[960];
703#define OPUS_FRAME_SAMPLES 960
705 int opus_frame_accumulated = 0;
710 log_error(
"Failed to create Opus encoder for audio render thread (client %u)", thread_client_id);
715 fps_t audio_fps_tracker = {0};
717 struct timespec last_packet_send_time;
718 (void)clock_gettime(CLOCK_MONOTONIC, &last_packet_send_time);
721 int mixer_debug_count = 0;
722 int backpressure_check_counter = 0;
723 int server_audio_frame_count = 0;
725 bool should_continue =
true;
728 struct timespec loop_start_time;
729 (void)clock_gettime(CLOCK_MONOTONIC, &loop_start_time);
735 log_debug(
"Audio render thread stopping for client %u (g_server_should_exit)", thread_client_id);
744 if (!should_continue) {
745 log_debug(
"Audio render thread stopping for client %u (should_continue=false)", thread_client_id);
763 bool active_snapshot = atomic_load(&client->
active);
767 if (!active_snapshot || !audio_queue_snapshot) {
772 struct timespec mix_start_time;
773 (void)clock_gettime(CLOCK_MONOTONIC, &mix_start_time);
779 int samples_to_read = 480;
788 float buffer_latency_ms = (float)available / 48.0f;
791 log_debug_every(500000,
"LATENCY: Server incoming buffer for client %u: %.1fms (%zu samples)",
795 if (available > 1920) {
796 samples_to_read = 960;
798 "LATENCY WARNING: Server buffer too full for client %u: %.1fms, reading double",
806 float queue_latency_ms = (float)queue_depth * 20.0f;
807 log_debug_every(500000,
"LATENCY: Server send queue for client %u: %.1fms (%zu packets)", client_id_snapshot,
808 queue_latency_ms, queue_depth);
811 int samples_mixed = 0;
815 SAFE_MEMSET(mix_buffer, samples_to_read *
sizeof(
float), 0, samples_to_read *
sizeof(
float));
818 int max_samples_in_frame = 0;
824 float temp_buffer[960];
829 if (samples_read > max_samples_in_frame) {
830 max_samples_in_frame = samples_read;
834 for (
int j = 0; j < samples_read; j++) {
835 mix_buffer[j] += temp_buffer[j];
839 samples_mixed = max_samples_in_frame;
843 "Audio mixer DISABLED (--no-audio-mixer): simple mixing, samples=%d for client %u", samples_mixed,
850 struct timespec mix_end_time;
851 (void)clock_gettime(CLOCK_MONOTONIC, &mix_end_time);
853 ((
uint64_t)mix_start_time.tv_sec * 1000000 + (
uint64_t)mix_start_time.tv_nsec / 1000);
855 if (mix_time_us > 2000) {
857 mix_time_us, (
float)mix_time_us / 1000.0f);
867 if (samples_mixed > 0 && (mixer_debug_count <= 3 || mixer_debug_count % 50 == 0)) {
868 log_info(
"Server mixer iteration #%d for client %u: samples_mixed=%d, opus_frame_accumulated=%d/%d",
869 mixer_debug_count, client_id_snapshot, samples_mixed, opus_frame_accumulated,
OPUS_FRAME_SAMPLES);
874 struct timespec accum_start = {0};
875 (void)clock_gettime(CLOCK_MONOTONIC, &accum_start);
878 int samples_to_copy = (samples_mixed <= space_available) ? samples_mixed : space_available;
881 if (samples_to_copy > 0) {
882 SAFE_MEMCPY(opus_frame_buffer + opus_frame_accumulated,
884 samples_to_copy *
sizeof(
float));
885 opus_frame_accumulated += samples_to_copy;
888 struct timespec accum_end = {0};
889 (void)clock_gettime(CLOCK_MONOTONIC, &accum_end);
891 ((
uint64_t)accum_start.tv_sec * 1000000 + (
uint64_t)accum_start.tv_nsec / 1000);
893 if (accum_time_us > 500) {
902 bool apply_backpressure =
false;
904 if (++backpressure_check_counter >= 100) {
905 backpressure_check_counter = 0;
909 apply_backpressure = (queue_depth > 50);
911 if (apply_backpressure) {
912 log_warn(
"Audio backpressure for client %u: queue depth %zu packets (%.1fs buffered)", client_id_snapshot,
913 queue_depth, (
float)queue_depth / 50.0f);
917 if (apply_backpressure) {
921 opus_frame_accumulated = 0;
929 struct timespec opus_start_time;
930 (void)clock_gettime(CLOCK_MONOTONIC, &opus_start_time);
935 struct timespec opus_end_time;
936 (void)clock_gettime(CLOCK_MONOTONIC, &opus_end_time);
938 ((
uint64_t)opus_start_time.tv_sec * 1000000 + (
uint64_t)opus_start_time.tv_nsec / 1000);
940 if (opus_time_us > 2000) {
942 client_id_snapshot, opus_time_us, (
float)opus_time_us / 1000.0f, opus_size);
947 float peak = 0.0f, rms = 0.0f;
949 float abs_val = fabsf(opus_frame_buffer[i]);
952 rms += opus_frame_buffer[i] * opus_frame_buffer[i];
956 server_audio_frame_count++;
957 if (server_audio_frame_count <= 5 || server_audio_frame_count % 20 == 0) {
959 log_info(
"Server audio frame #%d for client %u: samples_mixed=%d, Peak=%.6f, RMS=%.6f, opus_size=%d, "
960 "first4=[%.4f,%.4f,%.4f,%.4f]",
961 server_audio_frame_count, client_id_snapshot, samples_mixed, peak, rms, opus_size,
962 opus_frame_buffer[0], opus_frame_buffer[1], opus_frame_buffer[2], opus_frame_buffer[3]);
968 opus_frame_accumulated = 0;
970 if (opus_size <= 0) {
971 log_error(
"Failed to encode audio to Opus for client %u: opus_size=%d", client_id_snapshot, opus_size);
974 struct timespec queue_start = {0};
975 (void)clock_gettime(CLOCK_MONOTONIC, &queue_start);
980 struct timespec queue_end = {0};
981 (void)clock_gettime(CLOCK_MONOTONIC, &queue_end);
983 ((
uint64_t)queue_start.tv_sec * 1000000 + (
uint64_t)queue_start.tv_nsec / 1000);
985 if (queue_time_us > 500) {
990 log_debug(
"Failed to queue Opus audio for client %u", client_id_snapshot);
993 struct timespec current_time;
994 (void)clock_gettime(CLOCK_MONOTONIC, ¤t_time);
995 fps_frame(&audio_fps_tracker, ¤t_time,
"audio packet queued");
1005 struct timespec loop_end_time;
1006 (void)clock_gettime(CLOCK_MONOTONIC, &loop_end_time);
1009 ((
uint64_t)loop_start_time.tv_sec * 1000000 + (
uint64_t)loop_start_time.tv_nsec / 1000);
1013 const uint64_t target_loop_us = 10000;
1015 if (loop_elapsed_us >= target_loop_us) {
1018 "Audio processing took %lluus (%.1fms) - exceeds target %lluus (%.1fms) for client %u",
1019 loop_elapsed_us, (
float)loop_elapsed_us / 1000.0f, target_loop_us, (
float)target_loop_us / 1000.0f,
1025 long remaining_sleep_us = (long)(target_loop_us - loop_elapsed_us);
1032 log_debug(
"Audio render thread stopped for client %u", thread_client_id);
1122 if (!server_ctx || !client) {
1123 log_error(
"Cannot create render threads: NULL %s", !server_ctx ?
"server_ctx" :
"client");
1141 char thread_name[64];
1142 snprintf(thread_name,
sizeof(thread_name),
"video_render_%u", client->
client_id);
1153 snprintf(thread_name,
sizeof(thread_name),
"audio_render_%u", client->
client_id);
1274 if (is_shutting_down) {
1277 log_debug(
"Shutdown mode: joining video render thread for client %u (no timeout)", client->
client_id);
1280 log_warn(
"Video render thread for client %u failed to join during shutdown: %s", client->
client_id,
1284 log_debug(
"Calling asciichat_thread_join for video thread of client %u", client->
client_id);
1286 log_debug(
"asciichat_thread_join returned %d for video thread of client %u", result, client->
client_id);
1293 }
else if (result != -2) {
1294 if (is_shutting_down) {
1295 log_warn(
"Failed to join video render thread for client %u during shutdown (continuing): %s", client->
client_id,
1307 if (is_shutting_down) {
1310 log_debug(
"Shutdown mode: joining audio render thread for client %u (no timeout)", client->
client_id);
1313 log_warn(
"Audio render thread for client %u failed to join during shutdown: %s", client->
client_id,
1324 }
else if (result != -2) {
1325 if (is_shutting_down) {
1326 log_warn(
"Failed to join audio render thread for client %u during shutdown (continuing): %s", client->
client_id,
🔌 Cross-platform abstraction layer umbrella header for ascii-chat
⏱️ FPS tracking utility for monitoring frame throughput across all threads
size_t audio_ring_buffer_read(audio_ring_buffer_t *rb, float *data, size_t samples)
Read audio samples from ring buffer.
size_t audio_ring_buffer_available_read(audio_ring_buffer_t *rb)
Get number of samples available for reading.
opus_codec_t * opus_codec_create_encoder(opus_application_t application, int sample_rate, int bitrate)
Create an Opus encoder.
uint32_t * source_ids
Array of client IDs (one per source slot)
int max_sources
Maximum number of sources (allocated array sizes)
int mixer_process_excluding_source(mixer_t *mixer, float *output, int num_samples, uint32_t exclude_client_id)
Process audio from all sources except one (for per-client output)
void opus_codec_destroy(opus_codec_t *codec)
Destroy an Opus codec instance.
size_t opus_codec_encode(opus_codec_t *codec, const float *samples, int num_samples, uint8_t *out_data, size_t out_size)
Encode audio frame with Opus.
audio_ring_buffer_t ** source_buffers
Array of pointers to client audio ring buffers.
@ OPUS_APPLICATION_AUDIO
General audio (optimized for music)
#define SAFE_STRNCPY(dst, src, size)
#define SAFE_MEMSET(dest, dest_size, ch, count)
void fps_frame(fps_t *tracker, const struct timespec *current_time, const char *context)
Track a frame and detect lag conditions.
#define SAFE_STRERROR(errnum)
unsigned long long uint64_t
#define SAFE_MEMCPY(dest, dest_size, src, count)
void fps_init(fps_t *tracker, int expected_fps, const char *name)
Initialize FPS tracker.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
void asciichat_errno_cleanup(void)
Cleanup error system resources.
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_RATE_SLOW
Log rate limit: 10 seconds (10,000,000 microseconds)
#define LOG_RATE_DEFAULT
Log rate limit: 5 seconds (5,000,000 microseconds) - default for audio/video packets.
#define LOG_RATE_NORMAL
Log rate limit: 3 seconds (3,000,000 microseconds)
#define log_warn(...)
Log a WARN message.
#define log_info_every(interval_us, fmt,...)
Rate-limited INFO logging.
#define log_error(...)
Log an ERROR message.
#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.
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
int packet_queue_enqueue(packet_queue_t *queue, packet_type_t type, const void *data, size_t data_len, uint32_t client_id, bool copy_data)
Enqueue a packet into the queue.
size_t packet_queue_size(packet_queue_t *queue)
Get current number of packets in queue.
@ PACKET_TYPE_AUDIO_OPUS
Opus-encoded single audio frame.
void format_bytes_pretty(size_t bytes, char *out, size_t out_capacity)
Format byte count into human-readable string.
video_frame_t * video_frame_begin_write(video_frame_buffer_t *vfb)
Writer API: Start writing a new frame.
void video_frame_commit(video_frame_buffer_t *vfb)
Writer API: Commit the frame and swap buffers.
Platform initialization and static synchronization helpers.
🔊 Audio Capture and Playback Interface for ascii-chat
asciichat_error_t tcp_server_spawn_thread(tcp_server_t *server, socket_t client_socket, void *(*thread_func)(void *), void *thread_arg, int stop_id, const char *thread_name)
Spawn a worker thread for a client.
🔢 Mathematical Utility Functions
Multi-Source Audio Mixing and Processing System.
⚙️ Command-line options parsing and configuration management for ascii-chat
Opus audio codec wrapper for real-time encoding/decoding.
📬 Thread-safe packet queue system for per-client send threads
#define OPUS_FRAME_SAMPLES
void stop_client_render_threads(client_info_t *client)
Stop and cleanup per-client rendering threads.
mixer_t * g_audio_mixer
Global audio mixer from main.c - provides multi-client audio mixing.
rwlock_t g_client_manager_rwlock
Reader-writer lock protecting the global client manager.
atomic_bool g_server_should_exit
Global shutdown flag from main.c - coordinate graceful thread termination.
void * client_video_render_thread(void *arg)
Interruptible sleep function with platform-specific optimizations.
void * client_audio_render_thread(void *arg)
Main audio rendering thread function for individual clients.
int create_client_render_threads(server_context_t *server_ctx, client_info_t *client)
Create and initialize per-client rendering threads.
Per-client rendering threads with rate limiting.
ascii-chat Server Mode Entry Point Header
Per-client state management and lifecycle orchestration.
Server packet processing and protocol implementation.
bool any_clients_sending_video(void)
Check if any connected clients are currently sending video.
char * create_mixed_ascii_frame_for_client(uint32_t target_client_id, unsigned short width, unsigned short height, bool wants_stretch, size_t *out_size, bool *out_grid_changed, int *out_sources_count)
Generate personalized ASCII frame for a specific client.
Multi-client video mixing and ASCII frame generation.
Per-client state structure for server-side client management.
atomic_bool video_render_thread_running
terminal_capabilities_t terminal_caps
char display_name[MAX_DISPLAY_NAME_LEN]
asciichat_thread_t audio_render_thread
video_frame_buffer_t * outgoing_video_buffer
mutex_t client_state_mutex
atomic_bool audio_render_thread_running
atomic_int last_rendered_grid_sources
packet_queue_t * audio_queue
atomic_bool shutting_down
asciichat_thread_t video_render_thread
Main mixer structure for multi-source audio processing.
Opus codec context for encoding or decoding.
Thread-safe packet queue for producer-consumer communication.
Server context - encapsulates all server state.
tcp_server_t * tcp_server
TCP server managing connections.
uint8_t desired_fps
Client's desired frame rate (1-144 FPS)
Video frame buffer manager.
size_t allocated_buffer_size
Size of allocated data buffers (for cleanup)
uint64_t capture_timestamp_us
Timestamp when frame was captured (microseconds)
size_t size
Size of frame data in bytes.
void * data
Frame data pointer (points to pre-allocated buffer)
⏱️ High-precision timing utilities using sokol_time.h and uthash