ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
stream.h File Reference

Multi-client video mixing and ASCII frame generation. More...

Go to the source code of this file.

Functions

char * create_mixed_ascii_frame_for_client (const char *target_client_id, unsigned short width, unsigned short height, bool wants_stretch, size_t *out_size, bool *out_grid_changed, int *out_sources_count, acip_render_settings_t *out_rendering)
 Generate personalized ASCII frame for a specific client.
bool any_clients_sending_video (void)
 Queue ASCII frame for delivery to specific client.

Variables

atomic_t g_should_exit
 Global application exit flag (shared across all modes).

Detailed Description

Multi-client video mixing and ASCII frame generation.

Definition in file src/server/stream.h.

Function Documentation

◆ any_clients_sending_video()

bool any_clients_sending_video ( void )

Queue ASCII frame for delivery to specific client.

Packages a generated ASCII frame into a protocol packet and queues it for asynchronous delivery to the target client. This function handles all protocol details including headers, checksums, and metadata.

PACKET STRUCTURE CREATED:

  • ascii_frame_packet_t header containing:
    • width, height: Client terminal dimensions
    • original_size: Uncompressed frame size
    • compressed_size: Compressed size (currently 0)
    • checksum: CRC32 of frame data for integrity
    • flags: Capability flags (color support, etc.)
  • Frame data: Complete ASCII art string with ANSI codes

PROTOCOL INTEGRATION:

  • Converts dimensions to network byte order
  • Generates CRC32 checksum for error detection
  • Sets appropriate capability flags based on client
  • Uses PACKET_TYPE_ASCII_FRAME packet type

DELIVERY MECHANISM:

  • Queues packet in client's video packet queue
  • Send thread delivers packet asynchronously
  • Queue overflow handling prevents memory exhaustion
  • Error conditions are logged but don't affect other clients

MEMORY MANAGEMENT:

  • Creates temporary packet buffer for header+data combination
  • packet_queue_push_copy() copies data (safe to free temp buffer)
  • No memory leaks on success or failure paths
  • buffer domain allocation not used here (temporary buffer)

ERROR HANDLING:

  • Invalid parameters: Return -1, no side effects
  • Memory allocation failure: Logged, return -1
  • Queue overflow: Logged as debug (expected under load)
  • Client shutdown: Graceful failure without error spam
Parameters
clientTarget client for frame delivery
ascii_frameGenerated ASCII art string (null-terminated)
frame_sizeSize of ASCII frame in bytes
Returns
0 on successful queuing, -1 on error
Note
Frame delivery happens asynchronously via send thread
Queue overflow drops frames to maintain real-time performance
Checksums enable client-side integrity verification
See also
packet_queue_push_copy() For underlying queue implementation
asciichat_crc32() For checksum generation

Check if any connected clients are currently sending video

This function scans all active clients to determine if at least one is sending video frames. Used by render threads to avoid generating frames when no video sources are available (e.g., during webcam warmup).

LOCK OPTIMIZATION: Uses atomic reads only, no rwlock acquisition This is safe because client_id, active, and is_sending_video are all atomics

Returns
true if at least one client has is_sending_video flag set, false otherwise

Definition at line 1283 of file src/server/stream.c.

1283 {
1284 // LOCK OPTIMIZATION: Don't acquire rwlock - all fields we access are atomic
1285 // client_id, active, is_sending_video are all atomic variables
1286
1287 // Iterate through all client slots
1288 for (int i = 0; i < MAX_CLIENTS; i++) {
1289 client_info_t *client = &g_client_manager.clients[i];
1290
1291 // Skip uninitialized clients (atomic read)
1292 if (client->client_id[0] == '\0') {
1293 continue;
1294 }
1295
1296 // Check if client is active and sending video (both atomic reads)
1297 bool is_active = atomic_load_bool(&client->active);
1298 bool is_sending = atomic_load_bool(&client->is_sending_video);
1299
1300 if (is_active && is_sending) {
1301 return true;
1302 }
1303 }
1304
1305 return false;
1306}
#define MAX_CLIENTS
Maximum possible clients (static array size) - actual runtime limit set by –max-clients (1-128).
struct client_info client_info_t
client_manager_t g_client_manager
Hash table entry for client packet dispatch.
char client_id[MAX_CLIENT_ID_LEN]

