ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
video_frame.c
Go to the documentation of this file.
1
9#include <ascii-chat/common.h>
10#include <ascii-chat/asciichat_errno.h> // For asciichat_errno system
14#include <string.h>
15#include <stdlib.h>
16
18 if (client_id == 0) {
19 SET_ERRNO(ERROR_INVALID_PARAM, "Client ID is 0");
20 return NULL;
21 }
22
24
25 // Copy client_id string - we own this memory now
26 // (caller's client_id may be a stack variable that goes out of scope)
27 size_t client_id_len = strlen(client_id) + 1;
28 char *client_id_copy = SAFE_MALLOC(client_id_len, char *);
29 if (!client_id_copy) {
30 SET_ERRNO(ERROR_MEMORY, "Failed to allocate memory for client_id copy");
31 SAFE_FREE(vfb);
32 return NULL;
33 }
34 asciichat_error_t strcpy_result = SAFE_STRCPY(client_id_copy, client_id_len, client_id);
35 if (strcpy_result != ASCIICHAT_OK) {
36 log_error("Failed to copy client_id: %s", asciichat_error_string(strcpy_result));
37 SAFE_FREE(client_id_copy);
38 SAFE_FREE(vfb);
39 return NULL;
40 }
41 vfb->client_id = client_id_copy;
42 vfb->active = true;
43
44 // Initialize double buffers
45 vfb->front_buffer = &vfb->frames[0];
46 vfb->back_buffer = &vfb->frames[1];
47
48 // DEBUG: Log buffer initialization
49 log_info("VFB_INIT: vfb=%p, vfb->frames=%p, &frames[0]=%p, &frames[1]=%p, setting back_buffer=%p", (void *)vfb,
50 (void *)vfb->frames, (void *)&vfb->frames[0], (void *)&vfb->frames[1], (void *)vfb->back_buffer);
51
52 // Pre-allocate frame data buffers (2MB each for HD video)
53 const size_t frame_size = (size_t)MAX_FRAME_BUFFER_SIZE;
55
56 log_info("VFB_ALLOC_START: client_id=%s, frame_size=%zu (2MB=%zu), pool=%p", client_id, frame_size,
57 (size_t)(2 * 1024 * 1024), (void *)pool);
58
59 // Initialize frames - size starts at 0 until actual data is written!
60 vfb->frames[0].size = 0; // Start with 0 - will be set when data is written
61 vfb->frames[1].size = 0; // Start with 0 - will be set when data is written
62 vfb->frames[0].data = NULL;
63 vfb->frames[1].data = NULL;
64
65 // Initialize allocated_buffer_size to 0 - will be set AFTER successful allocation
66 vfb->allocated_buffer_size = 0;
67
68 // Attempt pool allocation
69 if (pool) {
70 vfb->frames[0].data = buffer_pool_alloc(pool, frame_size);
71 vfb->frames[1].data = buffer_pool_alloc(pool, frame_size);
72 log_info("VFB_POOL_ALLOC: frame[0].data=%p, frame[1].data=%p (from pool)", (void *)vfb->frames[0].data,
73 (void *)vfb->frames[1].data);
74 } else {
75 log_warn("VFB_NO_POOL: pool is NULL, will use malloc fallback");
76 }
77
78 // Fallback to aligned malloc if pool allocation failed or returned undersized buffer
79 // Note: buffer_pool_alloc may return undersized buffers if pool is exhausted and falls back to SAFE_MALLOC
80 if (!vfb->frames[0].data || !vfb->frames[1].data) {
81 log_info("VFB_MALLOC_FALLBACK: frame[0]=%s, frame[1]=%s", vfb->frames[0].data ? "OK" : "allocating",
82 vfb->frames[1].data ? "OK" : "allocating");
83 // 64-byte cache-line alignment improves performance for large video frames
84 // CRITICAL: Always use SAFE_MALLOC_ALIGNED for fallback to guarantee minimum size and alignment
85 if (!vfb->frames[0].data) {
86 vfb->frames[0].data = SAFE_MALLOC_ALIGNED(frame_size, 64, void *);
87 if (vfb->frames[0].data) {
88 log_info("VFB_MALLOC_FRAME0: allocated %zu bytes with 64-byte alignment", frame_size);
89 }
90 }
91 if (!vfb->frames[1].data) {
92 vfb->frames[1].data = SAFE_MALLOC_ALIGNED(frame_size, 64, void *);
93 if (vfb->frames[1].data) {
94 log_info("VFB_MALLOC_FRAME1: allocated %zu bytes with 64-byte alignment", frame_size);
95 }
96 }
97 log_info("VFB_MALLOC_RESULT: frame[0].data=%p, frame[1].data=%p (allocated with malloc)",
98 (void *)vfb->frames[0].data, (void *)vfb->frames[1].data);
99 }
100
101 // CRITICAL: Only set allocated_buffer_size after BOTH buffers are successfully allocated
102 // This prevents claiming 2MB buffers when actual allocation failed or returned smaller buffers
103 if (!vfb->frames[0].data || !vfb->frames[1].data) {
104 log_error("VFB_ALLOC_FAILED: frame[0].data=%p, frame[1].data=%p (cannot create frame buffer)",
105 (void *)vfb->frames[0].data, (void *)vfb->frames[1].data);
106 SET_ERRNO(ERROR_MEMORY, "Failed to allocate video frame buffers");
108 return NULL;
109 }
110
111 // CRITICAL: Verify we actually got 2MB buffers, not undersized fallbacks
112 // The buffer pool may return undersized buffers if exhausted, but we REQUIRE full-sized allocations
113 // Do this by checking if allocations came from fallback or if pool returned a pointer we got
114 // Since we can't check actual size from buffer_pool_alloc(), we enforce that both allocations succeeded
115 // and use SAFE_MALLOC_ALIGNED as fallback which guarantees correct size
116
117 // After our allocation logic above:
118 // - Pool allocations go through buffer_pool_alloc (may be undersized)
119 // - Fallback allocations use SAFE_MALLOC_ALIGNED (guaranteed 2MB)
120 // We verify BOTH succeeded with the fallback already ensuring size
121
122 vfb->allocated_buffer_size = frame_size; // Set to requested size
123
124 log_info("VFB_ALLOC_FINAL: allocated_buffer_size=%zu (requested=%zu), frame[0].data=%p, frame[1].data=%p",
125 vfb->allocated_buffer_size, frame_size, (void *)vfb->frames[0].data, (void *)vfb->frames[1].data);
126
127 log_info("VFB_ALLOC_SUCCESS: client_id=%s, allocated_buffer_size=%zu, frames[0].data=%p, frames[1].data=%p",
128 client_id, vfb->allocated_buffer_size, (void *)vfb->frames[0].data, (void *)vfb->frames[1].data);
129
130 // When buffers are allocated from the pool, they may contain leftover data from previous clients
131 // This ensures frames with size=0 are truly empty, preventing ghost frames during reconnection
132 if (vfb->frames[0].data) {
133 memset(vfb->frames[0].data, 0, frame_size);
134 }
135 if (vfb->frames[1].data) {
136 memset(vfb->frames[1].data, 0, frame_size);
137 }
138
139 // Initialize synchronization
140 log_info("VFB_MUTEX_INIT_START: About to init mutex for client_id=%s", client_id);
141 if (mutex_init(&vfb->swap_mutex, "video_frame_swap") != 0) {
142 log_error("VFB_MUTEX_INIT_FAILED: mutex_init returned error");
143 SET_ERRNO(ERROR_PLATFORM_INIT, "Failed to initialize mutex for video frame buffer");
145 return NULL;
146 }
147 log_info("VFB_MUTEX_INIT_DONE: mutex initialized successfully");
149
150 // Initialize statistics
154
155 log_debug("Created video frame buffer for client %u with double buffering", client_id);
156
157 // DEBUG: Final state check
158 log_info("VFB_FINAL: vfb=%p, back_buffer=%p (should be %p), frames[0].data=%p, frames[1].data=%p, allocated_size=%zu",
159 (void *)vfb, (void *)vfb->back_buffer, (void *)&vfb->frames[1], (void *)vfb->frames[0].data,
160 (void *)vfb->frames[1].data, vfb->allocated_buffer_size);
161
162 NAMED_REGISTER_VIDEO_FRAME_BUFFER(vfb, "buffer", NULL);
163
164 // Register atomic fields with descriptive names - vfb is their parent
165 NAMED_REGISTER_ATOMIC(&vfb->new_frame_available, "new_frame_available_flag", (uintptr_t)(const void *)(vfb));
166 NAMED_REGISTER_ATOMIC(&vfb->total_frames_received, "total_frames_received_counter", (uintptr_t)(const void *)(vfb));
167 NAMED_REGISTER_ATOMIC(&vfb->total_frames_dropped, "total_frames_dropped_counter", (uintptr_t)(const void *)(vfb));
168 NAMED_REGISTER_ATOMIC(&vfb->last_frame_sequence, "last_frame_sequence_number", (uintptr_t)(const void *)(vfb));
169
170 return vfb;
171}
172
174 if (!vfb) {
175 SET_ERRNO(ERROR_INVALID_PARAM, "Video frame buffer is NULL");
176 return;
177 }
178
179 NAMED_UNREGISTER(vfb);
180
181 // Unregister atomic fields
186
187 vfb->active = false;
188
189 // Free frame data
191 if (vfb->frames[0].data) {
192 if (pool) {
194 } else {
195 SAFE_FREE(vfb->frames[0].data);
196 }
197 }
198 if (vfb->frames[1].data) {
199 if (pool) {
201 } else {
202 SAFE_FREE(vfb->frames[1].data);
203 }
204 }
205
207
208 // Free the allocated client_id string
209 SAFE_FREE(vfb->client_id);
210
211 SAFE_FREE(vfb);
212}
213
215 if (!vfb) {
216 SET_ERRNO(ERROR_INVALID_PARAM, "Video frame buffer is NULL");
217 return NULL;
218 }
219 if (!vfb->active) {
220 SET_ERRNO(ERROR_INVALID_STATE, "vfb->active is not true");
221 return NULL;
222 }
223
224 // Writer always owns the back buffer
225 return vfb->back_buffer;
226}
227
229 if (!vfb) {
230 SET_ERRNO(ERROR_INVALID_PARAM, "Video frame buffer or active is NULL");
231 return;
232 }
233 if (!vfb->active) {
234 SET_ERRNO(ERROR_INVALID_STATE, "vfb->active is not true");
235 return;
236 }
237
238 // Keep the unread check, pointer swap, and publication atomic with respect
239 // to readers. Checking front_buffer before taking this mutex races with a
240 // reader consuming the previous frame and with another commit swapping it.
241 mutex_lock(&vfb->swap_mutex);
242 if (atomic_load_bool(&vfb->new_frame_available) && vfb->front_buffer->size > 0) {
244 if (drops == 1 || drops % 100 == 0) {
245 log_dev_every(4500 * US_PER_MS_INT, "Dropping frame for client %u (reader too slow, total drops: %llu)",
246 vfb->client_id, (unsigned long long)drops);
247 }
248 }
249 video_frame_t *temp = vfb->front_buffer;
250 vfb->front_buffer = vfb->back_buffer;
251 vfb->back_buffer = temp;
254
256}
257
259 if (!vfb) {
260 SET_ERRNO(ERROR_INVALID_PARAM, "Video frame buffer is not active");
261 return NULL;
262 }
263 if (!vfb->active) {
264 SET_ERRNO(ERROR_INVALID_STATE, "vfb->active is not true");
265 return NULL;
266 }
267
268 mutex_lock(&vfb->swap_mutex);
271 return NULL;
272 }
274 const video_frame_t *result = vfb->front_buffer;
276
277 return result;
278}
279
281 if (!vfb || !vfb->active)
282 return NULL;
283 mutex_lock(&vfb->swap_mutex);
284 const video_frame_t *frame = vfb->front_buffer;
285 const video_frame_t *result = frame && frame->size > 0 ? frame : NULL;
287 return result;
288}
289
291 if (!vfb || !stats) {
292 SET_ERRNO(ERROR_INVALID_PARAM, "Video frame buffer or stats is NULL");
293 return;
294 }
295 if (!vfb->active) {
296 SET_ERRNO(ERROR_INVALID_STATE, "vfb->active is not true");
297 return;
298 }
299
300 stats->total_frames = atomic_load_u64(&vfb->total_frames_received);
301 stats->dropped_frames = atomic_load_u64(&vfb->total_frames_dropped);
302 stats->drop_rate = (stats->total_frames > 0) ? (float)stats->dropped_frames / (float)stats->total_frames : 0.0f;
303 stats->avg_decode_time_ns = atomic_load_u64(&vfb->avg_decode_time_ns);
304 stats->avg_render_time_ns = atomic_load_u64(&vfb->avg_render_time_ns);
305}
306
307// Simple frame swap implementation for basic cases
310
311 // Pre-allocate both frames
312 const size_t frame_size = (size_t)MAX_FRAME_BUFFER_SIZE;
313 sfs->frame_a.data = SAFE_MALLOC(frame_size, void *);
314 sfs->frame_b.data = SAFE_MALLOC(frame_size, void *);
315
317 atomic_store_bool(&sfs->use_frame_a, false); // Next write goes to frame_b
318
319 return sfs;
320}
321
323 if (!sfs) {
324 SET_ERRNO(ERROR_INVALID_PARAM, "Simple frame swap is NULL");
325 return;
326 }
327 SAFE_FREE(sfs->frame_a.data);
328 SAFE_FREE(sfs->frame_b.data);
329 SAFE_FREE(sfs);
330}
331
332void simple_frame_swap_update(simple_frame_swap_t *sfs, const void *data, size_t size) {
333 if (!sfs || !data) {
334 SET_ERRNO(ERROR_INVALID_PARAM, "Simple frame swap or data is NULL");
335 return;
336 }
337
338 // Determine which frame to write to
339 bool use_a = atomic_load_bool(&sfs->use_frame_a);
340 video_frame_t *write_frame = use_a ? &sfs->frame_a : &sfs->frame_b;
341
342 // Copy data to write frame
343 if (size <= (size_t)MAX_FRAME_BUFFER_SIZE) {
344 SAFE_MEMCPY(write_frame->data, size, data, size);
345 write_frame->size = size;
346 write_frame->capture_timestamp_ns = time_get_ns();
347
348 // Atomically update current frame pointer
349 atomic_store_u64(&sfs->current_frame, (uintptr_t)write_frame);
350
351 // Toggle for next write
352 atomic_store_bool(&sfs->use_frame_a, !use_a);
353 }
354}
355
357 if (!sfs) {
358 SET_ERRNO(ERROR_INVALID_PARAM, "Simple frame swap is NULL");
359 return NULL;
360 }
361 uintptr_t frame_ptr = atomic_load_u64(&sfs->current_frame);
362 return (const video_frame_t *)(void *)frame_ptr; // NOLINT(bugprone-casting-through-void,performance-no-int-to-ptr)
363}
asciichat_error_stats_t stats
⚠️‼️ Comprehensive thread-local error context system for ascii-chat
uint64_t atomic_fetch_add_u64(atomic_t *a, uint64_t delta)
Atomically add to a uint64_t and return the previous value.
Definition atomic.c:248
void atomic_store_bool(atomic_t *a, bool value)
Atomically store a boolean value.
Definition atomic.c:177
bool atomic_load_bool(atomic_t *a)
Atomically load a boolean value.
Definition atomic.c:169
void atomic_store_u64(atomic_t *a, uint64_t value)
Atomically store a uint64_t value.
Definition atomic.c:241
uint64_t atomic_load_u64(atomic_t *a)
Atomically load a uint64_t value.
Definition atomic.c:233
buffer_pool_t * pool
🗃️ Lock-Free Unified Memory Buffer Pool with Lazy Allocation
⚙️ Common definitions, error codes, macros, and types shared throughout the application
Named object registry for debugging — log identifiable resource names.
buffer_pool_t * buffer_pool_get_global(void)
void buffer_pool_free(buffer_pool_t *pool, const void *data, size_t size)
Free a buffer back to the pool (lock-free)
void * buffer_pool_alloc(buffer_pool_t *pool, size_t size)
Allocate a buffer from the pool (lock-free fast path)
#define MAX_FRAME_BUFFER_SIZE
Maximum frame buffer size including headers and compression.
#define SAFE_MALLOC_ALIGNED(size, alignment, cast)
Definition common.h:349
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
#define SAFE_CALLOC(count, size, cast)
Definition common.h:274
#define SAFE_STRCPY(dest, dest_size, src)
Definition common.h:471
unsigned long long uint64_t
Definition common.h:59
#define SAFE_MEMCPY(dest, dest_size, src, count)
Definition common.h:468
#define NAMED_REGISTER_ATOMIC(a, name, parent_ptr)
Register an atomic_t with automatic format specifier.
#define NAMED_REGISTER_VIDEO_FRAME_BUFFER(buf, name, parent_ptr)
Register a video frame buffer with automatic format specifier.
#define NAMED_UNREGISTER(ptr)
Unregister a pointer.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_INVALID_STATE
@ ERROR_PLATFORM_INIT
Definition error_codes.h:60
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
uint64_t time_get_ns(void)
Get current monotonic time in nanoseconds.
Definition util/time.c:108
#define US_PER_MS_INT
Definition time.h:160
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
int mutex_init(mutex_t *mutex, const char *name)
Initialize a mutex with a name.
Definition threading.c:16
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
int mutex_destroy(mutex_t *mutex)
Destroy a mutex.
Definition threading.c:22
void video_frame_buffer_destroy(video_frame_buffer_t *vfb)
Destroy frame buffer and free all resources.
const video_frame_t * video_frame_peek_latest(video_frame_buffer_t *vfb)
video_frame_t * video_frame_begin_write(video_frame_buffer_t *vfb)
Writer API: Start writing a new frame.
const video_frame_t * simple_frame_swap_get(simple_frame_swap_t *sfs)
Get current frame from simple frame swap.
simple_frame_swap_t * simple_frame_swap_create(void)
Create a simple atomic frame swap.
video_frame_buffer_t * video_frame_buffer_create(const char *client_id)
Create a double-buffered video frame manager.
Definition video_frame.c:17
void video_frame_commit(video_frame_buffer_t *vfb)
Writer API: Commit the frame and swap buffers.
const video_frame_t * video_frame_get_latest(video_frame_buffer_t *vfb)
Reader API: Get latest frame if available.
void video_frame_get_stats(video_frame_buffer_t *vfb, video_frame_stats_t *stats)
Get frame statistics for quality monitoring.
void simple_frame_swap_update(simple_frame_swap_t *sfs, const void *data, size_t size)
Update frame data in simple frame swap.
void simple_frame_swap_destroy(simple_frame_swap_t *sfs)
Destroy simple frame swap and free resources.
#define log_dev_every(interval_us, fmt,...)
Rate-limited DEV logging.
Definition log/log.h:699
int frame
Definition splash.c:99
Unified buffer pool with lock-free fast path.
Definition buffer_pool.h:85
Simple atomic frame swap structure.
atomic_t use_frame_a
Atomic flag: true = use frame_a next, false = use frame_b.
video_frame_t frame_b
Second pre-allocated frame.
atomic_t current_frame
Atomic pointer to current frame (stored as uint64_t)
video_frame_t frame_a
First pre-allocated frame.
Video frame buffer manager.
bool active
True if buffer is active (receiving frames)
mutex_t swap_mutex
Brief mutex for pointer swap only.
atomic_t avg_render_time_ns
Average render time in nanoseconds (atomic)
video_frame_t * back_buffer
Currently written frame (writer owns)
atomic_t new_frame_available
Atomic flag: true when new frame available.
atomic_t total_frames_dropped
Total frames dropped (atomic counter)
video_frame_t frames[2]
Pre-allocated frame structures (reused forever)
size_t allocated_buffer_size
Size of allocated data buffers (for cleanup)
video_frame_t * front_buffer
Currently displayed frame (reader owns)
atomic_t avg_decode_time_ns
Average decode time in nanoseconds (atomic)
atomic_t total_frames_received
Total frames received (atomic counter)
atomic_t last_frame_sequence
Last frame sequence number (atomic)
const char * client_id
Client ID (name) this buffer belongs to.
Frame statistics structure.
Video frame structure.
size_t size
Size of frame data in bytes.
uint64_t capture_timestamp_ns
Timestamp when frame was captured (nanoseconds)
void * data
Frame data pointer (points to pre-allocated buffer)
⏱️ High-precision timing utilities using sokol_time.h and uthash