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

FFmpeg-based media decoder implementation. More...

Go to the source code of this file.

Data Structures

struct  ffmpeg_decoder_t
 FFmpeg decoder state for video and audio decoding. More...
 

Macros

#define TARGET_SAMPLE_RATE   48000
 
#define TARGET_CHANNELS   1
 
#define AVIO_BUFFER_SIZE   (64 * 1024)
 

Functions

ffmpeg_decoder_tffmpeg_decoder_create (const char *path)
 
ffmpeg_decoder_tffmpeg_decoder_create_stdin (void)
 
void ffmpeg_decoder_destroy (ffmpeg_decoder_t *decoder)
 
image_t * ffmpeg_decoder_read_video_frame (ffmpeg_decoder_t *decoder)
 
asciichat_error_t ffmpeg_decoder_start_prefetch (ffmpeg_decoder_t *decoder)
 Start the background frame prefetching thread.
 
void ffmpeg_decoder_stop_prefetch (ffmpeg_decoder_t *decoder)
 Stop the background frame prefetching thread.
 
bool ffmpeg_decoder_is_prefetch_running (ffmpeg_decoder_t *decoder)
 
bool ffmpeg_decoder_has_video (ffmpeg_decoder_t *decoder)
 
asciichat_error_t ffmpeg_decoder_get_video_dimensions (ffmpeg_decoder_t *decoder, int *width, int *height)
 
double ffmpeg_decoder_get_video_fps (ffmpeg_decoder_t *decoder)
 
size_t ffmpeg_decoder_read_audio_samples (ffmpeg_decoder_t *decoder, float *buffer, size_t num_samples)
 
bool ffmpeg_decoder_has_audio (ffmpeg_decoder_t *decoder)
 
asciichat_error_t ffmpeg_decoder_rewind (ffmpeg_decoder_t *decoder)
 
asciichat_error_t ffmpeg_decoder_seek_to_timestamp (ffmpeg_decoder_t *decoder, double timestamp_sec)
 
bool ffmpeg_decoder_at_end (ffmpeg_decoder_t *decoder)
 
double ffmpeg_decoder_get_duration (ffmpeg_decoder_t *decoder)
 
double ffmpeg_decoder_get_position (ffmpeg_decoder_t *decoder)
 

Detailed Description

FFmpeg-based media decoder implementation.

Definition in file ffmpeg_decoder.c.

Macro Definition Documentation

◆ AVIO_BUFFER_SIZE

#define AVIO_BUFFER_SIZE   (64 * 1024)

AVIO buffer size for stdin reading (64KB)

Definition at line 54 of file ffmpeg_decoder.c.

◆ TARGET_CHANNELS

#define TARGET_CHANNELS   1

Target audio channels (mono)

Definition at line 51 of file ffmpeg_decoder.c.

◆ TARGET_SAMPLE_RATE

#define TARGET_SAMPLE_RATE   48000

Target audio sample rate (48kHz for Opus compatibility)

Definition at line 48 of file ffmpeg_decoder.c.

Function Documentation

◆ ffmpeg_decoder_at_end()

bool ffmpeg_decoder_at_end ( ffmpeg_decoder_t decoder)

Definition at line 1246 of file ffmpeg_decoder.c.

1246 {
1247 return decoder && decoder->eof_reached;
1248}
bool eof_reached
Whether end of file was reached.

References ffmpeg_decoder_t::eof_reached.

Referenced by media_source_at_end(), media_source_read_audio(), and media_source_read_video().

◆ ffmpeg_decoder_create()

ffmpeg_decoder_t * ffmpeg_decoder_create ( const char *  path)

Definition at line 406 of file ffmpeg_decoder.c.

