ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
stream.c File Reference

🎬 Multi-client video mixer: frame generation, ASCII conversion, and per-client personalized rendering More...

Go to the source code of this file.

Data Structures

struct  image_source_t
 Image source structure for multi-client video mixing. More...
 

Functions

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.
 
int queue_audio_for_client (client_info_t *client, const void *audio_data, size_t data_size)
 Queue ASCII frame for delivery to specific client.
 
bool any_clients_sending_video (void)
 Check if any connected clients are currently sending video.
 

Variables

rwlock_t g_client_manager_rwlock
 Reader-writer lock protecting the global client manager.
 
client_manager_t g_client_manager
 Global client manager singleton - central coordination point.
 

Detailed Description

🎬 Multi-client video mixer: frame generation, ASCII conversion, and per-client personalized rendering

1. Collect video frames from all active clientsclientsCreate composite video layouts (single client, 2x2, 3x3 grids))Generate client-specific ASCII art with terminal capability awarenessawarenessProcess latest frames from double-buffer system for real-time performanceperformanceManage memory efficiently with buffer pools and zero-copy operationsoperationsSupport advanced rendering modes (half-block, color, custom palettes) VIDEO MIXING ARCHITECTURE:

The mixing system operates in several stages:

  1. FRAME COLLECTION:
    • Scans all active clients for available video frames
    • Uses per-client double-buffer system for smooth frame handling
    • Implements aggressive frame dropping to maintain real-time performance
    • Always uses the latest available frame for professional-grade performance
  2. LAYOUT CALCULATION:
    • Determines grid size based on number of active video sources
    • Supports: single (1x1), side-by-side (2x1), 2x2, 3x2, 3x3 layouts
    • Calculates aspect-ratio preserving dimensions for each cell
  3. COMPOSITE GENERATION:
    • Creates composite image with proper aspect ratio handling
    • Places each client's video in appropriate grid cell
    • Supports both normal and half-block rendering modes
  4. ASCII CONVERSION:
    • Converts composite to ASCII using client-specific capabilities
    • Respects terminal color support, palette preferences, UTF-8 capability
    • Generates ANSI escape sequences for color output
  5. PACKET GENERATION:
    • Wraps ASCII frames in protocol packets with metadata
    • Includes checksums, dimensions, and capability flags
    • Queues packets for delivery via client send threads

PER-CLIENT CUSTOMIZATION:

Unlike traditional video mixing that generates one output, this system creates personalized frames for each client:

TERMINAL CAPABILITY AWARENESS:

  • Color depth: 1-bit (mono), 8-color, 16-color, 256-color, 24-bit RGB
  • Character support: ASCII-only vs UTF-8 box drawing
  • Render modes: foreground, background, half-block (2x resolution)
  • Custom ASCII palettes: brightness-to-character mapping

PERFORMANCE OPTIMIZATIONS:

  • Per-client palette caches (avoid repeated initialization)
  • Double-buffer system for smooth frame delivery
  • Buffer pool allocation to reduce malloc/free overhead
  • Aggressive frame dropping under load
  • Zero-copy operations where possible

THREADING AND CONCURRENCY:

This module operates in a high-concurrency environment:

THREAD SAFETY MECHANISMS:

  • Double-buffer thread safety (atomic operations)
  • Reader-writer locks on client manager (allows concurrent reads)
  • Buffer pool thread safety (lock-free where possible)
  • Atomic snapshot operations for client state

PERFORMANCE CHARACTERISTICS:

  • Supports 60fps per client with linear scaling
  • Handles burst traffic with frame buffer overruns
  • Minimal CPU overhead with double-buffer system
  • Memory usage scales with number of active clients

FRAME BUFFER MANAGEMENT:

DOUBLE-BUFFER STRATEGY: Each client uses a double-buffer system for smooth frame delivery:

  • Always provides the latest available frame
  • No frame caching complexity or stale data concerns
  • Professional-grade real-time performance like Zoom/Google Meet
  • Memory managed via buffer pools

BUFFER OVERFLOW HANDLING: When clients send frames faster than processing:

  • Aggressive frame dropping (keep only latest)
  • Logarithmic drop rate based on buffer occupancy
  • Maintains real-time performance under load

INTEGRATION WITH OTHER MODULES:

  • render.c: Calls frame generation functions at 60fps per client
  • client.c: Provides client state and capabilities
  • protocol.c: Stores incoming video frames in per-client buffers
  • video/: Performs actual RGB-to-ASCII conversion
  • palette.c: Manages client-specific character palettes

WHY THIS MODULAR DESIGN:

The original server.c contained all video mixing logic inline, making it:

  • Impossible to understand the video pipeline
  • Difficult to optimize performance bottlenecks
  • Hard to add new rendering features
  • Challenging to debug frame generation issues

This separation provides:

  • Clear video processing pipeline
  • Isolated performance optimization
  • Easier feature development
  • Better error isolation and debugging

