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

🎞️ FFmpeg-based media decoder for video and audio streams More...

Go to the source code of this file.

Typedefs

typedef struct ffmpeg_decoder_t ffmpeg_decoder_t
 Opaque FFmpeg decoder handle.

Enumerations

enum  ffmpeg_frame_status_t { FFMPEG_FRAME_READY , FFMPEG_FRAME_WAITING , FFMPEG_FRAME_EOF , FFMPEG_FRAME_FAILED }
 Decode next video frame. More...

Functions

ffmpeg_decoder_t * ffmpeg_decoder_create (const char *path)
 Create FFmpeg decoder from file path.
ffmpeg_decoder_t * ffmpeg_decoder_create_stdin (void)
 Create FFmpeg decoder from stdin.
void ffmpeg_decoder_destroy (ffmpeg_decoder_t *decoder)
 Destroy FFmpeg decoder and free resources.
asciichat_error_t ffmpeg_decoder_start_prefetch (ffmpeg_decoder_t *decoder)
 Start background frame prefetching thread.
void ffmpeg_decoder_stop_prefetch (ffmpeg_decoder_t *decoder)
 Stop background frame prefetching thread.
void ffmpeg_decoder_set_exit_callback (ffmpeg_decoder_t *decoder, bool(*should_exit_callback)(void *), void *user_data)
 Set exit signal callback for graceful shutdown during I/O.
bool ffmpeg_decoder_is_prefetch_running (ffmpeg_decoder_t *decoder)
 Check if background frame prefetching thread is running.
ffmpeg_frame_status_t ffmpeg_decoder_poll_video (ffmpeg_decoder_t *decoder, image_t **frame, asciichat_error_context_t *failure)
image_t * ffmpeg_decoder_read_video_frame (ffmpeg_decoder_t *decoder)
bool ffmpeg_decoder_has_video (ffmpeg_decoder_t *decoder)
 Check if decoder has video stream.
asciichat_error_t ffmpeg_decoder_get_video_dimensions (ffmpeg_decoder_t *decoder, int *width, int *height)
 Get video dimensions.
double ffmpeg_decoder_get_video_fps (ffmpeg_decoder_t *decoder)
 Get video frame rate.
size_t ffmpeg_decoder_read_audio_samples (ffmpeg_decoder_t *decoder, float *buffer, size_t num_samples)
 Decode audio samples.
bool ffmpeg_decoder_has_audio (ffmpeg_decoder_t *decoder)
 Check if decoder has audio stream.
asciichat_error_t ffmpeg_decoder_rewind (ffmpeg_decoder_t *decoder)
 Seek to beginning of media.
asciichat_error_t ffmpeg_decoder_seek_to_timestamp (ffmpeg_decoder_t *decoder, double timestamp_sec)
 Seek to specific timestamp in media.
bool ffmpeg_decoder_at_end (ffmpeg_decoder_t *decoder)
 Check if decoder reached end of stream.
double ffmpeg_decoder_get_duration (ffmpeg_decoder_t *decoder)
 Get media duration in seconds.
double ffmpeg_decoder_get_position (ffmpeg_decoder_t *decoder)
 Get current playback position in seconds.
ffmpeg_decoder_t * ffmpeg_decoder_create_interruptible (const char *path, bool(*cancel)(void *), void *data)
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)

Detailed Description

🎞️ FFmpeg-based media decoder for video and audio streams

Definition in file ffmpeg_decoder.h.

Function Documentation

◆ ffmpeg_decoder_create_interruptible()

ffmpeg_decoder_t * ffmpeg_decoder_create_interruptible ( const char * path,
bool(* cancel )(void *),
void * data )

Definition at line 773 of file ffmpeg_decoder.c.

773 {
774 return ffmpeg_decoder_create_with_request_options(path, cancel, data, NULL, NULL, NULL);
775}
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)

References ffmpeg_decoder_create_with_request_options().

Referenced by ffmpeg_decoder_create(), and http_renderer_render_file().

◆ ffmpeg_decoder_create_with_request_options()

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 )

Definition at line 777 of file ffmpeg_decoder.c.

