ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
video_frame.c
Go to the documentation of this file.
1
8#include "video_frame.h"
9#include "common.h"
10#include "asciichat_errno.h" // For asciichat_errno system
11#include "buffer_pool.h"
12#include <string.h>
13#include <stdlib.h>
14
16 if (client_id == 0) {
17 SET_ERRNO(ERROR_INVALID_PARAM, "Client ID is 0");
18 return NULL;
19 }
20
22
23 vfb->client_id = client_id;
24 vfb->active = true;
25
26 // Initialize double buffers
27 vfb->front_buffer = &vfb->frames[0];
28 vfb->back_buffer = &vfb->frames[1];
29
30 // Pre-allocate frame data buffers (2MB each for HD video)
31 const size_t frame_size = (size_t)2 * 1024 * 1024;
33
34 // Initialize frames - size starts at 0 until actual data is written!
35 vfb->frames[0].size = 0; // Start with 0 - will be set when data is written
36 vfb->frames[1].size = 0; // Start with 0 - will be set when data is written
37 vfb->frames[0].data = NULL;
38 vfb->frames[1].data = NULL;
39
40 // Store the allocated buffer size for cleanup (different from data size!)
41 vfb->allocated_buffer_size = frame_size;
42
43 if (pool) {
44 vfb->frames[0].data = buffer_pool_alloc(pool, frame_size);
45 vfb->frames[1].data = buffer_pool_alloc(pool, frame_size);
46 }
47
48 if (!vfb->frames[0].data || !vfb->frames[1].data) {
49 // Fallback to aligned malloc if pool is exhausted or not available
50 // 64-byte cache-line alignment improves performance for large video frames
51 if (!vfb->frames[0].data)
52 vfb->frames[0].data = SAFE_MALLOC_ALIGNED(frame_size, 64, void *);
53 if (!vfb->frames[1].data)
54 vfb->frames[1].data = SAFE_MALLOC_ALIGNED(frame_size, 64, void *);
55 }
56
57 // When buffers are allocated from the pool, they may contain leftover data from previous clients
58 // This ensures frames with size=0 are truly empty, preventing ghost frames during reconnection
59 if (vfb->frames[0].data) {
60 memset(vfb->frames[0].data, 0, frame_size);
61 }
62 if (vfb->frames[1].data) {
63 memset(vfb->frames[1].data, 0, frame_size);
64 }
65
66 // Initialize synchronization
67 if (mutex_init(&vfb->swap_mutex) != 0) {
68 SET_ERRNO(ERROR_PLATFORM_INIT, "Failed to initialize mutex for video frame buffer");
70 return NULL;
71 }
72 atomic_store(&vfb->new_frame_available, false);
73
74 // Initialize statistics
75 atomic_store(&vfb->total_frames_received, 0);
76 atomic_store(&vfb->total_frames_dropped, 0);
77 atomic_store(&vfb->last_frame_sequence, 0);
78
79 log_debug("Created video frame buffer for client %u with double buffering", client_id);
80 return vfb;
81}
82
84 if (!vfb) {
85 SET_ERRNO(ERROR_INVALID_PARAM, "Video frame buffer is NULL");
86 return;
87 }
88
89 vfb->active = false;
90
91 // Free frame data
93 if (vfb->frames[0].data) {
94 if (pool) {
96 } else {
97 SAFE_FREE(vfb->frames[0].data);
98 }
99 }
100 if (vfb->frames[1].data) {
101 if (pool) {
103 } else {
104 SAFE_FREE(vfb->frames[1].data);
105 }
106 }
107
109 SAFE_FREE(vfb);
110}
111
113 if (!vfb) {
114 SET_ERRNO(ERROR_INVALID_PARAM, "Video frame buffer is NULL");
115 return NULL;
116 }
117 if (!vfb->active) {
118 SET_ERRNO(ERROR_INVALID_STATE, "vfb->active is not true");
119 return NULL;
120 }
121
122 // Writer always owns the back buffer
123 return vfb->back_buffer;
124}
125
127 if (!vfb) {
128 SET_ERRNO(ERROR_INVALID_PARAM, "Video frame buffer or active is NULL");
129 return;
130 }
131 if (!vfb->active) {
132 SET_ERRNO(ERROR_INVALID_STATE, "vfb->active is not true");
133 return;
134 }
135
136 // Check if reader has consumed the previous frame
137 if (atomic_load(&vfb->new_frame_available)) {
138 // Reader hasn't consumed yet - we're dropping a frame
139 uint64_t drops = atomic_fetch_add(&vfb->total_frames_dropped, 1) + 1;
140 // Throttle drop logging - only log every 100 drops to avoid spam
141 if (drops == 1 || drops % 100 == 0) {
142 log_debug("Dropping frame for client %u (reader too slow, total drops: %llu)", vfb->client_id,
143 (unsigned long long)drops);
144 }
145 }
146
147 // Atomic pointer swap - NO MUTEX NEEDED since video_frame_commit() is only called by one thread (the receive thread)
148 video_frame_t *temp = vfb->front_buffer;
149 vfb->front_buffer = vfb->back_buffer;
150 vfb->back_buffer = temp;
151
152 // Signal reader that new frame is available
153 atomic_store(&vfb->new_frame_available, true);
154 atomic_fetch_add(&vfb->total_frames_received, 1);
155}
156
158 if (!vfb) {
159 SET_ERRNO(ERROR_INVALID_PARAM, "Video frame buffer is not active");
160 return NULL;
161 }
162 if (!vfb->active) {
163 SET_ERRNO(ERROR_INVALID_STATE, "vfb->active is not true");
164 return NULL;
165 }
166
167 // Mark that we've consumed any new frame
168 atomic_exchange(&vfb->new_frame_available, false);
169
170 // Always return the front buffer (last valid frame)
171 // This prevents flickering - we keep showing the last frame
172 // until a new one arrives
173 return vfb->front_buffer;
174}
175
177 if (!vfb || !stats) {
178 SET_ERRNO(ERROR_INVALID_PARAM, "Video frame buffer or stats is NULL");
179 return;
180 }
181 if (!vfb->active) {
182 SET_ERRNO(ERROR_INVALID_STATE, "vfb->active is not true");
183 return;
184 }
185
186 stats->total_frames = atomic_load(&vfb->total_frames_received);
187 stats->dropped_frames = atomic_load(&vfb->total_frames_dropped);
188 stats->drop_rate = (stats->total_frames > 0) ? (float)stats->dropped_frames / (float)stats->total_frames : 0.0f;
189 stats->avg_decode_time_us = atomic_load(&vfb->avg_decode_time_us);
190 stats->avg_render_time_us = atomic_load(&vfb->avg_render_time_us);
191}
192
193// Simple frame swap implementation for basic cases
196
197 // Pre-allocate both frames
198 const size_t frame_size = (size_t)2 * 1024 * 1024;
199 sfs->frame_a.data = SAFE_MALLOC(frame_size, void *);
200 sfs->frame_b.data = SAFE_MALLOC(frame_size, void *);
201
202 atomic_store(&sfs->current_frame, (uintptr_t)&sfs->frame_a);
203 atomic_store(&sfs->use_frame_a, false); // Next write goes to frame_b
204
205 return sfs;
206}
207
209 if (!sfs) {
210 SET_ERRNO(ERROR_INVALID_PARAM, "Simple frame swap is NULL");
211 return;
212 }
213 SAFE_FREE(sfs->frame_a.data);
214 SAFE_FREE(sfs->frame_b.data);
215 SAFE_FREE(sfs);
216}
217
218void simple_frame_swap_update(simple_frame_swap_t *sfs, const void *data, size_t size) {
219 if (!sfs || !data) {
220 SET_ERRNO(ERROR_INVALID_PARAM, "Simple frame swap or data is NULL");
221 return;
222 }
223
224 // Determine which frame to write to
225 bool use_a = atomic_load(&sfs->use_frame_a);
226 video_frame_t *write_frame = use_a ? &sfs->frame_a : &sfs->frame_b;
227
228 // Copy data to write frame
229 if (size <= (size_t)2 * 1024 * 1024) {
230 SAFE_MEMCPY(write_frame->data, size, data, size);
231 write_frame->size = size;
232 write_frame->capture_timestamp_us = (uint64_t)time(NULL) * 1000000;
233
234 // Atomically update current frame pointer
235 atomic_store(&sfs->current_frame, (uintptr_t)write_frame);
236
237 // Toggle for next write
238 atomic_store(&sfs->use_frame_a, !use_a);
239 }
240}
241
243 if (!sfs) {
244 SET_ERRNO(ERROR_INVALID_PARAM, "Simple frame swap is NULL");
245 return NULL;
246 }
247 uintptr_t frame_ptr = atomic_load(&sfs->current_frame);
248 return (const video_frame_t *)(void *)frame_ptr; // NOLINT(bugprone-casting-through-void,performance-no-int-to-ptr)
249}
⚠️‼️ Error and/or exit() when things go bad.
🗃️ Lock-Free Unified Memory Buffer Pool with Lazy Allocation
buffer_pool_t * buffer_pool_get_global(void)
void buffer_pool_free(buffer_pool_t *pool, 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 SAFE_MALLOC_ALIGNED(size, alignment, cast)
Definition common.h:293
unsigned int uint32_t
Definition common.h:58
#define SAFE_FREE(ptr)
Definition common.h:320
#define SAFE_MALLOC(size, cast)
Definition common.h:208
#define SAFE_CALLOC(count, size, cast)
Definition common.h:218
unsigned long long uint64_t
Definition common.h:59
#define SAFE_MEMCPY(dest, dest_size, src, count)
Definition common.h:388
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
@ ERROR_INVALID_STATE
@ ERROR_PLATFORM_INIT
Definition error_codes.h:57
@ ERROR_INVALID_PARAM
#define log_debug(...)
Log a DEBUG message.
int mutex_init(mutex_t *mutex)
Initialize a mutex.
int mutex_destroy(mutex_t *mutex)
Destroy a mutex.
void video_frame_buffer_destroy(video_frame_buffer_t *vfb)
Destroy frame buffer and free all resources.
Definition video_frame.c:83
video_frame_buffer_t * video_frame_buffer_create(uint32_t client_id)
Create a double-buffered video frame manager.
Definition video_frame.c:15
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.
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.
Unified buffer pool with lock-free fast path.
Definition buffer_pool.h:90
Simple atomic frame swap structure.
atomic_uintptr_t current_frame
Atomic pointer to current frame.
video_frame_t frame_b
Second pre-allocated frame.
video_frame_t frame_a
First pre-allocated frame.
atomic_bool use_frame_a
Atomic flag: true = use frame_a next, false = use frame_b.
Video frame buffer manager.
bool active
True if buffer is active (receiving frames)
mutex_t swap_mutex
Brief mutex for pointer swap only.
video_frame_t * back_buffer
Currently written frame (writer owns)
atomic_uint avg_render_time_us
Average render time in microseconds (atomic)
atomic_ullong total_frames_received
Total frames received (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)
atomic_ullong last_frame_sequence
Last frame sequence number (atomic)
atomic_uint avg_decode_time_us
Average decode time in microseconds (atomic)
video_frame_t * front_buffer
Currently displayed frame (reader owns)
uint32_t client_id
Client ID this buffer belongs to.
atomic_bool new_frame_available
Atomic flag: true when new frame available.
atomic_ullong total_frames_dropped
Total frames dropped (atomic counter)
Frame statistics structure.
uint64_t dropped_frames
Total frames dropped (due to buffer full or errors)
uint64_t total_frames
Total frames received since creation.
uint32_t avg_render_time_us
Average frame render time in microseconds.
float drop_rate
Frame drop rate (dropped_frames / total_frames, 0.0-1.0)
uint32_t avg_decode_time_us
Average frame decode time in microseconds.
Video frame structure.
uint64_t capture_timestamp_us
Timestamp when frame was captured (microseconds)
size_t size
Size of frame data in bytes.
void * data
Frame data pointer (points to pre-allocated buffer)