ascii-chat
Binary v0.15.83-53-gb711d8720
Library lib/v0.3.2-701-gb711d8720
Video chat in your terminal
Loading...
Searching...
No Matches
source.h File Reference

🎬 Unified media source abstraction for webcam, files, and stdin More...

Go to the source code of this file.

Typedefs

typedef struct media_source_t media_source_t
 Opaque media source handle.

Enumerations

enum  media_source_type_t { MEDIA_SOURCE_WEBCAM , MEDIA_SOURCE_FILE , MEDIA_SOURCE_STDIN , MEDIA_SOURCE_TEST }
 Media source type enumeration. More...

Functions

media_source_t * media_source_create (media_source_type_t type, const char *path)
 Create a new media source.
void media_source_destroy (media_source_t *source)
 Destroy media source and free resources.
image_t * media_source_read_video (media_source_t *source)
 Read next video frame from media source.
bool media_source_has_video (media_source_t *source)
 Check if media source has video stream.
size_t media_source_read_audio (media_source_t *source, float *buffer, size_t num_samples)
 Read audio samples from media source.
bool media_source_has_audio (media_source_t *source)
 Check if media source has audio stream.
void media_source_set_loop (media_source_t *source, bool loop)
 Enable or disable looping.
void media_source_pause (media_source_t *source)
 Pause media playback.
void media_source_resume (media_source_t *source)
 Resume media playback after pause.
bool media_source_is_paused (media_source_t *source)
 Check if media source is paused.
void media_source_toggle_pause (media_source_t *source)
 Toggle pause state of media source.
bool media_source_at_end (media_source_t *source)
 Check if media source reached end of stream.
asciichat_error_t media_source_rewind (media_source_t *source)
 Rewind media source to beginning.
asciichat_error_t media_source_seek (media_source_t *source, double timestamp_sec)
 Seek media source to timestamp.
media_source_type_t media_source_get_type (media_source_t *source)
 Get media source type.
double media_source_get_duration (media_source_t *source)
 Get media duration in seconds.
double media_source_get_position (media_source_t *source)
 Get current playback position in seconds.
double media_source_get_video_fps (media_source_t *source)
 Get video frame rate in frames per second.
void media_source_set_audio_context (media_source_t *source, void *audio_ctx)
 Set audio context for playback buffer management.
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.
bool media_source_uses_webcam (media_source_t *source)
asciichat_error_t media_source_start_video (media_source_t *source)
media_source_t * media_source_create_interruptible (media_source_type_t type, const char *path, bool(*cancel)(void *), void *data)

Detailed Description

🎬 Unified media source abstraction for webcam, files, and stdin

Definition in file source.h.

Function Documentation

◆ media_source_create_interruptible()

media_source_t * media_source_create_interruptible ( media_source_type_t type,
const char * path,
bool(* cancel )(void *),
void * data )

Definition at line 225 of file source.c.