406 {
407 if (!path) {
408 SET_ERRNO(ERROR_INVALID_PARAM, "Path is NULL");
409 return NULL;
410 }
411
412 // Suppress FFmpeg's verbose debug logging (H.264 codec warnings, etc.)
413 // Only set this once, it's a global setting
414 static bool ffmpeg_log_level_set = false;
415 if (!ffmpeg_log_level_set) {
416 av_log_set_level(AV_LOG_QUIET); // Suppress all FFmpeg logging
417 av_log_set_callback(ffmpeg_silent_log_callback); // Install silent callback to discard all output
418 ffmpeg_log_level_set = true;
419 }
420
421 ffmpeg_decoder_t *decoder = SAFE_MALLOC(sizeof(ffmpeg_decoder_t), ffmpeg_decoder_t *);
422 if (!decoder) {
423 SET_ERRNO(ERROR_MEMORY, "Failed to allocate decoder");
424 return NULL;
425 }
426
427 memset(decoder, 0, sizeof(*decoder));
428 decoder->video_stream_idx = -1;
429 decoder->audio_stream_idx = -1;
430 decoder->last_video_pts = -1.0;
431 decoder->last_audio_pts = -1.0;
432
433 // Suppress FFmpeg's probing output by redirecting both stdout and stderr to /dev/null
434 // FFmpeg may write directly to either stream, so we suppress both
435 platform_stderr_redirect_handle_t stdio_handle = platform_stdout_stderr_redirect_to_null();
436
437 // Configure FFmpeg options for HTTP streaming performance
438 AVDictionary *options = NULL;
439
440 // For HTTP/HTTPS streams: enable fast probing and reconnection (validated via production-grade URL regex)
441 if (path && url_is_valid(path)) {
442 // Limit probing to 32KB for faster format detection
443 av_dict_set(&options, "probesize", "32768", 0);
444 // Analyze for 100ms max to determine streams quickly
445 av_dict_set(&options, "analyzeduration", "100000", 0);
446 // Enable auto-reconnection for interrupted connections
447 av_dict_set(&options, "reconnect", "1", 0);
448 // Allow reconnection for streamed protocols
449 av_dict_set(&options, "reconnect_streamed", "1", 0);
450 // Set reasonable I/O timeout (10 seconds)
451 av_dict_set(&options, "rw_timeout", "10000000", 0);
452 // Enable HTTP persistent connection (keep-alive) for better performance
453 av_dict_set(&options, "http_persistent", "1", 0);
454 // Reduce connect timeout to fail faster if server is unreachable
455 av_dict_set(&options, "connect_timeout", "5000000", 0);
456 }
457
458 // Open input file
459 int ret = avformat_open_input(&decoder->format_ctx, path, NULL, &options);
460 av_dict_free(&options); // Free options dictionary
461
462 if (ret < 0) {
463 platform_stdout_stderr_restore(stdio_handle);
464 SET_ERRNO(ERROR_MEDIA_OPEN, "Failed to open media file: %s", path);
465 SAFE_FREE(decoder);
466 return NULL;
467 }
468
469 // Find stream info
470 if (avformat_find_stream_info(decoder->format_ctx, NULL) < 0) {
471 platform_stdout_stderr_restore(stdio_handle);
472 SET_ERRNO(ERROR_MEDIA_DECODE, "Failed to find stream info");
473 avformat_close_input(&decoder->format_ctx);
474 SAFE_FREE(decoder);
475 return NULL;
476 }
477
478 // Install interrupt callback to allow seeking to interrupt long av_read_frame() calls
479 // Do this AFTER finding stream info to ensure format context is fully initialized
480 if (decoder->format_ctx) {
481 decoder->format_ctx->interrupt_callback.callback = ffmpeg_interrupt_callback;
482 decoder->format_ctx->interrupt_callback.opaque = decoder;
483 }
484
485 // Restore stdout and stderr after FFmpeg initialization
486 platform_stdout_stderr_restore(stdio_handle);
487
488 // Open video codec
489 asciichat_error_t err = open_codec_context(decoder->format_ctx, AVMEDIA_TYPE_VIDEO, &decoder->video_stream_idx,
490 &decoder->video_codec_ctx);
491 if (err != ASCIICHAT_OK) {
492 log_warn("Failed to open video codec (file may be audio-only)");
493 }
494
495 // Open audio codec - audio is enabled by default (no option needed)
496 // Always try to open audio codec, don't rely on GET_OPTION(audio_enabled) which has a default issue
497 err = open_codec_context(decoder->format_ctx, AVMEDIA_TYPE_AUDIO, &decoder->audio_stream_idx,
498 &decoder->audio_codec_ctx);
499 if (err != ASCIICHAT_OK) {
500 log_debug("No audio codec found (file may be video-only or audio codec not available)");
501 decoder->audio_stream_idx = -1;
502 decoder->audio_codec_ctx = NULL;
503 }
504
505 // Require at least one stream
506 if (decoder->video_stream_idx < 0 && decoder->audio_stream_idx < 0) {
507 SET_ERRNO(ERROR_MEDIA_DECODE, "No video or audio streams found");
508 ffmpeg_decoder_destroy(decoder);
509 return NULL;
510 }
511
512 // Allocate frame and packet
513 decoder->frame = av_frame_alloc();
514 decoder->packet = av_packet_alloc();
515 if (!decoder->frame || !decoder->packet) {
516 SET_ERRNO(ERROR_MEMORY, "Failed to allocate frame/packet");
517 ffmpeg_decoder_destroy(decoder);
518 return NULL;
519 }
520
521 // Initialize swscale context for video if present
522 if (decoder->video_codec_ctx) {
523 // Validate codec context has valid dimensions and pixel format
524 // For HTTP streams, these might not be valid until first frame is read
525 if (decoder->video_codec_ctx->width <= 0 || decoder->video_codec_ctx->height <= 0) {
526 log_warn("Video codec has invalid dimensions (%dx%d), will initialize swscale on first frame",
527 decoder->video_codec_ctx->width, decoder->video_codec_ctx->height);
528 // Don't create swscale context yet - will create it lazily on first frame read
529 } else if (decoder->video_codec_ctx->pix_fmt == AV_PIX_FMT_NONE) {
530 log_warn("Video codec has invalid pixel format, will initialize swscale on first frame");
531 // Don't create swscale context yet - will create it lazily on first frame read
532 } else {
533 // Create swscale context with valid parameters
534 decoder->sws_ctx =
535 sws_getContext(decoder->video_codec_ctx->width, decoder->video_codec_ctx->height,
536 decoder->video_codec_ctx->pix_fmt, decoder->video_codec_ctx->width,
537 decoder->video_codec_ctx->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL);
538 if (!decoder->sws_ctx) {
539 SET_ERRNO(ERROR_MEDIA_DECODE, "Failed to create swscale context");
540 ffmpeg_decoder_destroy(decoder);
541 return NULL;
542 }
543 }
544 }
545
546 // Initialize swresample context for audio if present
547 if (decoder->audio_codec_ctx) {
548 // Store output sample rate for position tracking
550
551 // Allocate resampler context
552 decoder->swr_ctx = swr_alloc();
553 if (!decoder->swr_ctx) {
554 SET_ERRNO(ERROR_MEMORY, "Failed to allocate swresample context");
555 ffmpeg_decoder_destroy(decoder);
556 return NULL;
557 }
558
559 // Set options
560 av_opt_set_chlayout(decoder->swr_ctx, "in_chlayout", &decoder->audio_codec_ctx->ch_layout, 0);
561 av_opt_set_int(decoder->swr_ctx, "in_sample_rate", decoder->audio_codec_ctx->sample_rate, 0);
562 av_opt_set_sample_fmt(decoder->swr_ctx, "in_sample_fmt", decoder->audio_codec_ctx->sample_fmt, 0);
563
564 AVChannelLayout out_ch_layout = AV_CHANNEL_LAYOUT_MONO;
565 av_opt_set_chlayout(decoder->swr_ctx, "out_chlayout", &out_ch_layout, 0);
566 av_opt_set_int(decoder->swr_ctx, "out_sample_rate", TARGET_SAMPLE_RATE, 0);
567 av_opt_set_sample_fmt(decoder->swr_ctx, "out_sample_fmt", AV_SAMPLE_FMT_FLT, 0);
568
569 // Initialize
570 if (swr_init(decoder->swr_ctx) < 0) {
571 SET_ERRNO(ERROR_MEDIA_DECODE, "Failed to initialize swresample context");
572 ffmpeg_decoder_destroy(decoder);
573 return NULL;
574 }
575
576 // Allocate audio buffer (10 seconds worth)
578 decoder->audio_buffer = SAFE_MALLOC(decoder->audio_buffer_size * sizeof(float), float *);
579 if (!decoder->audio_buffer) {
580 SET_ERRNO(ERROR_MEMORY, "Failed to allocate audio buffer");
581 ffmpeg_decoder_destroy(decoder);
582 return NULL;
583 }
584 }
585
586 // Initialize video frame prefetching system (for YouTube streaming)
587 if (decoder->video_stream_idx >= 0) {
588 int width = decoder->video_codec_ctx->width;
589 int height = decoder->video_codec_ctx->height;
590
591 // Create two prefetch image buffers for double-buffering
592 decoder->prefetch_image_a = image_new((size_t)width, (size_t)height);
593 decoder->prefetch_image_b = image_new((size_t)width, (size_t)height);
594 if (!decoder->prefetch_image_a || !decoder->prefetch_image_b) {
595 SET_ERRNO(ERROR_MEMORY, "Failed to allocate prefetch image buffers");
596 ffmpeg_decoder_destroy(decoder);
597 return NULL;
598 }
599
600 decoder->current_prefetch_image = decoder->prefetch_image_a;
601 decoder->prefetch_frame_ready = false;
602
603 // Initialize prefetch mutex
604 if (mutex_init(&decoder->prefetch_mutex) != 0) {
605 SET_ERRNO(ERROR_MEMORY, "Failed to initialize prefetch mutex");
606 ffmpeg_decoder_destroy(decoder);
607 return NULL;
608 }
609
610 if (cond_init(&decoder->prefetch_cond) != 0) {
611 SET_ERRNO(ERROR_MEMORY, "Failed to initialize prefetch condition variable");
612 mutex_destroy(&decoder->prefetch_mutex);
613 ffmpeg_decoder_destroy(decoder);
614 return NULL;
615 }
616
617 decoder->prefetch_thread_running = false;
618 decoder->prefetch_should_stop = false;
619 }
620
621 log_debug("FFmpeg decoder opened: %s (video=%s, audio=%s)", path, decoder->video_stream_idx >= 0 ? "yes" : "no",
622 decoder->audio_stream_idx >= 0 ? "yes" : "no");
623
624 return decoder;
625}
#define TARGET_SAMPLE_RATE
void ffmpeg_decoder_destroy(ffmpeg_decoder_t *decoder)
FFmpeg decoder state for video and audio decoding.
mutex_t prefetch_mutex
Protect prefetch state and FFmpeg decoder access.
AVCodecContext * video_codec_ctx
Video codec context.
image_t * prefetch_image_a
First prefetch buffer.
AVFormatContext * format_ctx
FFmpeg format/container context.
bool prefetch_thread_running
Whether prefetch thread is active.
image_t * prefetch_image_b
Second prefetch buffer.
cond_t prefetch_cond
Condition variable for pausing during seek.
struct SwrContext * swr_ctx
Software resampler for format conversion.
bool prefetch_frame_ready
Whether current_prefetch_image has valid data.
AVPacket * packet
Reusable packet for reading.
int video_stream_idx
Video stream index (-1 if none)
double last_audio_pts
Last audio presentation timestamp.
AVCodecContext * audio_codec_ctx
Audio codec context.
int audio_sample_rate
Audio sample rate (Hz)
size_t audio_buffer_size
Total size of audio buffer.
image_t * current_prefetch_image
Currently available prefetched frame.
float * audio_buffer
Buffer for partial audio frames.
double last_video_pts
Last video presentation timestamp.
bool prefetch_should_stop
Signal to stop prefetch thread.
int audio_stream_idx
Audio stream index (-1 if none)
AVFrame * frame
Reusable frame for decoding.
struct SwsContext * sws_ctx
Software scaler for format conversion.
int mutex_init(mutex_t *mutex)
Definition threading.c:16
int mutex_destroy(mutex_t *mutex)
Definition threading.c:21
bool url_is_valid(const char *url)
Definition url.c:81
image_t * image_new(size_t width, size_t height)
Definition video/image.c:36

