ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
Media Capture

📹 Webcam video capture and transmission to server More...

Files

file  capture.c
 ðŸ“¹ Client webcam capture: dedicated capture thread with frame rate limiting and network transmission
 
file  capture.h
 ascii-chat Client Media Capture Management Interface
 

Functions

int capture_init ()
 Initialize capture subsystem.
 
int capture_start_thread ()
 Start capture thread.
 
void capture_stop_thread ()
 Stop capture thread.
 
bool capture_thread_exited ()
 Check if capture thread has exited.
 
void capture_cleanup ()
 Cleanup capture subsystem.
 

Detailed Description

📹 Webcam video capture and transmission to server

Media Capture

Overview

The media capture subsystem manages webcam video capture, frame compression, and transmission to the server. Supports platform-specific webcam backends and test pattern mode for testing without hardware.

Implementation: src/client/capture.c, src/client/capture.h

Platform Support

Webcam Backends:

Capture Thread

static void *capture_thread_func(void *arg)
{
(void)arg;
// Calculate frame interval for target FPS
uint64_t frame_interval_usec = 1000000 / VIDEO_CAPTURE_FPS;
uint64_t start_time = get_monotonic_time_usec();
// Capture frame from webcam
video_frame_t *frame = webcam_capture();
if (!frame) {
platform_sleep_usec(frame_interval_usec);
continue;
}
// Optional compression
void *send_data = frame->data;
size_t send_size = frame->data_size;
void *compressed_data = NULL;
if (opt_compress) {
compressed_data = compress_frame(frame->data, frame->data_size, &send_size);
if (compressed_data) {
send_data = compressed_data;
}
}
// Send frame to server
send_packet_to_server(PACKET_TYPE_IMAGE_FRAME, send_data, send_size,
if (compressed_data) free(compressed_data);
if (frame->owns_data && frame->data) free(frame->data);
free(frame);
// Rate limiting
platform_sleep_usec(frame_interval_usec);
// Snapshot mode: exit after delay
if (opt_snapshot) {
uint64_t elapsed = (get_monotonic_time_usec() - start_time) / 1000000;
if (elapsed >= opt_snapshot_delay) {
break;
}
}
}
atomic_store(&g_capture_thread_exited, true);
return NULL;
}
bool server_connection_is_active()
Check if server connection is currently active.
uint32_t server_connection_get_client_id()
Get client ID assigned by server.
unsigned long long uint64_t
Definition common.h:59
@ PACKET_TYPE_IMAGE_FRAME
Complete RGB image with dimensions.
Definition packet.h:363
void signal_exit(void)
Definition misc.c:129
bool should_exit(void)
Definition misc.c:131
int frame
Definition splash.c:99
Video frame structure.

Snapshot Mode

Snapshot mode (--snapshot, --snapshot-delay <seconds>):

  • Useful for testing without continuous streaming
  • --snapshot: Capture single frame and exit
  • --snapshot-delay N: Capture for N seconds then exit
# Single frame test
./bin/ascii-chat client --snapshot
# 5-second test
./bin/ascii-chat client --snapshot --snapshot-delay 5

Test Pattern Mode

Test pattern mode (--test-pattern):

  • Generates synthetic video frames without webcam hardware
  • Useful for testing on Windows or systems without webcam
  • Uses platform-specific test pattern generation
# Use test pattern instead of webcam
./bin/ascii-chat client --test-pattern
See also
src/client/capture.c
src/client/capture.h
lib/video/webcam/webcam.h
Client Overview

Function Documentation

◆ capture_cleanup()

void capture_cleanup ( )

#include <capture.c>

Cleanup capture subsystem.

Cleanup capture subsystem

Stops capture thread and cleans up media source resources. Called during client shutdown.

Releases all platform resources associated with webcam access. This must be called after capture_stop_thread() and before process exit.

The cleanup process:

  1. Closes webcam device (platform-specific close)
  2. Frees frame buffers
  3. Releases platform driver resources
  4. Cleans up thread handles

Must be called in proper order during shutdown to avoid resource leaks and to restore the platform to a clean state.

See also
capture_init "Initialize capture subsystem"

Definition at line 554 of file client/capture.c.

554 {
556
557 // Destroy capture context
558 if (g_capture_capture_ctx) {
559 session_capture_destroy(g_capture_capture_ctx);
560 g_capture_capture_ctx = NULL;
561 }
562}
void capture_stop_thread()
Stop capture thread.
void session_capture_destroy(session_capture_ctx_t *ctx)
Destroy session capture context and free resources.

References capture_stop_thread(), and session_capture_destroy().

Referenced by client_crypto_handshake().

