From db3d0e97be78f34ab141b7c42c7de82560d587b8 Mon Sep 17 00:00:00 2001 From: WyattBlue Date: Sat, 13 Jun 2026 23:34:58 -0400 Subject: [PATCH 1/2] Disable most filters for smoke builds --- .github/workflows/smoke.yml | 3 +-- scripts/build-deps | 2 ++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml index 17c24fded..78d80a072 100644 --- a/.github/workflows/smoke.yml +++ b/.github/workflows/smoke.yml @@ -65,7 +65,6 @@ jobs: automake \ build-essential \ cmake \ - libjpeg-dev \ libtool \ libx264-dev \ nasm \ @@ -76,7 +75,7 @@ jobs: fi ;; macos-14) - brew install automake libtool libpng libvpx opus x264 + brew install automake libtool opus x264 ;; esac diff --git a/scripts/build-deps b/scripts/build-deps index ee90471c1..c6ab3d73f 100755 --- a/scripts/build-deps +++ b/scripts/build-deps @@ -68,6 +68,8 @@ echo ./configure --enable-libx264 \ --disable-bsfs \ --enable-bsf=chomp,extract_extradata,h264_mp4toannexb,setts \ + --disable-filters \ + --enable-filter=abuffer,abuffersink,aformat,aresample,atempo,buffer,buffersink,bwdif,color,lutrgb,palettegen,scale,testsrc,vflip,volume \ --enable-sse \ --enable-avx \ --enable-avx2 \ From 141dece74a95fcd0d8ed2ec9b7a6435afae6d9e2 Mon Sep 17 00:00:00 2001 From: WyattBlue Date: Sat, 13 Jun 2026 23:53:08 -0400 Subject: [PATCH 2/2] Enable loudnorm filter in smoke builds and harden its impl test_streams.py::test_loudnorm calls av.filter.loudnorm, so add loudnorm to the trimmed smoke-build filter list; without it the filter lookup returned NULL. That NULL also exposed a crash: loudnorm_get_stats() built an abuffer -> loudnorm -> abuffersink graph but ignored every return code, so a NULL loudnorm context was passed to avfilter_link() and segfaulted the interpreter. Bail out early if a required filter is missing, check the create_filter/link/graph_config return codes so a NULL context can never be linked, free the graph on the error path, and skip the 5s JSON wait when no graph ran. On failure the function returns NULL, which the Python wrapper already turns into a RuntimeError. --- av/filter/loudnorm_impl.c | 110 +++++++++++++++++++++++--------------- scripts/build-deps | 2 +- 2 files changed, 69 insertions(+), 43 deletions(-) diff --git a/av/filter/loudnorm_impl.c b/av/filter/loudnorm_impl.c index c4e182981..19cb1c554 100644 --- a/av/filter/loudnorm_impl.c +++ b/av/filter/loudnorm_impl.c @@ -57,6 +57,20 @@ char* loudnorm_get_stats( const char* loudnorm_args ) { char* result = NULL; + + // Bail out cleanly if FFmpeg was built without a filter we depend on, + // instead of dereferencing a NULL filter context further down. The caller + // turns a NULL return into a Python exception. + if (!avfilter_get_by_name("abuffer") || + !avfilter_get_by_name("abuffersink") || + !avfilter_get_by_name("loudnorm")) { + av_log(NULL, AV_LOG_ERROR, + "loudnorm: a required filter (abuffer/abuffersink/loudnorm) is not " + "available in this FFmpeg build\n"); + avformat_close_input(&fmt_ctx); + return NULL; + } + json_captured = 0; // Reset the captured flag memset(json_buffer, 0, sizeof(json_buffer)); // Clear the buffer @@ -76,7 +90,10 @@ char* loudnorm_get_stats( AVCodec *codec = NULL; AVCodecContext *codec_ctx = NULL; + AVPacket *packet = NULL; + AVFrame *frame = NULL, *filt_frame = NULL; int ret; + int graph_configured = 0; AVCodecParameters *codecpar = fmt_ctx->streams[audio_stream_index]->codecpar; codec = (AVCodec *)avcodec_find_decoder(codecpar->codec_id); @@ -98,20 +115,25 @@ char* loudnorm_get_stats( av_get_sample_fmt_name(codec_ctx->sample_fmt), ch_layout_str); - avfilter_graph_create_filter(&src_ctx, avfilter_get_by_name("abuffer"), - "src", args, NULL, filter_graph); - avfilter_graph_create_filter(&sink_ctx, avfilter_get_by_name("abuffersink"), - "sink", NULL, NULL, filter_graph); - avfilter_graph_create_filter(&loudnorm_ctx, avfilter_get_by_name("loudnorm"), - "loudnorm", loudnorm_args, NULL, filter_graph); + if (avfilter_graph_create_filter(&src_ctx, avfilter_get_by_name("abuffer"), + "src", args, NULL, filter_graph) < 0 || + avfilter_graph_create_filter(&sink_ctx, avfilter_get_by_name("abuffersink"), + "sink", NULL, NULL, filter_graph) < 0 || + avfilter_graph_create_filter(&loudnorm_ctx, avfilter_get_by_name("loudnorm"), + "loudnorm", loudnorm_args, NULL, filter_graph) < 0) { + goto end; + } - avfilter_link(src_ctx, 0, loudnorm_ctx, 0); - avfilter_link(loudnorm_ctx, 0, sink_ctx, 0); - avfilter_graph_config(filter_graph, NULL); + if (avfilter_link(src_ctx, 0, loudnorm_ctx, 0) < 0 || + avfilter_link(loudnorm_ctx, 0, sink_ctx, 0) < 0 || + avfilter_graph_config(filter_graph, NULL) < 0) { + goto end; + } + graph_configured = 1; - AVPacket *packet = av_packet_alloc(); - AVFrame *frame = av_frame_alloc(); - AVFrame *filt_frame = av_frame_alloc(); + packet = av_packet_alloc(); + frame = av_frame_alloc(); + filt_frame = av_frame_alloc(); while ((ret = av_read_frame(fmt_ctx, packet)) >= 0) { if (packet->stream_index != audio_stream_index) { @@ -157,46 +179,50 @@ char* loudnorm_get_stats( av_frame_unref(filt_frame); } - // Pushes graph - avfilter_graph_free(&filter_graph); - end: + // Freeing the graph uninits the loudnorm filter, which is what makes it + // emit its JSON stats through our log callback. Safe to call on NULL. + avfilter_graph_free(&filter_graph); avcodec_free_context(&codec_ctx); avformat_close_input(&fmt_ctx); av_frame_free(&filt_frame); av_frame_free(&frame); av_packet_free(&packet); - #ifdef _WIN32 - EnterCriticalSection(&json_mutex); - while (!json_captured) { - if (!SleepConditionVariableCS(&json_cond, &json_mutex, 5000)) { // 5 second timeout - fprintf(stderr, "Timeout waiting for JSON data\n"); - break; + // If the graph never configured we produced no stats; don't block waiting + // for JSON that will never arrive. Leave result NULL so the caller raises. + if (graph_configured) { + #ifdef _WIN32 + EnterCriticalSection(&json_mutex); + while (!json_captured) { + if (!SleepConditionVariableCS(&json_cond, &json_mutex, 5000)) { // 5 second timeout + fprintf(stderr, "Timeout waiting for JSON data\n"); + break; + } } - } - if (json_captured) { - result = _strdup(json_buffer); // Use _strdup on Windows - } - LeaveCriticalSection(&json_mutex); - #else - struct timespec timeout; - clock_gettime(CLOCK_REALTIME, &timeout); - timeout.tv_sec += 5; // 5 second timeout - - pthread_mutex_lock(&json_mutex); - while (json_captured == 0) { - int ret = pthread_cond_timedwait(&json_cond, &json_mutex, &timeout); - if (ret == ETIMEDOUT) { - fprintf(stderr, "Timeout waiting for JSON data\n"); - break; + if (json_captured) { + result = _strdup(json_buffer); // Use _strdup on Windows } + LeaveCriticalSection(&json_mutex); + #else + struct timespec timeout; + clock_gettime(CLOCK_REALTIME, &timeout); + timeout.tv_sec += 5; // 5 second timeout + + pthread_mutex_lock(&json_mutex); + while (json_captured == 0) { + int ret = pthread_cond_timedwait(&json_cond, &json_mutex, &timeout); + if (ret == ETIMEDOUT) { + fprintf(stderr, "Timeout waiting for JSON data\n"); + break; + } + } + if (json_captured) { + result = strdup(json_buffer); + } + pthread_mutex_unlock(&json_mutex); + #endif } - if (json_captured) { - result = strdup(json_buffer); - } - pthread_mutex_unlock(&json_mutex); - #endif av_log_set_callback(av_log_default_callback); return result; diff --git a/scripts/build-deps b/scripts/build-deps index c6ab3d73f..4449f2bc3 100755 --- a/scripts/build-deps +++ b/scripts/build-deps @@ -69,7 +69,7 @@ echo ./configure --disable-bsfs \ --enable-bsf=chomp,extract_extradata,h264_mp4toannexb,setts \ --disable-filters \ - --enable-filter=abuffer,abuffersink,aformat,aresample,atempo,buffer,buffersink,bwdif,color,lutrgb,palettegen,scale,testsrc,vflip,volume \ + --enable-filter=abuffer,abuffersink,aformat,aresample,atempo,buffer,buffersink,bwdif,color,loudnorm,lutrgb,palettegen,scale,testsrc,vflip,volume \ --enable-sse \ --enable-avx \ --enable-avx2 \