References ffmpeg_decoder_t::audio_buffer, ffmpeg_decoder_t::audio_buffer_size, ffmpeg_decoder_t::audio_codec_ctx, ffmpeg_decoder_t::audio_sample_rate, ffmpeg_decoder_t::audio_stream_idx, ffmpeg_decoder_t::current_prefetch_image, ffmpeg_decoder_destroy(), ffmpeg_decoder_t::format_ctx, ffmpeg_decoder_t::frame, image_new(), ffmpeg_decoder_t::last_audio_pts, ffmpeg_decoder_t::last_video_pts, mutex_destroy(), mutex_init(), ffmpeg_decoder_t::packet, ffmpeg_decoder_t::prefetch_cond, ffmpeg_decoder_t::prefetch_frame_ready, ffmpeg_decoder_t::prefetch_image_a, ffmpeg_decoder_t::prefetch_image_b, ffmpeg_decoder_t::prefetch_mutex, ffmpeg_decoder_t::prefetch_should_stop, ffmpeg_decoder_t::prefetch_thread_running, ffmpeg_decoder_t::swr_ctx, ffmpeg_decoder_t::sws_ctx, TARGET_SAMPLE_RATE, url_is_valid(), ffmpeg_decoder_t::video_codec_ctx, and ffmpeg_decoder_t::video_stream_idx.

Referenced by media_source_create().

◆ ffmpeg_decoder_create_stdin()

ffmpeg_decoder_t * ffmpeg_decoder_create_stdin ( void  )

Definition at line 627 of file ffmpeg_decoder.c.

