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

🎬 Video frame buffer management for client-specific ASCII rendering More...

Go to the source code of this file.

Functions

video_frame_buffer_t * video_frame_buffer_create (uint32_t client_id)
 
void video_frame_buffer_destroy (video_frame_buffer_t *vfb)
 
video_frame_t * video_frame_begin_write (video_frame_buffer_t *vfb)
 
void video_frame_commit (video_frame_buffer_t *vfb)
 
const video_frame_t * video_frame_get_latest (video_frame_buffer_t *vfb)
 
void video_frame_get_stats (video_frame_buffer_t *vfb, video_frame_stats_t *stats)
 
simple_frame_swap_t * simple_frame_swap_create (void)
 
void simple_frame_swap_destroy (simple_frame_swap_t *sfs)
 
void simple_frame_swap_update (simple_frame_swap_t *sfs, const void *data, size_t size)
 
const video_frame_t * simple_frame_swap_get (simple_frame_swap_t *sfs)
 

Detailed Description

🎬 Video frame buffer management for client-specific ASCII rendering

Definition in file video_frame.c.

Function Documentation

◆ simple_frame_swap_create()

simple_frame_swap_t * simple_frame_swap_create ( void  )

Definition at line 204 of file video_frame.c.

204 {
205 simple_frame_swap_t *sfs = SAFE_CALLOC(1, sizeof(simple_frame_swap_t), simple_frame_swap_t *);
206
207 // Pre-allocate both frames
208 const size_t frame_size = (size_t)MAX_FRAME_BUFFER_SIZE;
209 sfs->frame_a.data = SAFE_MALLOC(frame_size, void *);
210 sfs->frame_b.data = SAFE_MALLOC(frame_size, void *);
211
212 atomic_store(&sfs->current_frame, (uintptr_t)&sfs->frame_a);
213 atomic_store(&sfs->use_frame_a, false); // Next write goes to frame_b
214
215 return sfs;
216}

◆ simple_frame_swap_destroy()

void simple_frame_swap_destroy ( simple_frame_swap_t *  sfs)

Definition at line 218 of file video_frame.c.

218 {
219 if (!sfs) {
220 SET_ERRNO(ERROR_INVALID_PARAM, "Simple frame swap is NULL");
221 return;
222 }
223 SAFE_FREE(sfs->frame_a.data);
224 SAFE_FREE(sfs->frame_b.data);
225 SAFE_FREE(sfs);
226}

◆ simple_frame_swap_get()

const video_frame_t * simple_frame_swap_get ( simple_frame_swap_t *  sfs)

Definition at line 252 of file video_frame.c.

252 {
253 if (!sfs) {
254 SET_ERRNO(ERROR_INVALID_PARAM, "Simple frame swap is NULL");
255 return NULL;
256 }
257 uintptr_t frame_ptr = atomic_load(&sfs->current_frame);
258 return (const video_frame_t *)(void *)frame_ptr; // NOLINT(bugprone-casting-through-void,performance-no-int-to-ptr)
259}

◆ simple_frame_swap_update()

void simple_frame_swap_update ( simple_frame_swap_t *  sfs,
const void *  data,
size_t  size 
)

Definition at line 228 of file video_frame.c.

228 {
229 if (!sfs || !data) {
230 SET_ERRNO(ERROR_INVALID_PARAM, "Simple frame swap or data is NULL");
231 return;
232 }
233
234 // Determine which frame to write to
235 bool use_a = atomic_load(&sfs->use_frame_a);
236 video_frame_t *write_frame = use_a ? &sfs->frame_a : &sfs->frame_b;
237
238 // Copy data to write frame
239 if (size <= (size_t)MAX_FRAME_BUFFER_SIZE) {
240 SAFE_MEMCPY(write_frame->data, size, data, size);
241 write_frame->size = size;
242 write_frame->capture_timestamp_ns = time_get_ns();
243
244 // Atomically update current frame pointer
245 atomic_store(&sfs->current_frame, (uintptr_t)write_frame);
246
247 // Toggle for next write
248 atomic_store(&sfs->use_frame_a, !use_a);
249 }
250}
uint64_t time_get_ns(void)
Definition util/time.c:48

References time_get_ns().

◆ video_frame_begin_write()

video_frame_t * video_frame_begin_write ( video_frame_buffer_t *  vfb)

Definition at line 113 of file video_frame.c.

113 {
114 if (!vfb) {
115 SET_ERRNO(ERROR_INVALID_PARAM, "Video frame buffer is NULL");
116 return NULL;
117 }
118 if (!vfb->active) {
119 SET_ERRNO(ERROR_INVALID_STATE, "vfb->active is not true");
120 return NULL;
121 }
122
123 // Writer always owns the back buffer
124 return vfb->back_buffer;
125}

