107#include <stdatomic.h>
135static bool g_capture_thread_created =
false;
145static atomic_bool g_capture_thread_exited =
false;
153#define MAX_FRAME_WIDTH 480
155#define MAX_FRAME_HEIGHT 270
183static void calculate_optimal_dimensions(ssize_t original_width, ssize_t original_height, ssize_t max_width,
184 ssize_t max_height, ssize_t *result_width, ssize_t *result_height) {
186 float img_aspect = (float)original_width / (
float)original_height;
188 if (original_width <= max_width && original_height <= max_height) {
190 *result_width = original_width;
191 *result_height = original_height;
195 if ((
float)max_width / (float)max_height > img_aspect) {
197 *result_height = max_height;
198 *result_width = (ssize_t)(max_height * img_aspect);
201 *result_width = max_width;
202 *result_height = (ssize_t)(max_width / img_aspect);
223static image_t *process_frame_for_transmission(
image_t *original_image, ssize_t max_width, ssize_t max_height) {
224 if (!original_image) {
228 ssize_t resized_width, resized_height;
229 calculate_optimal_dimensions(original_image->
w, original_image->
h, max_width, max_height, &resized_width,
232 if (original_image->
w == resized_width && original_image->
h == resized_height) {
240 memcpy(copy->
pixels, original_image->
pixels, (
size_t)original_image->
w * original_image->
h *
sizeof(rgb_pixel_t));
289static void *webcam_capture_thread_func(
void *arg) {
291 struct timespec last_capture_time = {0, 0};
294 static fps_t fps_tracker = {0};
295 static bool fps_tracker_initialized =
false;
296 static uint64_t capture_frame_count = 0;
297 static struct timespec last_capture_frame_time = {0, 0};
298 static const uint32_t expected_capture_fps = 144;
299 if (!fps_tracker_initialized) {
300 fps_init(&fps_tracker, 144,
"WEBCAM_TX");
301 fps_tracker_initialized =
true;
313 const long CAPTURE_INTERVAL_MS = 1000 / 144;
314 struct timespec current_time;
315 (void)clock_gettime(CLOCK_MONOTONIC, ¤t_time);
316 long elapsed_ms = (current_time.tv_sec - last_capture_time.tv_sec) * 1000 +
317 (current_time.tv_nsec - last_capture_time.tv_nsec) / 1000000;
318 if (elapsed_ms < CAPTURE_INTERVAL_MS) {
326 log_info(
"No frame available from webcam yet (webcam_read returned NULL)");
332 (void)clock_gettime(CLOCK_MONOTONIC, ¤t_time);
333 fps_frame(&fps_tracker, ¤t_time,
"webcam frame captured");
339 if (!processed_image) {
355 log_warn(
"Connection lost before sending, stopping video transmission");
369 log_error(
"Failed to send image frame: %d", send_result);
376 capture_frame_count++;
380 ((
uint64_t)current_time.tv_sec * 1000000 + (
uint64_t)current_time.tv_nsec / 1000) -
381 ((
uint64_t)last_capture_frame_time.tv_sec * 1000000 + (
uint64_t)last_capture_frame_time.tv_nsec / 1000);
382 last_capture_frame_time = current_time;
385 uint64_t expected_interval_us = 1000000 / expected_capture_fps;
386 uint64_t lag_threshold_us = expected_interval_us + (expected_interval_us / 2);
389 if (capture_frame_count > 1 && frame_interval_us > lag_threshold_us) {
391 "CLIENT CAPTURE LAG: Frame captured %.1fms late (expected %.1fms, got %.1fms, actual fps: %.1f)",
392 (
double)(frame_interval_us - expected_interval_us) / 1000.0, (
double)expected_interval_us / 1000.0,
393 (
double)frame_interval_us / 1000.0, 1000000.0 / (
double)frame_interval_us);
397 last_capture_time = current_time;
401 processed_image = NULL;
405 log_info(
"Webcam capture thread stopped");
408 atomic_store(&g_capture_thread_exited,
true);
451 log_warn(
"Capture thread already created");
456 atomic_store(&g_capture_thread_exited,
false);
463 g_capture_thread_created =
true;
464 log_info(
"Webcam capture thread created successfully");
492 while (wait_count < 20 && !atomic_load(&g_capture_thread_exited)) {
495 if (wait_count % 5 == 0) {
501 if (!atomic_load(&g_capture_thread_exited)) {
502 log_warn(
"Capture thread not responding after 2 seconds - will be joined by thread pool");
506 g_capture_thread_created =
false;
516 return atomic_load(&g_capture_thread_exited);
๐ Cross-platform abstraction layer umbrella header for ascii-chat
โ ๏ธโผ๏ธ Error and/or exit() when things go bad.
#define LOG_ERRNO_IF_SET(message)
Check if any error occurred and log it if so.
ascii-chat Client Media Capture Management Interface
thread_pool_t * g_client_worker_pool
Global client worker thread pool.
bool should_exit()
Check if client should exit.
๐ Network byte order conversion helpers
โฑ๏ธ FPS tracking utility for monitoring frame throughput across all threads
int capture_start_thread()
Start capture thread.
int capture_init()
Initialize capture subsystem.
void capture_cleanup()
Cleanup capture subsystem.
void capture_stop_thread()
Stop capture thread.
bool capture_thread_exited()
Check if capture thread has exited.
bool server_connection_is_active()
Check if server connection is currently active.
acip_transport_t * server_connection_get_transport(void)
Get ACIP transport instance.
int threaded_send_stream_start_packet(uint32_t stream_type)
Thread-safe stream start packet transmission.
bool server_connection_is_lost()
Check if connection loss has been detected.
void server_connection_lost()
Signal that connection has been lost.
void fps_frame(fps_t *tracker, const struct timespec *current_time, const char *context)
Track a frame and detect lag conditions.
unsigned long long uint64_t
void fps_init(fps_t *tracker, int expected_fps, const char *name)
Initialize FPS tracker.
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
void asciichat_errno_cleanup(void)
Cleanup error system resources.
asciichat_error_t
Error and exit codes - unified status values (0-255)
#define LOG_RATE_FAST
Log rate limit: 1 second (1,000,000 microseconds)
#define log_warn(...)
Log a WARN message.
#define log_error(...)
Log an ERROR message.
#define log_info(...)
Log an INFO message.
#define log_warn_every(interval_us, fmt,...)
Rate-limited WARN logging.
#define STREAM_TYPE_VIDEO
Video stream.
#define GET_OPTION(field)
Safely get a specific option field (lock-free read)
void image_resize(const image_t *s, image_t *d)
Resize image using nearest-neighbor interpolation.
void image_destroy(image_t *p)
Destroy an image allocated with image_new()
image_t * image_new(size_t width, size_t height)
Create a new image with standard allocation.
asciichat_error_t webcam_init(unsigned short int webcam_index)
Initialize global webcam interface.
image_t * webcam_read(void)
Capture a frame from global webcam.
void webcam_flush(void)
Flush/interrupt any pending webcam read operations.
void webcam_cleanup(void)
Clean up global webcam interface.
asciichat_error_t acip_send_image_frame(acip_transport_t *transport, const void *pixel_data, uint32_t width, uint32_t height, uint32_t pixel_format)
Send image frame to server (client โ server)
ACIP client-side protocol library.
โ๏ธ Command-line options parsing and configuration management for ascii-chat
ACIP shared/bidirectional packet sending functions.
ascii-chat Server Mode Entry Point Header
ascii-chat Client Server Connection Management Interface
Transport instance structure.
int w
Image width in pixels (must be > 0)
int h
Image height in pixels (must be > 0)
rgb_pixel_t * pixels
Pixel data array (width * height RGB pixels, row-major order)
asciichat_error_t thread_pool_spawn(thread_pool_t *pool, void *(*thread_func)(void *), void *thread_arg, int stop_id, const char *thread_name)
Spawn a worker thread in the pool.
๐งต Generic thread pool abstraction for managing worker threads
โฑ๏ธ High-precision timing utilities using sokol_time.h and uthash
๐ผ๏ธ Safe overflow-checked buffer size calculations for images and video frames
๐งต Thread lifecycle management helpers
#define THREAD_IS_CREATED(created_flag)
Image Data Structures and Operations.