627 {
628 ffmpeg_decoder_t *decoder = SAFE_MALLOC(sizeof(ffmpeg_decoder_t), ffmpeg_decoder_t *);
629 if (!decoder) {
630 SET_ERRNO(ERROR_MEMORY, "Failed to allocate decoder");
631 return NULL;
632 }
633
634 memset(decoder, 0, sizeof(*decoder));
635 decoder->video_stream_idx = -1;
636 decoder->audio_stream_idx = -1;
637 decoder->is_stdin = true;
638 decoder->last_video_pts = -1.0;
639 decoder->last_audio_pts = -1.0;
640
641 // Allocate AVIO buffer
642 decoder->avio_buffer = SAFE_MALLOC(AVIO_BUFFER_SIZE, unsigned char *);
643 if (!decoder->avio_buffer) {
644 SET_ERRNO(ERROR_MEMORY, "Failed to allocate AVIO buffer");
645 SAFE_FREE(decoder);
646 return NULL;
647 }
648
649 // Create AVIO context for stdin
650 decoder->avio_ctx = avio_alloc_context(decoder->avio_buffer, AVIO_BUFFER_SIZE,
651 0, // write_flag
652 NULL, // opaque
653 stdin_read_packet,
654 NULL, // write_packet
655 NULL // seek (stdin is not seekable)
656 );
657
658 if (!decoder->avio_ctx) {
659 SET_ERRNO(ERROR_MEMORY, "Failed to create AVIO context");
660 SAFE_FREE(decoder->avio_buffer);
661 SAFE_FREE(decoder);
662 return NULL;
663 }
664
665 // Allocate format context
666 decoder->format_ctx = avformat_alloc_context();
667 if (!decoder->format_ctx) {
668 SET_ERRNO(ERROR_MEMORY, "Failed to allocate format context");
669 av_freep(&decoder->avio_ctx->buffer);
670 avio_context_free(&decoder->avio_ctx);
671 SAFE_FREE(decoder);
672 return NULL;
673 }
674
675 decoder->format_ctx->pb = decoder->avio_ctx;
676
677 // Suppress FFmpeg's probing output by redirecting both stdout and stderr to /dev/null
678 platform_stderr_redirect_handle_t stdio_handle = platform_stdout_stderr_redirect_to_null();
679
680 // Open input from stdin
681 if (avformat_open_input(&decoder->format_ctx, NULL, NULL, NULL) < 0) {
682 platform_stdout_stderr_restore(stdio_handle);
683 SET_ERRNO(ERROR_MEDIA_OPEN, "Failed to open stdin");
684 av_freep(&decoder->avio_ctx->buffer);
685 avio_context_free(&decoder->avio_ctx);
686 avformat_free_context(decoder->format_ctx);
687 SAFE_FREE(decoder);
688 return NULL;
689 }
690
691 // Find stream info
692 if (avformat_find_stream_info(decoder->format_ctx, NULL) < 0) {
693 platform_stdout_stderr_restore(stdio_handle);
694 SET_ERRNO(ERROR_MEDIA_DECODE, "Failed to find stream info from stdin");
695 ffmpeg_decoder_destroy(decoder);
696 return NULL;
697 }
698
699 platform_stdout_stderr_restore(stdio_handle);
700
701 // Open codecs (same as file-based decoder)
702 asciichat_error_t err = open_codec_context(decoder->format_ctx, AVMEDIA_TYPE_VIDEO, &decoder->video_stream_idx,
703 &decoder->video_codec_ctx);
704 if (err != ASCIICHAT_OK) {
705 log_warn("Failed to open video codec from stdin");
706 }
707
708 if (GET_OPTION(audio_enabled)) {
709 err = open_codec_context(decoder->format_ctx, AVMEDIA_TYPE_AUDIO, &decoder->audio_stream_idx,
710 &decoder->audio_codec_ctx);
711 if (err != ASCIICHAT_OK) {
712 log_warn("Failed to open audio codec from stdin");
713 }
714 } else {
715 decoder->audio_stream_idx = -1;
716 decoder->audio_codec_ctx = NULL;
717 log_debug("Audio decoding disabled by user option");
718 }
719
720 if (decoder->video_stream_idx < 0 && decoder->audio_stream_idx < 0) {
721 SET_ERRNO(ERROR_MEDIA_DECODE, "No video or audio streams found in stdin");
722 ffmpeg_decoder_destroy(decoder);
723 return NULL;
724 }
725
726 // Allocate frame and packet
727 decoder->frame = av_frame_alloc();
728 decoder->packet = av_packet_alloc();
729 if (!decoder->frame || !decoder->packet) {
730 SET_ERRNO(ERROR_MEMORY, "Failed to allocate frame/packet");
731 ffmpeg_decoder_destroy(decoder);
732 return NULL;
733 }
734
735 // Initialize swscale/swresample (same as file-based)
736 if (decoder->video_codec_ctx) {
737 // Validate codec context has valid dimensions and pixel format
738 // For stdin/HTTP streams, these might not be valid until first frame is read
739 if (decoder->video_codec_ctx->width <= 0 || decoder->video_codec_ctx->height <= 0) {
740 log_warn("Video codec has invalid dimensions (%dx%d), will initialize swscale on first frame",
741 decoder->video_codec_ctx->width, decoder->video_codec_ctx->height);
742 // Don't create swscale context yet - will create it lazily on first frame read
743 } else if (decoder->video_codec_ctx->pix_fmt == AV_PIX_FMT_NONE) {
744 log_warn("Video codec has invalid pixel format, will initialize swscale on first frame");
745 // Don't create swscale context yet - will create it lazily on first frame read
746 } else {
747 // Create swscale context with valid parameters
748 decoder->sws_ctx =
749 sws_getContext(decoder->video_codec_ctx->width, decoder->video_codec_ctx->height,
750 decoder->video_codec_ctx->pix_fmt, decoder->video_codec_ctx->width,
751 decoder->video_codec_ctx->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL);
752 if (!decoder->sws_ctx) {
753 SET_ERRNO(ERROR_MEDIA_DECODE, "Failed to create swscale context");
754 ffmpeg_decoder_destroy(decoder);
755 return NULL;
756 }
757 }
758 }
759
760 if (decoder->audio_codec_ctx) {
761 decoder->swr_ctx = swr_alloc();
762 if (!decoder->swr_ctx) {
763 SET_ERRNO(ERROR_MEMORY, "Failed to allocate swresample context");
764 ffmpeg_decoder_destroy(decoder);
765 return NULL;
766 }
767
768 av_opt_set_chlayout(decoder->swr_ctx, "in_chlayout", &decoder->audio_codec_ctx->ch_layout, 0);
769 av_opt_set_int(decoder->swr_ctx, "in_sample_rate", decoder->audio_codec_ctx->sample_rate, 0);
770 av_opt_set_sample_fmt(decoder->swr_ctx, "in_sample_fmt", decoder->audio_codec_ctx->sample_fmt, 0);
771
772 AVChannelLayout out_ch_layout = AV_CHANNEL_LAYOUT_MONO;
773 av_opt_set_chlayout(decoder->swr_ctx, "out_chlayout", &out_ch_layout, 0);
774 av_opt_set_int(decoder->swr_ctx, "out_sample_rate", TARGET_SAMPLE_RATE, 0);
775 av_opt_set_sample_fmt(decoder->swr_ctx, "out_sample_fmt", AV_SAMPLE_FMT_FLT, 0);
776
777 if (swr_init(decoder->swr_ctx) < 0) {
778 SET_ERRNO(ERROR_MEDIA_DECODE, "Failed to initialize swresample context");
779 ffmpeg_decoder_destroy(decoder);
780 return NULL;
781 }
782
784 decoder->audio_buffer = SAFE_MALLOC(decoder->audio_buffer_size * sizeof(float), float *);
785 if (!decoder->audio_buffer) {
786 SET_ERRNO(ERROR_MEMORY, "Failed to allocate audio buffer");
787 ffmpeg_decoder_destroy(decoder);
788 return NULL;
789 }
790 }
791
792 log_debug("FFmpeg decoder opened from stdin (video=%s, audio=%s)", decoder->video_stream_idx >= 0 ? "yes" : "no",
793 decoder->audio_stream_idx >= 0 ? "yes" : "no");
794
795 return decoder;
796}
#define AVIO_BUFFER_SIZE
AVIOContext * avio_ctx
Custom I/O context for stdin.
unsigned char * avio_buffer
Buffer for custom I/O.
bool is_stdin
Whether reading from stdin.