226 {
227 ASCII_OTEL_SCOPE(MEDIA_SOURCE_CREATE_INTERRUPTIBLE);
229 if (!source) {
230 SET_ERRNO(ERROR_MEMORY, "Failed to allocate media source");
231 return NULL;
232 }
233
234 memset(source, 0, sizeof(*source));
235 source->type = type;
236 source->loop_enabled = false;
237 atomic_store_bool(&source->is_paused, false);
238
239 // Initialize mutex for protecting shared decoder access (for YouTube URLs)
240 if (mutex_init(&source->decoder_mutex, "media_decoder") != 0) {
241 SET_ERRNO(ERROR_MEMORY, "Failed to initialize mutex");
243 return NULL;
244 }
245
246 switch (type) {
247 case MEDIA_SOURCE_WEBCAM: {
248 // Parse webcam index from path (if provided)
249 unsigned short int index = 0;
250 if (path) {
251 char *endptr;
252 errno = 0;
253 long parsed = strtol(path, &endptr, 10);
254 if (*endptr == '\0' && errno == 0 && parsed >= 0 && parsed <= USHRT_MAX) {
255 index = (unsigned short int)parsed;
256 } else if (*endptr != '\0' || errno != 0) {
257 log_error("Invalid webcam index: %s", path);
258 }
259 }
260
261 source->webcam_index = index;
262 if (GET_OPTION(waveform) || GET_OPTION(fft))
263 break;
264 asciichat_error_t webcam_error = webcam_init_context(&source->webcam_ctx, index);
265 if (webcam_error != ASCIICHAT_OK) {
266 asciichat_error_context_t webcam_context;
267 bool has_webcam_context = HAS_ERRNO(&webcam_context);
268 // Webcam init failed - log and cleanup
269 NOTICE(WARNING, "CAMERA UNAVAILABLE", "Failed to initialize webcam device %u: %s", index,
270 asciichat_error_format(has_webcam_context ? webcam_context.context_message : NULL, webcam_error));
271 // Destroy mutexes before freeing source
272 mutex_destroy(&source->decoder_mutex);
274
275 // Add media-level context while preserving the webcam failure as its cause.
276 if (webcam_error == ERROR_WEBCAM_IN_USE) {
277 SET_ERRNO(ERROR_WEBCAM_IN_USE, "Webcam device %u is in use", index);
278 } else {
279 SET_ERRNO(ERROR_WEBCAM, "Failed to initialize webcam device %u", index);
280 }
281 return NULL;
282 }
283
284 log_debug("Media source: Webcam device %u", index);
285 break;
286 }
287
288 case MEDIA_SOURCE_FILE: {
290 if (!path || path[0] == '\0') {
291 SET_ERRNO(ERROR_INVALID_PARAM, "File path is required for FILE source");
292 // Destroy mutexes before freeing source
293 mutex_destroy(&source->decoder_mutex);
295 return NULL;
296 }
297
298 // Resolve URL using smart FFmpeg/yt-dlp routing
299 const char *effective_path = path;
300 char resolved_url[MEDIA_STREAM_URL_SIZE] = {0};
301 char resolved_audio_url[MEDIA_STREAM_URL_SIZE] = {0};
302 yt_dlp_request_options_t video_request = {0}, audio_request = {0};
303 const char *yt_dlp_options = GET_OPTION(yt_dlp_options);
304
305 log_debug("Resolving URL: %s", path);
306 asciichat_error_t resolve_err =
307 media_source_resolve_url(path, yt_dlp_options, resolved_url, sizeof(resolved_url), resolved_audio_url,
308 sizeof(resolved_audio_url), &video_request, &audio_request, cancel, data);
309 if (resolve_err != ASCIICHAT_OK) {
310 log_debug("Failed to resolve URL %s", asciichat_error_format(NULL, resolve_err));
311 // Destroy mutexes before freeing source
312 mutex_destroy(&source->decoder_mutex);
314 return NULL;
315 }
316 if (cancel && cancel(data)) {
317 mutex_destroy(&source->decoder_mutex);
319 return NULL;
320 }
321 effective_path = resolved_url;
322 log_debug("Using resolved URL for playback");
323
324 // Store original URL for potential re-extraction if needed
325 source->original_youtube_url = platform_strdup(path);
326 if (!source->original_youtube_url) {
327 log_warn("Failed to cache original URL");
328 }
329
330 // Cache file path for potential reopen on loop
331 source->file_path = platform_strdup(effective_path);
332 if (!source->file_path) {
333 SET_ERRNO(ERROR_MEMORY, "Failed to duplicate file path");
334 SAFE_FREE(source->original_youtube_url);
335 // Destroy mutexes before freeing source
336 mutex_destroy(&source->decoder_mutex);
338 return NULL;
339 }
340
341 // Always use separate decoders for video and audio
342 // This allows independent read rates and avoids lock contention
345 effective_path, cancel, data, video_request.headers, video_request.cookies,
346 video_request.proxy_specified ? video_request.proxy : NULL);
347 if (!source->video_decoder) {
348 SET_ERRNO(ERROR_MEDIA_OPEN, "Media initialization failed: video decoder could not open input");
349 if (!cancel)
350 NOTICE(WARNING, "MEDIA COULD NOT BE OPENED", "Failed to open media file for video: %s: %s", effective_path,
352 LOG_ERRNO_IF_SET("Media initialization failed");
353 SAFE_FREE(source->file_path);
354 SAFE_FREE(source->original_youtube_url);
355 // Destroy mutexes before freeing source
356 mutex_destroy(&source->decoder_mutex);
358 return NULL;
359 }
360
361 ASSERT_NO_ERRNO_SINCE(decoder_scope);
362
363 decoder_scope = errno_checkpoint();
364 const char *audio_path = resolved_audio_url[0] ? resolved_audio_url : effective_path;
365 const yt_dlp_request_options_t *audio_settings = resolved_audio_url[0] ? &audio_request : &video_request;
367 audio_path, cancel, data, audio_settings->headers, audio_settings->cookies,
368 audio_settings->proxy_specified ? audio_settings->proxy : NULL);
369 if (!source->audio_decoder) {
370 SET_ERRNO(ERROR_MEDIA_OPEN, "Media initialization failed: audio decoder could not open input");
371 LOG_ERRNO_IF_SET("Media initialization failed");
372 ffmpeg_decoder_destroy(source->video_decoder);
373 source->video_decoder = NULL;
374 SAFE_FREE(source->file_path);
375 SAFE_FREE(source->original_youtube_url);
376 // Destroy mutexes before freeing source
377 mutex_destroy(&source->decoder_mutex);
379 return NULL;
380 }
381 source->is_shared_decoder = false;
382
383 ASSERT_NO_ERRNO_SINCE(decoder_scope);
384 // yt-dlp emits selected formats in selector order, which may be audio first.
385 if (resolved_audio_url[0] && !ffmpeg_decoder_has_video(source->video_decoder) &&
386 ffmpeg_decoder_has_video(source->audio_decoder) && ffmpeg_decoder_has_audio(source->video_decoder)) {
387 ffmpeg_decoder_t *audio = source->video_decoder;
388 source->video_decoder = source->audio_decoder;
389 source->audio_decoder = audio;
390 }
391
392 // Start prefetch after choosing the decoder that actually contains video.
394 asciichat_error_t prefetch_err = ffmpeg_decoder_has_video(source->video_decoder)
395 ? ffmpeg_decoder_start_prefetch(source->video_decoder)
396 : ASCIICHAT_OK;
397 if (prefetch_err != ASCIICHAT_OK) {
398 log_error("Failed to start video prefetch thread: %s", asciichat_error_format(NULL, prefetch_err));
399 ffmpeg_decoder_destroy(source->video_decoder);
400 ffmpeg_decoder_destroy(source->audio_decoder);
401 SAFE_FREE(source->file_path);
402 SAFE_FREE(source->original_youtube_url);
403 mutex_destroy(&source->decoder_mutex);
405 return NULL;
406 }
407 ASSERT_NO_ERRNO_SINCE(prefetch_scope);
409 // Both decoders succeeded: failed extraction/prefetch attempts are resolved.
411 ASSERT_NO_ERRNO_SINCE(media_scope);
412 log_debug("Media source: URL resolved to stream (separate video/audio decoders)");
413 break;
414 }
415
416 case MEDIA_SOURCE_STDIN: {
417 // Create separate decoders for video and audio from stdin
418 source->video_decoder = ffmpeg_decoder_create_stdin();
419 if (!source->video_decoder) {
420 log_error("Failed to open stdin for video input");
421 // Destroy mutexes before freeing source
422 mutex_destroy(&source->decoder_mutex);
424 return NULL;
425 }
426
427 // Start prefetch thread for stdin video frames
429 asciichat_error_t prefetch_err = ffmpeg_decoder_has_video(source->video_decoder)
430 ? ffmpeg_decoder_start_prefetch(source->video_decoder)
431 : ASCIICHAT_OK;
432 if (prefetch_err != ASCIICHAT_OK) {
433 log_error("Failed to start stdin video prefetch thread: %s", asciichat_error_format(NULL, prefetch_err));
434 ffmpeg_decoder_destroy(source->video_decoder);
435 SAFE_FREE(source->file_path);
436 SAFE_FREE(source->original_youtube_url);
437 mutex_destroy(&source->decoder_mutex);
439 return NULL;
440 }
441
442 ASSERT_NO_ERRNO_SINCE(prefetch_scope);
444 source->audio_decoder = ffmpeg_decoder_create_stdin();
445 if (!source->audio_decoder) {
446 log_error("Failed to open stdin for audio input");
447 ffmpeg_decoder_destroy(source->video_decoder);
448 source->video_decoder = NULL;
449 // Destroy mutexes before freeing source
450 mutex_destroy(&source->decoder_mutex);
452 return NULL;
453 }
454
455 log_debug("Media source: stdin (separate video/audio decoders)");
456 break;
457 }
458
459 case MEDIA_SOURCE_TEST: {
460 // Test pattern state is encapsulated in media_source_t
461 source->webcam_index = 0;
462 source->webcam_ctx = NULL; // No context needed for test pattern
463 source->test_pattern = NULL;
464 source->test_start_ns = 0;
465
466 log_debug("Media source: Test pattern");
467 break;
468 }
469
470 default:
471 SET_ERRNO(ERROR_INVALID_PARAM, "Unknown media source type: %d", type);
472 // Destroy mutexes before freeing source
473 mutex_destroy(&source->decoder_mutex);
475 return NULL;
476 }
477
478 if ((type == MEDIA_SOURCE_FILE || type == MEDIA_SOURCE_STDIN) && !ffmpeg_decoder_has_video(source->video_decoder) &&
479 ffmpeg_decoder_has_audio(source->audio_decoder)) {
480 source->audio_only = true;
481 }
482
483 /* Register media source with named registry */
484 char media_type_str[MEDIA_SOURCE_MEDIA_TYPE_BUFFER_SIZE];
485 switch (type) {
487 snprintf(media_type_str, sizeof(media_type_str), "media_source:webcam");
488 break;
490 snprintf(media_type_str, sizeof(media_type_str), "media_source:file");
491 break;
493 snprintf(media_type_str, sizeof(media_type_str), "media_source:test");
494 break;
495 default:
496 snprintf(media_type_str, sizeof(media_type_str), "media_source:unknown");
497 break;
498 }
499 NAMED_REGISTER(source, media_type_str, "media_source_t", "0x%tx", NULL);
500
501 /* Register media source's sync primitives with hierarchical naming */
502 NAMED_REGISTER_MUTEX(&source->decoder_mutex, "decoder_mutex", (uintptr_t)(const void *)(source));
503 NAMED_REGISTER_ATOMIC(&source->is_paused, "media_paused", source);
504
505 return source;
506}
void errno_scope_end(asciichat_errno_scope_t scope, asciichat_errno_outcome_t outcome)
asciichat_errno_scope_t errno_checkpoint(void)
asciichat_errno_scope_t errno_scope_begin(void)
const char * asciichat_error_format(const char *message, asciichat_error_t code)
#define SET_ERRNO(code,...)
#define HAS_ERRNO(context)
#define ASSERT_NO_ERRNO_SINCE(scope)
@ ASCIICHAT_ERRNO_HANDLED
@ ASCIICHAT_ERRNO_DISMISSED
#define LOG_ERRNO_IF_SET(message)
ffmpeg_decoder_t * ffmpeg_decoder_create_with_request_options(const char *path, bool(*cancel)(void *), void *data, const char *headers, const char *cookies, const char *proxy)
audio_visualization_source_t source
Definition fft.c:24
#define SAFE_FREE(ptr)
Definition common.h:377
#define SAFE_MALLOC(size, cast)
Definition common.h:265
#define NAMED_REGISTER_ATOMIC(a, name, parent_ptr)
Register an atomic_t with automatic format specifier.
#define NAMED_REGISTER(ptr, name, type, fmt, parent_ptr)
Register any pointer with base name, type, format spec, and location (auto-suffix).
#define NAMED_REGISTER_MUTEX(mutex, name, parent_ptr)
Register a mutex with automatic format specifier.
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ERROR_WEBCAM_IN_USE
Definition error_codes.h:60
@ ERROR_MEMORY
Definition error_codes.h:51
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_INVALID_PARAM
@ ERROR_MEDIA_OPEN
Definition error_codes.h:66
@ ERROR_WEBCAM
Definition error_codes.h:59
#define MEDIA_STREAM_URL_SIZE
#define log_warn(...)
Log a WARN message.
Definition log/log.h:621
#define log_error(...)
Log an ERROR message.
Definition log/log.h:634
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
bool ffmpeg_decoder_has_video(ffmpeg_decoder_t *decoder)
Check if decoder has video stream.
bool ffmpeg_decoder_has_audio(ffmpeg_decoder_t *decoder)
Check if decoder has audio stream.
asciichat_error_t ffmpeg_decoder_start_prefetch(ffmpeg_decoder_t *decoder)
Start the background frame prefetching thread.
void ffmpeg_decoder_destroy(ffmpeg_decoder_t *decoder)
Destroy FFmpeg decoder and free resources.
ffmpeg_decoder_t * ffmpeg_decoder_create_stdin(void)
Create FFmpeg decoder from stdin.
@ MEDIA_SOURCE_STDIN
Piped or redirected input.
Definition source.h:100
@ MEDIA_SOURCE_WEBCAM
Hardware webcam device.
Definition source.h:98
@ MEDIA_SOURCE_FILE
Media file (video/audio).
Definition source.h:99
@ MEDIA_SOURCE_TEST
Test pattern generator.
Definition source.h:101
#define GET_OPTION(field)
Safely get a specific option field (lock-free read).
Definition options.h:1336
int errno
asciichat_error_t webcam_init_context(webcam_context_t **ctx, unsigned short int device_index)
Initialize webcam context for advanced operations.
#define NOTICE(severity, title,...)
Definition notice.h:68
#define ASCII_OTEL_SCOPE(id)
Definition otel.h:72
static bool size_t index
Definition queries.h:8
#define MEDIA_SOURCE_MEDIA_TYPE_BUFFER_SIZE
Definition source.c:7
When code needs to work with asciichat errno snapshot, it keeps total, copied here instead of scatter...
FFmpeg decoder state for video and audio decoding.
Media source for video and audio capture.
Definition source.c:47
char headers[YT_DLP_HEADERS_CAPACITY]
Definition yt_dlp.h:20
char cookies[YT_DLP_COOKIES_CAPACITY]
Definition yt_dlp.h:21
char proxy[YT_DLP_PROXY_CAPACITY]
Definition yt_dlp.h:22

