ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
Protocol Handler

📡 Network packet processing and protocol implementation More...

Files

file  protocol.c
 ðŸ“¡ Server packet processor: client communication handling, protocol state management, and packet dispatch
 
file  protocol.h
 Server packet processing and protocol implementation.
 

Functions

void handle_audio_opus_batch_packet (client_info_t *client, const void *data, size_t len)
 Process AUDIO_OPUS_BATCH packet - efficient Opus-encoded audio batch from client.
 
void handle_audio_opus_packet (client_info_t *client, const void *data, size_t len)
 Process AUDIO_OPUS packet - decode single Opus frame from client.
 

Detailed Description

📡 Network packet processing and protocol implementation

Server Protocol README

Overview

The protocol handler module processes all incoming packets from clients, validates packet structure, updates client state, and stores media data for render threads. It serves as the communication bridge between clients and the server, translating network packets into actionable state changes and media data storage.

Implementation: src/server/protocol.c, src/server/protocol.h

Key Responsibilities:

  • Parse and validate incoming packets from clients
  • Update client state based on received packet data
  • Store media data (video frames, audio samples) in client buffers
  • Coordinate with other modules for media processing
  • Generate appropriate server responses to client requests
  • Maintain protocol compliance and packet format standards

Packet Processing Architecture

Processing Flow

Packet processing follows a three-stage pipeline:

Stage 1: Packet Reception (in client.c receive thread):

// Receive raw packet data from socket
int packet_len = recv_with_timeout(client->socket, buffer, sizeof(buffer), timeout);
// Validate packet header (magic number, CRC32, size)
if (validate_packet_header(buffer, packet_len) < 0) {
log_warn("Invalid packet header from client %u", client_id);
return -1;
}
// Decrypt packet if encryption enabled
if (client->crypto_initialized) {
decrypt_packet(buffer, packet_len, &client->crypto_ctx);
}
// Dispatch to appropriate handler based on packet type
dispatch_packet_to_handler(client_id, buffer, packet_len);
#define log_warn(...)
Log a WARN message.
ssize_t recv_with_timeout(socket_t sockfd, void *buf, size_t len, int timeout_seconds)
Receive data with timeout.
Definition network.c:272

Stage 2: Handler Function (this module):

// Validate packet payload structure
if (packet_len < sizeof(packet_type_t)) {
log_warn("Packet too small from client %u", client_id);
return -1;
}
// Parse packet type
packet_type_t type = get_packet_type(buffer);
// Route to handler function based on type
switch (type) {
handle_client_join_packet(client_id, buffer, packet_len);
break;
handle_image_frame_packet(client_id, buffer, packet_len);
break;
// ... other packet types ...
}
packet_type_t
Network protocol packet type enumeration.
Definition packet.h:281
@ PACKET_TYPE_IMAGE_FRAME
Complete RGB image with dimensions.
Definition packet.h:288
@ PACKET_TYPE_CLIENT_JOIN
Client announces capability to send media.
Definition packet.h:300
void handle_client_join_packet(client_info_t *client, const void *data, size_t len)
Process CLIENT_JOIN packet - client announces identity and capabilities.
void handle_image_frame_packet(client_info_t *client, void *data, size_t len)
Process IMAGE_FRAME packet - store client's video data for rendering.

Stage 3: Response Generation (via packet queues):

// Queue response packet for delivery
packet_queue_enqueue(client->video_packet_queue, response_packet, response_len);
// Send thread will deliver packet asynchronously
// (no blocking I/O in handler functions)
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.

Supported Packet Types

Client Lifecycle Packets

PACKET_TYPE_CLIENT_JOIN:

  • Initial client capabilities and identity
  • Contains display name, terminal capabilities, client version
  • Triggers client state initialization
  • Generates server welcome response

PACKET_TYPE_CLIENT_LEAVE:

  • Clean disconnect notification
  • Triggers graceful client cleanup
  • No response required

PACKET_TYPE_CLIENT_CAPABILITIES:

  • Terminal capabilities and preferences update
  • Contains color depth, palette preferences, UTF-8 support
  • Updates client state for frame generation
  • Triggers frame regeneration with new capabilities

Media Streaming Packets