References client_info::active, client_info::client_id, g_client_manager, client_info::is_sending_video, and MAX_CLIENTS.

Referenced by client_video_render_thread().

◆ create_mixed_ascii_frame_for_client()

char * create_mixed_ascii_frame_for_client ( const char * target_client_id,
unsigned short width,
unsigned short height,
bool wants_stretch,
size_t * out_size,
bool * out_grid_changed,
int * out_sources_count,
acip_render_settings_t * out_rendering )

Generate personalized ASCII frame for a specific client.

This is the core video mixing function that creates customized ASCII art frames for individual clients. It collects video from all active clients, creates an appropriate grid layout, and converts to ASCII using the target client's terminal capabilities.

ALGORITHM OVERVIEW:

  1. FRAME COLLECTION PHASE:
    • Scan all active clients for available video frames
    • Use double-buffer system to get latest frames
    • Implement aggressive frame dropping under load (maintains real-time)
    • Always use the most current frame for professional-grade quality
  2. LAYOUT CALCULATION PHASE:
    • Determine grid dimensions based on active client count
    • Calculate cell sizes with aspect ratio preservation
    • Handle special cases: single client, multiple clients
  3. COMPOSITE GENERATION PHASE:
    • Create composite image with appropriate dimensions
    • Place each client's video in grid cell with proper scaling
    • Support both normal and half-block rendering modes
  4. ASCII CONVERSION PHASE:
    • Convert composite to ASCII using client capabilities
    • Apply client-specific palette and color settings
    • Generate ANSI escape sequences if supported

PERFORMANCE OPTIMIZATIONS:

FRAME BUFFER MANAGEMENT:

  • Aggressive frame dropping: always read latest available
  • Double-buffer system: ensures smooth frame delivery
  • buffer domain allocation: reduces malloc/free overhead
  • Zero-copy operations where possible

CONCURRENCY OPTIMIZATIONS:

  • Reader locks on client manager (allows parallel execution)
  • Double-buffer atomic operations (lock-free access)
  • Atomic snapshots of client state
  • Thread-safe buffer operations

CLIENT CAPABILITY INTEGRATION:

TERMINAL AWARENESS:

  • Respects client's color depth capabilities
  • Uses client's preferred ASCII palette
  • Handles UTF-8 vs ASCII-only terminals
  • Supports half-block mode for 2x vertical resolution

RENDERING MODES SUPPORTED:

  • RENDER_MODE_FOREGROUND: Traditional ASCII art
  • RENDER_MODE_BACKGROUND: Inverted colors for better contrast
  • RENDER_MODE_HALF_BLOCK: Unicode half-blocks for 2x resolution

ERROR HANDLING STRATEGY:

  • Invalid dimensions: Logged and frame generation aborted
  • Memory allocation failures: Graceful degradation, cleanup resources
  • Client disconnections: Skip client in current frame, continue
  • Buffer overruns: Frame dropping maintains real-time performance
  • Capability missing: Wait for capabilities before generating frames

MEMORY MANAGEMENT:

  • All image operations use buffer domain allocation
  • Frame data is properly freed after use
  • Cached frames use deep copies (prevent corruption)
  • Automatic cleanup on function exit
Parameters
target_client_idClient who will receive this customized frame
widthTerminal width in characters for this client
heightTerminal height in characters for this client
wants_stretchUnused parameter (aspect ratio always preserved)
out_sizePointer to store resulting ASCII frame size
Returns
Allocated ASCII frame string (caller must free), or NULL on error
Note
This function is called at 60fps per client by render threads
Generated frame is customized for target client's capabilities
Memory allocated from buffer domain - caller must free properly
Warning
Function performs extensive validation - invalid input returns NULL
Client capabilities must be received before frame generation
See also
ascii_convert_with_capabilities() For ASCII conversion implementation
framebuffer_read_multi_frame() For frame Owned Buffers operations
calculate_fit_dimensions_pixel() For aspect ratio calculations