References ASCII_OTEL_SCOPE, ASCIICHAT_ERRNO_DISMISSED, ASCIICHAT_ERRNO_HANDLED, asciichat_error_format(), ASCIICHAT_OK, ASSERT_NO_ERRNO_SINCE, asciichat_error_context_t::context_message, yt_dlp_request_options_t::cookies, errno, errno_checkpoint(), errno_scope_begin(), errno_scope_end(), ERROR_INVALID_PARAM, ERROR_MEDIA_OPEN, ERROR_MEMORY, ERROR_WEBCAM, ERROR_WEBCAM_IN_USE, ffmpeg_decoder_create_stdin(), ffmpeg_decoder_create_with_request_options(), ffmpeg_decoder_destroy(), ffmpeg_decoder_has_audio(), ffmpeg_decoder_has_video(), ffmpeg_decoder_start_prefetch(), GET_OPTION, HAS_ERRNO, yt_dlp_request_options_t::headers, index, log_debug, LOG_ERRNO_IF_SET, log_error, log_warn, MEDIA_SOURCE_FILE, MEDIA_SOURCE_MEDIA_TYPE_BUFFER_SIZE, MEDIA_SOURCE_STDIN, MEDIA_SOURCE_TEST, MEDIA_SOURCE_WEBCAM, MEDIA_STREAM_URL_SIZE, NAMED_REGISTER, NAMED_REGISTER_ATOMIC, NAMED_REGISTER_MUTEX, NOTICE, yt_dlp_request_options_t::proxy, yt_dlp_request_options_t::proxy_specified, SAFE_FREE, SAFE_MALLOC, SET_ERRNO, source, and webcam_init_context().