779 {
780 ASCII_OTEL_SCOPE(FFMPEG_DECODER_CREATE_INTERRUPTIBLE);
781 if (!path) {
782 SET_ERRNO(ERROR_INVALID_PARAM, "Path is NULL");
783 return NULL;
784 }
785
786 // Suppress FFmpeg's verbose debug logging (H.264 codec warnings, etc.)
787 // Only set this once, it's a global setting
788 static bool ffmpeg_log_level_set = false;
789 if (!ffmpeg_log_level_set) {
790 av_log_set_level(AV_LOG_QUIET); // Suppress all FFmpeg logging
791 av_log_set_callback(ffmpeg_silent_log_callback); // Install silent callback to discard all output
792 ffmpeg_log_level_set = true;
793 }
794
796 if (!decoder) {
797 SET_ERRNO(ERROR_MEMORY, "Failed to allocate decoder");
798 return NULL;
799 }
800
801 memset(decoder, 0, sizeof(*decoder));
802 atomic_ptr_store(&decoder->should_exit_callback, (void *)cancel);
803 atomic_ptr_store(&decoder->exit_callback_user_data, data);
804 decoder->format_ctx = avformat_alloc_context();
805 if (!decoder->format_ctx) {
806 SAFE_FREE(decoder);
807 return NULL;
808 }
809 decoder->format_ctx->interrupt_callback.callback = ffmpeg_interrupt_callback;
810 decoder->format_ctx->interrupt_callback.opaque = decoder;
811 decoder->video_stream_idx = -1;
812 decoder->audio_stream_idx = -1;
813 decoder->last_video_pts = -1.0;
814 decoder->last_audio_pts = -1.0;
815
816 // Capture FFmpeg's probing output (FFmpeg may write directly to either stream)
817 int ret = 0;
818 LOG_IO("ffmpeg", {
819 // Configure FFmpeg options for HTTP streaming performance
820 AVDictionary *options = NULL;
821
822 // FFmpeg accepts HTTP authorities that the application URL validator rejects.
823 // Classify the protocol rather than validating the host for timeout settings.
824 bool is_http = path && (platform_strncasecmp(path, "http://", sizeof("http://") - 1) == 0 ||
825 platform_strncasecmp(path, "https://", sizeof("https://") - 1) == 0);
827 bool is_url = is_http || (path && strstr(path, "://") && url_is_valid(path));
829 if (is_url) {
830 if (headers && *headers)
831 av_dict_set(&options, "headers", headers, 0);
832 if (cookies && *cookies)
833 av_dict_set(&options, "cookies", cookies, 0);
834 if (proxy)
835 av_dict_set(&options, "http_proxy", proxy, 0);
836 // Limit probing to 32KB for faster format detection
837 av_dict_set(&options, "probesize", "32768", 0);
838 // Analyze for 100ms max to determine streams quickly
839 av_dict_set(&options, "analyzeduration", "100000", 0);
840 // Enable auto-reconnection for interrupted connections
841 av_dict_set(&options, "reconnect", "1", 0);
842 // Allow reconnection for streamed protocols
843 av_dict_set(&options, "reconnect_streamed", "1", 0);
844 // Set reasonable I/O timeout (10 seconds)
845 av_dict_set(&options, "rw_timeout", "10000000", 0);
846 // Enable HTTP persistent connection (keep-alive) for better performance
847 av_dict_set(&options, "http_persistent", "1", 0);
848 // Reduce connect timeout to fail faster if server is unreachable
849 av_dict_set(&options, "connect_timeout", "5000000", 0);
850 }
851
852 // Open input file
853 ret = traced_format_open(&decoder->format_ctx, path, NULL, &options);
854 av_dict_free(&options); // Free options dictionary
855
856 if (ret >= 0) {
857 // Find stream info
858 if (traced_format_probe(decoder->format_ctx, NULL) < 0) {
859 ret = -1;
860 }
861 }
862 });
863
864 if (ret < 0) {
865 if (ret == -1) {
866 SET_ERRNO(ERROR_MEDIA_DECODE, "Failed to find stream info");
867 } else {
868 SET_ERRNO(ERROR_MEDIA_OPEN, "Failed to open media file: %s", path);
869 }
870 if (decoder->format_ctx) {
871 avformat_close_input(&decoder->format_ctx);
872 }
873 SAFE_FREE(decoder);
874 return NULL;
875 }
876
877 // Install interrupt callback to allow seeking to interrupt long traced_packet_read() calls
878 // Do this AFTER finding stream info to ensure format context is fully initialized
879 if (decoder->format_ctx) {
880 decoder->format_ctx->interrupt_callback.callback = ffmpeg_interrupt_callback;
881 decoder->format_ctx->interrupt_callback.opaque = decoder;
882 }
883
884 // Open video codec
885 asciichat_error_t err = open_optional_codec_context(decoder->format_ctx, AVMEDIA_TYPE_VIDEO,
886 &decoder->video_stream_idx, &decoder->video_codec_ctx);
887 if (err != ASCIICHAT_OK) {
888 ffmpeg_decoder_destroy(decoder);
889 return NULL;
890 }
891
892 // Open audio codec - audio is enabled by default (no option needed)
893 // Always try to open audio codec, don't rely on GET_OPTION(audio_enabled) which has a default issue
894 err = open_optional_codec_context(decoder->format_ctx, AVMEDIA_TYPE_AUDIO, &decoder->audio_stream_idx,
895 &decoder->audio_codec_ctx);
896 if (err != ASCIICHAT_OK) {
897 ffmpeg_decoder_destroy(decoder);
898 return NULL;
899 }
900
901 // Require at least one stream
902 if (decoder->video_stream_idx < 0 && decoder->audio_stream_idx < 0) {
903 SET_ERRNO(ERROR_MEDIA_DECODE, "No video or audio streams found");
904 ffmpeg_decoder_destroy(decoder);
905 return NULL;
906 }
907
908 // Allocate frame and packet
909 decoder->frame = av_frame_alloc();
910 decoder->packet = av_packet_alloc();
911 if (!decoder->frame || !decoder->packet) {
912 SET_ERRNO(ERROR_MEMORY, "Failed to allocate frame/packet");
913 ffmpeg_decoder_destroy(decoder);
914 return NULL;
915 }
916
917 // Initialize swscale context for video if present
918 if (decoder->video_codec_ctx) {
919 // Validate codec context has valid dimensions and pixel format
920 // For HTTP streams, these might not be valid until first frame is read
921 if (decoder->video_codec_ctx->width <= 0 || decoder->video_codec_ctx->height <= 0) {
922 log_warn("Video codec has invalid dimensions (%dx%d), will initialize swscale on first frame",
923 decoder->video_codec_ctx->width, decoder->video_codec_ctx->height);
924 // Don't create swscale context yet - will create it lazily on first frame read
925 } else if (decoder->video_codec_ctx->pix_fmt == AV_PIX_FMT_NONE) {
926 log_warn("Video codec has invalid pixel format, will initialize swscale on first frame");
927 // Don't create swscale context yet - will create it lazily on first frame read
928 } else {
929 // Create swscale context with valid parameters
930 LOG_IO("swscaler", {
931 decoder->sws_ctx =
932 sws_getContext(decoder->video_codec_ctx->width, decoder->video_codec_ctx->height,
933 decoder->video_codec_ctx->pix_fmt, decoder->video_codec_ctx->width,
934 decoder->video_codec_ctx->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL);
935 });
936 if (!decoder->sws_ctx) {
937 SET_ERRNO(ERROR_MEDIA_DECODE, "Failed to create swscale context");
938 ffmpeg_decoder_destroy(decoder);
939 return NULL;
940 }
941 }
942 }
943
944 // Initialize swresample context for audio if present
945 if (decoder->audio_codec_ctx) {
946 // Store output sample rate for position tracking
948
949 // Allocate resampler context
950 decoder->swr_ctx = swr_alloc();
951 if (!decoder->swr_ctx) {
952 SET_ERRNO(ERROR_MEMORY, "Failed to allocate swresample context");
953 ffmpeg_decoder_destroy(decoder);
954 return NULL;
955 }
956
957 // Set options
958 av_opt_set_chlayout(decoder->swr_ctx, "in_chlayout", &decoder->audio_codec_ctx->ch_layout, 0);
959 av_opt_set_int(decoder->swr_ctx, "in_sample_rate", decoder->audio_codec_ctx->sample_rate, 0);
960 av_opt_set_sample_fmt(decoder->swr_ctx, "in_sample_fmt", decoder->audio_codec_ctx->sample_fmt, 0);
961
962 AVChannelLayout out_ch_layout = AV_CHANNEL_LAYOUT_STEREO;
963 av_opt_set_chlayout(decoder->swr_ctx, "out_chlayout", &out_ch_layout, 0);
964 av_opt_set_int(decoder->swr_ctx, "out_sample_rate", TARGET_SAMPLE_RATE, 0);
965 av_opt_set_sample_fmt(decoder->swr_ctx, "out_sample_fmt", AV_SAMPLE_FMT_FLT, 0);
966
967 // A mono source stays centered at unity gain in the stereo pipeline.
968 if (decoder->audio_codec_ctx->ch_layout.nb_channels == 1) {
969 const double matrix[] = {1.0, 1.0};
970 swr_set_matrix(decoder->swr_ctx, matrix, 1);
971 }
972 // Initialize
973 if (swr_init(decoder->swr_ctx) < 0) {
974 SET_ERRNO(ERROR_MEDIA_DECODE, "Failed to initialize swresample context");
975 ffmpeg_decoder_destroy(decoder);
976 return NULL;
977 }
978
979 // Allocate audio buffer (10 seconds worth)
981 decoder->audio_buffer = SAFE_MALLOC(decoder->audio_buffer_size * sizeof(float), float *);
982 if (!decoder->audio_buffer) {
983 SET_ERRNO(ERROR_MEMORY, "Failed to allocate audio buffer");
984 ffmpeg_decoder_destroy(decoder);
985 return NULL;
986 }
987 }
988
989 // Initialize video frame prefetching system (for YouTube streaming)
990 if (decoder->video_stream_idx >= 0) {
991 int width = decoder->video_codec_ctx->width;
992 int height = decoder->video_codec_ctx->height;
993
994 // Create two prefetch image buffers for double-buffering
995 decoder->prefetch_image_a = image_new((size_t)width, (size_t)height);
996 decoder->prefetch_image_b = image_new((size_t)width, (size_t)height);
997 if (!decoder->prefetch_image_a || !decoder->prefetch_image_b) {
998 SET_ERRNO(ERROR_MEMORY, "Failed to allocate prefetch image buffers");
999 ffmpeg_decoder_destroy(decoder);
1000 return NULL;
1001 }
1002
1003 decoder->current_prefetch_image = decoder->prefetch_image_a;
1004 decoder->prefetch_frame_ready = false;
1005
1006 // Initialize prefetch mutex
1007 if (mutex_init(&decoder->prefetch_mutex, "ffmpeg_prefetch") != 0) {
1008 SET_ERRNO(ERROR_MEMORY, "Failed to initialize prefetch mutex");
1009 ffmpeg_decoder_destroy(decoder);
1010 return NULL;
1011 }
1012
1013 if (cond_init(&decoder->prefetch_cond, "ffmpeg_prefetch") != 0) {
1014 SET_ERRNO(ERROR_MEMORY, "Failed to initialize prefetch condition variable");
1015 mutex_destroy(&decoder->prefetch_mutex);
1016 ffmpeg_decoder_destroy(decoder);
1017 return NULL;
1018 }
1019
1020 if (mutex_init(&decoder->read_frame_mutex, "ffmpeg_read_frame") != 0) {
1021 SET_ERRNO(ERROR_MEMORY, "Failed to initialize read_frame mutex");
1022 mutex_destroy(&decoder->prefetch_mutex);
1023 cond_destroy(&decoder->prefetch_cond);
1024 ffmpeg_decoder_destroy(decoder);
1025 return NULL;
1026 }
1027
1028 decoder->prefetch_thread_running = false;
1029 decoder->prefetch_should_stop = false;
1030 }
1031
1032 log_debug("FFmpeg decoder opened: %s (video=%s, audio=%s)", path, decoder->video_stream_idx >= 0 ? "yes" : "no",
1033 decoder->audio_stream_idx >= 0 ? "yes" : "no");
1034
1036 snprintf(decoder_name, sizeof(decoder_name), "%s", path);
1037 NAMED_REGISTER_FFMPEG_DECODER(decoder, decoder_name, NULL);
1038 NAMED_REGISTER_ATOMIC(&decoder->eof_reached, "ffmpeg_eof", decoder);
1039
1040 return decoder;
1041}
void errno_scope_end(asciichat_errno_scope_t scope, asciichat_errno_outcome_t outcome)
asciichat_errno_scope_t errno_scope_begin(void)
#define SET_ERRNO(code,...)
@ ASCIICHAT_ERRNO_DISMISSED
#define MEDIA_FFMPEG_DECODER_DECODER_NAME_BUFFER_SIZE
#define TARGET_CHANNELS
#define TARGET_SAMPLE_RATE
unsigned int height
Definition fft.c:23
unsigned int width
Definition fft.c:23
#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_FFMPEG_DECODER(decoder, name, parent_ptr)
Register an FFmpeg decoder with automatic format specifier.
asciichat_error_t
Error and exit codes - unified status values (0-255).
Definition error_codes.h:44
@ ERROR_MEMORY
Definition error_codes.h:51
@ ASCIICHAT_OK
Definition error_codes.h:46
@ ERROR_MEDIA_DECODE
Definition error_codes.h:67
@ ERROR_INVALID_PARAM
@ ERROR_MEDIA_OPEN
Definition error_codes.h:66
#define log_warn(...)
Log a WARN message.
Definition log/log.h:621
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:595
void ffmpeg_decoder_destroy(ffmpeg_decoder_t *decoder)
Destroy FFmpeg decoder and free resources.
bool url_is_valid(const char *url)
URL validation using shared parsing and ICU hostname checks.
Definition url.c:159
image_t * image_new(size_t width, size_t height)
Create a new image with standard allocation.
#define LOG_IO(prefix, block)
Definition io.h:104
#define ASCII_OTEL_SCOPE(id)
Definition otel.h:72
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.
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.
atomic_t eof_reached
Whether end of file was reached.
atomic_ptr_t exit_callback_user_data
User data for exit callback.
bool prefetch_thread_running
Whether prefetch thread is active.
image_t * prefetch_image_b
Second prefetch buffer.
atomic_ptr_t should_exit_callback
Callback to check if app should exit.
cond_t prefetch_cond
Condition variable for pausing during seek.
mutex_t read_frame_mutex
Protect traced_packet_read() calls from video and audio threads.
struct SwrContext * swr_ctx
Software resampler for format conversion.
AVPacket * packet
Reusable packet for reading.
int video_stream_idx
Video stream index (-1 if none).
_Atomic bool prefetch_should_stop
Signal to stop prefetch thread.
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.
_Atomic bool prefetch_frame_ready
Whether current_prefetch_image has valid data.
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.