Definition at line 1024 of file src/server/stream.c.

1026 {
1027 (void)wants_stretch; // Unused - we always handle aspect ratio ourselves
1028
1029 // Register the stream counter once even when per-client render workers enter
1030 // this function concurrently.
1031 static _Atomic bool stream_atomics_registered = false;
1032 if (!atomic_exchange_explicit(&stream_atomics_registered, true, memory_order_acq_rel)) {
1033 NAMED_REGISTER_ATOMIC(&g_previous_active_video_count, "server_video_source_count_for_layout_detection", NULL);
1034 }
1035
1036 uint64_t frame_gen_start_ns = time_get_ns();
1037
1038 // Initialize output parameters
1039 if (out_grid_changed) {
1040 *out_grid_changed = false;
1041 }
1042 if (out_sources_count) {
1043 *out_sources_count = 0;
1044 }
1045
1046 if (!out_size || width == 0 || height == 0) {
1047 SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameters: width=%u, height=%u, out_size=%p", width, height, out_size);
1048 return NULL;
1049 }
1050
1051 // Collect all active clients and their image sources
1052 image_source_t sources[MAX_CLIENTS];
1053 uint64_t collect_start_ns = time_get_ns();
1054 int source_count = collect_video_sources(sources, MAX_CLIENTS);
1055 uint64_t collect_end_ns = time_get_ns();
1056
1057 // Count sources that actually have video data
1058 int sources_with_video = 0;
1059 for (int i = 0; i < source_count; i++) {
1060 if (sources[i].has_video && sources[i].image) {
1061 sources_with_video++;
1062 }
1063 }
1064
1065 log_dev_every(333 * NS_PER_MS_INT, "FRAME_GEN_START: target_client=%s sources=%d collect=%.1fms", target_client_id,
1066 sources_with_video, (collect_end_ns - collect_start_ns) / NS_PER_MS);
1067
1068 // Return the source count for debugging/tracking
1069 if (out_sources_count) {
1070 *out_sources_count = sources_with_video;
1071 }
1072
1073 // GRID LAYOUT CHANGE DETECTION:
1074 // Check if the number of active video sources has changed
1075 // NOTE: We only UPDATE the count and SIGNAL the change via out parameter
1076 // Broadcasting CLEAR_CONSOLE must happen AFTER the new frames are written to buffers
1077 // to prevent race condition where CLEAR arrives before new frame is ready
1078 uint64_t previous_count = atomic_load_u64(&g_previous_active_video_count);
1079 if (sources_with_video != (int)previous_count) {
1080 // Use compare-and-swap to ensure only ONE thread detects the change
1081 if (atomic_cas_u64(&g_previous_active_video_count, &previous_count, (uint64_t)sources_with_video)) {
1084 "Grid layout changing: %d -> %d active video sources - caller will broadcast clear AFTER buffering frame",
1085 previous_count, sources_with_video);
1086 if (out_grid_changed) {
1087 *out_grid_changed = true; // Signal to caller
1088 }
1089 }
1090 }
1091
1092 // No active video sources - don't generate placeholder frames
1093 image_t *composite = NULL;
1094
1095 if (sources_with_video == 0) {
1096 *out_size = 0;
1097 // No active video sources for client - this isn't an error.
1098 // Return NULL to indicate no frame should be sent
1099 return NULL;
1100 }
1101
1102 if (sources_with_video == 1) {
1103 // Single source handling - create composite and convert to ASCII
1104 // Note: create_single_source_composite returns a reference to sources[i].image
1105 // which could be modified by other threads. Make a copy to prevent concurrent
1106 // modification during ascii_convert_with_capabilities.
1107 image_t *single_source = create_single_source_composite(sources, source_count, target_client_id, width, height);
1108 if (single_source) {
1109 composite = image_new_copy(single_source);
1110 if (!composite) {
1111 SET_ERRNO(ERROR_MEMORY, "Failed to copy single source composite");
1112 *out_size = 0;
1113 return NULL;
1114 }
1115 }
1116 } else {
1117 // Multiple sources - create grid layout
1118 composite =
1119 create_multi_source_composite(sources, source_count, sources_with_video, target_client_id, width, height);
1120 }
1121
1122 char *out = NULL;
1123
1124 if (!composite) {
1125 SET_ERRNO(ERROR_INVALID_STATE, "Per-client %s: Failed to create composite image", target_client_id);
1126 *out_size = 0;
1127 return NULL;
1128 }
1129
1130 // Convert composite to ASCII using client capabilities
1131 // Pass terminal dimensions so the frame can be padded to full width
1132 char *ascii_frame = convert_composite_to_ascii(composite, target_client_id, width, height, out_rendering, sources,
1133 source_count, sources_with_video);
1134
1135 if (ascii_frame) {
1136 // The frame should have been null-terminated by the padding functions.
1137 // Use strlen() which is optimized and reliable
1138 size_t ascii_len = strlen(ascii_frame);
1139
1140 // Safety check: don't accept unreasonably large frames (10MB limit)
1141 if (ascii_len > 10 * 1024 * 1024) {
1142 log_error("Frame size exceeds 10MB safety limit (possible buffer overflow)");
1143 SET_ERRNO(ERROR_INVALID_PARAM, "Frame size exceeds 10MB");
1144 SAFE_FREE(ascii_frame);
1145 image_destroy(composite);
1146 *out_size = 0;
1147 return NULL;
1148 }
1149
1150 *out_size = ascii_len; // Renderer owns complete rows; never truncate to a color reset.
1151
1152 log_dev_every(LOG_RATE_SLOW, "Final frame size=%zu bytes for client %s", *out_size, target_client_id);
1153
1154 // Debug: Log the last 50 bytes of the frame to see what's really there
1155 if (*out_size >= 50) {
1156 char hex_buf[SERVER_STREAM_HEX_BUFFER_SIZE] = {0};
1157 size_t hex_len = 0;
1158 const uint8_t *last_bytes = (const uint8_t *)ascii_frame + (*out_size - 50);
1159 for (int i = 0; i < 50 && hex_len < sizeof(hex_buf) - 5; i++) {
1160 hex_len += snprintf(hex_buf + hex_len, sizeof(hex_buf) - hex_len, "%02X ", last_bytes[i]);
1161 }
1162 log_dev_every(4500 * US_PER_MS_INT, "FRAME_LAST_50_BYTES (hex): %s", hex_buf);
1163
1164 // Also log as ASCII for readability
1165 char ascii_buf[SERVER_STREAM_ASCII_BUFFER_SIZE] = {0};
1166 for (int i = 0; i < 50 && i < (int)sizeof(ascii_buf) - 1; i++) {
1167 if (last_bytes[i] >= 32 && last_bytes[i] < 127) {
1168 ascii_buf[i] = (char)last_bytes[i];
1169 } else if (last_bytes[i] == '\n') {
1170 ascii_buf[i] = 'N';
1171 } else if (last_bytes[i] == '\0') {
1172 ascii_buf[i] = '0';
1173 } else {
1174 ascii_buf[i] = '.';
1175 }
1176 }
1177 log_dev_every(4500 * US_PER_MS_INT, "FRAME_LAST_50_ASCII: %s", ascii_buf);
1178 }
1179
1180 out = ascii_frame;
1181 } else {
1182 SET_ERRNO(ERROR_TERMINAL, "Per-client %s: Failed to convert image to ASCII", target_client_id);
1183 *out_size = 0;
1184 }
1185
1186 if (composite) {
1187 image_destroy(composite);
1188 }
1189 for (int i = 0; i < source_count; i++) {
1190 if (sources[i].image) {
1191 image_destroy(sources[i].image);
1192 }
1193 }
1194
1195 uint64_t frame_gen_end_ns = time_get_ns();
1196 uint64_t frame_gen_duration_ns = frame_gen_end_ns - frame_gen_start_ns;
1197 if (frame_gen_duration_ns > 10 * NS_PER_MS_INT) { // Log if > 10ms
1198 char duration_str[TIME_DURATION_BUFFER_SIZE];
1199 time_pretty((uint64_t)((double)frame_gen_duration_ns), -1, duration_str, sizeof(duration_str));
1200 log_warn_every(LOG_RATE_DEFAULT, "SLOW_FRAME_GENERATION: Client %s full frame gen took %s", target_client_id,
1201 duration_str);
1202 }
1203
1204 return out;
1205}
#define SET_ERRNO(code,...)
unsigned int height
Definition fft.c:23
unsigned int width
Definition fft.c:23
#define SAFE_FREE(ptr)
Definition common.h:377
unsigned long long uint64_t
Definition common.h:63
unsigned char uint8_t
Definition common.h:60
#define NAMED_REGISTER_ATOMIC(a, name, parent_ptr)
Register an atomic_t with automatic format specifier.
@ ERROR_INVALID_STATE
@ ERROR_MEMORY
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
@ ERROR_TERMINAL
Definition error_codes.h:64
#define LOG_RATE_SLOW
Log rate limit: 10 seconds (10,000,000 microseconds).
Definition log_rates.h:35
#define LOG_RATE_DEFAULT
Log rate limit: 5 seconds (5,000,000 microseconds) - default for audio/video packets.
Definition log_rates.h:32
#define log_error(...)
Log an ERROR message.
Definition log/log.h:634
int time_pretty(uint64_t nanoseconds, int decimals, char *buffer, size_t buffer_size)
Format nanoseconds as a compact duration with configurable sub-minute precision.
Definition time.c:374
#define TIME_DURATION_BUFFER_SIZE
Definition time.h:40
#define NS_PER_MS_INT
Definition time.h:168
#define NS_PER_MS
Definition time.h:159
#define US_PER_MS_INT
Definition time.h:172
image_t * image_new_copy(const image_t *source)
Create a copy of an existing image.
void image_destroy(image_t *image)
Destroy an image allocated with image_new().
#define log_warn_every(interval_us, fmt,...)
Rate-limited WARN logging.
Definition log/log.h:755
#define log_dev_every(interval_us, fmt,...)
Rate-limited DEV logging.
Definition log/log.h:746
static bool size_t gotel_query_t * out
Definition queries.h:8
#define SERVER_STREAM_HEX_BUFFER_SIZE
#define SERVER_STREAM_ASCII_BUFFER_SIZE
One client's video input as seen by the compositor.
SIMD-friendly storage for one RGB pixel. r, g, and b hold the color channels and padding keeps the la...