References ffmpeg_decoder_t::audio_buffer, ffmpeg_decoder_t::audio_buffer_size, ffmpeg_decoder_t::audio_codec_ctx, ffmpeg_decoder_t::audio_stream_idx, ffmpeg_decoder_t::avio_buffer, AVIO_BUFFER_SIZE, ffmpeg_decoder_t::avio_ctx, ffmpeg_decoder_destroy(), ffmpeg_decoder_t::format_ctx, ffmpeg_decoder_t::frame, ffmpeg_decoder_t::is_stdin, ffmpeg_decoder_t::last_audio_pts, ffmpeg_decoder_t::last_video_pts, ffmpeg_decoder_t::packet, ffmpeg_decoder_t::swr_ctx, ffmpeg_decoder_t::sws_ctx, TARGET_SAMPLE_RATE, ffmpeg_decoder_t::video_codec_ctx, and ffmpeg_decoder_t::video_stream_idx.

Referenced by media_source_create().

◆ ffmpeg_decoder_destroy()

void ffmpeg_decoder_destroy ( ffmpeg_decoder_t decoder)

Definition at line 798 of file ffmpeg_decoder.c.

798 {
799 if (!decoder) {
800 return;
801 }
802
803 // Stop prefetch thread (signal it to stop and wait for it to finish)
804 if (decoder->prefetch_thread_running) {
805 mutex_lock(&decoder->prefetch_mutex);
806 decoder->prefetch_should_stop = true;
807 mutex_unlock(&decoder->prefetch_mutex);
808
809 // Wait for thread to finish
810 asciichat_thread_join(&decoder->prefetch_thread, NULL);
811 decoder->prefetch_thread_running = false;
812 }
813
814 // Clean up prefetch state
815 cond_destroy(&decoder->prefetch_cond);
816 mutex_destroy(&decoder->prefetch_mutex);
817
818 // Free prefetch image buffers
819 if (decoder->prefetch_image_a) {
821 decoder->prefetch_image_a = NULL;
822 }
823 if (decoder->prefetch_image_b) {
825 decoder->prefetch_image_b = NULL;
826 }
827
828 // Don't destroy current_image - it points to one of the prefetch buffers
829 // which have already been destroyed above
830 decoder->current_image = NULL;
831
832 // Free audio buffer
833 SAFE_FREE(decoder->audio_buffer);
834
835 // Free swscale context
836 if (decoder->sws_ctx) {
837 sws_freeContext(decoder->sws_ctx);
838 decoder->sws_ctx = NULL;
839 }
840
841 // Free swresample context
842 if (decoder->swr_ctx) {
843 swr_free(&decoder->swr_ctx);
844 }
845
846 // Free frame and packet
847 if (decoder->frame) {
848 av_frame_free(&decoder->frame);
849 }
850 if (decoder->packet) {
851 av_packet_free(&decoder->packet);
852 }
853
854 // Free codec contexts
855 if (decoder->video_codec_ctx) {
856 avcodec_free_context(&decoder->video_codec_ctx);
857 }
858 if (decoder->audio_codec_ctx) {
859 avcodec_free_context(&decoder->audio_codec_ctx);
860 }
861
862 // Free format context
863 if (decoder->format_ctx) {
864 avformat_close_input(&decoder->format_ctx);
865 }
866
867 // Free AVIO context (stdin only)
868 if (decoder->avio_ctx) {
869 av_freep(&decoder->avio_ctx->buffer);
870 avio_context_free(&decoder->avio_ctx);
871 }
872
873 SAFE_FREE(decoder);
874}
asciichat_thread_t prefetch_thread
Prefetch thread handle.
image_t * current_image
Working buffer for decoding.
int asciichat_thread_join(asciichat_thread_t *thread, void **retval)
Definition threading.c:46
void image_destroy(image_t *p)
Definition video/image.c:85

References asciichat_thread_join(), ffmpeg_decoder_t::audio_buffer, ffmpeg_decoder_t::audio_codec_ctx, ffmpeg_decoder_t::avio_ctx, ffmpeg_decoder_t::current_image, ffmpeg_decoder_t::format_ctx, ffmpeg_decoder_t::frame, image_destroy(), mutex_destroy(), ffmpeg_decoder_t::packet, ffmpeg_decoder_t::prefetch_cond, ffmpeg_decoder_t::prefetch_image_a, ffmpeg_decoder_t::prefetch_image_b, ffmpeg_decoder_t::prefetch_mutex, ffmpeg_decoder_t::prefetch_should_stop, ffmpeg_decoder_t::prefetch_thread, ffmpeg_decoder_t::prefetch_thread_running, ffmpeg_decoder_t::swr_ctx, ffmpeg_decoder_t::sws_ctx, and ffmpeg_decoder_t::video_codec_ctx.

Referenced by ffmpeg_decoder_create(), ffmpeg_decoder_create_stdin(), media_source_create(), and media_source_destroy().

◆ ffmpeg_decoder_get_duration()

double ffmpeg_decoder_get_duration ( ffmpeg_decoder_t decoder)

Definition at line 1250 of file ffmpeg_decoder.c.

1250 {
1251 if (!decoder || !decoder->format_ctx) {
1252 return -1.0;
1253 }
1254
1255 if (decoder->format_ctx->duration == AV_NOPTS_VALUE) {
1256 return -1.0;
1257 }
1258
1259 return (double)decoder->format_ctx->duration / AV_TIME_BASE;
1260}

References ffmpeg_decoder_t::format_ctx.

Referenced by media_source_get_duration().

◆ ffmpeg_decoder_get_position()

double ffmpeg_decoder_get_position ( ffmpeg_decoder_t decoder)

Definition at line 1262 of file ffmpeg_decoder.c.

1262 {
1263 if (!decoder) {
1264 return -1.0;
1265 }
1266
1267 // Prefer sample-based position tracking (continuous, works before frames are decoded)
1268 if (decoder->audio_sample_rate > 0 && decoder->audio_samples_read >= 0) {
1269 return (double)decoder->audio_samples_read / (double)decoder->audio_sample_rate;
1270 }
1271
1272 // Fallback to frame-based position if available
1273 if (decoder->last_video_pts >= 0.0) {
1274 return decoder->last_video_pts;
1275 } else if (decoder->last_audio_pts >= 0.0) {
1276 return decoder->last_audio_pts;
1277 }
1278
1279 return -1.0;
1280}
uint64_t audio_samples_read
Total audio samples decoded and output.

References ffmpeg_decoder_t::audio_sample_rate, ffmpeg_decoder_t::audio_samples_read, ffmpeg_decoder_t::last_audio_pts, and ffmpeg_decoder_t::last_video_pts.

Referenced by media_source_get_position(), media_source_read_audio(), and media_source_seek().

◆ ffmpeg_decoder_get_video_dimensions()

asciichat_error_t ffmpeg_decoder_get_video_dimensions ( ffmpeg_decoder_t decoder,
int *  width,
int *  height 
)

Definition at line 991 of file ffmpeg_decoder.c.

