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

📹 Unified media capture implementation More...

Go to the source code of this file.

Data Structures

struct  session_capture_ctx
 Internal session capture context structure. More...
 

Macros

#define SESSION_MAX_FRAME_WIDTH   480
 Maximum frame width for network transmission (bandwidth optimization)
 
#define SESSION_MAX_FRAME_HEIGHT   270
 Maximum frame height for network transmission (bandwidth optimization)
 

Functions

session_capture_ctx_t * session_mirror_capture_create (const session_capture_config_t *config)
 
session_capture_ctx_t * session_network_capture_create (uint32_t target_fps)
 
session_capture_ctx_t * session_capture_create (const session_capture_config_t *config)
 
void session_capture_destroy (session_capture_ctx_t *ctx)
 
image_t * session_capture_read_frame (session_capture_ctx_t *ctx)
 
image_t * session_capture_process_for_transmission (session_capture_ctx_t *ctx, image_t *frame)
 
void session_capture_sleep_for_fps (session_capture_ctx_t *ctx)
 
bool session_capture_at_end (session_capture_ctx_t *ctx)
 
bool session_capture_is_valid (session_capture_ctx_t *ctx)
 
double session_capture_get_current_fps (session_capture_ctx_t *ctx)
 
uint32_t session_capture_get_target_fps (session_capture_ctx_t *ctx)
 
bool session_capture_has_audio (session_capture_ctx_t *ctx)
 
size_t session_capture_read_audio (session_capture_ctx_t *ctx, float *buffer, size_t num_samples)
 
bool session_capture_using_file_audio (session_capture_ctx_t *ctx)
 
void * session_capture_get_media_source (session_capture_ctx_t *ctx)
 
void * session_capture_get_audio_context (session_capture_ctx_t *ctx)
 
void session_capture_set_audio_context (session_capture_ctx_t *ctx, void *audio_ctx)
 
asciichat_error_t session_capture_sync_audio_to_video (session_capture_ctx_t *ctx)
 

Detailed Description

📹 Unified media capture implementation

Implements the session capture abstraction layer for unified media source handling across client, mirror, and discovery modes.

Author
Zachary Fogg me@zf.nosp@m.o.gg
Date
January 2026

Definition in file lib/session/capture.c.

Macro Definition Documentation

◆ SESSION_MAX_FRAME_HEIGHT

#define SESSION_MAX_FRAME_HEIGHT   270

Maximum frame height for network transmission (bandwidth optimization)

Definition at line 36 of file lib/session/capture.c.

◆ SESSION_MAX_FRAME_WIDTH

#define SESSION_MAX_FRAME_WIDTH   480

Maximum frame width for network transmission (bandwidth optimization)

Definition at line 33 of file lib/session/capture.c.

Function Documentation

◆ session_capture_at_end()

bool session_capture_at_end ( session_capture_ctx_t *  ctx)

Definition at line 481 of file lib/session/capture.c.

481 {
482 if (!ctx || !ctx->initialized || !ctx->source) {
483 return true;
484 }
485
486 return media_source_at_end(ctx->source);
487}
bool media_source_at_end(media_source_t *source)
Definition source.c:646

References media_source_at_end().

Referenced by session_render_loop().

◆ session_capture_create()

session_capture_ctx_t * session_capture_create ( const session_capture_config_t *  config)

Definition at line 197 of file lib/session/capture.c.