References ERROR_INVALID_PARAM, ERROR_INVALID_STATE, ERROR_MEMORY, ERROR_TERMINAL, height, image_destroy(), image_new_copy(), log_dev_every, log_error, LOG_RATE_DEFAULT, LOG_RATE_SLOW, log_warn_every, MAX_CLIENTS, NAMED_REGISTER_ATOMIC, NS_PER_MS, NS_PER_MS_INT, out, SAFE_FREE, SERVER_STREAM_ASCII_BUFFER_SIZE, SERVER_STREAM_HEX_BUFFER_SIZE, SET_ERRNO, TIME_DURATION_BUFFER_SIZE, time_pretty(), US_PER_MS_INT, and width.

Referenced by client_video_render_thread().

Variable Documentation

◆ g_should_exit

atomic_t g_should_exit
extern

Global application exit flag (shared across all modes).

Global shutdown flag referenced by lib/logging.c and lib/debug/lock.c In production builds, this is defined in main.c and set during shutdown. For tests, we stub it out as false (tests don't perform shutdown).

Definition at line 34 of file globals.c.

34{0};

Referenced by ATOMIC_INIT_AUTO(), client_audio_render_thread(), client_video_render_thread(), handle_image_frame_h265_packet(), handle_image_frame_packet(), server_main(), should_exit(), signal_exit(), and stats_logger_thread().