PACKET_TYPE_STREAM_START:

  • Signals client is ready to send audio/video
  • Contains stream type flags (audio, video, both)
  • Enables render threads for this client
  • Triggers frame generation activation

PACKET_TYPE_STREAM_STOP:

  • Signals client is stopping audio/video transmission
  • Disables render threads for this client
  • Clears media buffers

PACKET_TYPE_IMAGE_FRAME:

  • Raw RGB video frame data
  • Contains frame dimensions, timestamp, compressed data
  • Stored in client's incoming video buffer
  • Processed by video render thread

PACKET_TYPE_AUDIO_BATCH:

  • Batched audio samples (efficient format)
  • Contains sample count, sample rate, compressed audio data
  • Stored in client's incoming audio ring buffer
  • Processed by audio render thread

PACKET_TYPE_AUDIO (legacy):

  • Single audio sample packet (deprecated)
  • Replaced by PACKET_TYPE_AUDIO_BATCH
  • Still supported for backwards compatibility

Control Protocol Packets

PACKET_TYPE_PING:

  • Client keepalive request
  • Generates PACKET_TYPE_PONG response
  • Used for connection health monitoring

PACKET_TYPE_PONG:

  • Server keepalive response
  • Acknowledges client ping
  • Confirms bidirectional connectivity

State Management

Client State Updates

All client state modifications use the snapshot pattern:

// Handler function updates client state
const void *data, size_t len)
{
// 1. Get client (with read lock)
client_info_t *client = NULL;
client_id);
// 2. Acquire per-client mutex
// 3. Update client state fields
client->width = packet->width;
client->height = packet->height;
client->capabilities = packet->capabilities;
client->palette = packet->palette;
// 4. Release mutex immediately
// 5. Release read lock
// 6. Process using local copies (no locks held)
log_info("Client %u capabilities updated: %dx%d, palette=%s", client_id,
packet->width, packet->height, palette_name(packet->palette));
}
unsigned int uint32_t
Definition common.h:58
#define log_info(...)
Log an INFO message.
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
Definition mutex.h:140
#define rwlock_rdlock(lock)
Acquire a read lock (with debug tracking in debug builds)
Definition rwlock.h:194
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
Definition mutex.h:175
#define rwlock_rdunlock(lock)
Release a read lock (with debug tracking in debug builds)
Definition rwlock.h:231
void handle_client_capabilities_packet(client_info_t *client, const void *data, size_t len)
Process CLIENT_CAPABILITIES packet - configure client-specific rendering.
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.
Per-client state structure for server-side client management.
atomic_ushort height
atomic_ushort width
mutex_t client_state_mutex
client_info_t * clients_by_id
uthash head pointer for O(1) client_id -> client_info_t* lookups

State Fields:

  • Terminal dimensions (width, height)
  • Terminal capabilities (color depth, UTF-8 support, palette)
  • Stream status (audio enabled, video enabled)
  • Client identity (display name, client version)
  • Connection metadata (connect time, last packet time)

Media Buffer Coordination

Video Frames:

  • Stored in client->incoming_video_buffer (double-buffer system)
  • Thread-safe buffer access via mutex
  • Processed by video render thread at 60fps
  • Frame dropping under load (keep only latest frame)

Audio Samples:

  • Stored in client->incoming_audio_buffer (lock-free ring buffer)
  • Thread-safe lock-free operations
  • Processed by audio render thread at 172fps
  • Automatic overflow handling (dropped samples logged)

Packet Validation Strategy

All handlers validate packets before processing:

const void *data, size_t len)
{
// 1. Validate packet size
if (len < sizeof(image_frame_packet_t)) {
log_warn("Image frame packet too small from client %u: %zu < %zu",
client_id, len, sizeof(image_frame_packet_t));
return;
}
// 2. Validate packet structure
const image_frame_packet_t *packet = (const image_frame_packet_t *)data;
if (packet->width == 0 || packet->height == 0) {
log_warn("Invalid frame dimensions from client %u: %ux%u",
client_id, packet->width, packet->height);
return;
}
// 3. Validate frame data size
size_t expected_size = packet->width * packet->height * 3; // RGB
if (packet->data_size > expected_size) {
log_warn("Frame data size mismatch from client %u: %zu > %zu",
client_id, packet->data_size, expected_size);
return;
}
// 4. Validate client capabilities
if (!client->video_enabled) {
log_warn("Client %u sent video but video not enabled", client_id);
return;
}
// 5. Process packet (all validations passed)
store_video_frame(client_id, packet);
}
uint32_t height
Image height in pixels.
Definition packet.h:772
uint32_t width
Image width in pixels.
Definition packet.h:770
Image frame packet structure (Packet Type 3)
Definition packet.h:768

