@@ -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-
163182end :
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 ;
0 commit comments