Skip to content

Commit 141dece

Browse files
committed
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.
1 parent db3d0e9 commit 141dece

2 files changed

Lines changed: 69 additions & 43 deletions

File tree

av/filter/loudnorm_impl.c

Lines changed: 68 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,20 @@ char* loudnorm_get_stats(
5757
const char* loudnorm_args
5858
) {
5959
char* result = NULL;
60+
61+
// Bail out cleanly if FFmpeg was built without a filter we depend on,
62+
// instead of dereferencing a NULL filter context further down. The caller
63+
// turns a NULL return into a Python exception.
64+
if (!avfilter_get_by_name("abuffer") ||
65+
!avfilter_get_by_name("abuffersink") ||
66+
!avfilter_get_by_name("loudnorm")) {
67+
av_log(NULL, AV_LOG_ERROR,
68+
"loudnorm: a required filter (abuffer/abuffersink/loudnorm) is not "
69+
"available in this FFmpeg build\n");
70+
avformat_close_input(&fmt_ctx);
71+
return NULL;
72+
}
73+
6074
json_captured = 0; // Reset the captured flag
6175
memset(json_buffer, 0, sizeof(json_buffer)); // Clear the buffer
6276

@@ -76,7 +90,10 @@ char* loudnorm_get_stats(
7690

7791
AVCodec *codec = NULL;
7892
AVCodecContext *codec_ctx = NULL;
93+
AVPacket *packet = NULL;
94+
AVFrame *frame = NULL, *filt_frame = NULL;
7995
int ret;
96+
int graph_configured = 0;
8097

8198
AVCodecParameters *codecpar = fmt_ctx->streams[audio_stream_index]->codecpar;
8299
codec = (AVCodec *)avcodec_find_decoder(codecpar->codec_id);
@@ -98,20 +115,25 @@ char* loudnorm_get_stats(
98115
av_get_sample_fmt_name(codec_ctx->sample_fmt),
99116
ch_layout_str);
100117

101-
avfilter_graph_create_filter(&src_ctx, avfilter_get_by_name("abuffer"),
102-
"src", args, NULL, filter_graph);
103-
avfilter_graph_create_filter(&sink_ctx, avfilter_get_by_name("abuffersink"),
104-
"sink", NULL, NULL, filter_graph);
105-
avfilter_graph_create_filter(&loudnorm_ctx, avfilter_get_by_name("loudnorm"),
106-
"loudnorm", loudnorm_args, NULL, filter_graph);
118+
if (avfilter_graph_create_filter(&src_ctx, avfilter_get_by_name("abuffer"),
119+
"src", args, NULL, filter_graph) < 0 ||
120+
avfilter_graph_create_filter(&sink_ctx, avfilter_get_by_name("abuffersink"),
121+
"sink", NULL, NULL, filter_graph) < 0 ||
122+
avfilter_graph_create_filter(&loudnorm_ctx, avfilter_get_by_name("loudnorm"),
123+
"loudnorm", loudnorm_args, NULL, filter_graph) < 0) {
124+
goto end;
125+
}
107126

108-
avfilter_link(src_ctx, 0, loudnorm_ctx, 0);
109-
avfilter_link(loudnorm_ctx, 0, sink_ctx, 0);
110-
avfilter_graph_config(filter_graph, NULL);
127+
if (avfilter_link(src_ctx, 0, loudnorm_ctx, 0) < 0 ||
128+
avfilter_link(loudnorm_ctx, 0, sink_ctx, 0) < 0 ||
129+
avfilter_graph_config(filter_graph, NULL) < 0) {
130+
goto end;
131+
}
132+
graph_configured = 1;
111133

112-
AVPacket *packet = av_packet_alloc();
113-
AVFrame *frame = av_frame_alloc();
114-
AVFrame *filt_frame = av_frame_alloc();
134+
packet = av_packet_alloc();
135+
frame = av_frame_alloc();
136+
filt_frame = av_frame_alloc();
115137

116138
while ((ret = av_read_frame(fmt_ctx, packet)) >= 0) {
117139
if (packet->stream_index != audio_stream_index) {
@@ -157,46 +179,50 @@ char* loudnorm_get_stats(
157179
av_frame_unref(filt_frame);
158180
}
159181

160-
// Pushes graph
161-
avfilter_graph_free(&filter_graph);
162-
163182
end:
183+
// Freeing the graph uninits the loudnorm filter, which is what makes it
184+
// emit its JSON stats through our log callback. Safe to call on NULL.
185+
avfilter_graph_free(&filter_graph);
164186
avcodec_free_context(&codec_ctx);
165187
avformat_close_input(&fmt_ctx);
166188
av_frame_free(&filt_frame);
167189
av_frame_free(&frame);
168190
av_packet_free(&packet);
169191

170-
#ifdef _WIN32
171-
EnterCriticalSection(&json_mutex);
172-
while (!json_captured) {
173-
if (!SleepConditionVariableCS(&json_cond, &json_mutex, 5000)) { // 5 second timeout
174-
fprintf(stderr, "Timeout waiting for JSON data\n");
175-
break;
192+
// If the graph never configured we produced no stats; don't block waiting
193+
// for JSON that will never arrive. Leave result NULL so the caller raises.
194+
if (graph_configured) {
195+
#ifdef _WIN32
196+
EnterCriticalSection(&json_mutex);
197+
while (!json_captured) {
198+
if (!SleepConditionVariableCS(&json_cond, &json_mutex, 5000)) { // 5 second timeout
199+
fprintf(stderr, "Timeout waiting for JSON data\n");
200+
break;
201+
}
176202
}
177-
}
178-
if (json_captured) {
179-
result = _strdup(json_buffer); // Use _strdup on Windows
180-
}
181-
LeaveCriticalSection(&json_mutex);
182-
#else
183-
struct timespec timeout;
184-
clock_gettime(CLOCK_REALTIME, &timeout);
185-
timeout.tv_sec += 5; // 5 second timeout
186-
187-
pthread_mutex_lock(&json_mutex);
188-
while (json_captured == 0) {
189-
int ret = pthread_cond_timedwait(&json_cond, &json_mutex, &timeout);
190-
if (ret == ETIMEDOUT) {
191-
fprintf(stderr, "Timeout waiting for JSON data\n");
192-
break;
203+
if (json_captured) {
204+
result = _strdup(json_buffer); // Use _strdup on Windows
193205
}
206+
LeaveCriticalSection(&json_mutex);
207+
#else
208+
struct timespec timeout;
209+
clock_gettime(CLOCK_REALTIME, &timeout);
210+
timeout.tv_sec += 5; // 5 second timeout
211+
212+
pthread_mutex_lock(&json_mutex);
213+
while (json_captured == 0) {
214+
int ret = pthread_cond_timedwait(&json_cond, &json_mutex, &timeout);
215+
if (ret == ETIMEDOUT) {
216+
fprintf(stderr, "Timeout waiting for JSON data\n");
217+
break;
218+
}
219+
}
220+
if (json_captured) {
221+
result = strdup(json_buffer);
222+
}
223+
pthread_mutex_unlock(&json_mutex);
224+
#endif
194225
}
195-
if (json_captured) {
196-
result = strdup(json_buffer);
197-
}
198-
pthread_mutex_unlock(&json_mutex);
199-
#endif
200226

201227
av_log_set_callback(av_log_default_callback);
202228
return result;

scripts/build-deps

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ echo ./configure
6969
--disable-bsfs \
7070
--enable-bsf=chomp,extract_extradata,h264_mp4toannexb,setts \
7171
--disable-filters \
72-
--enable-filter=abuffer,abuffersink,aformat,aresample,atempo,buffer,buffersink,bwdif,color,lutrgb,palettegen,scale,testsrc,vflip,volume \
72+
--enable-filter=abuffer,abuffersink,aformat,aresample,atempo,buffer,buffersink,bwdif,color,loudnorm,lutrgb,palettegen,scale,testsrc,vflip,volume \
7373
--enable-sse \
7474
--enable-avx \
7575
--enable-avx2 \

0 commit comments

Comments
 (0)