MEMORY MANAGEMENT PHILOSOPHY:

  • All allocations use buffer pools where possible
  • Frame data is reference-counted (avoid copies)
  • Double-buffer system eliminates need for frame copies
  • Automatic cleanup on client disconnect
  • Graceful degradation on allocation failures
Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
September 2025
Version
2.0 (Post-Modularization)
See also
render.c For frame generation scheduling
video/ For RGB-to-ASCII conversion implementation
palette.c For client Character Palettes management

Definition in file stream.c.

Function Documentation

◆ any_clients_sending_video()

bool any_clients_sending_video ( void  )

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 1192 of file stream.c.

1192 {
1193 // LOCK OPTIMIZATION: Don't acquire rwlock - all fields we access are atomic
1194 // client_id, active, is_sending_video are all atomic variables
1195
1196 // Iterate through all client slots
1197 for (int i = 0; i < MAX_CLIENTS; i++) {
1199
1200 // Skip uninitialized clients (atomic read)
1201 if (atomic_load(&client->client_id) == 0) {
1202 continue;
1203 }
1204
1205 // Check if client is active and sending video (both atomic reads)
1206 bool is_active = atomic_load(&client->active);
1207 bool is_sending = atomic_load(&client->is_sending_video);
1208
1209 if (is_active && is_sending) {
1210 return true;
1211 }
1212 }
1213
1214 return false;
1215}
#define MAX_CLIENTS
Maximum possible clients (static array size) - actual runtime limit set by –max-clients (1-32)
Definition limits.h:23
client_manager_t g_client_manager
Global client manager singleton - central coordination point.
Per-client state structure for server-side client management.
atomic_uint client_id
atomic_bool is_sending_video
atomic_bool active
client_info_t clients[MAX_CLIENTS]
Array of client_info_t structures (backing storage)

References client_info::active, client_info::client_id, client_manager_t::clients, 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 ( 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.

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 pool 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 pool 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 pool - 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 buffer operations
calculate_fit_dimensions_pixel() For aspect ratio calculations

Definition at line 959 of file stream.c.

961 {
962 (void)wants_stretch; // Unused - we always handle aspect ratio ourselves
963
964 // Initialize output parameters
965 if (out_grid_changed) {
966 *out_grid_changed = false;
967 }
968 if (out_sources_count) {
969 *out_sources_count = 0;
970 }
971
972 if (!out_size || width == 0 || height == 0) {
974 "Invalid parameters for create_mixed_ascii_frame_for_client: width=%u, height=%u, out_size=%p", width,
975 height, out_size);
976 return NULL;
977 }
978
979 // Collect all active clients and their image sources
981 int source_count = collect_video_sources(sources, MAX_CLIENTS);
982
983 // Count sources that actually have video data
984 int sources_with_video = 0;
985 for (int i = 0; i < source_count; i++) {
986 if (sources[i].has_video && sources[i].image) {
987 sources_with_video++;
988 }
989 }
990
991 // Return the source count for debugging/tracking
992 if (out_sources_count) {
993 *out_sources_count = sources_with_video;
994 }
995
996 // GRID LAYOUT CHANGE DETECTION:
997 // Check if the number of active video sources has changed
998 // NOTE: We only UPDATE the count and SIGNAL the change via out parameter
999 // Broadcasting CLEAR_CONSOLE must happen AFTER the new frames are written to buffers
1000 // to prevent race condition where CLEAR arrives before new frame is ready
1001 int previous_count = atomic_load(&g_previous_active_video_count);
1002 if (sources_with_video != previous_count) {
1003 // Use compare-and-swap to ensure only ONE thread detects the change
1004 if (atomic_compare_exchange_strong(&g_previous_active_video_count, &previous_count, sources_with_video)) {
1005 log_info(
1006 "Grid layout changing: %d -> %d active video sources - caller will broadcast clear AFTER buffering frame",
1007 previous_count, sources_with_video);
1008 if (out_grid_changed) {
1009 *out_grid_changed = true; // Signal to caller
1010 }
1011 }
1012 }
1013
1014 // No active video sources - don't generate placeholder frames
1015 image_t *composite = NULL;
1016
1017 if (sources_with_video == 0) {
1018 *out_size = 0;
1019 // No active video sources for client - this isn't an error.
1020 // Return NULL to indicate no frame should be sent
1021 return NULL;
1022 }
1023
1024 if (sources_with_video == 1) {
1025 // Single source handling
1026 composite = create_single_source_composite(sources, source_count, target_client_id, width, height);
1027 } else {
1028 // Multiple sources - create grid layout
1029 composite =
1030 create_multi_source_composite(sources, source_count, sources_with_video, target_client_id, width, height);
1031 }
1032
1033 char *out = NULL;
1034
1035 if (!composite) {
1036 SET_ERRNO(ERROR_INVALID_STATE, "Per-client %u: Failed to create composite image", target_client_id);
1037 *out_size = 0;
1038 out = NULL;
1039 }
1040
1041 // Convert composite to ASCII using client capabilities
1042 char *ascii_frame = convert_composite_to_ascii(composite, target_client_id, width, height);
1043
1044 if (ascii_frame) {
1045 *out_size = strlen(ascii_frame);
1046 out = ascii_frame;
1047 } else {
1048 SET_ERRNO(ERROR_TERMINAL, "Per-client %u: Failed to convert image to ASCII", target_client_id);
1049 *out_size = 0;
1050 }
1051
1052 if (composite) {
1053 image_destroy_to_pool(composite);
1054 }
1055 for (int i = 0; i < source_count; i++) {
1056 if (sources[i].image) {
1057 image_destroy_to_pool(sources[i].image);
1058 }
1059 }
1060
1061 return out;
1062}
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
@ ERROR_INVALID_STATE
@ ERROR_INVALID_PARAM
@ ERROR_TERMINAL
Definition error_codes.h:66
#define log_info(...)
Log an INFO message.
void image_destroy_to_pool(image_t *image)
Destroy an image allocated from buffer pool.
Image source structure for multi-client video mixing.
Definition stream.c:229
Image structure.