Referenced by client_video_render_thread(), and handle_image_frame_packet().

◆ video_frame_buffer_create()

video_frame_buffer_t * video_frame_buffer_create ( uint32_t  client_id)

Definition at line 16 of file video_frame.c.

16 {
17 if (client_id == 0) {
18 SET_ERRNO(ERROR_INVALID_PARAM, "Client ID is 0");
19 return NULL;
20 }
21
22 video_frame_buffer_t *vfb = SAFE_CALLOC(1, sizeof(video_frame_buffer_t), video_frame_buffer_t *);
23
24 vfb->client_id = client_id;
25 vfb->active = true;
26
27 // Initialize double buffers
28 vfb->front_buffer = &vfb->frames[0];
29 vfb->back_buffer = &vfb->frames[1];
30
31 // Pre-allocate frame data buffers (2MB each for HD video)
32 const size_t frame_size = (size_t)MAX_FRAME_BUFFER_SIZE;
33 buffer_pool_t *pool = buffer_pool_get_global();
34
35 // Initialize frames - size starts at 0 until actual data is written!
36 vfb->frames[0].size = 0; // Start with 0 - will be set when data is written
37 vfb->frames[1].size = 0; // Start with 0 - will be set when data is written
38 vfb->frames[0].data = NULL;
39 vfb->frames[1].data = NULL;
40
41 // Store the allocated buffer size for cleanup (different from data size!)
42 vfb->allocated_buffer_size = frame_size;
43
44 if (pool) {
45 vfb->frames[0].data = buffer_pool_alloc(pool, frame_size);
46 vfb->frames[1].data = buffer_pool_alloc(pool, frame_size);
47 }
48
49 if (!vfb->frames[0].data || !vfb->frames[1].data) {
50 // Fallback to aligned malloc if pool is exhausted or not available
51 // 64-byte cache-line alignment improves performance for large video frames
52 if (!vfb->frames[0].data)
53 vfb->frames[0].data = SAFE_MALLOC_ALIGNED(frame_size, 64, void *);
54 if (!vfb->frames[1].data)
55 vfb->frames[1].data = SAFE_MALLOC_ALIGNED(frame_size, 64, void *);
56 }
57
58 // When buffers are allocated from the pool, they may contain leftover data from previous clients
59 // This ensures frames with size=0 are truly empty, preventing ghost frames during reconnection
60 if (vfb->frames[0].data) {
61 memset(vfb->frames[0].data, 0, frame_size);
62 }
63 if (vfb->frames[1].data) {
64 memset(vfb->frames[1].data, 0, frame_size);
65 }
66
67 // Initialize synchronization
68 if (mutex_init(&vfb->swap_mutex) != 0) {
69 SET_ERRNO(ERROR_PLATFORM_INIT, "Failed to initialize mutex for video frame buffer");
71 return NULL;
72 }
73 atomic_store(&vfb->new_frame_available, false);
74
75 // Initialize statistics
76 atomic_store(&vfb->total_frames_received, 0);
77 atomic_store(&vfb->total_frames_dropped, 0);
78 atomic_store(&vfb->last_frame_sequence, 0);
79
80 log_debug("Created video frame buffer for client %u with double buffering", client_id);
81 return vfb;
82}
buffer_pool_t * buffer_pool_get_global(void)
void * buffer_pool_alloc(buffer_pool_t *pool, size_t size)
Definition buffer_pool.c:99
int mutex_init(mutex_t *mutex)
Definition threading.c:16
void video_frame_buffer_destroy(video_frame_buffer_t *vfb)
Definition video_frame.c:84

References buffer_pool_alloc(), buffer_pool_get_global(), mutex_init(), and video_frame_buffer_destroy().

Referenced by add_client(), and add_webrtc_client().

◆ video_frame_buffer_destroy()

void video_frame_buffer_destroy ( video_frame_buffer_t *  vfb)

Definition at line 84 of file video_frame.c.

84 {
85 if (!vfb) {
86 SET_ERRNO(ERROR_INVALID_PARAM, "Video frame buffer is NULL");
87 return;
88 }
89
90 vfb->active = false;
91
92 // Free frame data
93 buffer_pool_t *pool = buffer_pool_get_global();
94 if (vfb->frames[0].data) {
95 if (pool) {
96 buffer_pool_free(pool, vfb->frames[0].data, vfb->allocated_buffer_size);
97 } else {
98 SAFE_FREE(vfb->frames[0].data);
99 }
100 }
101 if (vfb->frames[1].data) {
102 if (pool) {
103 buffer_pool_free(pool, vfb->frames[1].data, vfb->allocated_buffer_size);
104 } else {
105 SAFE_FREE(vfb->frames[1].data);
106 }
107 }
108
109 mutex_destroy(&vfb->swap_mutex);
110 SAFE_FREE(vfb);
111}
void buffer_pool_free(buffer_pool_t *pool, void *data, size_t size)
int mutex_destroy(mutex_t *mutex)
Definition threading.c:21