Validation Checks:

  • Packet size matches expected structure size
  • Packet type matches handler function
  • Payload fields are within valid ranges
  • Client capabilities permit the operation
  • Buffer pointers are valid before access
  • Network byte order conversion where needed

Error Handling Philosophy

Invalid Packets:

  • Logged but don't disconnect clients
  • Malformed packets are silently dropped
  • Detailed error logging for troubleshooting
  • Protocol compliance checking prevents crashes

Buffer Allocation Failures:

  • Handled gracefully with error return
  • Client continues receiving other packets
  • Render threads handle missing frames gracefully
  • No server crash from allocation failures

Network Errors During Responses:

  • Don't affect client state
  • Logged for debugging
  • Send thread handles connection loss detection
  • No blocking I/O in handler functions

Shutdown Conditions:

  • Handlers check g_server_should_exit flag
  • Early return from handlers during shutdown
  • Avoids error spam during cleanup
  • Clean thread exit without errors

Integration with Other Modules

Integration with client.c

Called By:

  • Receive threads call protocol handler functions
  • Receive thread receives packets and dispatches to handlers

Provides To:

  • Client state update functions
  • Media buffer storage functions
  • Packet validation utilities

Integration with render.c

Consumed By:

  • Render threads consume media data stored by handlers
  • Video render thread reads from incoming_video_buffer
  • Audio render thread reads from incoming_audio_buffer

Provides To:

  • Video frame data for frame generation
  • Audio sample data for audio mixing
  • Client state for frame customization

Integration with stream.c

Used By:

  • Stream generation uses client capabilities set by handlers
  • Frame generation adapts to terminal dimensions
  • Palette selection based on client preferences

Provides To:

  • Client terminal capabilities
  • Client rendering preferences
  • Frame generation parameters

Performance Characteristics

Processing Speed:

  • Packet validation is O(1) per packet
  • State updates use minimal locking
  • Media storage uses efficient buffer systems
  • No blocking I/O in handler functions

Memory Usage:

  • Packet buffers: Temporary allocations (freed after processing)
  • Media buffers: Per-client fixed allocations
  • State structures: Minimal overhead per client

Concurrency:

  • Per-client handler isolation (no shared state)
  • Thread-safe state updates (mutex protection)
  • Lock-free media buffers where possible
  • Minimal lock contention (snapshot pattern)

Best Practices

DO:

  • Always validate packet size before accessing fields
  • Use snapshot pattern for client state access
  • Check client capabilities before operations
  • Handle errors gracefully without disconnecting clients
  • Use atomic operations for thread control flags

DON'T:

  • Don't access packet fields without size validation
  • Don't hold locks during media processing
  • Don't perform blocking I/O in handler functions
  • Don't skip error checking on state updates
  • Don't ignore shutdown flags during processing
See also
src/server/protocol.c
src/server/protocol.h
Server Overview
Client Management
Render Threads
topic_packet_types

Function Documentation

◆ handle_audio_opus_batch_packet()

void handle_audio_opus_batch_packet ( client_info_t client,
const void *  data,
size_t  len 
)

#include <protocol.c>

Process AUDIO_OPUS_BATCH packet - efficient Opus-encoded audio batch from client.

Handles batched Opus-encoded audio frames sent by the client. This provides ~98% bandwidth reduction compared to raw PCM audio while maintaining excellent audio quality.

PACKET STRUCTURE EXPECTED:

  • opus_batch_header_t (16 bytes):
    • sample_rate (4 bytes)
    • frame_duration (4 bytes)
    • frame_count (4 bytes)
    • reserved (4 bytes)
  • Opus encoded data (variable size, typically ~60 bytes/frame @ 24kbps)