991 {
992 if (!decoder || decoder->video_stream_idx < 0) {
993 return ERROR_INVALID_PARAM;
994 }
995
996 if (width) {
997 *width = decoder->video_codec_ctx->width;
998 }
999 if (height) {
1000 *height = decoder->video_codec_ctx->height;
1001 }
1002
1003 return ASCIICHAT_OK;
1004}

References ffmpeg_decoder_t::video_codec_ctx, and ffmpeg_decoder_t::video_stream_idx.

◆ ffmpeg_decoder_get_video_fps()

double ffmpeg_decoder_get_video_fps ( ffmpeg_decoder_t decoder)

Definition at line 1006 of file ffmpeg_decoder.c.

1006 {
1007 if (!decoder || decoder->video_stream_idx < 0) {
1008 return -1.0;
1009 }
1010
1011 AVStream *stream = decoder->format_ctx->streams[decoder->video_stream_idx];
1012
1013 // Try avg_frame_rate first (average frame rate from entire stream)
1014 double fps = av_q2d_safe(stream->avg_frame_rate);
1015
1016 // Fallback to r_frame_rate if avg_frame_rate is invalid or zero
1017 // r_frame_rate is the "real" frame rate based on codec parameters
1018 // This is more reliable for YouTube videos and some video codecs
1019 if (fps <= 0.0) {
1020 fps = av_q2d_safe(stream->r_frame_rate);
1021 }
1022
1023 return fps;
1024}

References ffmpeg_decoder_t::format_ctx, and ffmpeg_decoder_t::video_stream_idx.

Referenced by media_source_get_video_fps().

◆ ffmpeg_decoder_has_audio()

bool ffmpeg_decoder_has_audio ( ffmpeg_decoder_t decoder)

Definition at line 1136 of file ffmpeg_decoder.c.

1136 {
1137 return decoder && decoder->audio_stream_idx >= 0;
1138}

References ffmpeg_decoder_t::audio_stream_idx.

Referenced by media_source_has_audio().

◆ ffmpeg_decoder_has_video()

bool ffmpeg_decoder_has_video ( ffmpeg_decoder_t decoder)

Definition at line 987 of file ffmpeg_decoder.c.

987 {
988 return decoder && decoder->video_stream_idx >= 0;
989}

References ffmpeg_decoder_t::video_stream_idx.

Referenced by media_source_has_video().

◆ ffmpeg_decoder_is_prefetch_running()

bool ffmpeg_decoder_is_prefetch_running ( ffmpeg_decoder_t decoder)

Definition at line 980 of file ffmpeg_decoder.c.

980 {
981 if (!decoder) {
982 return false;
983 }
984 return decoder->prefetch_thread_running;
985}

References ffmpeg_decoder_t::prefetch_thread_running.

◆ ffmpeg_decoder_read_audio_samples()

size_t ffmpeg_decoder_read_audio_samples ( ffmpeg_decoder_t decoder,
float *  buffer,
size_t  num_samples 
)

Definition at line 1030 of file ffmpeg_decoder.c.

1030 {
1031 if (!decoder || decoder->audio_stream_idx < 0 || !buffer || num_samples == 0) {
1032 return 0;
1033 }
1034
1035 size_t samples_written = 0;
1036
1037 // First, drain any buffered samples
1038 if (decoder->audio_buffer_offset > 0) {
1039 size_t available = decoder->audio_buffer_offset;
1040 size_t to_copy = (available < num_samples) ? available : num_samples;
1041
1042 memcpy(buffer, decoder->audio_buffer, to_copy * sizeof(float));
1043 samples_written += to_copy;
1044
1045 // Shift buffer
1046 if (to_copy < available) {
1047 memmove(decoder->audio_buffer, decoder->audio_buffer + to_copy, (available - to_copy) * sizeof(float));
1048 }
1049 decoder->audio_buffer_offset -= to_copy;
1050
1051 if (samples_written >= num_samples) {
1052 return samples_written;
1053 }
1054 }
1055
1056 // Read more packets to fill the request
1057 static uint64_t packet_count = 0;
1058 while (samples_written < num_samples) {
1059 int ret = av_read_frame(decoder->format_ctx, decoder->packet);
1060 if (ret < 0) {
1061 if (ret == AVERROR_EOF) {
1062 decoder->eof_reached = true;
1063 }
1064 break;
1065 }
1066
1067 // Check if this is an audio packet
1068 if (decoder->packet->stream_index != decoder->audio_stream_idx) {
1069 av_packet_unref(decoder->packet);
1070 continue;
1071 }
1072
1073 log_info_every(50 * US_PER_MS_INT, "Audio packet #%lu: pts=%ld dts=%ld duration=%d size=%d", packet_count++,
1074 decoder->packet->pts, decoder->packet->dts, decoder->packet->duration, decoder->packet->size);
1075
1076 // Send packet to decoder
1077 ret = avcodec_send_packet(decoder->audio_codec_ctx, decoder->packet);
1078 av_packet_unref(decoder->packet);
1079
1080 if (ret < 0) {
1081 log_warn("Error sending audio packet to decoder");
1082 continue;
1083 }
1084
1085 // Receive all decoded frames from this packet
1086 // Important: a single packet can produce multiple frames. Must drain all before next packet.
1087 while (1) {
1088 ret = avcodec_receive_frame(decoder->audio_codec_ctx, decoder->frame);
1089 if (ret == AVERROR(EAGAIN)) {
1090 break; // No more frames from this packet, get next packet
1091 } else if (ret < 0) {
1092 log_warn("Error receiving audio frame from decoder");
1093 goto audio_read_done;
1094 }
1095
1096 // Update position tracking
1097 decoder->last_audio_pts =
1098 get_frame_pts_seconds(decoder->frame, decoder->format_ctx->streams[decoder->audio_stream_idx]->time_base);
1099
1100 // Resample to target format
1101 float *out_buf = buffer + samples_written;
1102 int out_samples = (int)(num_samples - samples_written);
1103
1104 uint8_t *out_ptr = (uint8_t *)out_buf;
1105 int converted = swr_convert(decoder->swr_ctx, &out_ptr, out_samples, (const uint8_t **)decoder->frame->data,
1106 decoder->frame->nb_samples);
1107
1108 if (converted > 0) {
1109 samples_written += (size_t)converted;
1110 }
1111
1112 if (samples_written >= num_samples) {
1113 goto audio_read_done;
1114 }
1115 }
1116 }
1117
1118audio_read_done:
1119 // Flush resampler buffer if we haven't filled the full request
1120 // The resampler may have buffered samples that need to be output
1121 if (samples_written < num_samples) {
1122 int remaining_space = (int)(num_samples - samples_written);
1123 uint8_t *out_ptr = (uint8_t *)(buffer + samples_written);
1124 int flushed = swr_convert(decoder->swr_ctx, &out_ptr, remaining_space, NULL, 0);
1125 if (flushed > 0) {
1126 samples_written += (size_t)flushed;
1127 }
1128 }
1129
1130 // Update sample-based position tracking
1131 decoder->audio_samples_read += samples_written;
1132
1133 return samples_written;
1134}
size_t audio_buffer_offset
Current offset in audio buffer.