◆ capture_init()

int capture_init ( )

#include <capture.c>

Initialize capture subsystem.

Initialize capture subsystem

Sets up media source (webcam, file, or stdin) and prepares capture system for operation. Must be called once during client initialization.

Returns
0 on success, negative on error

Initializes the media source and capture resources. Determines media source type from command-line options, then sets up platform-specific drivers and buffers. This must be called before any other capture functions.

The initialization process:

  1. Determines media source type from options (test pattern, file, stdin, or webcam)
  2. Creates appropriate media source (media_source_create())
  3. For WEBCAM: Initializes platform driver (V4L2/AVFoundation/Media Foundation)
  4. For FILE/STDIN: Opens FFmpeg decoder with format detection
  5. For TEST: Initializes procedural test pattern generator
  6. Initializes frame buffers and capture parameters

Media source selection (in order):

  1. If --test-pattern → MEDIA_SOURCE_TEST
  2. Else if --file - → MEDIA_SOURCE_STDIN
  3. Else if --file <path> → MEDIA_SOURCE_FILE
  4. Else if --url <url> → MEDIA_SOURCE_FILE (network URL)
  5. Else → MEDIA_SOURCE_WEBCAM (with –webcam-index)

Options affecting initialization:

  • --test-pattern: Use synthetic test pattern (any platform, no hardware needed)
  • --file <path>: Media file to play (mp4, avi, mkv, gif, etc.)
  • --file -: Read media from stdin (any FFmpeg format)
  • --url <url>: Network stream (HTTP/HTTPS/RTSP/YouTube/etc.)
  • --webcam-index <N>: Which camera to open (default: 0, first camera)
  • --media-loop: Loop file playback when EOF reached
  • --webcam-flip: Flip video vertically (webcam and test patterns only)
  • --fps <rate>: Target frame rate in FPS (1-144, default: 60)

Error handling:

  • WEBCAM: Device not found, driver error, device busy
  • FILE: File not found, unsupported format, FFmpeg error
  • STDIN: FFmpeg format detection failure
  • TEST: Rarely fails (only if display dimensions unavailable)
Returns
ASCIICHAT_OK on success, error code on failure:
  • ERROR_WEBCAM: Device not found or driver error
  • ERROR_WEBCAM_IN_USE: Device busy (another app using it)
  • ERROR_FILE_NOT_FOUND: Media file not found
  • ERROR_FORMAT: Unsupported media format
  • ERROR_NOT_SUPPORTED: Platform doesn't support this source type
Note
This is called during client initialization, before entering the connection loop.
The actual frame capture happens in the capture thread (capture_start_thread).
Frame resizing to 800x600 (max) happens during capture, not initialization.
See also
media_source_create "Media source creation"
capture_start_thread "Start capture thread"
capture_cleanup "Cleanup resources"

Definition at line 413 of file client/capture.c.

413 {
414 // Build capture configuration from options
415 session_capture_config_t config = {0};
416 const char *media_url = GET_OPTION(media_url);
417 const char *media_file = GET_OPTION(media_file);
418 bool media_from_stdin = GET_OPTION(media_from_stdin);
419
420 if (media_url && media_url[0] != '\0') {
421 // Network URL streaming (takes priority over --file)
422 // Don't open webcam when streaming from URL
423 config.type = MEDIA_SOURCE_FILE;
424 config.path = media_url;
425 config.loop = false; // Network URLs cannot be looped
426 log_debug("Using network URL: %s (webcam disabled)", media_url);
427 } else if (media_file && media_file[0] != '\0') {
428 // File or stdin streaming - don't open webcam
429 config.type = media_from_stdin ? MEDIA_SOURCE_STDIN : MEDIA_SOURCE_FILE;
430 config.path = media_file;
431 config.loop = GET_OPTION(media_loop) && !media_from_stdin;
432 log_debug("Using media %s: %s (webcam disabled)", media_from_stdin ? "stdin" : "file", media_file);
433 } else if (GET_OPTION(test_pattern)) {
434 // Test pattern mode - don't open real webcam
435 config.type = MEDIA_SOURCE_TEST;
436 config.path = NULL;
437 log_debug("Using test pattern mode");
438 } else {
439 // Webcam mode (default)
440 static char webcam_index_str[32];
441 safe_snprintf(webcam_index_str, sizeof(webcam_index_str), "%u", GET_OPTION(webcam_index));
442 config.type = MEDIA_SOURCE_WEBCAM;
443 config.path = webcam_index_str;
444 log_debug("Using webcam device %u", GET_OPTION(webcam_index));
445 }
447 config.resize_for_network = true; // Client always resizes for network transmission
448
449 // Configure audio capture with fallback to microphone
450 config.enable_audio = true;
451 config.audio_fallback_to_mic = true;
453
454 // Add seek timestamp if specified
455 config.initial_seek_timestamp = GET_OPTION(media_seek_timestamp);
456
457 // Create capture context using session library
458 g_capture_capture_ctx = session_capture_create(&config);
459 if (!g_capture_capture_ctx) {
460 // Check if there's already an error set (e.g., ERROR_WEBCAM_IN_USE)
461 asciichat_error_t existing_error = GET_ERRNO();
462 log_debug("session_capture_create failed, GET_ERRNO() returned: %d", existing_error);
463 if (existing_error != ASCIICHAT_OK) {
464 log_debug("Returning existing error code %d", existing_error);
465 return existing_error;
466 }
467 SET_ERRNO(ERROR_MEDIA_INIT, "Failed to initialize capture source");
468 return -1;
469 }
470
471 return 0;
472}
#define CAPTURE_TARGET_FPS
audio_context_t * audio_get_context(void)
Get the global audio context for use by other subsystems.
#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
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_MEDIA_INIT
Definition error_codes.h:70
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
@ 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
#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
session_capture_ctx_t * session_capture_create(const session_capture_config_t *config)
Legacy function - creates either mirror or network capture based on options.
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)
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)