References ERROR_INVALID_PARAM, ERROR_INVALID_STATE, ERROR_TERMINAL, image_destroy_to_pool(), log_info, MAX_CLIENTS, and SET_ERRNO.

Referenced by client_video_render_thread().

◆ queue_audio_for_client()

int queue_audio_for_client ( client_info_t client,
const void *  audio_data,
size_t  data_size 
)

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_enqueue() copies data (safe to free temp buffer)
  • No memory leaks on success or failure paths
  • Buffer pool 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_enqueue() For underlying queue implementation
asciichat_crc32() For checksum generation

Queue audio data for delivery to specific client

Queues mixed audio data for delivery to a specific client. This is a simple wrapper around the packet queue system for audio delivery.

AUDIO PIPELINE INTEGRATION:

  • Called by audio mixing threads after combining multiple client streams
  • Audio data is already in final format (float samples, mixed)
  • No additional processing required at this stage

PACKET DETAILS:

  • Uses PACKET_TYPE_AUDIO packet type
  • Audio data is raw float samples
  • No special headers or metadata required
  • Sample rate and format determined by audio system

DELIVERY CHARACTERISTICS:

  • Higher priority than video packets (lower latency)
  • Uses client's audio packet queue
  • Send thread prioritizes audio over video
  • Queue overflow drops oldest audio to maintain real-time

ERROR HANDLING:

  • Invalid parameters: Return -1 immediately
  • Queue overflow: Handled by packet queue (drops old data)
  • Client shutdown: Graceful failure
Parameters
clientTarget client for audio delivery
audio_dataMixed audio samples (float format)
data_sizeSize of audio data in bytes
Returns
0 on successful queuing, -1 on error
Note
Audio has priority over video in send thread
Real-time audio requires queue overflow handling
See also
packet_queue_enqueue() For underlying queue implementation
mixer.c For Audio mixing implementation

Definition at line 1172 of file stream.c.

1172 {
1173 if (!client || !client->audio_queue || !audio_data || data_size == 0) {
1174 return -1;
1175 }
1176
1177 return packet_queue_enqueue(client->audio_queue, PACKET_TYPE_AUDIO, audio_data, data_size, 0, true);
1178}
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.
@ PACKET_TYPE_AUDIO
Single audio packet (legacy)
Definition packet.h:291
packet_queue_t * audio_queue

References client_info::audio_queue, packet_queue_enqueue(), and PACKET_TYPE_AUDIO.

Variable Documentation

◆ g_client_manager

client_manager_t g_client_manager
extern

Global client manager singleton - central coordination point.

Global client manager for signal handler access.

This is the primary data structure for managing all connected clients. It serves as the bridge between main.c's connection accept loop and the per-client threading architecture.

STRUCTURE COMPONENTS:

  • clients[]: Array backing storage for client_info_t structs
  • client_hashtable: O(1) lookup table for client_id -> client_info_t*
  • client_count: Current number of active clients
  • mutex: Legacy mutex (mostly replaced by rwlock)
  • next_client_id: Monotonic counter for unique client identification

THREAD SAFETY: Protected by g_client_manager_rwlock for concurrent access

Definition at line 189 of file src/server/client.c.

189{0};

Referenced by __attribute__(), any_clients_sending_video(), broadcast_server_state_to_all_clients(), find_client_by_socket(), send_server_state_to_client(), stats_logger_thread(), and update_server_stats().

◆ g_client_manager_rwlock

rwlock_t g_client_manager_rwlock
extern

Reader-writer lock protecting the global client manager.

This lock enables high-performance concurrent access patterns:

  • Multiple threads can read client data simultaneously (stats, rendering)
  • Only one thread can modify client data at a time (add/remove operations)
  • Eliminates contention between read-heavy operations

USAGE PATTERN:

Definition at line 204 of file src/server/client.c.

204{0};

Referenced by __attribute__(), broadcast_server_state_to_all_clients(), find_client_by_socket(), stats_logger_thread(), and update_server_stats().