ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
common/session/capture.c
Go to the documentation of this file.
1
13#include "session/capture.h"
14#include <ascii-chat/common.h>
15#include <ascii-chat/log/log.h>
20#include <ascii-chat/util/fps.h>
23
24#include <stdio.h>
25#include <string.h>
26#include <time.h>
27
28/* ============================================================================
29 * Session Capture Constants
30 * ============================================================================ */
31
33#define SESSION_MAX_FRAME_WIDTH 320
34
36#define SESSION_MAX_FRAME_HEIGHT 180
37
38/* ============================================================================
39 * Session Capture Context Structure
40 * ============================================================================ */
41
96
97/* ============================================================================
98 * Internal Helper Functions
99 * ============================================================================ */
100
114static void calculate_optimal_dimensions(ssize_t original_width, ssize_t original_height, ssize_t max_width,
115 ssize_t max_height, ssize_t *result_width, ssize_t *result_height) {
116 // Calculate original aspect ratio
117 float img_aspect = (float)original_width / (float)original_height;
118
119 // Check if image needs resizing
120 if (original_width <= max_width && original_height <= max_height) {
121 // Image is already within bounds - use as-is
122 *result_width = original_width;
123 *result_height = original_height;
124 return;
125 }
126
127 // Determine scaling factor based on which dimension is the limiting factor
128 if ((float)max_width / (float)max_height > img_aspect) {
129 // Max box is wider than image aspect - scale by height
130 *result_height = max_height;
131 *result_width = (ssize_t)(max_height * img_aspect);
132 } else {
133 // Max box is taller than image aspect - scale by width
134 *result_width = max_width;
135 *result_height = (ssize_t)(max_width / img_aspect);
136 }
137}
138
139/* ============================================================================
140 * Session Capture Lifecycle Functions
141 * ============================================================================ */
142
144 // Mirror capture requires a config with media source info
145 if (!config) {
146 return SET_ERRNO(ERROR_INVALID_PARAM, "Mirror capture requires explicit config"), NULL;
147 }
148
149 // Delegate to the main implementation
150 return session_capture_create(config);
151}
152
154 // Network modes don't capture media - create minimal context for keyboard/audio only
156 if (!ctx) {
157 return NULL;
158 }
159
160 // Set FPS (use provided value or default to 60)
161 ctx->target_fps = target_fps > 0 ? target_fps : 60;
162 ctx->resize_for_network = false;
163
164 // Audio and keyboard will be set up separately by callers
165 ctx->audio_enabled = false;
166 ctx->source = NULL;
167
168 // Initialize adaptive sleep for consistency
169 uint64_t baseline_sleep_ns = NS_PER_SEC_INT / ctx->target_fps;
170 adaptive_sleep_config_t sleep_config = {.baseline_sleep_ns = baseline_sleep_ns,
171 .min_speed_multiplier = 0.5,
172 .max_speed_multiplier = 2.0,
173 .speedup_rate = 0.1,
174 .slowdown_rate = 0.1};
175 adaptive_sleep_init(&ctx->sleep_state, &sleep_config);
176
177 // Initialize minimal FPS tracker (won't be used but keep structure consistent)
178 char *tracker_name = SAFE_MALLOC(32, char *);
179 if (!tracker_name) {
180 SAFE_FREE(ctx);
181 return NULL;
182 }
183 safe_snprintf(tracker_name, 32, "NETWORK_CAPTURE");
184 fps_init(&ctx->fps_tracker, 60, tracker_name);
185
186 ctx->start_time_ns = time_get_ns();
187 ctx->initialized = true;
188
189 log_debug("Created network capture context (no local media source)");
190 return ctx;
191}
192
194 // Auto-create config from command-line options if NULL
195 session_capture_config_t auto_config = {0};
196 if (!config) {
197 // Auto-initialization from GET_OPTION() only works when options have been parsed
198 // Verify by checking if we can access options (would SET_ERRNO if not)
199 const char *media_file = GET_OPTION(media_file);
200 bool media_from_stdin = GET_OPTION(media_from_stdin);
201
202 if (media_file[0] != '\0') {
203 // File or stdin streaming
204 auto_config.type = media_from_stdin ? MEDIA_SOURCE_STDIN : MEDIA_SOURCE_FILE;
205 auto_config.path = media_file;
206 auto_config.loop = GET_OPTION(media_loop) && !media_from_stdin;
207 } else if (GET_OPTION(test_pattern)) {
208 // Test pattern mode
209 auto_config.type = MEDIA_SOURCE_TEST;
210 auto_config.path = NULL;
211 } else {
212 // Webcam mode (default)
213 static char webcam_index_str[32];
214 unsigned int idx = GET_OPTION(webcam_index);
215 safe_snprintf(webcam_index_str, sizeof(webcam_index_str), "%u", idx);
216 auto_config.type = MEDIA_SOURCE_WEBCAM;
217 auto_config.path = webcam_index_str;
218 log_debug("Webcam mode: using device index %u", idx);
219 }
220
221 // Default settings suitable for local display
222 auto_config.target_fps = 60;
223 auto_config.resize_for_network = false;
224 config = &auto_config;
225 }
226
227 // Check if we should exit before starting initialization
228 if (config->should_exit_callback && config->should_exit_callback(config->callback_data)) {
229 return NULL;
230 }
231
232 // Allocate context
234
235 // Store configuration
236 ctx->target_fps = config->target_fps > 0 ? config->target_fps : 60;
238
239 // Store audio configuration
240 ctx->audio_enabled = config->enable_audio;
242 ctx->mic_audio_ctx = config->mic_audio_ctx;
243 ctx->using_file_audio = false;
244 ctx->file_has_audio = false;
245
246 // Create media source from config type and path
247 ctx->source = media_source_create(config->type, config->path);
248
249 if (!ctx->source) {
250 // Preserve existing error if set, otherwise set generic error
251 asciichat_error_t existing_error = GET_ERRNO();
252 if (existing_error == ASCIICHAT_OK) {
253 SET_ERRNO(ERROR_MEDIA_INIT, "Failed to create media source");
254 }
255 SAFE_FREE(ctx);
256 return NULL;
257 }
258
261 SAFE_FREE(ctx);
262 return NULL;
263 }
264
265 // Set up exit callback on media source for graceful shutdown during I/O
266 // This allows Ctrl+C to abort blocking FFmpeg calls like YouTube HTTP requests
267 if (config->should_exit_callback && ctx->source) {
269 }
270
271 // Detect if media source has audio
272 if (ctx->audio_enabled && ctx->source) {
274 if (ctx->file_has_audio) {
275 ctx->using_file_audio = true;
276 log_info("Audio capture enabled: using file audio");
277 } else if (ctx->audio_fallback_enabled && ctx->mic_audio_ctx) {
278 ctx->using_file_audio = false;
279 log_info("Audio capture enabled: file has no audio, using microphone fallback");
280 } else {
281 ctx->audio_enabled = false;
282 log_debug("Audio capture disabled: no file audio and no fallback configured");
283 }
284 }
285
286 // Enable loop if requested (only for file sources)
287 if (config->loop && config->type == MEDIA_SOURCE_FILE) {
288 media_source_set_loop(ctx->source, true);
289 }
290
291 // Set initial pause state if requested (only for file sources)
292 // Note: We don't pause yet - we'll pause AFTER reading the first frame in the render loop
293 // This is because paused sources return NULL from read_video()
294 if (config->type == MEDIA_SOURCE_FILE && GET_OPTION(pause)) {
296 log_debug("Will pause after first frame (--pause flag)");
297 }
298
299 // Perform initial seek if requested
300 if (config->initial_seek_timestamp > 0.0) {
301 log_debug("Seeking to %.2f seconds", config->initial_seek_timestamp);
303 if (seek_err != ASCIICHAT_OK) {
304 log_warn("Failed to seek to %.2f seconds: %d", config->initial_seek_timestamp, seek_err);
305 // Continue anyway - seeking is best-effort
306 } else {
307 log_debug("Successfully seeked to %.2f seconds", config->initial_seek_timestamp);
308 // Codec buffers are flushed during seek, so next frame read will use correct position
309
310 // Reset timing state after seek to prevent FPS calculation confusion
311 // (frame_count and elapsed_time must match the new playback position)
312 ctx->frame_count = 0;
313 ctx->start_time_ns = time_get_ns();
314 }
315 }
316
317 // Initialize adaptive sleep for frame rate limiting
318 // Calculate baseline sleep time in nanoseconds from target FPS
319 uint64_t baseline_sleep_ns = NS_PER_SEC_INT / ctx->target_fps;
320 adaptive_sleep_config_t sleep_config = {
321 .baseline_sleep_ns = baseline_sleep_ns,
322 .min_speed_multiplier = 0.5, // Allow slowing down to 50% of baseline
323 .max_speed_multiplier = 2.0, // Allow speeding up to 200% of baseline
324 .speedup_rate = 0.1, // Adapt by 10% per frame if possible
325 .slowdown_rate = 0.1 // Adapt by 10% per frame if possible
326 };
327 adaptive_sleep_init(&ctx->sleep_state, &sleep_config);
328
329 // Initialize FPS tracker
330 // Note: Must allocate tracker_name on heap since fps_init stores a pointer to it
331 char *tracker_name = SAFE_MALLOC(32, char *);
332 if (!tracker_name) {
334 SAFE_FREE(ctx);
335 return NULL;
336 }
337 safe_snprintf(tracker_name, 32, "CAPTURE_%u", ctx->target_fps);
338 fps_init(&ctx->fps_tracker, (int)ctx->target_fps, tracker_name);
339
340 // Record start time for FPS calculation (nanoseconds)
341 ctx->start_time_ns = time_get_ns();
342
343 ctx->initialized = true;
344 return ctx;
345}
346
348 if (!ctx) {
349 return;
350 }
351
352 // Destroy media source
353 if (ctx->source) {
355 ctx->source = NULL;
356 }
357
358 // Free FPS tracker name (allocated in session_capture_create)
359 if (ctx->fps_tracker.tracker_name) {
360 char *temp = (char *)ctx->fps_tracker.tracker_name;
361 SAFE_FREE(temp);
362 }
363
364 ctx->initialized = false;
365 SAFE_FREE(ctx);
366}
367
368/* ============================================================================
369 * Session Capture Operations
370 * ============================================================================ */
371
373 if (!ctx || !ctx->initialized || !ctx->source) {
374 return NULL;
375 }
376
377 static uint64_t last_frame_time_ns = 0;
378 uint64_t frame_request_time_ns = time_get_ns();
379
381
382 if (frame) {
383 uint64_t frame_available_time_ns = time_get_ns();
384
385 // Track frame for FPS reporting
386 fps_frame_ns(&ctx->fps_tracker, frame_available_time_ns, "frame captured");
387 ctx->frame_count++;
388
389 // Log frame-to-frame timing
390 if (last_frame_time_ns > 0) {
391 uint64_t time_since_last_frame_ns = time_elapsed_ns(last_frame_time_ns, frame_request_time_ns);
392 uint64_t time_to_get_frame_ns = time_elapsed_ns(frame_request_time_ns, frame_available_time_ns);
393 double since_last_ms = (double)time_since_last_frame_ns / NS_PER_MS;
394 double to_get_ms = (double)time_to_get_frame_ns / NS_PER_MS;
395
396 if (ctx->frame_count % 30 == 0) {
397 log_dev_every(3 * US_PER_SEC_INT, "FRAME_TIMING[%lu]: since_last=%.1f ms, to_get=%.1f ms", ctx->frame_count,
398 since_last_ms, to_get_ms);
399 }
400 }
401 last_frame_time_ns = frame_available_time_ns;
402
403 // Handle --pause flag: pause after first frame is read
406 ctx->paused_after_first_frame = true;
407 log_info("Paused (--pause flag)");
408 }
409 }
410
411 return frame;
412}
413
415 if (!ctx || !frame) {
416 SET_ERRNO(ERROR_INVALID_PARAM, "session_capture_process_for_transmission: NULL parameter");
417 return NULL;
418 }
419
420 // If resize is not enabled, just create a copy
421 if (!ctx->resize_for_network) {
422 image_t *copy = image_new(frame->w, frame->h);
423 if (!copy) {
424 SET_ERRNO(ERROR_MEMORY, "Failed to allocate image copy");
425 return NULL;
426 }
427 memcpy(copy->pixels, frame->pixels, (size_t)frame->w * (size_t)frame->h * sizeof(rgb_pixel_t));
428 return copy;
429 }
430
431 // Calculate optimal dimensions for network transmission
432 ssize_t resized_width, resized_height;
433 calculate_optimal_dimensions(frame->w, frame->h, SESSION_MAX_FRAME_WIDTH, SESSION_MAX_FRAME_HEIGHT, &resized_width,
434 &resized_height);
435
436 // Check if resizing is actually needed
437 if (frame->w == resized_width && frame->h == resized_height) {
438 // No resizing needed - create a copy
439 image_t *copy = image_new(frame->w, frame->h);
440 if (!copy) {
441 SET_ERRNO(ERROR_MEMORY, "Failed to allocate image copy");
442 return NULL;
443 }
444 memcpy(copy->pixels, frame->pixels, (size_t)frame->w * (size_t)frame->h * sizeof(rgb_pixel_t));
445 return copy;
446 }
447
448 // Create new image for resized frame
449 image_t *resized = image_new(resized_width, resized_height);
450 if (!resized) {
451 SET_ERRNO(ERROR_MEMORY, "Failed to allocate resized image buffer");
452 return NULL;
453 }
454
455 // Perform resizing operation
456 image_resize(frame, resized);
457
458 return resized;
459}
460
462 if (!ctx || !ctx->initialized || !ctx->source) {
463 return;
464 }
465
466 // For file/stdin sources: use direct FPS sleep (no adaptation)
467 // This avoids the adaptive sleep multipliers which cause 2x slowdown
469 if (source_type == MEDIA_SOURCE_FILE || source_type == MEDIA_SOURCE_STDIN) {
471 if (target_fps > 0) {
472 uint64_t sleep_ns = NS_PER_SEC_INT / target_fps;
473 platform_sleep_ns(sleep_ns);
474 }
475 return;
476 }
477
478 // For network/webcam sources: use adaptive sleep to handle variable network conditions
479 // queue_depth=0, target_depth=0 maintains constant frame rate
480 adaptive_sleep_do(&ctx->sleep_state, 0, 0);
481}
482
484 if (!ctx || !ctx->initialized || !ctx->source) {
485 return true;
486 }
487
488 return media_source_at_end(ctx->source);
489}
490
492 return ctx != NULL && ctx->initialized && ctx->source != NULL;
493}
494
496 if (!ctx || !ctx->initialized || ctx->frame_count == 0) {
497 return 0.0;
498 }
499
500 // Calculate elapsed time in nanoseconds then convert to seconds
502 double elapsed_sec = time_ns_to_s(elapsed_ns);
503
504 if (elapsed_sec <= 0.0) {
505 return 0.0;
506 }
507
508 return (double)ctx->frame_count / elapsed_sec;
509}
510
512 if (!ctx) {
513 return 0;
514 }
515 return ctx->target_fps;
516}
517
519 if (!ctx || !ctx->initialized) {
520 return false;
521 }
522 return ctx->audio_enabled;
523}
524
525size_t session_capture_read_audio(session_capture_ctx_t *ctx, float *buffer, size_t num_samples) {
526 if (!ctx || !ctx->initialized || !buffer || num_samples == 0) {
527 return 0;
528 }
529
530 if (!ctx->audio_enabled) {
531 return 0;
532 }
533
534 // If using file audio, read from media source
535 if (ctx->using_file_audio && ctx->source) {
536 return media_source_read_audio(ctx->source, buffer, num_samples);
537 }
538
539 // If using microphone fallback, read from mic context
540 if (ctx->mic_audio_ctx) {
541 // mic_audio_ctx is actually an audio_context_t pointer
542 // We need to read from its capture_buffer audio ring buffer
543 audio_context_t *audio_ctx = (audio_context_t *)ctx->mic_audio_ctx;
544 if (audio_ctx && audio_ctx->capture_buffer) {
545 return audio_ring_buffer_read(audio_ctx->capture_buffer, buffer, num_samples);
546 }
547 }
548
549 return 0;
550}
551
553 if (!ctx || !ctx->initialized) {
554 return false;
555 }
556 return ctx->using_file_audio;
557}
558
560 if (!ctx || !ctx->initialized || !ctx->source) {
561 return NULL;
562 }
563 return (void *)ctx->source;
564}
565
567 if (!ctx || !ctx->initialized) {
568 return NULL;
569 }
570 return ctx->audio_ctx;
571}
572
574 if (ctx) {
575 ctx->audio_ctx = audio_ctx;
576 }
577}
⚠️‼️ Comprehensive thread-local error context system for ascii-chat
void session_capture_set_audio_context(session_capture_ctx_t *ctx, void *audio_ctx)
Set the audio context for playback control.
void * session_capture_get_audio_context(session_capture_ctx_t *ctx)
Get the audio context for playback control.
#define SESSION_MAX_FRAME_WIDTH
Maximum frame width for network transmission (bandwidth optimization)
#define SESSION_MAX_FRAME_HEIGHT
Maximum frame height for network transmission (bandwidth optimization)
📹 Unified media capture abstraction for session-based video sources
⚙️ Common definitions, error codes, macros, and types shared throughout the application
⏱️ FPS tracking utility for monitoring frame throughput across all threads
size_t audio_ring_buffer_read(audio_ring_buffer_t *rb, float *data, size_t samples)
Read audio samples from ring buffer.
unsigned int uint32_t
Definition common.h:58
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
void fps_frame_ns(fps_t *tracker, uint64_t current_time_ns, const char *context)
Track a frame and detect lag conditions (nanosecond version - PRIMARY)
Definition fps.c:52
#define SAFE_CALLOC(count, size, cast)
Definition common.h:274
unsigned long long uint64_t
Definition common.h:59
void fps_init(fps_t *tracker, int expected_fps, const char *name)
Initialize FPS tracker.
Definition fps.c:32
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
#define GET_ERRNO()
Get current error code (0 if no error)
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_MEDIA_INIT
Definition error_codes.h:70
@ ERROR_INVALID_PARAM
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
bool media_source_at_end(media_source_t *source)
Check if media source reached end of stream.
Definition source.c:772
size_t media_source_read_audio(media_source_t *source, float *buffer, size_t num_samples)
Read audio samples from media source.
Definition source.c:652
media_source_type_t
Media source type enumeration.
Definition source.h:81
bool media_source_has_audio(media_source_t *source)
Check if media source has audio stream.
Definition source.c:737
void media_source_destroy(media_source_t *source)
Destroy media source and free resources.
Definition source.c:448
void media_source_pause(media_source_t *source)
Pause media playback.
Definition source.c:1004
image_t * media_source_read_video(media_source_t *source)
Read next video frame from media source.
Definition source.c:523
asciichat_error_t media_source_seek(media_source_t *source, double timestamp_sec)
Seek media source to timestamp.
Definition source.c:851
void media_source_set_exit_callback(media_source_t *source, bool(*should_exit_callback)(void *), void *user_data)
Set exit signal callback for graceful shutdown during I/O.
Definition source.c:1047
media_source_type_t media_source_get_type(media_source_t *source)
Get media source type.
Definition source.c:930
media_source_t * media_source_create(media_source_type_t type, const char *path)
Create a new media source.
Definition source.c:187
void media_source_set_loop(media_source_t *source, bool loop)
Enable or disable looping.
Definition source.c:760
@ MEDIA_SOURCE_STDIN
Piped or redirected input.
Definition source.h:84
@ MEDIA_SOURCE_WEBCAM
Hardware webcam device.
Definition source.h:82
@ MEDIA_SOURCE_FILE
Media file (video/audio)
Definition source.h:83
@ MEDIA_SOURCE_TEST
Test pattern generator.
Definition source.h:85
uint64_t time_get_ns(void)
Get current monotonic time in nanoseconds.
Definition util/time.c:108
#define NS_PER_SEC_INT
Definition time.h:157
#define NS_PER_MS
Definition time.h:147
void adaptive_sleep_init(adaptive_sleep_state_t *state, const adaptive_sleep_config_t *config)
Initialize adaptive sleep state with configuration.
Definition util/time.c:609
uint64_t time_elapsed_ns(uint64_t start_ns, uint64_t end_ns)
Calculate elapsed time with wraparound safety.
Definition util/time.c:150
void adaptive_sleep_do(adaptive_sleep_state_t *state, size_t queue_depth, size_t target_depth)
Calculate sleep time and immediately sleep for that duration.
Definition util/time.c:693
#define US_PER_SEC_INT
Definition time.h:161
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:148
void platform_sleep_ns(uint64_t ns)
Platform-safe sleep function with nanosecond precision.
bool session_capture_has_audio(session_capture_ctx_t *ctx)
Check if capture source has audio available.
image_t * session_capture_process_for_transmission(session_capture_ctx_t *ctx, image_t *frame)
Process a frame for network transmission (resize if needed)
session_capture_ctx_t * session_mirror_capture_create(const session_capture_config_t *config)
Create a new session capture context.
bool session_capture_at_end(session_capture_ctx_t *ctx)
Check if capture source has reached end of stream.
double session_capture_get_current_fps(session_capture_ctx_t *ctx)
Get the current FPS being achieved by the capture source.
size_t session_capture_read_audio(session_capture_ctx_t *ctx, float *buffer, size_t num_samples)
Read audio samples from capture source.
image_t * session_capture_read_frame(session_capture_ctx_t *ctx)
Read the next video frame from the capture source.
bool session_capture_is_valid(session_capture_ctx_t *ctx)
Check if capture context is initialized and valid.
bool session_capture_using_file_audio(session_capture_ctx_t *ctx)
Check if currently using file audio vs microphone fallback.
uint32_t session_capture_get_target_fps(session_capture_ctx_t *ctx)
Get the target FPS configured for this capture context.
session_capture_ctx_t * session_capture_create(const session_capture_config_t *config)
Legacy function - creates either mirror or network capture based on options.
session_capture_ctx_t * session_network_capture_create(uint32_t target_fps)
Create network mode capture context without media source.
void * session_capture_get_media_source(session_capture_ctx_t *ctx)
Get the underlying media source from capture context.
void session_capture_sleep_for_fps(session_capture_ctx_t *ctx)
Sleep to maintain target frame rate.
void session_capture_destroy(session_capture_ctx_t *ctx)
Destroy session capture context and free resources.
void image_resize(const image_t *s, image_t *d)
Resize image using nearest-neighbor interpolation.
image_t * image_new(size_t width, size_t height)
Create a new image with standard allocation.
🔊 Audio Capture and Playback Interface for ascii-chat
⚙️ Unified options parsing system for ascii-chat with builder pattern and lock-free access
📝 Logging API with multiple log levels and terminal output control
#define log_dev_every(interval_us, fmt,...)
Rate-limited DEV logging.
Definition log/log.h:699
asciichat_error_t media_source_start_video(media_source_t *source)
Definition source.c:506
🎬 Unified media source abstraction for webcam, files, and stdin
int frame
Definition splash.c:99
Configuration for adaptive sleep behavior.
Definition time.h:677
uint64_t baseline_sleep_ns
Normal sleep time in nanoseconds (when queue is at target)
Definition time.h:678
Runtime state for adaptive sleep.
Definition time.h:693
Audio context for full-duplex capture and playback.
audio_ring_buffer_t * capture_buffer
OLD: Worker → Network (will become processed_capture_rb)
FPS tracking state.
Definition fps.h:52
const char * tracker_name
Definition fps.h:58
Image structure.
rgb_pixel_t * pixels
Pixel data array (width * height RGB pixels, row-major order)
Media source for video and audio capture.
Definition source.c:34
RGB pixel structure.
Configuration for session capture context.
media_source_type_t type
Media source type (WEBCAM, FILE, STDIN, TEST)
const char * path
Device index (for webcam) or file path (for file sources)
uint32_t target_fps
Target frame rate in FPS (e.g., 60 for display, 144 for network)
double initial_seek_timestamp
Seek to this timestamp after opening media (0.0 = no seek)
session_capture_should_exit_fn should_exit_callback
Optional: callback to check if initialization should be cancelled (e.g., shutdown signal)
void * callback_data
Opaque data passed to should_exit_callback.
bool audio_fallback_to_mic
Fall back to microphone if file audio is not available.
bool resize_for_network
Resize frames to network-optimal dimensions (MAX_FRAME_WIDTH x MAX_FRAME_HEIGHT)
bool loop
Enable loop playback for file sources.
bool enable_audio
Enable audio capture from media source.
void * mic_audio_ctx
Microphone audio context for fallback (borrowed, not owned)
Internal session capture context structure.
fps_t fps_tracker
FPS tracker for monitoring capture rate.
void * mic_audio_ctx
Microphone audio context for fallback (borrowed, not owned)
bool file_has_audio
File has audio stream available.
adaptive_sleep_state_t sleep_state
Adaptive sleep state for frame rate limiting.
bool using_file_audio
Using file audio (true) or microphone fallback (false)
bool paused_after_first_frame
Whether we've already paused after the first frame.
bool audio_enabled
Audio is enabled for capture.
uint32_t target_fps
Target frames per second.
uint64_t start_time_ns
Start time for FPS calculation (nanoseconds)
bool initialized
Context has been successfully initialized.
bool should_pause_after_first_frame
Pause media source after first frame is read (–pause flag)
bool resize_for_network
Whether to resize frames for network transmission.
media_source_t * source
Underlying media source (webcam, file, stdin, test)
void * audio_ctx
Main audio context for playback (borrowed, not owned)
bool audio_fallback_enabled
Fall back to microphone if file has no audio.
uint64_t frame_count
Frame count for FPS calculation.
⏱️ High-precision timing utilities using sokol_time.h and uthash