PROCESSING FLOW:

  1. Parse batch header
  2. Decode each Opus frame back to PCM samples (960 samples/frame @ 48kHz)
  3. Write decoded samples to client's incoming audio buffer

PERFORMANCE:

  • Bandwidth: ~60 bytes/frame vs ~3840 bytes/frame for raw PCM (64:1 compression)
  • Quality: Excellent at 24 kbps VOIP mode
  • Latency: 20ms frames for real-time audio
Parameters
clientClient that sent the audio packet
dataPacket payload data
lenPacket payload length
See also
av_receive_audio_opus_batch() For Packet Types parsing
opus_codec_decode() For Opus decoding
handle_audio_batch_packet() For raw PCM Audio batch handling

Definition at line 1156 of file server/protocol.c.

1156 {
1157 log_debug_every(LOG_RATE_SLOW, "Received Opus audio batch from client %u (len=%zu)", atomic_load(&client->client_id),
1158 len);
1159
1160 VALIDATE_NOTNULL_DATA(client, data, "AUDIO_OPUS_BATCH");
1161 VALIDATE_AUDIO_STREAM_ENABLED(client, "AUDIO_OPUS_BATCH");
1162 VALIDATE_RESOURCE_INITIALIZED(client, client->opus_decoder, "Opus decoder");
1163
1164 // Parse Opus batch packet
1165 const uint8_t *opus_data = NULL;
1166 size_t opus_size = 0;
1167 const uint16_t *frame_sizes = NULL;
1168 int sample_rate = 0;
1169 int frame_duration = 0;
1170 int frame_count = 0;
1171
1172 asciichat_error_t result = packet_parse_opus_batch(data, len, &opus_data, &opus_size, &frame_sizes, &sample_rate,
1173 &frame_duration, &frame_count);
1174
1175 if (result != ASCIICHAT_OK) {
1176 disconnect_client_for_bad_data(client, "Failed to parse AUDIO_OPUS_BATCH packet");
1177 return;
1178 }
1179
1180 VALIDATE_NONZERO(client, frame_count, "frame_count", "AUDIO_OPUS_BATCH");
1181 VALIDATE_NONZERO(client, opus_size, "opus_size", "AUDIO_OPUS_BATCH");
1182
1183 // Calculate samples per frame (20ms @ 48kHz = 960 samples)
1184 int samples_per_frame = (sample_rate * frame_duration) / 1000;
1185 VALIDATE_RANGE(client, samples_per_frame, 1, 4096, "samples_per_frame", "AUDIO_OPUS_BATCH");
1186
1187 // Use static buffer for common case to avoid malloc in hot path
1188 // Typical batches: 1-32 frames of 960 samples = up to 30,720 samples
1189 // Static buffer holds 32 frames @ 48kHz 20ms = 30,720 samples (120KB)
1190#define OPUS_DECODE_STATIC_MAX_SAMPLES (32 * 960)
1191 static float static_decode_buffer[OPUS_DECODE_STATIC_MAX_SAMPLES];
1192
1193 size_t total_samples = (size_t)samples_per_frame * (size_t)frame_count;
1194 float *decoded_samples;
1195 bool used_malloc = false;
1196
1197 if (total_samples <= OPUS_DECODE_STATIC_MAX_SAMPLES) {
1198 decoded_samples = static_decode_buffer;
1199 } else {
1200 // Unusual large batch - fall back to malloc
1201 log_warn("Client %u: Large audio batch requires malloc (%zu samples)", atomic_load(&client->client_id),
1202 total_samples);
1203 decoded_samples = SAFE_MALLOC(total_samples * sizeof(float), float *);
1204 if (!decoded_samples) {
1205 SET_ERRNO(ERROR_MEMORY, "Failed to allocate buffer for Opus decoded samples");
1206 return;
1207 }
1208 used_malloc = true;
1209 }
1210
1211 // Decode each Opus frame using frame_sizes array
1212 int total_decoded = 0;
1213 size_t opus_offset = 0;
1214
1215 for (int i = 0; i < frame_count; i++) {
1216 // Get exact frame size from frame_sizes array (convert from network byte order)
1217 size_t frame_size = (size_t)NET_TO_HOST_U16(frame_sizes[i]);
1218
1219 // DEBUG: Log the actual bytes of each Opus frame
1220 if (frame_size > 0) {
1221 log_debug_every(LOG_RATE_DEFAULT, "Client %u: Opus frame %d: size=%zu, first_bytes=[0x%02x,0x%02x,0x%02x,0x%02x]",
1222 atomic_load(&client->client_id), i, frame_size, opus_data[opus_offset] & 0xFF,
1223 frame_size > 1 ? (opus_data[opus_offset + 1] & 0xFF) : 0,
1224 frame_size > 2 ? (opus_data[opus_offset + 2] & 0xFF) : 0,
1225 frame_size > 3 ? (opus_data[opus_offset + 3] & 0xFF) : 0);
1226 }
1227
1228 if (opus_offset + frame_size > opus_size) {
1229 log_error("Client %u: Frame %d size overflow (offset=%zu, frame_size=%zu, total=%zu)",
1230 atomic_load(&client->client_id), i + 1, opus_offset, frame_size, opus_size);
1231 if (used_malloc) {
1232 SAFE_FREE(decoded_samples);
1233 }
1234 return;
1235 }
1236
1237 // SECURITY: Bounds check before writing decoded samples to prevent buffer overflow
1238 // An attacker could send malicious Opus frames that decode to more samples than expected
1239 if ((size_t)total_decoded + (size_t)samples_per_frame > total_samples) {
1240 log_error("Client %u: Opus decode would overflow buffer (decoded=%d, frame_samples=%d, max=%zu)",
1241 atomic_load(&client->client_id), total_decoded, samples_per_frame, total_samples);
1242 if (used_malloc) {
1243 SAFE_FREE(decoded_samples);
1244 }
1245 return;
1246 }
1247
1248 int decoded_count = opus_codec_decode((opus_codec_t *)client->opus_decoder, &opus_data[opus_offset], frame_size,
1249 &decoded_samples[total_decoded], samples_per_frame);
1250
1251 if (decoded_count < 0) {
1252 log_error("Client %u: Opus decoding failed for frame %d/%d (size=%zu)", atomic_load(&client->client_id), i + 1,
1253 frame_count, frame_size);
1254 if (used_malloc) {
1255 SAFE_FREE(decoded_samples);
1256 }
1257 return;
1258 }
1259
1260 total_decoded += decoded_count;
1261 opus_offset += frame_size;
1262 }
1263
1264 log_debug_every(LOG_RATE_DEFAULT, "Client %u: Decoded %d Opus frames -> %d samples", atomic_load(&client->client_id),
1265 frame_count, total_decoded);
1266
1267 // DEBUG: Log sample values to detect all-zero issue
1268 static int server_decode_count = 0;
1269 server_decode_count++;
1270 if (total_decoded > 0 && (server_decode_count <= 10 || server_decode_count % 100 == 0)) {
1271 float peak = 0.0f, rms = 0.0f;
1272 for (int i = 0; i < total_decoded && i < 100; i++) {
1273 float abs_val = fabsf(decoded_samples[i]);
1274 if (abs_val > peak)
1275 peak = abs_val;
1276 rms += decoded_samples[i] * decoded_samples[i];
1277 }
1278 rms = sqrtf(rms / (total_decoded > 100 ? 100 : total_decoded));
1279 // Log first 4 bytes of Opus data to compare with client encode
1280 log_info("SERVER OPUS DECODE #%d from client %u: decoded_rms=%.6f, opus_first4=[0x%02x,0x%02x,0x%02x,0x%02x]",
1281 server_decode_count, atomic_load(&client->client_id), rms, opus_size > 0 ? opus_data[0] : 0,
1282 opus_size > 1 ? opus_data[1] : 0, opus_size > 2 ? opus_data[2] : 0, opus_size > 3 ? opus_data[3] : 0);
1283 }
1284
1285 // Write decoded samples to client's incoming audio buffer
1286 // Note: audio_ring_buffer_write returns error code, not sample count
1287 // Buffer overflow warnings are logged inside audio_ring_buffer_write if buffer is full
1288 if (client->incoming_audio_buffer && total_decoded > 0) {
1289 asciichat_error_t result = audio_ring_buffer_write(client->incoming_audio_buffer, decoded_samples, total_decoded);
1290 if (result != ASCIICHAT_OK) {
1291 log_error("Client %u: Failed to write decoded audio to buffer: %d", atomic_load(&client->client_id), result);
1292 }
1293 }
1294
1295 if (used_malloc) {
1296 SAFE_FREE(decoded_samples);
1297 }
1298}
#define NET_TO_HOST_U16(val)
Definition endian.h:116
asciichat_error_t audio_ring_buffer_write(audio_ring_buffer_t *rb, const float *data, int samples)
Write audio samples to ring buffer.
int opus_codec_decode(opus_codec_t *codec, const uint8_t *data, size_t data_len, float *out_samples, int out_num_samples)
Decode Opus audio frame.
Definition opus_codec.c:128
unsigned short uint16_t
Definition common.h:57
#define SAFE_FREE(ptr)
Definition common.h:320
#define SAFE_MALLOC(size, cast)
Definition common.h:208
unsigned char uint8_t
Definition common.h:56
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:46
@ ERROR_MEMORY
Definition error_codes.h:53
@ ASCIICHAT_OK
Definition error_codes.h:48
#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.
#define log_debug_every(interval_us, fmt,...)
Rate-limited DEBUG logging.
asciichat_error_t packet_parse_opus_batch(const void *packet_data, size_t packet_len, const uint8_t **out_opus_data, size_t *out_opus_size, const uint16_t **out_frame_sizes, int *out_sample_rate, int *out_frame_duration, int *out_frame_count)
Parse Opus audio batch packet header and extract frame data.
#define VALIDATE_RESOURCE_INITIALIZED(client, resource, resource_name)
#define VALIDATE_AUDIO_STREAM_ENABLED(client, packet_name)
#define VALIDATE_NONZERO(client, value, value_name, packet_name)
#define VALIDATE_NOTNULL_DATA(client, data, packet_name)
#define VALIDATE_RANGE(client, value, min_val, max_val, value_name, packet_name)
void disconnect_client_for_bad_data(client_info_t *client, const char *format,...)
#define OPUS_DECODE_STATIC_MAX_SAMPLES
atomic_uint client_id
audio_ring_buffer_t * incoming_audio_buffer
Opus codec context for encoding or decoding.
Definition opus_codec.h:95