197 {
198 // Auto-create config from command-line options if NULL
199 session_capture_config_t auto_config = {0};
200 if (!config) {
201 // Auto-initialization from GET_OPTION() only works when options have been parsed
202 // Verify by checking if we can access options (would SET_ERRNO if not)
203 const char *media_file = GET_OPTION(media_file);
204 bool media_from_stdin = GET_OPTION(media_from_stdin);
205
206 if (media_file[0] != '\0') {
207 // File or stdin streaming
208 auto_config.type = media_from_stdin ? MEDIA_SOURCE_STDIN : MEDIA_SOURCE_FILE;
209 auto_config.path = media_file;
210 auto_config.loop = GET_OPTION(media_loop) && !media_from_stdin;
211 } else if (GET_OPTION(test_pattern)) {
212 // Test pattern mode
213 auto_config.type = MEDIA_SOURCE_TEST;
214 auto_config.path = NULL;
215 } else {
216 // Webcam mode (default)
217 static char webcam_index_str[32];
218 safe_snprintf(webcam_index_str, sizeof(webcam_index_str), "%u", GET_OPTION(webcam_index));
219 auto_config.type = MEDIA_SOURCE_WEBCAM;
220 auto_config.path = webcam_index_str;
221 }
222
223 // Default settings suitable for local display
224 auto_config.target_fps = 60;
225 auto_config.resize_for_network = false;
226 config = &auto_config;
227 }
228
229 // Check if we should exit before starting initialization
230 if (config->should_exit_callback && config->should_exit_callback(config->callback_data)) {
231 return NULL;
232 }
233
234 // Allocate context
235 session_capture_ctx_t *ctx = SAFE_CALLOC(1, sizeof(session_capture_ctx_t), session_capture_ctx_t *);
236
237 // Store configuration
238 ctx->target_fps = config->target_fps > 0 ? config->target_fps : 60;
239 ctx->resize_for_network = config->resize_for_network;
240
241 // Store audio configuration
242 ctx->audio_enabled = config->enable_audio;
243 ctx->audio_fallback_enabled = config->audio_fallback_to_mic;
244 ctx->mic_audio_ctx = config->mic_audio_ctx;
245 ctx->using_file_audio = false;
246 ctx->file_has_audio = false;
247
248 // Use pre-created media source if provided, otherwise create a new one
249 // (Allows reusing media source from probing phase to avoid redundant yt-dlp calls)
250 if (config->media_source) {
251 ctx->source = config->media_source;
252 ctx->source_owned = false; // Caller owns this source
253 log_debug("Using pre-created media source (avoids redundant YouTube extraction)");
254 } else {
255 ctx->source = media_source_create(config->type, config->path);
256 ctx->source_owned = true; // We own this source and must destroy it
257 }
258
259 if (!ctx->source) {
260 // Preserve existing error if set, otherwise set generic error
261 asciichat_error_t existing_error = GET_ERRNO();
262 if (existing_error == ASCIICHAT_OK) {
263 SET_ERRNO(ERROR_MEDIA_INIT, "Failed to create media source");
264 }
265 SAFE_FREE(ctx);
266 return NULL;
267 }
268
269 // Detect if media source has audio
270 if (ctx->audio_enabled && ctx->source) {
271 ctx->file_has_audio = media_source_has_audio(ctx->source);
272 if (ctx->file_has_audio) {
273 ctx->using_file_audio = true;
274 log_info("Audio capture enabled: using file audio");
275 } else if (ctx->audio_fallback_enabled && ctx->mic_audio_ctx) {
276 ctx->using_file_audio = false;
277 log_info("Audio capture enabled: file has no audio, using microphone fallback");
278 } else {
279 ctx->audio_enabled = false;
280 log_debug("Audio capture disabled: no file audio and no fallback configured");
281 }
282 }
283
284 // Enable loop if requested (only for file sources)
285 if (config->loop && config->type == MEDIA_SOURCE_FILE) {
286 media_source_set_loop(ctx->source, true);
287 }
288
289 // Set initial pause state if requested (only for file sources)
290 // Note: We don't pause yet - we'll pause AFTER reading the first frame in the render loop
291 // This is because paused sources return NULL from read_video()
292 if (config->type == MEDIA_SOURCE_FILE && GET_OPTION(pause)) {
293 ctx->should_pause_after_first_frame = true;
294 log_debug("Will pause after first frame (--pause flag)");
295 }
296
297 // Perform initial seek if requested
298 if (config->initial_seek_timestamp > 0.0) {
299 log_debug("Seeking to %.2f seconds", config->initial_seek_timestamp);
300 asciichat_error_t seek_err = media_source_seek(ctx->source, config->initial_seek_timestamp);
301 if (seek_err != ASCIICHAT_OK) {
302 log_warn("Failed to seek to %.2f seconds: %d", config->initial_seek_timestamp, seek_err);
303 // Continue anyway - seeking is best-effort
304 } else {
305 log_debug("Successfully seeked to %.2f seconds", config->initial_seek_timestamp);
306 // Codec buffers are flushed during seek, so next frame read will use correct position
307
308 // Reset timing state after seek to prevent FPS calculation confusion
309 // (frame_count and elapsed_time must match the new playback position)
310 ctx->frame_count = 0;
311 ctx->start_time_ns = time_get_ns();
312
313 // For snapshot mode with immediate snapshot (delay=0), we need to wait for prefetch thread to
314 // decode the seeked frame. This is a workaround for a timing issue where the prefetch thread
315 // needs time to decode the frame at the new position after seeking.
316 // The sleep duration must be long enough for the prefetch thread to decode one frame.
317 float snapshot_delay = GET_OPTION(snapshot_delay);
318 if (GET_OPTION(snapshot_mode) && snapshot_delay == 0.0f) {
319 // HTTP streams are slow - need 1+ second for first frame decode after seek
320 // Local files are faster - 100-200ms is usually sufficient
321 // Use 1 second as a safe default to handle both cases
322 log_debug("Waiting for prefetch thread after seek (snapshot_delay=0, HTTP streams need ~1 second)");
323 platform_sleep_us(US_PER_SEC_INT); // 1 second - ensures prefetch thread has delivered seeked frame
324 }
325 }
326 }
327
328 // Initialize adaptive sleep for frame rate limiting
329 // Calculate baseline sleep time in nanoseconds from target FPS
330 uint64_t baseline_sleep_ns = NS_PER_SEC_INT / ctx->target_fps;
331 adaptive_sleep_config_t sleep_config = {
332 .baseline_sleep_ns = baseline_sleep_ns,
333 .min_speed_multiplier = 0.5, // Allow slowing down to 50% of baseline
334 .max_speed_multiplier = 2.0, // Allow speeding up to 200% of baseline
335 .speedup_rate = 0.1, // Adapt by 10% per frame if possible
336 .slowdown_rate = 0.1 // Adapt by 10% per frame if possible
337 };
338 adaptive_sleep_init(&ctx->sleep_state, &sleep_config);
339
340 // Initialize FPS tracker
341 // Note: Must allocate tracker_name on heap since fps_init stores a pointer to it
342 char *tracker_name = SAFE_MALLOC(32, char *);
343 if (!tracker_name) {
344 media_source_destroy(ctx->source);
345 SAFE_FREE(ctx);
346 return NULL;
347 }
348 safe_snprintf(tracker_name, 32, "CAPTURE_%u", ctx->target_fps);
349 fps_init(&ctx->fps_tracker, (int)ctx->target_fps, tracker_name);
350
351 // Record start time for FPS calculation (nanoseconds)
352 ctx->start_time_ns = time_get_ns();
353
354 ctx->initialized = true;
355 return ctx;
356}
void fps_init(fps_t *tracker, int expected_fps, const char *name)
Definition fps.c:32
void platform_sleep_us(unsigned int us)
bool media_source_has_audio(media_source_t *source)
Definition source.c:611
void media_source_destroy(media_source_t *source)
Definition source.c:360
asciichat_error_t media_source_seek(media_source_t *source, double timestamp_sec)
Definition source.c:757
media_source_t * media_source_create(media_source_type_t type, const char *path)
Definition source.c:172
void media_source_set_loop(media_source_t *source, bool loop)
Definition source.c:634
int safe_snprintf(char *buffer, size_t buffer_size, const char *format,...)
Safe formatted string printing to buffer.
Definition system.c:456
uint64_t time_get_ns(void)
Definition util/time.c:48
void adaptive_sleep_init(adaptive_sleep_state_t *state, const adaptive_sleep_config_t *config)
Definition util/time.c:393