References ffmpeg_decoder_t::audio_buffer, ffmpeg_decoder_t::audio_buffer_offset, ffmpeg_decoder_t::audio_codec_ctx, ffmpeg_decoder_t::audio_samples_read, ffmpeg_decoder_t::audio_stream_idx, ffmpeg_decoder_t::eof_reached, ffmpeg_decoder_t::format_ctx, ffmpeg_decoder_t::frame, ffmpeg_decoder_t::last_audio_pts, ffmpeg_decoder_t::packet, and ffmpeg_decoder_t::swr_ctx.

Referenced by media_source_read_audio().

◆ ffmpeg_decoder_read_video_frame()

image_t * ffmpeg_decoder_read_video_frame ( ffmpeg_decoder_t decoder)

Definition at line 880 of file ffmpeg_decoder.c.

880 {
881 if (!decoder || decoder->video_stream_idx < 0) {
882 return NULL;
883 }
884
885 // Try to get a prefetched frame from the background thread (preferred path)
886 mutex_lock(&decoder->prefetch_mutex);
887 if (decoder->prefetch_frame_ready && decoder->current_prefetch_image) {
888 // Release the previous buffer (rendering is now complete)
889 if (decoder->current_read_buffer == decoder->prefetch_image_a) {
890 decoder->buffer_a_in_use = false;
891 } else if (decoder->current_read_buffer == decoder->prefetch_image_b) {
892 decoder->buffer_b_in_use = false;
893 }
894
895 image_t *frame = decoder->current_prefetch_image;
896 decoder->prefetch_frame_ready = false;
897
898 // Mark the new buffer as in use (prevent prefetch thread from overwriting it during rendering)
899 if (frame == decoder->prefetch_image_a) {
900 decoder->buffer_a_in_use = true;
901 } else if (frame == decoder->prefetch_image_b) {
902 decoder->buffer_b_in_use = true;
903 }
904
905 decoder->current_read_buffer = frame;
906 mutex_unlock(&decoder->prefetch_mutex);
907
908 // Use the prefetched frame
909 decoder->current_image = frame;
910 log_dev_every(5 * US_PER_SEC_INT, "Using prefetched frame");
911 return frame;
912 }
913 mutex_unlock(&decoder->prefetch_mutex);
914
915 // No fallback synchronous decode - rely on background prefetch thread
916 // Skipping frames when prefetch not ready allows audio timing to advance
917 // This is critical for proper audio-video sync when prefetch is active
918 log_dev_every(5 * US_PER_SEC_INT,
919 "Prefetch frame not ready, skipping to next iteration (allow prefetch to catch up)");
920 return NULL;
921}
image_t * current_read_buffer
Buffer main thread is currently reading/rendering.
bool buffer_b_in_use
Whether prefetch_image_b is being read by main thread.
bool buffer_a_in_use
Whether prefetch_image_a is being read by main thread.

References ffmpeg_decoder_t::buffer_a_in_use, ffmpeg_decoder_t::buffer_b_in_use, ffmpeg_decoder_t::current_image, ffmpeg_decoder_t::current_prefetch_image, ffmpeg_decoder_t::current_read_buffer, ffmpeg_decoder_t::prefetch_frame_ready, ffmpeg_decoder_t::prefetch_image_a, ffmpeg_decoder_t::prefetch_image_b, ffmpeg_decoder_t::prefetch_mutex, and ffmpeg_decoder_t::video_stream_idx.

Referenced by media_source_read_video().

◆ ffmpeg_decoder_rewind()

asciichat_error_t ffmpeg_decoder_rewind ( ffmpeg_decoder_t decoder)

Definition at line 1144 of file ffmpeg_decoder.c.

1144 {
1145 if (!decoder) {
1146 return ERROR_INVALID_PARAM;
1147 }
1148
1149 if (decoder->is_stdin) {
1150 return ERROR_NOT_SUPPORTED; // Cannot seek stdin
1151 }
1152
1153 // Flush codec buffers
1154 if (decoder->video_codec_ctx) {
1155 avcodec_flush_buffers(decoder->video_codec_ctx);
1156 }
1157 if (decoder->audio_codec_ctx) {
1158 avcodec_flush_buffers(decoder->audio_codec_ctx);
1159 }
1160
1161 // Seek to beginning
1162 if (av_seek_frame(decoder->format_ctx, -1, 0, AVSEEK_FLAG_BACKWARD) < 0) {
1163 return SET_ERRNO(ERROR_MEDIA_SEEK, "Failed to seek to beginning");
1164 }
1165
1166 decoder->eof_reached = false;
1167 decoder->audio_buffer_offset = 0;
1168 decoder->last_video_pts = -1.0;
1169 decoder->last_audio_pts = -1.0;
1170 decoder->audio_samples_read = 0; // Reset sample counter to 0
1171
1172 return ASCIICHAT_OK;
1173}

References ffmpeg_decoder_t::audio_buffer_offset, ffmpeg_decoder_t::audio_codec_ctx, ffmpeg_decoder_t::audio_samples_read, ffmpeg_decoder_t::eof_reached, ffmpeg_decoder_t::format_ctx, ffmpeg_decoder_t::is_stdin, ffmpeg_decoder_t::last_audio_pts, ffmpeg_decoder_t::last_video_pts, and ffmpeg_decoder_t::video_codec_ctx.

Referenced by media_source_rewind().

◆ ffmpeg_decoder_seek_to_timestamp()

asciichat_error_t ffmpeg_decoder_seek_to_timestamp ( ffmpeg_decoder_t decoder,
double  timestamp_sec 
)

Definition at line 1175 of file ffmpeg_decoder.c.