Referenced by media_source_create(), and session_capture_create().

◆ media_source_start_video()

asciichat_error_t media_source_start_video ( media_source_t * source)

Definition at line 571 of file source.c.

571 {
572 ASCII_OTEL_SCOPE(MEDIA_SOURCE_START_VIDEO);
573 if (!source)
574 return SET_ERRNO(ERROR_INVALID_PARAM, "Missing media source");
575 if ((!source->audio_only && source->type != MEDIA_SOURCE_WEBCAM) || source->webcam_ctx || GET_OPTION(test_pattern) ||
576 GET_OPTION(waveform) || GET_OPTION(fft))
577 return ASCIICHAT_OK;
578 if (source->type != MEDIA_SOURCE_WEBCAM)
579 source->webcam_index = GET_OPTION(webcam_index);
580 asciichat_error_t err = webcam_init_context(&source->webcam_ctx, source->webcam_index);
581 if (err != ASCIICHAT_OK)
582 return SET_ERRNO(err, "Audio-only media requires webcam %u", source->webcam_index);
583 log_info("Audio-only media: using webcam %u for video", source->webcam_index);
584 return ASCIICHAT_OK;
585}
#define log_info(...)
Log an INFO message.
Definition log/log.h:608
test pattern is intentionally a small, concrete record. Fields such as image, rgba give callers the i...

References ASCII_OTEL_SCOPE, ASCIICHAT_OK, ERROR_INVALID_PARAM, GET_OPTION, log_info, MEDIA_SOURCE_WEBCAM, SET_ERRNO, source, and webcam_init_context().

Referenced by media_source_read_video(), and session_capture_create().

◆ media_source_uses_webcam()

bool media_source_uses_webcam ( media_source_t * source)

Definition at line 587 of file source.c.

587 {
588 return source && ((source->type == MEDIA_SOURCE_WEBCAM && !GET_OPTION(waveform) && !GET_OPTION(fft)) ||
589 (source->audio_only && !GET_OPTION(test_pattern) && !GET_OPTION(waveform) && !GET_OPTION(fft)));
590}

References GET_OPTION, MEDIA_SOURCE_WEBCAM, and source.