References adaptive_sleep_init(), fps_init(), media_source_create(), media_source_destroy(), media_source_has_audio(), media_source_seek(), media_source_set_loop(), platform_sleep_us(), safe_snprintf(), and time_get_ns().

Referenced by capture_init(), display_init(), session_mirror_capture_create(), and session_participant_start_video_capture().

◆ session_capture_destroy()

void session_capture_destroy ( session_capture_ctx_t *  ctx)

Definition at line 358 of file lib/session/capture.c.

358 {
359 if (!ctx) {
360 return;
361 }
362
363 // Destroy media source only if we own it (not if provided by caller)
364 if (ctx->source && ctx->source_owned) {
365 media_source_destroy(ctx->source);
366 ctx->source = NULL;
367 }
368
369 // Free FPS tracker name (allocated in session_capture_create)
370 if (ctx->fps_tracker.tracker_name) {
371 char *temp = (char *)ctx->fps_tracker.tracker_name;
372 SAFE_FREE(temp);
373 }
374
375 ctx->initialized = false;
376 SAFE_FREE(ctx);
377}

References media_source_destroy().

Referenced by capture_cleanup(), display_cleanup(), session_client_like_run(), and session_participant_destroy().

◆ session_capture_get_audio_context()

void * session_capture_get_audio_context ( session_capture_ctx_t *  ctx)