References buffer_pool_free(), buffer_pool_get_global(), and mutex_destroy().

Referenced by add_client(), add_webrtc_client(), cleanup_client_media_buffers(), and video_frame_buffer_create().

◆ video_frame_commit()

void video_frame_commit ( video_frame_buffer_t *  vfb)

Definition at line 127 of file video_frame.c.

127 {
128 if (!vfb) {
129 SET_ERRNO(ERROR_INVALID_PARAM, "Video frame buffer or active is NULL");
130 return;
131 }
132 if (!vfb->active) {
133 SET_ERRNO(ERROR_INVALID_STATE, "vfb->active is not true");
134 return;
135 }
136
137 // Check if reader has consumed the previous frame
138 if (atomic_load(&vfb->new_frame_available)) {
139 // Reader hasn't consumed yet - we're dropping a frame
140 uint64_t drops = atomic_fetch_add(&vfb->total_frames_dropped, 1) + 1;
141 // Throttle drop logging - only log every 100 drops to avoid spam
142 if (drops == 1 || drops % 100 == 0) {
143 log_dev_every(4500 * US_PER_MS_INT, "Dropping frame for client %u (reader too slow, total drops: %llu)",
144 vfb->client_id, (unsigned long long)drops);
145 }
146 }
147
148 // Pointer swap using mutex for thread safety
149 // The send thread reads front_buffer while render thread swaps
150 // Without this mutex, the send thread could read a stale front_buffer pointer
151 // mid-swap, causing it to see size=0 on newly-initialized frames
152 mutex_lock(&vfb->swap_mutex);
153 video_frame_t *temp = vfb->front_buffer;
154 vfb->front_buffer = vfb->back_buffer;
155 vfb->back_buffer = temp;
156 mutex_unlock(&vfb->swap_mutex);
157
158 // Signal reader that new frame is available
159 atomic_store(&vfb->new_frame_available, true);
160 atomic_fetch_add(&vfb->total_frames_received, 1);
161}

Referenced by client_video_render_thread(), and handle_image_frame_packet().

◆ video_frame_get_latest()

const video_frame_t * video_frame_get_latest ( video_frame_buffer_t *  vfb)

Definition at line 163 of file video_frame.c.

163 {
164 if (!vfb) {
165 SET_ERRNO(ERROR_INVALID_PARAM, "Video frame buffer is not active");
166 return NULL;
167 }
168 if (!vfb->active) {
169 SET_ERRNO(ERROR_INVALID_STATE, "vfb->active is not true");
170 return NULL;
171 }
172
173 // Mark that we've consumed any new frame
174 atomic_exchange(&vfb->new_frame_available, false);
175
176 // Use mutex to safely read front_buffer pointer
177 // (in case render thread is swapping)
178 // This ensures we get a consistent pointer and don't read mid-swap
179 mutex_lock(&vfb->swap_mutex);
180 const video_frame_t *result = vfb->front_buffer;
181 mutex_unlock(&vfb->swap_mutex);
182
183 return result;
184}

Referenced by client_send_thread_func().

◆ video_frame_get_stats()

void video_frame_get_stats ( video_frame_buffer_t *  vfb,
video_frame_stats_t *  stats 
)

Definition at line 186 of file video_frame.c.

186 {
187 if (!vfb || !stats) {
188 SET_ERRNO(ERROR_INVALID_PARAM, "Video frame buffer or stats is NULL");
189 return;
190 }
191 if (!vfb->active) {
192 SET_ERRNO(ERROR_INVALID_STATE, "vfb->active is not true");
193 return;
194 }
195
196 stats->total_frames = atomic_load(&vfb->total_frames_received);
197 stats->dropped_frames = atomic_load(&vfb->total_frames_dropped);
198 stats->drop_rate = (stats->total_frames > 0) ? (float)stats->dropped_frames / (float)stats->total_frames : 0.0f;
199 stats->avg_decode_time_ns = atomic_load(&vfb->avg_decode_time_ns);
200 stats->avg_render_time_ns = atomic_load(&vfb->avg_render_time_ns);
201}

Referenced by stats_logger_thread(), and update_server_stats().