References ASCIICHAT_OK, session_capture_config_t::audio_fallback_to_mic, audio_get_context(), CAPTURE_TARGET_FPS, session_capture_config_t::enable_audio, ERROR_MEDIA_INIT, GET_ERRNO, GET_OPTION, session_capture_config_t::initial_seek_timestamp, log_debug, session_capture_config_t::loop, MEDIA_SOURCE_FILE, MEDIA_SOURCE_STDIN, MEDIA_SOURCE_TEST, MEDIA_SOURCE_WEBCAM, session_capture_config_t::mic_audio_ctx, session_capture_config_t::path, session_capture_config_t::resize_for_network, safe_snprintf(), session_capture_create(), SET_ERRNO, session_capture_config_t::target_fps, and session_capture_config_t::type.

◆ capture_start_thread()

int capture_start_thread ( )

#include <capture.c>

Start capture thread.

Start capture thread

Creates and starts the webcam capture thread. Also sends stream start notification to server.

Returns
0 on success, negative on error

Spawns the capture thread in the global client worker thread pool. The thread runs the main capture loop, continuously acquiring frames from the media source, processing them (resize, format conversion), and transmitting to the server with FPS rate limiting.

The capture loop implements this pipeline:

Frame Acquisition:

  1. Read frame from media source (media_source_read_video())
  2. Verify frame is valid (non-null, valid dimensions)
  3. Measure acquisition time for timing/debug

Frame Processing (Resize & Format):

  1. Calculate optimal resize dimensions (fit-to-bounds, aspect ratio preserved)
  2. Scale frame to max 800x600 using high-quality resizing
  3. Format frame as RGB (already normalized by media source)
  4. Add black padding if frame smaller than target area

FPS Synchronization:

  1. Calculate frame interval from target FPS (e.g., 60 FPS = 16.67ms)
  2. Measure actual processing time (acquisition + resize)
  3. Sleep for remaining interval to maintain target FPS
  4. Account for monotonic clock drift and OS scheduling

Network Transmission:

  1. Optionally compress frame if –compress is enabled
  2. Serialize frame to PACKET_TYPE_IMAGE_FRAME format (width + height + pixels)
  3. Send packet to server via send_packet_to_server()
  4. Log transmission metrics (size, latency, dropped frames)

Loop Control:

  1. Check if in snapshot mode and should exit (–snapshot mode)
  2. Check for shutdown signal (capture_stop_thread() called)
  3. Check for connection loss (reconnect trigger)
  4. Loop back to step 1

FPS Behavior:

  • Target FPS from --fps option (default: 60)
  • Achieved via sleep-based rate limiting (monotonic clock)
  • Maintains consistent frame intervals even if processing takes variable time
  • Adapts to system load and media source frame availability

Must be called after server connection is established and protocol thread started.

Returns
ASCIICHAT_OK on success, error code on failure:
  • ERROR_THREAD: Thread pool full or system thread creation failed
  • ERROR_NO_MEMORY: Insufficient memory for thread resources
Note
Must be paired with capture_stop_thread() to properly clean up.
Frame resizing and FPS limiting are handled transparently by the capture thread.
Rate limiting is frame-by-frame, not based on network round-trip time.
See also
capture_stop_thread "Stop capture thread"
capture_thread_exited "Check if thread has exited"
media_source_read_video "Frame acquisition from media source"