Definition at line 564 of file lib/session/capture.c.

564 {
565 if (!ctx || !ctx->initialized) {
566 return NULL;
567 }
568 return ctx->audio_ctx;
569}

Referenced by session_handle_keyboard_input().

◆ session_capture_get_current_fps()

double session_capture_get_current_fps ( session_capture_ctx_t *  ctx)

Definition at line 493 of file lib/session/capture.c.

493 {
494 if (!ctx || !ctx->initialized || ctx->frame_count == 0) {
495 return 0.0;
496 }
497
498 // Calculate elapsed time in nanoseconds then convert to seconds
499 uint64_t elapsed_ns = time_elapsed_ns(ctx->start_time_ns, time_get_ns());
500 double elapsed_sec = time_ns_to_s(elapsed_ns);
501
502 if (elapsed_sec <= 0.0) {
503 return 0.0;
504 }
505
506 return (double)ctx->frame_count / elapsed_sec;
507}
uint64_t time_elapsed_ns(uint64_t start_ns, uint64_t end_ns)
Definition util/time.c:90

References time_elapsed_ns(), and time_get_ns().

◆ session_capture_get_media_source()

void * session_capture_get_media_source ( session_capture_ctx_t *  ctx)

Definition at line 557 of file lib/session/capture.c.

557 {
558 if (!ctx || !ctx->initialized || !ctx->source) {
559 return NULL;
560 }
561 return (void *)ctx->source;
562}

Referenced by session_client_like_run(), session_handle_keyboard_input(), and session_render_loop().

◆ session_capture_get_target_fps()

uint32_t session_capture_get_target_fps ( session_capture_ctx_t *  ctx)

Definition at line 509 of file lib/session/capture.c.

509 {
510 if (!ctx) {
511 return 0;
512 }
513 return ctx->target_fps;
514}

Referenced by session_render_loop().

◆ session_capture_has_audio()

bool session_capture_has_audio ( session_capture_ctx_t *  ctx)

Definition at line 516 of file lib/session/capture.c.

516 {
517 if (!ctx || !ctx->initialized) {
518 return false;
519 }
520 return ctx->audio_enabled;
521}

◆ session_capture_is_valid()

bool session_capture_is_valid ( session_capture_ctx_t *  ctx)

Definition at line 489 of file lib/session/capture.c.

489 {
490 return ctx != NULL && ctx->initialized && ctx->source != NULL;
491}

◆ session_capture_process_for_transmission()

image_t * session_capture_process_for_transmission ( session_capture_ctx_t *  ctx,
image_t *  frame 
)

Definition at line 425 of file lib/session/capture.c.

425 {
426 if (!ctx || !frame) {
427 SET_ERRNO(ERROR_INVALID_PARAM, "session_capture_process_for_transmission: NULL parameter");
428 return NULL;
429 }
430
431 // If resize is not enabled, just create a copy
432 if (!ctx->resize_for_network) {
433 image_t *copy = image_new(frame->w, frame->h);
434 if (!copy) {
435 SET_ERRNO(ERROR_MEMORY, "Failed to allocate image copy");
436 return NULL;
437 }
438 memcpy(copy->pixels, frame->pixels, (size_t)frame->w * (size_t)frame->h * sizeof(rgb_pixel_t));
439 return copy;
440 }
441
442 // Calculate optimal dimensions for network transmission
443 ssize_t resized_width, resized_height;
444 calculate_optimal_dimensions(frame->w, frame->h, SESSION_MAX_FRAME_WIDTH, SESSION_MAX_FRAME_HEIGHT, &resized_width,
445 &resized_height);
446
447 // Check if resizing is actually needed
448 if (frame->w == resized_width && frame->h == resized_height) {
449 // No resizing needed - create a copy
450 image_t *copy = image_new(frame->w, frame->h);
451 if (!copy) {
452 SET_ERRNO(ERROR_MEMORY, "Failed to allocate image copy");
453 return NULL;
454 }
455 memcpy(copy->pixels, frame->pixels, (size_t)frame->w * (size_t)frame->h * sizeof(rgb_pixel_t));
456 return copy;
457 }
458
459 // Create new image for resized frame
460 image_t *resized = image_new(resized_width, resized_height);
461 if (!resized) {
462 SET_ERRNO(ERROR_MEMORY, "Failed to allocate resized image buffer");
463 return NULL;
464 }
465
466 // Perform resizing operation
467 image_resize(frame, resized);
468
469 return resized;
470}
#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)
void image_resize(const image_t *s, image_t *d)
image_t * image_new(size_t width, size_t height)
Definition video/image.c:36