References ASCIICHAT_OK, audio_ring_buffer_write(), client_info::client_id, disconnect_client_for_bad_data(), ERROR_MEMORY, client_info::incoming_audio_buffer, log_debug_every, log_error, log_info, LOG_RATE_DEFAULT, LOG_RATE_SLOW, log_warn, NET_TO_HOST_U16, opus_codec_decode(), OPUS_DECODE_STATIC_MAX_SAMPLES, client_info::opus_decoder, packet_parse_opus_batch(), SAFE_FREE, SAFE_MALLOC, SET_ERRNO, VALIDATE_AUDIO_STREAM_ENABLED, VALIDATE_NONZERO, VALIDATE_NOTNULL_DATA, VALIDATE_RANGE, and VALIDATE_RESOURCE_INITIALIZED.

Referenced by process_decrypted_packet().

◆ handle_audio_opus_packet()

void handle_audio_opus_packet ( client_info_t client,
const void *  data,
size_t  len 
)

#include <protocol.c>

Process AUDIO_OPUS packet - decode single Opus frame from client.

Handles single Opus-encoded audio frames sent by clients. The packet format includes a 16-byte header followed by the Opus-encoded data.

PACKET STRUCTURE:

  • Offset 0: sample_rate (uint32_t, native byte order - bug in client)
  • Offset 4: frame_duration (uint32_t, native byte order - bug in client)
  • Offset 8: reserved (8 bytes)
  • Offset 16: Opus-encoded audio data