References ASCII_OTEL_SCOPE, ASCIICHAT_ERRNO_DISMISSED, ASCIICHAT_OK, 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_t::eof_reached, errno_scope_begin(), errno_scope_end(), ERROR_INVALID_PARAM, ERROR_MEDIA_DECODE, ERROR_MEDIA_OPEN, ERROR_MEMORY, ffmpeg_decoder_t::exit_callback_user_data, ffmpeg_decoder_destroy(), ffmpeg_decoder_t::format_ctx, ffmpeg_decoder_t::frame, height, image_new(), ffmpeg_decoder_t::last_audio_pts, ffmpeg_decoder_t::last_video_pts, log_debug, LOG_IO, log_warn, MEDIA_FFMPEG_DECODER_DECODER_NAME_BUFFER_SIZE, NAMED_REGISTER_ATOMIC, NAMED_REGISTER_FFMPEG_DECODER, 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::read_frame_mutex, SAFE_FREE, SAFE_MALLOC, SET_ERRNO, ffmpeg_decoder_t::should_exit_callback, ffmpeg_decoder_t::swr_ctx, ffmpeg_decoder_t::sws_ctx, TARGET_CHANNELS, TARGET_SAMPLE_RATE, url_is_valid(), ffmpeg_decoder_t::video_codec_ctx, ffmpeg_decoder_t::video_stream_idx, and width.

Referenced by ffmpeg_decoder_create_interruptible(), and media_source_create_interruptible().