References image_new(), image_resize(), SESSION_MAX_FRAME_HEIGHT, and SESSION_MAX_FRAME_WIDTH.

◆ session_capture_read_audio()

size_t session_capture_read_audio ( session_capture_ctx_t *  ctx,
float *  buffer,
size_t  num_samples 
)

Definition at line 523 of file lib/session/capture.c.

523 {
524 if (!ctx || !ctx->initialized || !buffer || num_samples == 0) {
525 return 0;
526 }
527
528 if (!ctx->audio_enabled) {
529 return 0;
530 }
531
532 // If using file audio, read from media source
533 if (ctx->using_file_audio && ctx->source) {
534 return media_source_read_audio(ctx->source, buffer, num_samples);
535 }
536
537 // If using microphone fallback, read from mic context
538 if (ctx->mic_audio_ctx) {
539 // mic_audio_ctx is actually an audio_context_t pointer
540 // We need to read from its capture_buffer audio ring buffer
541 audio_context_t *audio_ctx = (audio_context_t *)ctx->mic_audio_ctx;
542 if (audio_ctx && audio_ctx->capture_buffer) {
543 return audio_ring_buffer_read(audio_ctx->capture_buffer, buffer, num_samples);
544 }
545 }
546
547 return 0;
548}
size_t audio_ring_buffer_read(audio_ring_buffer_t *rb, float *data, size_t samples)
size_t media_source_read_audio(media_source_t *source, float *buffer, size_t num_samples)
Definition source.c:526

References audio_ring_buffer_read(), and media_source_read_audio().

◆ session_capture_read_frame()

image_t * session_capture_read_frame ( session_capture_ctx_t *  ctx)

Definition at line 383 of file lib/session/capture.c.

383 {
384 if (!ctx || !ctx->initialized || !ctx->source) {
385 return NULL;
386 }
387
388 static uint64_t last_frame_time_ns = 0;
389 uint64_t frame_request_time_ns = time_get_ns();
390
391 image_t *frame = media_source_read_video(ctx->source);
392
393 if (frame) {
394 uint64_t frame_available_time_ns = time_get_ns();
395
396 // Track frame for FPS reporting
397 fps_frame_ns(&ctx->fps_tracker, frame_available_time_ns, "frame captured");
398 ctx->frame_count++;
399
400 // Log frame-to-frame timing
401 if (last_frame_time_ns > 0) {
402 uint64_t time_since_last_frame_ns = time_elapsed_ns(last_frame_time_ns, frame_request_time_ns);
403 uint64_t time_to_get_frame_ns = time_elapsed_ns(frame_request_time_ns, frame_available_time_ns);
404 double since_last_ms = (double)time_since_last_frame_ns / NS_PER_MS;
405 double to_get_ms = (double)time_to_get_frame_ns / NS_PER_MS;
406
407 if (ctx->frame_count % 30 == 0) {
408 log_dev_every(3 * US_PER_SEC_INT, "FRAME_TIMING[%lu]: since_last=%.1f ms, to_get=%.1f ms", ctx->frame_count,
409 since_last_ms, to_get_ms);
410 }
411 }
412 last_frame_time_ns = frame_available_time_ns;
413
414 // Handle --pause flag: pause after first frame is read
415 if (ctx->should_pause_after_first_frame && !ctx->paused_after_first_frame) {
416 media_source_pause(ctx->source);
417 ctx->paused_after_first_frame = true;
418 log_info("Paused (--pause flag)");
419 }
420 }
421
422 return frame;
423}
void fps_frame_ns(fps_t *tracker, uint64_t current_time_ns, const char *context)
Definition fps.c:52
void media_source_pause(media_source_t *source)
Definition source.c:908
image_t * media_source_read_video(media_source_t *source)
Definition source.c:408

References fps_frame_ns(), media_source_pause(), media_source_read_video(), time_elapsed_ns(), and time_get_ns().

Referenced by session_render_loop().

◆ session_capture_set_audio_context()

void session_capture_set_audio_context ( session_capture_ctx_t *  ctx,
void *  audio_ctx 
)

Definition at line 571 of file lib/session/capture.c.