Definition at line 483 of file client/capture.c.

483 {
484 if (THREAD_IS_CREATED(g_capture_thread_created)) {
485 log_warn("Capture thread already created");
486 return 0;
487 }
488
489 // Register capture atomics with named debug registry
490 static bool capture_atomics_registered = false;
491 if (!capture_atomics_registered) {
492 NAMED_REGISTER_ATOMIC(&g_capture_thread_exited, "capture_webcam_thread_exit_confirmation", NULL);
493 capture_atomics_registered = true;
494 }
495
496 // Start webcam capture thread
497 atomic_store_bool(&g_capture_thread_exited, false);
498 if (thread_pool_spawn(g_client_worker_pool, webcam_capture_thread_func, NULL, 2, "webcam_capture") != ASCIICHAT_OK) {
499 SET_ERRNO(ERROR_THREAD, "Webcam capture thread creation failed");
500 LOG_ERRNO_IF_SET("Webcam capture thread creation failed");
501 return -1;
502 }
503
504 g_capture_thread_created = true;
505 log_debug("Webcam capture thread created successfully");
506
507 return 0;
508}
#define LOG_ERRNO_IF_SET(message)
Check if any error occurred and log it if so.
void atomic_store_bool(atomic_t *a, bool value)
Atomically store a boolean value.
Definition atomic.c:177
thread_pool_t * g_client_worker_pool
Global client worker thread pool.
asciichat_error_t thread_pool_spawn(void *pool, void *(*thread_func)(void *), void *thread_arg, int stop_id, const char *name)
Spawn a worker thread in the pool.
#define NAMED_REGISTER_ATOMIC(a, name, parent_ptr)
Register an atomic_t with automatic format specifier.
@ ERROR_THREAD
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define THREAD_IS_CREATED(created_flag)

References ASCIICHAT_OK, atomic_store_bool(), ERROR_THREAD, g_client_worker_pool, log_debug, LOG_ERRNO_IF_SET, log_warn, NAMED_REGISTER_ATOMIC, SET_ERRNO, THREAD_IS_CREATED, and thread_pool_spawn().

Referenced by protocol_start_connection().

◆ capture_stop_thread()

void capture_stop_thread ( )

#include <capture.c>

Stop capture thread.

Stop capture thread

Gracefully stops the capture thread and cleans up resources. Safe to call multiple times.

Signals the capture thread to exit and waits for it to join cleanly. Called during connection loss handling or shutdown.

The function:

  1. Sets shutdown flag
  2. Polls capture_thread_exited() until thread exits
  3. Releases thread-specific resources

Must be called before reconnection to prevent resource leaks.

Note
This function blocks until the capture thread exits. Typical wait time: <1 second (one frame interval).
See also
capture_start_thread "Start capture thread"

Definition at line 517 of file client/capture.c.

517 {
518 if (!THREAD_IS_CREATED(g_capture_thread_created)) {
519 return;
520 }
521
522 // Wait for thread to exit gracefully
523 int wait_count = 0;
524 while (wait_count < 20 && !atomic_load_bool(&g_capture_thread_exited)) {
525 platform_sleep_us(100 * US_PER_MS_INT); // 100ms
526 wait_count++;
527 }
528
529 if (!atomic_load_bool(&g_capture_thread_exited)) {
530 log_warn("Capture thread not responding after 2 seconds - will be joined by thread pool");
531 }
532
533 // Thread will be joined by thread_pool_stop_all() in protocol_stop_connection()
534 g_capture_thread_created = false;
535}
bool atomic_load_bool(atomic_t *a)
Atomically load a boolean value.
Definition atomic.c:169
#define US_PER_MS_INT
Definition time.h:160
void platform_sleep_us(unsigned int us)
High-precision sleep function with microsecond precision.

References atomic_load_bool(), log_warn, platform_sleep_us(), THREAD_IS_CREATED, and US_PER_MS_INT.

Referenced by capture_cleanup(), and protocol_stop_connection().

◆ capture_thread_exited()

bool capture_thread_exited ( )

#include <capture.c>

Check if capture thread has exited.

Check if capture thread has exited

Returns
true if thread has exited, false otherwise

Atomically checks whether the capture thread has finished execution. The main thread uses this to wait for clean thread shutdown before reconnection or final cleanup.

Returns
true if thread exited, false otherwise
Note
The main thread typically polls this in a 10-100ms loop until true.
See also
capture_stop_thread "Stop capture thread"

Definition at line 543 of file client/capture.c.

543 {
544 return atomic_load_bool(&g_capture_thread_exited);
545}

References atomic_load_bool().