Parameters
clientClient info structure
dataPacket payload
lenPacket length

Definition at line 1318 of file server/protocol.c.

1318 {
1319 log_debug_every(LOG_RATE_DEFAULT, "Received Opus audio from client %u (len=%zu)", atomic_load(&client->client_id),
1320 len);
1321
1322 if (VALIDATE_PACKET_NOT_NULL(client, data, "AUDIO_OPUS")) {
1323 return;
1324 }
1325
1326 // Minimum size: 16-byte header + at least 1 byte of Opus data
1327 if (len < 17) {
1328 disconnect_client_for_bad_data(client, "AUDIO_OPUS packet too small: %zu bytes", len);
1329 return;
1330 }
1331
1332 if (!atomic_load(&client->is_sending_audio)) {
1333 disconnect_client_for_bad_data(client, "AUDIO_OPUS received before audio stream enabled");
1334 return;
1335 }
1336
1337 if (!client->opus_decoder) {
1338 disconnect_client_for_bad_data(client, "Opus decoder not initialized");
1339 return;
1340 }
1341
1342 // Parse header (16 bytes) - convert from network byte order
1343 const uint8_t *buf = (const uint8_t *)data;
1344 uint32_t sample_rate_net, frame_duration_net;
1345 memcpy(&sample_rate_net, buf, 4);
1346 memcpy(&frame_duration_net, buf + 4, 4);
1347 uint32_t sample_rate = NET_TO_HOST_U32(sample_rate_net);
1348 uint32_t frame_duration = NET_TO_HOST_U32(frame_duration_net);
1349
1350 // Extract Opus data (after 16-byte header)
1351 const uint8_t *opus_data = buf + 16;
1352 size_t opus_size = len - 16;
1353
1354 // Validate parameters
1355 if (sample_rate == 0 || sample_rate > 192000) {
1356 disconnect_client_for_bad_data(client, "AUDIO_OPUS invalid sample_rate: %u", sample_rate);
1357 return;
1358 }
1359
1360 if (frame_duration == 0 || frame_duration > 120) {
1361 disconnect_client_for_bad_data(client, "AUDIO_OPUS invalid frame_duration: %u ms", frame_duration);
1362 return;
1363 }
1364
1365 // Calculate expected samples per frame
1366 int samples_per_frame = (int)((sample_rate * frame_duration) / 1000);
1367 if (samples_per_frame <= 0 || samples_per_frame > 5760) { // Max 120ms @ 48kHz
1368 disconnect_client_for_bad_data(client, "AUDIO_OPUS invalid samples_per_frame: %d", samples_per_frame);
1369 return;
1370 }
1371
1372 // Decode Opus frame
1373 float decoded_samples[5760]; // Max Opus frame size (120ms @ 48kHz)
1374 int decoded_count =
1375 opus_codec_decode((opus_codec_t *)client->opus_decoder, opus_data, opus_size, decoded_samples, samples_per_frame);
1376
1377 if (decoded_count < 0) {
1378 log_error("Client %u: Opus decoding failed (size=%zu)", atomic_load(&client->client_id), opus_size);
1379 return;
1380 }
1381
1382 log_debug_every(LOG_RATE_VERY_FAST, "Client %u: Decoded Opus frame -> %d samples", atomic_load(&client->client_id),
1383 decoded_count);
1384
1385 // Write decoded samples to client's incoming audio buffer
1386 if (client->incoming_audio_buffer && decoded_count > 0) {
1387 asciichat_error_t write_result =
1388 audio_ring_buffer_write(client->incoming_audio_buffer, decoded_samples, decoded_count);
1389 if (write_result != ASCIICHAT_OK) {
1390 log_error("Failed to write decoded Opus samples to buffer: %s", asciichat_error_string(write_result));
1391 }
1392 }
1393}
#define NET_TO_HOST_U32(val)
Definition endian.h:86
#define LOG_RATE_VERY_FAST
Log rate limit: 0.1 seconds (100,000 microseconds) - for very high frequency operations.
Definition log_rates.h:23
#define VALIDATE_PACKET_NOT_NULL(client, data, packet_name)
atomic_bool is_sending_audio

References ASCIICHAT_OK, audio_ring_buffer_write(), client_info::client_id, disconnect_client_for_bad_data(), client_info::incoming_audio_buffer, client_info::is_sending_audio, log_debug_every, log_error, LOG_RATE_DEFAULT, LOG_RATE_VERY_FAST, NET_TO_HOST_U32, opus_codec_decode(), client_info::opus_decoder, and VALIDATE_PACKET_NOT_NULL.