571 {
572 if (ctx) {
573 ctx->audio_ctx = audio_ctx;
574 }
575}

Referenced by session_client_like_run().

◆ session_capture_sleep_for_fps()

void session_capture_sleep_for_fps ( session_capture_ctx_t *  ctx)

Definition at line 472 of file lib/session/capture.c.

472 {
473 if (!ctx || !ctx->initialized) {
474 return;
475 }
476
477 // Use adaptive sleep with queue_depth=0, target_depth=0 for constant rate
478 adaptive_sleep_do(&ctx->sleep_state, 0, 0);
479}
void adaptive_sleep_do(adaptive_sleep_state_t *state, size_t queue_depth, size_t target_depth)
Definition util/time.c:465

References adaptive_sleep_do().

◆ session_capture_sync_audio_to_video()

asciichat_error_t session_capture_sync_audio_to_video ( session_capture_ctx_t *  ctx)

Definition at line 577 of file lib/session/capture.c.

577 {
578 // DEPRECATED: This function is deprecated. Seeking audio to match video causes
579 // audio playback interruptions. Audio and video stay naturally synchronized when
580 // decoding independently from the same source.
581
582 if (!ctx || !ctx->initialized || !ctx->source) {
583 return ERROR_INVALID_PARAM;
584 }
585 return media_source_sync_audio_to_video(ctx->source);
586}
asciichat_error_t media_source_sync_audio_to_video(media_source_t *source)
Definition source.c:723

References media_source_sync_audio_to_video().

◆ session_capture_using_file_audio()

bool session_capture_using_file_audio ( session_capture_ctx_t *  ctx)

Definition at line 550 of file lib/session/capture.c.

550 {
551 if (!ctx || !ctx->initialized) {
552 return false;
553 }
554 return ctx->using_file_audio;
555}

◆ session_mirror_capture_create()

session_capture_ctx_t * session_mirror_capture_create ( const session_capture_config_t *  config)

Definition at line 146 of file lib/session/capture.c.

146 {
147 // Mirror capture requires a config with media source info
148 if (!config) {
149 return SET_ERRNO(ERROR_INVALID_PARAM, "Mirror capture requires explicit config"), NULL;
150 }
151
152 // Delegate to the main implementation
153 return session_capture_create(config);
154}
session_capture_ctx_t * session_capture_create(const session_capture_config_t *config)

References session_capture_create().

Referenced by session_client_like_run().

◆ session_network_capture_create()

session_capture_ctx_t * session_network_capture_create ( uint32_t  target_fps)

Definition at line 156 of file lib/session/capture.c.

156 {
157 // Network modes don't capture media - create minimal context for keyboard/audio only
158 session_capture_ctx_t *ctx = SAFE_CALLOC(1, sizeof(session_capture_ctx_t), session_capture_ctx_t *);
159 if (!ctx) {
160 return NULL;
161 }
162
163 // Set FPS (use provided value or default to 60)
164 ctx->target_fps = target_fps > 0 ? target_fps : 60;
165 ctx->resize_for_network = false;
166
167 // Audio and keyboard will be set up separately by callers
168 ctx->audio_enabled = false;
169 ctx->source = NULL;
170 ctx->source_owned = false;
171
172 // Initialize adaptive sleep for consistency
173 uint64_t baseline_sleep_ns = NS_PER_SEC_INT / ctx->target_fps;
174 adaptive_sleep_config_t sleep_config = {.baseline_sleep_ns = baseline_sleep_ns,
175 .min_speed_multiplier = 0.5,
176 .max_speed_multiplier = 2.0,
177 .speedup_rate = 0.1,
178 .slowdown_rate = 0.1};
179 adaptive_sleep_init(&ctx->sleep_state, &sleep_config);
180
181 // Initialize minimal FPS tracker (won't be used but keep structure consistent)
182 char *tracker_name = SAFE_MALLOC(32, char *);
183 if (!tracker_name) {
184 SAFE_FREE(ctx);
185 return NULL;
186 }
187 safe_snprintf(tracker_name, 32, "NETWORK_CAPTURE");
188 fps_init(&ctx->fps_tracker, 60, tracker_name);
189
190 ctx->start_time_ns = time_get_ns();
191 ctx->initialized = true;
192
193 log_debug("Created network capture context (no local media source)");
194 return ctx;
195}

References adaptive_sleep_init(), fps_init(), safe_snprintf(), and time_get_ns().

Referenced by session_client_like_run().