1175 {
1176 if (!decoder) {
1177 return ERROR_INVALID_PARAM;
1178 }
1179
1180 if (decoder->is_stdin) {
1181 return ERROR_NOT_SUPPORTED; // Cannot seek stdin
1182 }
1183
1184 // Hold mutex during entire seek operation to prevent race with prefetch thread
1185 mutex_lock(&decoder->prefetch_mutex);
1186
1187 // Set flag to pause prefetch thread via condition variable
1188 decoder->seeking_in_progress = true;
1189
1190 // Convert seconds to FFmpeg time base units (AV_TIME_BASE = 1,000,000)
1191 int64_t target_ts = (int64_t)(timestamp_sec * AV_TIME_BASE);
1192
1193 // For HTTP streams, use simple keyframe seeking (faster than frame-accurate seeking)
1194 // HTTP seeking is expensive and can break stream state, so prefer speed over precision
1195 int seek_ret = av_seek_frame(decoder->format_ctx, -1, target_ts, AVSEEK_FLAG_BACKWARD);
1196 if (seek_ret < 0) {
1197 // Fallback: try without backward flag
1198 seek_ret = av_seek_frame(decoder->format_ctx, -1, target_ts, 0);
1199 }
1200
1201 if (seek_ret < 0) {
1202 decoder->seeking_in_progress = false;
1203 cond_signal(&decoder->prefetch_cond);
1204 mutex_unlock(&decoder->prefetch_mutex);
1205 return SET_ERRNO(ERROR_MEDIA_SEEK, "Failed to seek to timestamp %.2f seconds", timestamp_sec);
1206 }
1207
1208 // Flush codec buffers AFTER seeking
1209 if (decoder->video_codec_ctx) {
1210 avcodec_flush_buffers(decoder->video_codec_ctx);
1211 }
1212 if (decoder->audio_codec_ctx) {
1213 avcodec_flush_buffers(decoder->audio_codec_ctx);
1214 }
1215
1216 // Reset state
1217 decoder->eof_reached = false;
1218 decoder->audio_buffer_offset = 0;
1219 // Clear any stale audio data in buffer
1220 if (decoder->audio_buffer) {
1221 memset(decoder->audio_buffer, 0, decoder->audio_buffer_size * sizeof(float));
1222 }
1223 decoder->last_video_pts = -1.0;
1224 decoder->last_audio_pts = -1.0;
1225 decoder->prefetch_frame_ready = false;
1226 // Set audio_samples_read to match the seek target so position tracking works correctly
1227 decoder->audio_samples_read = (uint64_t)(timestamp_sec * decoder->audio_sample_rate);
1228
1229 // Reset current_read_buffer and mark both buffers as not in use
1230 // After seeking, the prefetch thread may reallocate buffers, so current_read_buffer
1231 // could point to freed memory. Clear it so the next read doesn't try to release stale pointers.
1232 decoder->current_read_buffer = NULL;
1233 decoder->buffer_a_in_use = false;
1234 decoder->buffer_b_in_use = false;
1235
1236 // Resume prefetch thread
1237 decoder->seeking_in_progress = false;
1238 cond_signal(&decoder->prefetch_cond);
1239
1240 // Release mutex - prefetch thread can resume
1241 mutex_unlock(&decoder->prefetch_mutex);
1242
1243 return ASCIICHAT_OK;
1244}
bool seeking_in_progress
Signal to pause prefetch thread during seek.

References ffmpeg_decoder_t::audio_buffer, ffmpeg_decoder_t::audio_buffer_offset, ffmpeg_decoder_t::audio_buffer_size, ffmpeg_decoder_t::audio_codec_ctx, ffmpeg_decoder_t::audio_sample_rate, ffmpeg_decoder_t::audio_samples_read, ffmpeg_decoder_t::buffer_a_in_use, ffmpeg_decoder_t::buffer_b_in_use, ffmpeg_decoder_t::current_read_buffer, ffmpeg_decoder_t::eof_reached, ffmpeg_decoder_t::format_ctx, ffmpeg_decoder_t::is_stdin, ffmpeg_decoder_t::last_audio_pts, ffmpeg_decoder_t::last_video_pts, ffmpeg_decoder_t::prefetch_cond, ffmpeg_decoder_t::prefetch_frame_ready, ffmpeg_decoder_t::prefetch_mutex, ffmpeg_decoder_t::seeking_in_progress, and ffmpeg_decoder_t::video_codec_ctx.

Referenced by media_source_seek().

◆ ffmpeg_decoder_start_prefetch()

asciichat_error_t ffmpeg_decoder_start_prefetch ( ffmpeg_decoder_t decoder)

Start the background frame prefetching thread.

Starts a background thread that continuously reads and decodes video frames, storing them in double-buffer structures. The render loop pulls frames from these buffers. This prevents the render thread from blocking on YouTube HTTP requests.

Definition at line 930 of file ffmpeg_decoder.c.

930 {
931 if (!decoder || decoder->video_stream_idx < 0) {
932 return ERROR_INVALID_PARAM;
933 }
934
935 if (!decoder->prefetch_image_a || !decoder->prefetch_image_b) {
936 return ERROR_INVALID_PARAM;
937 }
938
939 // Already running
940 if (decoder->prefetch_thread_running) {
941 return ASCIICHAT_OK;
942 }
943
944 // Reset stop flag and create thread
945 decoder->prefetch_should_stop = false;
946
947 int thread_err = asciichat_thread_create(&decoder->prefetch_thread, ffmpeg_decoder_prefetch_thread_func, decoder);
948 if (thread_err != 0) {
949 return SET_ERRNO(ERROR_THREAD, "Failed to create video prefetch thread");
950 }
951
952 decoder->prefetch_thread_running = true;
953 return ASCIICHAT_OK;
954}
int asciichat_thread_create(asciichat_thread_t *thread, void *(*start_routine)(void *), void *arg)
Definition threading.c:42

References asciichat_thread_create(), ffmpeg_decoder_t::prefetch_image_a, ffmpeg_decoder_t::prefetch_image_b, ffmpeg_decoder_t::prefetch_should_stop, ffmpeg_decoder_t::prefetch_thread, ffmpeg_decoder_t::prefetch_thread_running, and ffmpeg_decoder_t::video_stream_idx.

Referenced by media_source_create().

◆ ffmpeg_decoder_stop_prefetch()

void ffmpeg_decoder_stop_prefetch ( ffmpeg_decoder_t decoder)

Stop the background frame prefetching thread.

Definition at line 959 of file ffmpeg_decoder.c.

959 {
960 if (!decoder || !decoder->prefetch_thread_running) {
961 return;
962 }
963
964 decoder->prefetch_should_stop = true;
965 // Wait up to 2 seconds for thread to stop
966 // The interrupt callback should cause av_read_frame to abort quickly
967 int join_result = asciichat_thread_join_timeout(&decoder->prefetch_thread, NULL, 2000 * NS_PER_MS_INT);
968
969 if (join_result == 0) {
970 // Thread exited successfully
971 decoder->prefetch_thread_running = false;
972 } else {
973 // Timeout: thread is still running (blocked on I/O)
974 // Mark as stopped anyway - we'll create a new thread on restart
975 // The old thread will eventually finish and exit
976 decoder->prefetch_thread_running = false;
977 }
978}

References ffmpeg_decoder_t::prefetch_should_stop, ffmpeg_decoder_t::prefetch_thread, and ffmpeg_decoder_t::prefetch_thread_running.