339 client_info_t *client = (client_info_t *)arg;
341 log_error(
"NULL client pointer in video render thread");
347 uint32_t thread_client_id = atomic_load(&client->client_id);
348 socket_t thread_socket = client->socket;
349 bool is_webrtc = (thread_socket == INVALID_SOCKET_VALUE);
352 log_debug(
"Video render thread: client_id=%u, webrtc=%d", thread_client_id, is_webrtc);
357 bool has_caps = client->has_terminal_caps;
358 int desired_fps = has_caps ? client->terminal_caps.desired_fps : 0;
359 if (has_caps && desired_fps > 0) {
360 client_fps = desired_fps;
361 log_debug(
"Client %u requested FPS: %d (has_caps=%d, desired_fps=%d)", thread_client_id, client_fps, has_caps,
364 log_debug(
"Client %u using default FPS: %d (has_caps=%d, desired_fps=%d)", thread_client_id, client_fps, has_caps,
368 int base_frame_interval_ms = 1000 / client_fps;
369 log_debug(
"Client %u render interval: %dms (%d FPS)", thread_client_id, base_frame_interval_ms, client_fps);
372 fps_t video_fps_tracker = {0};
373 fps_init(&video_fps_tracker, client_fps,
"SERVER VIDEO");
376 adaptive_sleep_state_t sleep_state = {0};
377 adaptive_sleep_config_t config = {
378 .baseline_sleep_ns = (uint64_t)(NS_PER_SEC_INT / client_fps),
379 .min_speed_multiplier = 1.0,
380 .max_speed_multiplier = 1.0,
386 log_info(
"Video render loop STARTING for client %u", thread_client_id);
388 bool should_continue =
true;
389 while (should_continue && !atomic_load(&
g_server_should_exit) && !atomic_load(&client->shutting_down)) {
390 log_dev_every(10 * NS_PER_MS_INT,
"Video render loop iteration for client %u", thread_client_id);
394 log_debug(
"Video render thread stopping for client %u (g_server_should_exit)", thread_client_id);
398 bool video_running = atomic_load(&client->video_render_thread_running);
399 bool active = atomic_load(&client->active);
400 bool shutting_down = atomic_load(&client->shutting_down);
402 should_continue = video_running && active && !shutting_down;
404 if (!should_continue) {
405 log_debug(
"Video render thread stopping for client %u (should_continue=false: video_running=%d, active=%d, "
407 thread_client_id, video_running, active, shutting_down);
419 should_continue = atomic_load(&client->video_render_thread_running) && atomic_load(&client->active) &&
420 !atomic_load(&client->shutting_down);
421 if (!should_continue) {
429 uint32_t client_id_snapshot = atomic_load(&client->client_id);
430 unsigned short width_snapshot = atomic_load(&client->width);
431 unsigned short height_snapshot = atomic_load(&client->height);
432 bool active_snapshot = atomic_load(&client->active);
435 if (!active_snapshot) {
440 size_t frame_size = 0;
446 static bool last_has_sources =
false;
447 if (has_video_sources != last_has_sources) {
448 log_warn(
"DIAGNOSTIC: Client %u video sources: %s", thread_client_id,
449 has_video_sources ?
"AVAILABLE" :
"UNAVAILABLE");
450 last_has_sources = has_video_sources;
453 log_debug_every(5 * NS_PER_MS_INT,
454 "Video render iteration for client %u: has_video_sources=%d, width=%u, height=%u", thread_client_id,
455 has_video_sources, width_snapshot, height_snapshot);
458 if (width_snapshot == 0 || height_snapshot == 0) {
459 log_dev_every(5 * NS_PER_MS_INT,
460 "Skipping frame generation for client %u: dimensions not yet received (width=%u, height=%u)",
461 thread_client_id, width_snapshot, height_snapshot);
465 if (has_video_sources) {
466 int sources_count = 0;
470 static uint32_t frame_gen_count = 0;
471 static uint64_t frame_gen_start_time = 0;
474 if (frame_gen_count == 1) {
475 frame_gen_start_time = current_time_ns;
479 if (frame_gen_count % 120 == 0) {
480 uint64_t elapsed_ns = current_time_ns - frame_gen_start_time;
481 double gen_fps = (120.0 / (elapsed_ns / (double)NS_PER_SEC_INT));
482 log_warn(
"DIAGNOSTIC: Client %u LOOP running at %.1f FPS (120 iterations in %.2fs)", thread_client_id, gen_fps,
483 elapsed_ns / (
double)NS_PER_SEC_INT);
486 log_dev_every(5 * NS_PER_MS_INT,
487 "About to call create_mixed_ascii_frame_for_client for client %u with dims %ux%u", thread_client_id,
488 width_snapshot, height_snapshot);
490 false, &frame_size, NULL, &sources_count);
493 static uint32_t last_frame_hash = -1;
494 uint32_t current_frame_hash = 0;
495 bool frame_is_new =
false;
496 if (ascii_frame && frame_size > 0) {
497 for (
size_t i = 0; i < frame_size && i < 1000; i++) {
498 current_frame_hash = (uint32_t)((uint64_t)current_frame_hash * 31 + ((
unsigned char *)ascii_frame)[i]);
500 if (current_frame_hash != last_frame_hash) {
501 log_info(
"RENDER_FRAME CHANGE: Client %u frame #%zu sources=%d hash=0x%08x (prev=0x%08x)", thread_client_id,
502 frame_size, sources_count, current_frame_hash, last_frame_hash);
503 last_frame_hash = current_frame_hash;
506 log_dev_every(25000,
"RENDER_FRAME DUPLICATE: Client %u frame #%zu sources=%d hash=0x%08x (no change)",
507 thread_client_id, frame_size, sources_count, current_frame_hash);
508 frame_is_new =
false;
512 log_dev_every(5 * NS_PER_MS_INT,
513 "create_mixed_ascii_frame_for_client returned: ascii_frame=%p, frame_size=%zu, sources_count=%d",
514 (
void *)ascii_frame, frame_size, sources_count);
517 if (ascii_frame && frame_size > 0) {
518 log_debug_every(5 * NS_PER_MS_INT,
"Buffering frame for client %u (size=%zu)", thread_client_id, frame_size);
521 atomic_store(&client->last_rendered_grid_sources, sources_count);
525 video_frame_buffer_t *vfb_snapshot = client->outgoing_video_buffer;
531 if (write_frame->data && frame_size <= vfb_snapshot->allocated_buffer_size) {
532 memcpy(write_frame->data, ascii_frame, frame_size);
533 write_frame->size = frame_size;
534 write_frame->capture_timestamp_ns = current_time_ns;
543 char commit_duration_str[32];
545 sizeof(commit_duration_str));
547 static uint32_t commits_count = 0;
548 static uint64_t commits_start_time = 0;
550 if (commits_count == 1) {
551 commits_start_time = commit_end_ns;
553 if (commits_count % 10 == 0) {
554 uint64_t elapsed_ns = commit_end_ns - commits_start_time;
555 double commit_fps = (10.0 / (elapsed_ns / (double)NS_PER_SEC_INT));
556 log_warn(
"DIAGNOSTIC: Client %u UNIQUE frames being sent at %.1f FPS (10 commits counted)",
557 thread_client_id, commit_fps);
560 log_info(
"[FRAME_COMMIT_TIMING] Client %u frame commit took %s (hash=0x%08x)", thread_client_id,
561 commit_duration_str, current_frame_hash);
564 log_dev_every(25000,
"Skipping commit for duplicate frame for client %u (hash=0x%08x)",
565 thread_client_id, current_frame_hash);
569 char pretty_size[64];
573 uint32_t ascii_hash = 0;
574 for (
size_t i = 0; i < frame_size && i < 1000; i++) {
575 ascii_hash = (uint32_t)((((uint64_t)ascii_hash << 5) - ascii_hash) + (
unsigned char)ascii_frame[i]);
577 log_dev_every(5 * NS_PER_MS_INT,
"Client %u: Rendered ASCII frame size=%s hash=0x%08x sources=%d",
578 thread_client_id, pretty_size, ascii_hash, sources_count);
581 log_warn(
"Frame too large for buffer: %zu > %zu", frame_size, vfb_snapshot->allocated_buffer_size);
585 fps_frame_ns(&video_fps_tracker, current_time_ns,
"frame rendered");
589 SAFE_FREE(ascii_frame);
592 log_dev_every(10 * NS_PER_MS_INT,
"Per-client render: No video sources available for client %u",
598 log_debug(
"Skipping frame generation for client %u (no video sources)", thread_client_id);
603 log_debug(
"Video render thread stopped for client %u", thread_client_id);
724 client_info_t *client = (client_info_t *)arg;
727 log_error(
"Invalid client info in audio render thread");
733 uint32_t thread_client_id = atomic_load(&client->client_id);
734 char thread_display_name[64];
735 bool is_webrtc = (client->socket == INVALID_SOCKET_VALUE);
740 mutex_lock(&client->client_state_mutex);
741 SAFE_STRNCPY(thread_display_name, client->display_name,
sizeof(thread_display_name));
742 mutex_unlock(&client->client_state_mutex);
745 log_debug(
"Audio render thread started for client %u (%s), webrtc=%d", thread_client_id, thread_display_name,
752 float mix_buffer[960];
756#define OPUS_FRAME_SAMPLES 960
758 int opus_frame_accumulated = 0;
763 log_error(
"Failed to create Opus encoder for audio render thread (client %u)", thread_client_id);
768 fps_t audio_fps_tracker = {0};
772 adaptive_sleep_state_t audio_sleep_state = {0};
773 adaptive_sleep_config_t audio_config = {
774 .baseline_sleep_ns = 10 * NS_PER_MS_INT,
775 .min_speed_multiplier = 1.0,
776 .max_speed_multiplier = 1.0,
783 int mixer_debug_count = 0;
784 int backpressure_check_counter = 0;
785 int server_audio_frame_count = 0;
787 bool should_continue =
true;
788 while (should_continue && !atomic_load(&
g_server_should_exit) && !atomic_load(&client->shutting_down)) {
789 log_debug_every(LOG_RATE_SLOW,
"Audio render loop iteration for client %u", thread_client_id);
793 log_debug(
"Audio render thread stopping for client %u (g_server_should_exit)", thread_client_id);
799 should_continue = (((int)atomic_load(&client->audio_render_thread_running) != 0) &&
800 ((
int)atomic_load(&client->active) != 0) && !atomic_load(&client->shutting_down));
802 if (!should_continue) {
803 log_debug(
"Audio render thread stopping for client %u (should_continue=false)", thread_client_id);
808 log_dev_every(10 * NS_PER_MS_INT,
"Audio render waiting for mixer (client %u)", thread_client_id);
812 platform_sleep_ns(10 * NS_PER_MS_INT);
820 uint32_t client_id_snapshot = atomic_load(&client->client_id);
821 bool active_snapshot = atomic_load(&client->active);
822 packet_queue_t *audio_queue_snapshot = client->audio_queue;
825 if (!active_snapshot || !audio_queue_snapshot) {
830 START_TIMER(
"mix_%u", client_id_snapshot);
836 int samples_to_read = 480;
845 float buffer_latency_ms = (float)available / 48.0f;
848 log_dev_every(5 * NS_PER_MS_INT,
"LATENCY: Server incoming buffer for client %u: %.1fms (%zu samples)",
852 if (available > 1920) {
853 samples_to_read = 960;
854 log_dev_every(LOG_RATE_DEFAULT,
855 "LATENCY WARNING: Server buffer too full for client %u: %.1fms, reading double",
863 float queue_latency_ms = (float)queue_depth * 20.0f;
864 log_dev_every(5 * NS_PER_MS_INT,
"LATENCY: Server send queue for client %u: %.1fms (%zu packets)",
865 client_id_snapshot, queue_latency_ms, queue_depth);
868 int samples_mixed = 0;
869 if (GET_OPTION(no_audio_mixer)) {
872 SAFE_MEMSET(mix_buffer, samples_to_read *
sizeof(
float), 0, samples_to_read *
sizeof(
float));
875 int max_samples_in_frame = 0;
881 float temp_buffer[960];
886 if (samples_read > max_samples_in_frame) {
887 max_samples_in_frame = samples_read;
891 for (
int j = 0; j < samples_read; j++) {
892 mix_buffer[j] += temp_buffer[j];
896 samples_mixed = max_samples_in_frame;
899 log_debug_every(LOG_RATE_DEFAULT,
900 "Audio mixer DISABLED (--no-audio-mixer): simple mixing, samples=%d for client %u", samples_mixed,
907 STOP_TIMER_AND_LOG_EVERY(dev, NS_PER_SEC_INT, 5 * NS_PER_MS_INT,
"mix_%u",
"Mixer for client %u: took",
917 log_dev_every(4500 * US_PER_MS_INT,
918 "Server mixer iteration #%d for client %u: samples_mixed=%d, opus_frame_accumulated=%d/%d",
919 mixer_debug_count, client_id_snapshot, samples_mixed, opus_frame_accumulated,
OPUS_FRAME_SAMPLES);
923 START_TIMER(
"accum_%u", client_id_snapshot);
926 int samples_to_copy = (samples_mixed <= space_available) ? samples_mixed : space_available;
929 if (samples_to_copy > 0) {
930 SAFE_MEMCPY(opus_frame_buffer + opus_frame_accumulated,
932 samples_to_copy *
sizeof(
float));
933 opus_frame_accumulated += samples_to_copy;
936 STOP_TIMER_AND_LOG_EVERY(dev, NS_PER_SEC_INT, 2 * NS_PER_MS_INT,
"accum_%u",
"Accumulate for client %u: took",
944 bool apply_backpressure =
false;
946 if (++backpressure_check_counter >= 100) {
947 backpressure_check_counter = 0;
951 apply_backpressure = (queue_depth > 50);
953 if (apply_backpressure) {
954 log_warn_every(4500 * US_PER_MS_INT,
955 "Audio backpressure for client %u: queue depth %zu packets (%.1fs buffered)",
956 client_id_snapshot, queue_depth, (
float)queue_depth / 50.0f);
960 if (apply_backpressure) {
964 opus_frame_accumulated = 0;
965 platform_sleep_ns(5800 * NS_PER_US_INT);
970 uint8_t opus_buffer[1024];
972 START_TIMER(
"opus_encode_%u", client_id_snapshot);
977 STOP_TIMER_AND_LOG_EVERY(dev, NS_PER_SEC_INT, 10 * NS_PER_MS_INT,
"opus_encode_%u",
978 "Opus encode for client %u: took", client_id_snapshot);
982 float peak = 0.0f, rms = 0.0f;
984 float abs_val = fabsf(opus_frame_buffer[i]);
987 rms += opus_frame_buffer[i] * opus_frame_buffer[i];
991 server_audio_frame_count++;
992 if (server_audio_frame_count <= 5 || server_audio_frame_count % 20 == 0) {
994 log_dev_every(4500 * US_PER_MS_INT,
995 "Server audio frame #%d for client %u: samples_mixed=%d, Peak=%.6f, RMS=%.6f, opus_size=%d, "
996 "first4=[%.4f,%.4f,%.4f,%.4f]",
997 server_audio_frame_count, client_id_snapshot, samples_mixed, peak, rms, opus_size,
998 opus_frame_buffer[0], opus_frame_buffer[1], opus_frame_buffer[2], opus_frame_buffer[3]);
1004 opus_frame_accumulated = 0;
1006 if (opus_size <= 0) {
1007 log_error(
"Failed to encode audio to Opus for client %u: opus_size=%d", client_id_snapshot, opus_size);
1010 START_TIMER(
"audio_queue_%u", client_id_snapshot);
1012 int result =
packet_queue_enqueue(audio_queue_snapshot, PACKET_TYPE_AUDIO_OPUS_BATCH, opus_buffer,
1013 (
size_t)opus_size, 0,
true);
1015 STOP_TIMER_AND_LOG_EVERY(dev, NS_PER_SEC_INT, 1 * NS_PER_MS_INT,
"audio_queue_%u",
1016 "Audio queue for client %u: took", client_id_snapshot);
1019 log_debug(
"Failed to queue Opus audio for client %u", client_id_snapshot);
1035 log_debug(
"Audio render thread stopped for client %u", thread_client_id);
1260 SET_ERRNO(ERROR_INVALID_PARAM,
"Client is NULL");
1264 log_debug(
"Stopping render threads for client %u", client->client_id);
1267 atomic_store(&client->video_render_thread_running,
false);
1268 atomic_store(&client->audio_render_thread_running,
false);
1274 if (asciichat_thread_is_initialized(&client->video_render_thread)) {
1275 log_debug(
"Joining video render thread for client %u", client->client_id);
1277 if (is_shutting_down) {
1280 log_debug(
"Shutdown mode: joining video render thread for client %u (no timeout)", client->client_id);
1283 log_warn(
"Video render thread for client %u failed to join during shutdown: %s", client->client_id,
1284 SAFE_STRERROR(result));
1287 log_debug(
"Calling asciichat_thread_join for video thread of client %u", client->client_id);
1289 log_debug(
"asciichat_thread_join returned %d for video thread of client %u", result, client->client_id);
1294 log_debug(
"Video render thread joined for client %u", client->client_id);
1296 }
else if (result != -2) {
1297 if (is_shutting_down) {
1298 log_warn(
"Failed to join video render thread for client %u during shutdown (continuing): %s", client->client_id,
1299 SAFE_STRERROR(result));
1301 log_error(
"Failed to join video render thread for client %u: %s", client->client_id, SAFE_STRERROR(result));
1305 asciichat_thread_init(&client->video_render_thread);
1308 if (asciichat_thread_is_initialized(&client->audio_render_thread)) {
1310 if (is_shutting_down) {
1313 log_debug(
"Shutdown mode: joining audio render thread for client %u (no timeout)", client->client_id);
1316 log_warn(
"Audio render thread for client %u failed to join during shutdown: %s", client->client_id,
1317 SAFE_STRERROR(result));
1325 log_debug(
"Audio render thread joined for client %u", client->client_id);
1327 }
else if (result != -2) {
1328 if (is_shutting_down) {
1329 log_warn(
"Failed to join audio render thread for client %u during shutdown (continuing): %s", client->client_id,
1330 SAFE_STRERROR(result));
1332 log_error(
"Failed to join audio render thread for client %u: %s", client->client_id, SAFE_STRERROR(result));
1336 asciichat_thread_init(&client->audio_render_thread);
1343 log_debug(
"Successfully destroyed render threads for client %u", client->client_id);