blob: 1fd85bbf790073833eade3d453efc1a98b0a1f4d
1 | /* |
2 | * Copyright (c) 2012 Clément Bœsch |
3 | * |
4 | * This file is part of FFmpeg. |
5 | * |
6 | * FFmpeg is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU Lesser General Public |
8 | * License as published by the Free Software Foundation; either |
9 | * version 2.1 of the License, or (at your option) any later version. |
10 | * |
11 | * FFmpeg is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
14 | * Lesser General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU Lesser General Public |
17 | * License along with FFmpeg; if not, write to the Free Software |
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
19 | */ |
20 | |
21 | /** |
22 | * @file |
23 | * EBU R.128 implementation |
24 | * @see http://tech.ebu.ch/loudness |
25 | * @see https://www.youtube.com/watch?v=iuEtQqC-Sqo "EBU R128 Introduction - Florian Camerer" |
26 | * @todo implement start/stop/reset through filter command injection |
27 | * @todo support other frequencies to avoid resampling |
28 | */ |
29 | |
30 | #include <math.h> |
31 | |
32 | #include "libavutil/avassert.h" |
33 | #include "libavutil/avstring.h" |
34 | #include "libavutil/channel_layout.h" |
35 | #include "libavutil/dict.h" |
36 | #include "libavutil/ffmath.h" |
37 | #include "libavutil/xga_font_data.h" |
38 | #include "libavutil/opt.h" |
39 | #include "libavutil/timestamp.h" |
40 | #include "libswresample/swresample.h" |
41 | #include "audio.h" |
42 | #include "avfilter.h" |
43 | #include "formats.h" |
44 | #include "internal.h" |
45 | |
46 | #define MAX_CHANNELS 63 |
47 | |
48 | /* pre-filter coefficients */ |
49 | #define PRE_B0 1.53512485958697 |
50 | #define PRE_B1 -2.69169618940638 |
51 | #define PRE_B2 1.19839281085285 |
52 | #define PRE_A1 -1.69065929318241 |
53 | #define PRE_A2 0.73248077421585 |
54 | |
55 | /* RLB-filter coefficients */ |
56 | #define RLB_B0 1.0 |
57 | #define RLB_B1 -2.0 |
58 | #define RLB_B2 1.0 |
59 | #define RLB_A1 -1.99004745483398 |
60 | #define RLB_A2 0.99007225036621 |
61 | |
62 | #define ABS_THRES -70 ///< silence gate: we discard anything below this absolute (LUFS) threshold |
63 | #define ABS_UP_THRES 10 ///< upper loud limit to consider (ABS_THRES being the minimum) |
64 | #define HIST_GRAIN 100 ///< defines histogram precision |
65 | #define HIST_SIZE ((ABS_UP_THRES - ABS_THRES) * HIST_GRAIN + 1) |
66 | |
67 | /** |
68 | * A histogram is an array of HIST_SIZE hist_entry storing all the energies |
69 | * recorded (with an accuracy of 1/HIST_GRAIN) of the loudnesses from ABS_THRES |
70 | * (at 0) to ABS_UP_THRES (at HIST_SIZE-1). |
71 | * This fixed-size system avoids the need of a list of energies growing |
72 | * infinitely over the time and is thus more scalable. |
73 | */ |
74 | struct hist_entry { |
75 | int count; ///< how many times the corresponding value occurred |
76 | double energy; ///< E = 10^((L + 0.691) / 10) |
77 | double loudness; ///< L = -0.691 + 10 * log10(E) |
78 | }; |
79 | |
80 | struct integrator { |
81 | double *cache[MAX_CHANNELS]; ///< window of filtered samples (N ms) |
82 | int cache_pos; ///< focus on the last added bin in the cache array |
83 | double sum[MAX_CHANNELS]; ///< sum of the last N ms filtered samples (cache content) |
84 | int filled; ///< 1 if the cache is completely filled, 0 otherwise |
85 | double rel_threshold; ///< relative threshold |
86 | double sum_kept_powers; ///< sum of the powers (weighted sums) above absolute threshold |
87 | int nb_kept_powers; ///< number of sum above absolute threshold |
88 | struct hist_entry *histogram; ///< histogram of the powers, used to compute LRA and I |
89 | }; |
90 | |
91 | struct rect { int x, y, w, h; }; |
92 | |
93 | typedef struct { |
94 | const AVClass *class; ///< AVClass context for log and options purpose |
95 | |
96 | /* peak metering */ |
97 | int peak_mode; ///< enabled peak modes |
98 | double *true_peaks; ///< true peaks per channel |
99 | double *sample_peaks; ///< sample peaks per channel |
100 | double *true_peaks_per_frame; ///< true peaks in a frame per channel |
101 | #if CONFIG_SWRESAMPLE |
102 | SwrContext *swr_ctx; ///< over-sampling context for true peak metering |
103 | double *swr_buf; ///< resampled audio data for true peak metering |
104 | int swr_linesize; |
105 | #endif |
106 | |
107 | /* video */ |
108 | int do_video; ///< 1 if video output enabled, 0 otherwise |
109 | int w, h; ///< size of the video output |
110 | struct rect text; ///< rectangle for the LU legend on the left |
111 | struct rect graph; ///< rectangle for the main graph in the center |
112 | struct rect gauge; ///< rectangle for the gauge on the right |
113 | AVFrame *outpicref; ///< output picture reference, updated regularly |
114 | int meter; ///< select a EBU mode between +9 and +18 |
115 | int scale_range; ///< the range of LU values according to the meter |
116 | int y_zero_lu; ///< the y value (pixel position) for 0 LU |
117 | int *y_line_ref; ///< y reference values for drawing the LU lines in the graph and the gauge |
118 | |
119 | /* audio */ |
120 | int nb_channels; ///< number of channels in the input |
121 | double *ch_weighting; ///< channel weighting mapping |
122 | int sample_count; ///< sample count used for refresh frequency, reset at refresh |
123 | |
124 | /* Filter caches. |
125 | * The mult by 3 in the following is for X[i], X[i-1] and X[i-2] */ |
126 | double x[MAX_CHANNELS * 3]; ///< 3 input samples cache for each channel |
127 | double y[MAX_CHANNELS * 3]; ///< 3 pre-filter samples cache for each channel |
128 | double z[MAX_CHANNELS * 3]; ///< 3 RLB-filter samples cache for each channel |
129 | |
130 | #define I400_BINS (48000 * 4 / 10) |
131 | #define I3000_BINS (48000 * 3) |
132 | struct integrator i400; ///< 400ms integrator, used for Momentary loudness (M), and Integrated loudness (I) |
133 | struct integrator i3000; ///< 3s integrator, used for Short term loudness (S), and Loudness Range (LRA) |
134 | |
135 | /* I and LRA specific */ |
136 | double integrated_loudness; ///< integrated loudness in LUFS (I) |
137 | double loudness_range; ///< loudness range in LU (LRA) |
138 | double lra_low, lra_high; ///< low and high LRA values |
139 | |
140 | /* misc */ |
141 | int loglevel; ///< log level for frame logging |
142 | int metadata; ///< whether or not to inject loudness results in frames |
143 | int dual_mono; ///< whether or not to treat single channel input files as dual-mono |
144 | double pan_law; ///< pan law value used to calculate dual-mono measurements |
145 | } EBUR128Context; |
146 | |
147 | enum { |
148 | PEAK_MODE_NONE = 0, |
149 | PEAK_MODE_SAMPLES_PEAKS = 1<<1, |
150 | PEAK_MODE_TRUE_PEAKS = 1<<2, |
151 | }; |
152 | |
153 | #define OFFSET(x) offsetof(EBUR128Context, x) |
154 | #define A AV_OPT_FLAG_AUDIO_PARAM |
155 | #define V AV_OPT_FLAG_VIDEO_PARAM |
156 | #define F AV_OPT_FLAG_FILTERING_PARAM |
157 | static const AVOption ebur128_options[] = { |
158 | { "video", "set video output", OFFSET(do_video), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, V|F }, |
159 | { "size", "set video size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str = "640x480"}, 0, 0, V|F }, |
160 | { "meter", "set scale meter (+9 to +18)", OFFSET(meter), AV_OPT_TYPE_INT, {.i64 = 9}, 9, 18, V|F }, |
161 | { "framelog", "force frame logging level", OFFSET(loglevel), AV_OPT_TYPE_INT, {.i64 = -1}, INT_MIN, INT_MAX, A|V|F, "level" }, |
162 | { "info", "information logging level", 0, AV_OPT_TYPE_CONST, {.i64 = AV_LOG_INFO}, INT_MIN, INT_MAX, A|V|F, "level" }, |
163 | { "verbose", "verbose logging level", 0, AV_OPT_TYPE_CONST, {.i64 = AV_LOG_VERBOSE}, INT_MIN, INT_MAX, A|V|F, "level" }, |
164 | { "metadata", "inject metadata in the filtergraph", OFFSET(metadata), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, A|V|F }, |
165 | { "peak", "set peak mode", OFFSET(peak_mode), AV_OPT_TYPE_FLAGS, {.i64 = PEAK_MODE_NONE}, 0, INT_MAX, A|F, "mode" }, |
166 | { "none", "disable any peak mode", 0, AV_OPT_TYPE_CONST, {.i64 = PEAK_MODE_NONE}, INT_MIN, INT_MAX, A|F, "mode" }, |
167 | { "sample", "enable peak-sample mode", 0, AV_OPT_TYPE_CONST, {.i64 = PEAK_MODE_SAMPLES_PEAKS}, INT_MIN, INT_MAX, A|F, "mode" }, |
168 | { "true", "enable true-peak mode", 0, AV_OPT_TYPE_CONST, {.i64 = PEAK_MODE_TRUE_PEAKS}, INT_MIN, INT_MAX, A|F, "mode" }, |
169 | { "dualmono", "treat mono input files as dual-mono", OFFSET(dual_mono), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, A|F }, |
170 | { "panlaw", "set a specific pan law for dual-mono files", OFFSET(pan_law), AV_OPT_TYPE_DOUBLE, {.dbl = -3.01029995663978}, -10.0, 0.0, A|F }, |
171 | { NULL }, |
172 | }; |
173 | |
174 | AVFILTER_DEFINE_CLASS(ebur128); |
175 | |
176 | static const uint8_t graph_colors[] = { |
177 | 0xdd, 0x66, 0x66, // value above 0LU non reached |
178 | 0x66, 0x66, 0xdd, // value below 0LU non reached |
179 | 0x96, 0x33, 0x33, // value above 0LU reached |
180 | 0x33, 0x33, 0x96, // value below 0LU reached |
181 | 0xdd, 0x96, 0x96, // value above 0LU line non reached |
182 | 0x96, 0x96, 0xdd, // value below 0LU line non reached |
183 | 0xdd, 0x33, 0x33, // value above 0LU line reached |
184 | 0x33, 0x33, 0xdd, // value below 0LU line reached |
185 | }; |
186 | |
187 | static const uint8_t *get_graph_color(const EBUR128Context *ebur128, int v, int y) |
188 | { |
189 | const int below0 = y > ebur128->y_zero_lu; |
190 | const int reached = y >= v; |
191 | const int line = ebur128->y_line_ref[y] || y == ebur128->y_zero_lu; |
192 | const int colorid = 4*line + 2*reached + below0; |
193 | return graph_colors + 3*colorid; |
194 | } |
195 | |
196 | static inline int lu_to_y(const EBUR128Context *ebur128, double v) |
197 | { |
198 | v += 2 * ebur128->meter; // make it in range [0;...] |
199 | v = av_clipf(v, 0, ebur128->scale_range); // make sure it's in the graph scale |
200 | v = ebur128->scale_range - v; // invert value (y=0 is on top) |
201 | return v * ebur128->graph.h / ebur128->scale_range; // rescale from scale range to px height |
202 | } |
203 | |
204 | #define FONT8 0 |
205 | #define FONT16 1 |
206 | |
207 | static const uint8_t font_colors[] = { |
208 | 0xdd, 0xdd, 0x00, |
209 | 0x00, 0x96, 0x96, |
210 | }; |
211 | |
212 | static void drawtext(AVFrame *pic, int x, int y, int ftid, const uint8_t *color, const char *fmt, ...) |
213 | { |
214 | int i; |
215 | char buf[128] = {0}; |
216 | const uint8_t *font; |
217 | int font_height; |
218 | va_list vl; |
219 | |
220 | if (ftid == FONT16) font = avpriv_vga16_font, font_height = 16; |
221 | else if (ftid == FONT8) font = avpriv_cga_font, font_height = 8; |
222 | else return; |
223 | |
224 | va_start(vl, fmt); |
225 | vsnprintf(buf, sizeof(buf), fmt, vl); |
226 | va_end(vl); |
227 | |
228 | for (i = 0; buf[i]; i++) { |
229 | int char_y, mask; |
230 | uint8_t *p = pic->data[0] + y*pic->linesize[0] + (x + i*8)*3; |
231 | |
232 | for (char_y = 0; char_y < font_height; char_y++) { |
233 | for (mask = 0x80; mask; mask >>= 1) { |
234 | if (font[buf[i] * font_height + char_y] & mask) |
235 | memcpy(p, color, 3); |
236 | else |
237 | memcpy(p, "\x00\x00\x00", 3); |
238 | p += 3; |
239 | } |
240 | p += pic->linesize[0] - 8*3; |
241 | } |
242 | } |
243 | } |
244 | |
245 | static void drawline(AVFrame *pic, int x, int y, int len, int step) |
246 | { |
247 | int i; |
248 | uint8_t *p = pic->data[0] + y*pic->linesize[0] + x*3; |
249 | |
250 | for (i = 0; i < len; i++) { |
251 | memcpy(p, "\x00\xff\x00", 3); |
252 | p += step; |
253 | } |
254 | } |
255 | |
256 | static int config_video_output(AVFilterLink *outlink) |
257 | { |
258 | int i, x, y; |
259 | uint8_t *p; |
260 | AVFilterContext *ctx = outlink->src; |
261 | EBUR128Context *ebur128 = ctx->priv; |
262 | AVFrame *outpicref; |
263 | |
264 | /* check if there is enough space to represent everything decently */ |
265 | if (ebur128->w < 640 || ebur128->h < 480) { |
266 | av_log(ctx, AV_LOG_ERROR, "Video size %dx%d is too small, " |
267 | "minimum size is 640x480\n", ebur128->w, ebur128->h); |
268 | return AVERROR(EINVAL); |
269 | } |
270 | outlink->w = ebur128->w; |
271 | outlink->h = ebur128->h; |
272 | |
273 | #define PAD 8 |
274 | |
275 | /* configure text area position and size */ |
276 | ebur128->text.x = PAD; |
277 | ebur128->text.y = 40; |
278 | ebur128->text.w = 3 * 8; // 3 characters |
279 | ebur128->text.h = ebur128->h - PAD - ebur128->text.y; |
280 | |
281 | /* configure gauge position and size */ |
282 | ebur128->gauge.w = 20; |
283 | ebur128->gauge.h = ebur128->text.h; |
284 | ebur128->gauge.x = ebur128->w - PAD - ebur128->gauge.w; |
285 | ebur128->gauge.y = ebur128->text.y; |
286 | |
287 | /* configure graph position and size */ |
288 | ebur128->graph.x = ebur128->text.x + ebur128->text.w + PAD; |
289 | ebur128->graph.y = ebur128->gauge.y; |
290 | ebur128->graph.w = ebur128->gauge.x - ebur128->graph.x - PAD; |
291 | ebur128->graph.h = ebur128->gauge.h; |
292 | |
293 | /* graph and gauge share the LU-to-pixel code */ |
294 | av_assert0(ebur128->graph.h == ebur128->gauge.h); |
295 | |
296 | /* prepare the initial picref buffer */ |
297 | av_frame_free(&ebur128->outpicref); |
298 | ebur128->outpicref = outpicref = |
299 | ff_get_video_buffer(outlink, outlink->w, outlink->h); |
300 | if (!outpicref) |
301 | return AVERROR(ENOMEM); |
302 | outlink->sample_aspect_ratio = (AVRational){1,1}; |
303 | |
304 | /* init y references values (to draw LU lines) */ |
305 | ebur128->y_line_ref = av_calloc(ebur128->graph.h + 1, sizeof(*ebur128->y_line_ref)); |
306 | if (!ebur128->y_line_ref) |
307 | return AVERROR(ENOMEM); |
308 | |
309 | /* black background */ |
310 | memset(outpicref->data[0], 0, ebur128->h * outpicref->linesize[0]); |
311 | |
312 | /* draw LU legends */ |
313 | drawtext(outpicref, PAD, PAD+16, FONT8, font_colors+3, " LU"); |
314 | for (i = ebur128->meter; i >= -ebur128->meter * 2; i--) { |
315 | y = lu_to_y(ebur128, i); |
316 | x = PAD + (i < 10 && i > -10) * 8; |
317 | ebur128->y_line_ref[y] = i; |
318 | y -= 4; // -4 to center vertically |
319 | drawtext(outpicref, x, y + ebur128->graph.y, FONT8, font_colors+3, |
320 | "%c%d", i < 0 ? '-' : i > 0 ? '+' : ' ', FFABS(i)); |
321 | } |
322 | |
323 | /* draw graph */ |
324 | ebur128->y_zero_lu = lu_to_y(ebur128, 0); |
325 | p = outpicref->data[0] + ebur128->graph.y * outpicref->linesize[0] |
326 | + ebur128->graph.x * 3; |
327 | for (y = 0; y < ebur128->graph.h; y++) { |
328 | const uint8_t *c = get_graph_color(ebur128, INT_MAX, y); |
329 | |
330 | for (x = 0; x < ebur128->graph.w; x++) |
331 | memcpy(p + x*3, c, 3); |
332 | p += outpicref->linesize[0]; |
333 | } |
334 | |
335 | /* draw fancy rectangles around the graph and the gauge */ |
336 | #define DRAW_RECT(r) do { \ |
337 | drawline(outpicref, r.x, r.y - 1, r.w, 3); \ |
338 | drawline(outpicref, r.x, r.y + r.h, r.w, 3); \ |
339 | drawline(outpicref, r.x - 1, r.y, r.h, outpicref->linesize[0]); \ |
340 | drawline(outpicref, r.x + r.w, r.y, r.h, outpicref->linesize[0]); \ |
341 | } while (0) |
342 | DRAW_RECT(ebur128->graph); |
343 | DRAW_RECT(ebur128->gauge); |
344 | |
345 | return 0; |
346 | } |
347 | |
348 | static int config_audio_input(AVFilterLink *inlink) |
349 | { |
350 | AVFilterContext *ctx = inlink->dst; |
351 | EBUR128Context *ebur128 = ctx->priv; |
352 | |
353 | /* Force 100ms framing in case of metadata injection: the frames must have |
354 | * a granularity of the window overlap to be accurately exploited. |
355 | * As for the true peaks mode, it just simplifies the resampling buffer |
356 | * allocation and the lookup in it (since sample buffers differ in size, it |
357 | * can be more complex to integrate in the one-sample loop of |
358 | * filter_frame()). */ |
359 | if (ebur128->metadata || (ebur128->peak_mode & PEAK_MODE_TRUE_PEAKS)) |
360 | inlink->min_samples = |
361 | inlink->max_samples = |
362 | inlink->partial_buf_size = inlink->sample_rate / 10; |
363 | return 0; |
364 | } |
365 | |
366 | static int config_audio_output(AVFilterLink *outlink) |
367 | { |
368 | int i; |
369 | AVFilterContext *ctx = outlink->src; |
370 | EBUR128Context *ebur128 = ctx->priv; |
371 | const int nb_channels = av_get_channel_layout_nb_channels(outlink->channel_layout); |
372 | |
373 | #define BACK_MASK (AV_CH_BACK_LEFT |AV_CH_BACK_CENTER |AV_CH_BACK_RIGHT| \ |
374 | AV_CH_TOP_BACK_LEFT|AV_CH_TOP_BACK_CENTER|AV_CH_TOP_BACK_RIGHT| \ |
375 | AV_CH_SIDE_LEFT |AV_CH_SIDE_RIGHT| \ |
376 | AV_CH_SURROUND_DIRECT_LEFT |AV_CH_SURROUND_DIRECT_RIGHT) |
377 | |
378 | ebur128->nb_channels = nb_channels; |
379 | ebur128->ch_weighting = av_calloc(nb_channels, sizeof(*ebur128->ch_weighting)); |
380 | if (!ebur128->ch_weighting) |
381 | return AVERROR(ENOMEM); |
382 | |
383 | for (i = 0; i < nb_channels; i++) { |
384 | /* channel weighting */ |
385 | const uint16_t chl = av_channel_layout_extract_channel(outlink->channel_layout, i); |
386 | if (chl & (AV_CH_LOW_FREQUENCY|AV_CH_LOW_FREQUENCY_2)) { |
387 | ebur128->ch_weighting[i] = 0; |
388 | } else if (chl & BACK_MASK) { |
389 | ebur128->ch_weighting[i] = 1.41; |
390 | } else { |
391 | ebur128->ch_weighting[i] = 1.0; |
392 | } |
393 | |
394 | if (!ebur128->ch_weighting[i]) |
395 | continue; |
396 | |
397 | /* bins buffer for the two integration window (400ms and 3s) */ |
398 | ebur128->i400.cache[i] = av_calloc(I400_BINS, sizeof(*ebur128->i400.cache[0])); |
399 | ebur128->i3000.cache[i] = av_calloc(I3000_BINS, sizeof(*ebur128->i3000.cache[0])); |
400 | if (!ebur128->i400.cache[i] || !ebur128->i3000.cache[i]) |
401 | return AVERROR(ENOMEM); |
402 | } |
403 | |
404 | #if CONFIG_SWRESAMPLE |
405 | if (ebur128->peak_mode & PEAK_MODE_TRUE_PEAKS) { |
406 | int ret; |
407 | |
408 | ebur128->swr_buf = av_malloc_array(nb_channels, 19200 * sizeof(double)); |
409 | ebur128->true_peaks = av_calloc(nb_channels, sizeof(*ebur128->true_peaks)); |
410 | ebur128->true_peaks_per_frame = av_calloc(nb_channels, sizeof(*ebur128->true_peaks_per_frame)); |
411 | ebur128->swr_ctx = swr_alloc(); |
412 | if (!ebur128->swr_buf || !ebur128->true_peaks || |
413 | !ebur128->true_peaks_per_frame || !ebur128->swr_ctx) |
414 | return AVERROR(ENOMEM); |
415 | |
416 | av_opt_set_int(ebur128->swr_ctx, "in_channel_layout", outlink->channel_layout, 0); |
417 | av_opt_set_int(ebur128->swr_ctx, "in_sample_rate", outlink->sample_rate, 0); |
418 | av_opt_set_sample_fmt(ebur128->swr_ctx, "in_sample_fmt", outlink->format, 0); |
419 | |
420 | av_opt_set_int(ebur128->swr_ctx, "out_channel_layout", outlink->channel_layout, 0); |
421 | av_opt_set_int(ebur128->swr_ctx, "out_sample_rate", 192000, 0); |
422 | av_opt_set_sample_fmt(ebur128->swr_ctx, "out_sample_fmt", outlink->format, 0); |
423 | |
424 | ret = swr_init(ebur128->swr_ctx); |
425 | if (ret < 0) |
426 | return ret; |
427 | } |
428 | #endif |
429 | |
430 | if (ebur128->peak_mode & PEAK_MODE_SAMPLES_PEAKS) { |
431 | ebur128->sample_peaks = av_calloc(nb_channels, sizeof(*ebur128->sample_peaks)); |
432 | if (!ebur128->sample_peaks) |
433 | return AVERROR(ENOMEM); |
434 | } |
435 | |
436 | return 0; |
437 | } |
438 | |
439 | #define ENERGY(loudness) (ff_exp10(((loudness) + 0.691) / 10.)) |
440 | #define LOUDNESS(energy) (-0.691 + 10 * log10(energy)) |
441 | #define DBFS(energy) (20 * log10(energy)) |
442 | |
443 | static struct hist_entry *get_histogram(void) |
444 | { |
445 | int i; |
446 | struct hist_entry *h = av_calloc(HIST_SIZE, sizeof(*h)); |
447 | |
448 | if (!h) |
449 | return NULL; |
450 | for (i = 0; i < HIST_SIZE; i++) { |
451 | h[i].loudness = i / (double)HIST_GRAIN + ABS_THRES; |
452 | h[i].energy = ENERGY(h[i].loudness); |
453 | } |
454 | return h; |
455 | } |
456 | |
457 | static av_cold int init(AVFilterContext *ctx) |
458 | { |
459 | EBUR128Context *ebur128 = ctx->priv; |
460 | AVFilterPad pad; |
461 | |
462 | if (ebur128->loglevel != AV_LOG_INFO && |
463 | ebur128->loglevel != AV_LOG_VERBOSE) { |
464 | if (ebur128->do_video || ebur128->metadata) |
465 | ebur128->loglevel = AV_LOG_VERBOSE; |
466 | else |
467 | ebur128->loglevel = AV_LOG_INFO; |
468 | } |
469 | |
470 | if (!CONFIG_SWRESAMPLE && (ebur128->peak_mode & PEAK_MODE_TRUE_PEAKS)) { |
471 | av_log(ctx, AV_LOG_ERROR, |
472 | "True-peak mode requires libswresample to be performed\n"); |
473 | return AVERROR(EINVAL); |
474 | } |
475 | |
476 | // if meter is +9 scale, scale range is from -18 LU to +9 LU (or 3*9) |
477 | // if meter is +18 scale, scale range is from -36 LU to +18 LU (or 3*18) |
478 | ebur128->scale_range = 3 * ebur128->meter; |
479 | |
480 | ebur128->i400.histogram = get_histogram(); |
481 | ebur128->i3000.histogram = get_histogram(); |
482 | if (!ebur128->i400.histogram || !ebur128->i3000.histogram) |
483 | return AVERROR(ENOMEM); |
484 | |
485 | ebur128->integrated_loudness = ABS_THRES; |
486 | ebur128->loudness_range = 0; |
487 | |
488 | /* insert output pads */ |
489 | if (ebur128->do_video) { |
490 | pad = (AVFilterPad){ |
491 | .name = av_strdup("out0"), |
492 | .type = AVMEDIA_TYPE_VIDEO, |
493 | .config_props = config_video_output, |
494 | }; |
495 | if (!pad.name) |
496 | return AVERROR(ENOMEM); |
497 | ff_insert_outpad(ctx, 0, &pad); |
498 | } |
499 | pad = (AVFilterPad){ |
500 | .name = av_asprintf("out%d", ebur128->do_video), |
501 | .type = AVMEDIA_TYPE_AUDIO, |
502 | .config_props = config_audio_output, |
503 | }; |
504 | if (!pad.name) |
505 | return AVERROR(ENOMEM); |
506 | ff_insert_outpad(ctx, ebur128->do_video, &pad); |
507 | |
508 | /* summary */ |
509 | av_log(ctx, AV_LOG_VERBOSE, "EBU +%d scale\n", ebur128->meter); |
510 | |
511 | return 0; |
512 | } |
513 | |
514 | #define HIST_POS(power) (int)(((power) - ABS_THRES) * HIST_GRAIN) |
515 | |
516 | /* loudness and power should be set such as loudness = -0.691 + |
517 | * 10*log10(power), we just avoid doing that calculus two times */ |
518 | static int gate_update(struct integrator *integ, double power, |
519 | double loudness, int gate_thres) |
520 | { |
521 | int ipower; |
522 | double relative_threshold; |
523 | int gate_hist_pos; |
524 | |
525 | /* update powers histograms by incrementing current power count */ |
526 | ipower = av_clip(HIST_POS(loudness), 0, HIST_SIZE - 1); |
527 | integ->histogram[ipower].count++; |
528 | |
529 | /* compute relative threshold and get its position in the histogram */ |
530 | integ->sum_kept_powers += power; |
531 | integ->nb_kept_powers++; |
532 | relative_threshold = integ->sum_kept_powers / integ->nb_kept_powers; |
533 | if (!relative_threshold) |
534 | relative_threshold = 1e-12; |
535 | integ->rel_threshold = LOUDNESS(relative_threshold) + gate_thres; |
536 | gate_hist_pos = av_clip(HIST_POS(integ->rel_threshold), 0, HIST_SIZE - 1); |
537 | |
538 | return gate_hist_pos; |
539 | } |
540 | |
541 | static int filter_frame(AVFilterLink *inlink, AVFrame *insamples) |
542 | { |
543 | int i, ch, idx_insample; |
544 | AVFilterContext *ctx = inlink->dst; |
545 | EBUR128Context *ebur128 = ctx->priv; |
546 | const int nb_channels = ebur128->nb_channels; |
547 | const int nb_samples = insamples->nb_samples; |
548 | const double *samples = (double *)insamples->data[0]; |
549 | AVFrame *pic = ebur128->outpicref; |
550 | |
551 | #if CONFIG_SWRESAMPLE |
552 | if (ebur128->peak_mode & PEAK_MODE_TRUE_PEAKS) { |
553 | const double *swr_samples = ebur128->swr_buf; |
554 | int ret = swr_convert(ebur128->swr_ctx, (uint8_t**)&ebur128->swr_buf, 19200, |
555 | (const uint8_t **)insamples->data, nb_samples); |
556 | if (ret < 0) |
557 | return ret; |
558 | for (ch = 0; ch < nb_channels; ch++) |
559 | ebur128->true_peaks_per_frame[ch] = 0.0; |
560 | for (idx_insample = 0; idx_insample < ret; idx_insample++) { |
561 | for (ch = 0; ch < nb_channels; ch++) { |
562 | ebur128->true_peaks[ch] = FFMAX(ebur128->true_peaks[ch], fabs(*swr_samples)); |
563 | ebur128->true_peaks_per_frame[ch] = FFMAX(ebur128->true_peaks_per_frame[ch], |
564 | fabs(*swr_samples)); |
565 | swr_samples++; |
566 | } |
567 | } |
568 | } |
569 | #endif |
570 | |
571 | for (idx_insample = 0; idx_insample < nb_samples; idx_insample++) { |
572 | const int bin_id_400 = ebur128->i400.cache_pos; |
573 | const int bin_id_3000 = ebur128->i3000.cache_pos; |
574 | |
575 | #define MOVE_TO_NEXT_CACHED_ENTRY(time) do { \ |
576 | ebur128->i##time.cache_pos++; \ |
577 | if (ebur128->i##time.cache_pos == I##time##_BINS) { \ |
578 | ebur128->i##time.filled = 1; \ |
579 | ebur128->i##time.cache_pos = 0; \ |
580 | } \ |
581 | } while (0) |
582 | |
583 | MOVE_TO_NEXT_CACHED_ENTRY(400); |
584 | MOVE_TO_NEXT_CACHED_ENTRY(3000); |
585 | |
586 | for (ch = 0; ch < nb_channels; ch++) { |
587 | double bin; |
588 | |
589 | if (ebur128->peak_mode & PEAK_MODE_SAMPLES_PEAKS) |
590 | ebur128->sample_peaks[ch] = FFMAX(ebur128->sample_peaks[ch], fabs(*samples)); |
591 | |
592 | ebur128->x[ch * 3] = *samples++; // set X[i] |
593 | |
594 | if (!ebur128->ch_weighting[ch]) |
595 | continue; |
596 | |
597 | /* Y[i] = X[i]*b0 + X[i-1]*b1 + X[i-2]*b2 - Y[i-1]*a1 - Y[i-2]*a2 */ |
598 | #define FILTER(Y, X, name) do { \ |
599 | double *dst = ebur128->Y + ch*3; \ |
600 | double *src = ebur128->X + ch*3; \ |
601 | dst[2] = dst[1]; \ |
602 | dst[1] = dst[0]; \ |
603 | dst[0] = src[0]*name##_B0 + src[1]*name##_B1 + src[2]*name##_B2 \ |
604 | - dst[1]*name##_A1 - dst[2]*name##_A2; \ |
605 | } while (0) |
606 | |
607 | // TODO: merge both filters in one? |
608 | FILTER(y, x, PRE); // apply pre-filter |
609 | ebur128->x[ch * 3 + 2] = ebur128->x[ch * 3 + 1]; |
610 | ebur128->x[ch * 3 + 1] = ebur128->x[ch * 3 ]; |
611 | FILTER(z, y, RLB); // apply RLB-filter |
612 | |
613 | bin = ebur128->z[ch * 3] * ebur128->z[ch * 3]; |
614 | |
615 | /* add the new value, and limit the sum to the cache size (400ms or 3s) |
616 | * by removing the oldest one */ |
617 | ebur128->i400.sum [ch] = ebur128->i400.sum [ch] + bin - ebur128->i400.cache [ch][bin_id_400]; |
618 | ebur128->i3000.sum[ch] = ebur128->i3000.sum[ch] + bin - ebur128->i3000.cache[ch][bin_id_3000]; |
619 | |
620 | /* override old cache entry with the new value */ |
621 | ebur128->i400.cache [ch][bin_id_400 ] = bin; |
622 | ebur128->i3000.cache[ch][bin_id_3000] = bin; |
623 | } |
624 | |
625 | /* For integrated loudness, gating blocks are 400ms long with 75% |
626 | * overlap (see BS.1770-2 p5), so a re-computation is needed each 100ms |
627 | * (4800 samples at 48kHz). */ |
628 | if (++ebur128->sample_count == 4800) { |
629 | double loudness_400, loudness_3000; |
630 | double power_400 = 1e-12, power_3000 = 1e-12; |
631 | AVFilterLink *outlink = ctx->outputs[0]; |
632 | const int64_t pts = insamples->pts + |
633 | av_rescale_q(idx_insample, (AVRational){ 1, inlink->sample_rate }, |
634 | outlink->time_base); |
635 | |
636 | ebur128->sample_count = 0; |
637 | |
638 | #define COMPUTE_LOUDNESS(m, time) do { \ |
639 | if (ebur128->i##time.filled) { \ |
640 | /* weighting sum of the last <time> ms */ \ |
641 | for (ch = 0; ch < nb_channels; ch++) \ |
642 | power_##time += ebur128->ch_weighting[ch] * ebur128->i##time.sum[ch]; \ |
643 | power_##time /= I##time##_BINS; \ |
644 | } \ |
645 | loudness_##time = LOUDNESS(power_##time); \ |
646 | } while (0) |
647 | |
648 | COMPUTE_LOUDNESS(M, 400); |
649 | COMPUTE_LOUDNESS(S, 3000); |
650 | |
651 | /* Integrated loudness */ |
652 | #define I_GATE_THRES -10 // initially defined to -8 LU in the first EBU standard |
653 | |
654 | if (loudness_400 >= ABS_THRES) { |
655 | double integrated_sum = 0; |
656 | int nb_integrated = 0; |
657 | int gate_hist_pos = gate_update(&ebur128->i400, power_400, |
658 | loudness_400, I_GATE_THRES); |
659 | |
660 | /* compute integrated loudness by summing the histogram values |
661 | * above the relative threshold */ |
662 | for (i = gate_hist_pos; i < HIST_SIZE; i++) { |
663 | const int nb_v = ebur128->i400.histogram[i].count; |
664 | nb_integrated += nb_v; |
665 | integrated_sum += nb_v * ebur128->i400.histogram[i].energy; |
666 | } |
667 | if (nb_integrated) { |
668 | ebur128->integrated_loudness = LOUDNESS(integrated_sum / nb_integrated); |
669 | /* dual-mono correction */ |
670 | if (nb_channels == 1 && ebur128->dual_mono) { |
671 | ebur128->integrated_loudness -= ebur128->pan_law; |
672 | } |
673 | } |
674 | } |
675 | |
676 | /* LRA */ |
677 | #define LRA_GATE_THRES -20 |
678 | #define LRA_LOWER_PRC 10 |
679 | #define LRA_HIGHER_PRC 95 |
680 | |
681 | /* XXX: example code in EBU 3342 is ">=" but formula in BS.1770 |
682 | * specs is ">" */ |
683 | if (loudness_3000 >= ABS_THRES) { |
684 | int nb_powers = 0; |
685 | int gate_hist_pos = gate_update(&ebur128->i3000, power_3000, |
686 | loudness_3000, LRA_GATE_THRES); |
687 | |
688 | for (i = gate_hist_pos; i < HIST_SIZE; i++) |
689 | nb_powers += ebur128->i3000.histogram[i].count; |
690 | if (nb_powers) { |
691 | int n, nb_pow; |
692 | |
693 | /* get lower loudness to consider */ |
694 | n = 0; |
695 | nb_pow = LRA_LOWER_PRC * nb_powers / 100. + 0.5; |
696 | for (i = gate_hist_pos; i < HIST_SIZE; i++) { |
697 | n += ebur128->i3000.histogram[i].count; |
698 | if (n >= nb_pow) { |
699 | ebur128->lra_low = ebur128->i3000.histogram[i].loudness; |
700 | break; |
701 | } |
702 | } |
703 | |
704 | /* get higher loudness to consider */ |
705 | n = nb_powers; |
706 | nb_pow = LRA_HIGHER_PRC * nb_powers / 100. + 0.5; |
707 | for (i = HIST_SIZE - 1; i >= 0; i--) { |
708 | n -= ebur128->i3000.histogram[i].count; |
709 | if (n < nb_pow) { |
710 | ebur128->lra_high = ebur128->i3000.histogram[i].loudness; |
711 | break; |
712 | } |
713 | } |
714 | |
715 | // XXX: show low & high on the graph? |
716 | ebur128->loudness_range = ebur128->lra_high - ebur128->lra_low; |
717 | } |
718 | } |
719 | |
720 | /* dual-mono correction */ |
721 | if (nb_channels == 1 && ebur128->dual_mono) { |
722 | loudness_400 -= ebur128->pan_law; |
723 | loudness_3000 -= ebur128->pan_law; |
724 | } |
725 | |
726 | #define LOG_FMT "M:%6.1f S:%6.1f I:%6.1f LUFS LRA:%6.1f LU" |
727 | |
728 | /* push one video frame */ |
729 | if (ebur128->do_video) { |
730 | int x, y, ret; |
731 | uint8_t *p; |
732 | |
733 | const int y_loudness_lu_graph = lu_to_y(ebur128, loudness_3000 + 23); |
734 | const int y_loudness_lu_gauge = lu_to_y(ebur128, loudness_400 + 23); |
735 | |
736 | /* draw the graph using the short-term loudness */ |
737 | p = pic->data[0] + ebur128->graph.y*pic->linesize[0] + ebur128->graph.x*3; |
738 | for (y = 0; y < ebur128->graph.h; y++) { |
739 | const uint8_t *c = get_graph_color(ebur128, y_loudness_lu_graph, y); |
740 | |
741 | memmove(p, p + 3, (ebur128->graph.w - 1) * 3); |
742 | memcpy(p + (ebur128->graph.w - 1) * 3, c, 3); |
743 | p += pic->linesize[0]; |
744 | } |
745 | |
746 | /* draw the gauge using the momentary loudness */ |
747 | p = pic->data[0] + ebur128->gauge.y*pic->linesize[0] + ebur128->gauge.x*3; |
748 | for (y = 0; y < ebur128->gauge.h; y++) { |
749 | const uint8_t *c = get_graph_color(ebur128, y_loudness_lu_gauge, y); |
750 | |
751 | for (x = 0; x < ebur128->gauge.w; x++) |
752 | memcpy(p + x*3, c, 3); |
753 | p += pic->linesize[0]; |
754 | } |
755 | |
756 | /* draw textual info */ |
757 | drawtext(pic, PAD, PAD - PAD/2, FONT16, font_colors, |
758 | LOG_FMT " ", // padding to erase trailing characters |
759 | loudness_400, loudness_3000, |
760 | ebur128->integrated_loudness, ebur128->loudness_range); |
761 | |
762 | /* set pts and push frame */ |
763 | pic->pts = pts; |
764 | ret = ff_filter_frame(outlink, av_frame_clone(pic)); |
765 | if (ret < 0) |
766 | return ret; |
767 | } |
768 | |
769 | if (ebur128->metadata) { /* happens only once per filter_frame call */ |
770 | char metabuf[128]; |
771 | #define META_PREFIX "lavfi.r128." |
772 | |
773 | #define SET_META(name, var) do { \ |
774 | snprintf(metabuf, sizeof(metabuf), "%.3f", var); \ |
775 | av_dict_set(&insamples->metadata, name, metabuf, 0); \ |
776 | } while (0) |
777 | |
778 | #define SET_META_PEAK(name, ptype) do { \ |
779 | if (ebur128->peak_mode & PEAK_MODE_ ## ptype ## _PEAKS) { \ |
780 | char key[64]; \ |
781 | for (ch = 0; ch < nb_channels; ch++) { \ |
782 | snprintf(key, sizeof(key), \ |
783 | META_PREFIX AV_STRINGIFY(name) "_peaks_ch%d", ch); \ |
784 | SET_META(key, ebur128->name##_peaks[ch]); \ |
785 | } \ |
786 | } \ |
787 | } while (0) |
788 | |
789 | SET_META(META_PREFIX "M", loudness_400); |
790 | SET_META(META_PREFIX "S", loudness_3000); |
791 | SET_META(META_PREFIX "I", ebur128->integrated_loudness); |
792 | SET_META(META_PREFIX "LRA", ebur128->loudness_range); |
793 | SET_META(META_PREFIX "LRA.low", ebur128->lra_low); |
794 | SET_META(META_PREFIX "LRA.high", ebur128->lra_high); |
795 | |
796 | SET_META_PEAK(sample, SAMPLES); |
797 | SET_META_PEAK(true, TRUE); |
798 | } |
799 | |
800 | av_log(ctx, ebur128->loglevel, "t: %-10s " LOG_FMT, |
801 | av_ts2timestr(pts, &outlink->time_base), |
802 | loudness_400, loudness_3000, |
803 | ebur128->integrated_loudness, ebur128->loudness_range); |
804 | |
805 | #define PRINT_PEAKS(str, sp, ptype) do { \ |
806 | if (ebur128->peak_mode & PEAK_MODE_ ## ptype ## _PEAKS) { \ |
807 | av_log(ctx, ebur128->loglevel, " " str ":"); \ |
808 | for (ch = 0; ch < nb_channels; ch++) \ |
809 | av_log(ctx, ebur128->loglevel, " %5.1f", DBFS(sp[ch])); \ |
810 | av_log(ctx, ebur128->loglevel, " dBFS"); \ |
811 | } \ |
812 | } while (0) |
813 | |
814 | PRINT_PEAKS("SPK", ebur128->sample_peaks, SAMPLES); |
815 | PRINT_PEAKS("FTPK", ebur128->true_peaks_per_frame, TRUE); |
816 | PRINT_PEAKS("TPK", ebur128->true_peaks, TRUE); |
817 | av_log(ctx, ebur128->loglevel, "\n"); |
818 | } |
819 | } |
820 | |
821 | return ff_filter_frame(ctx->outputs[ebur128->do_video], insamples); |
822 | } |
823 | |
824 | static int query_formats(AVFilterContext *ctx) |
825 | { |
826 | EBUR128Context *ebur128 = ctx->priv; |
827 | AVFilterFormats *formats; |
828 | AVFilterChannelLayouts *layouts; |
829 | AVFilterLink *inlink = ctx->inputs[0]; |
830 | AVFilterLink *outlink = ctx->outputs[0]; |
831 | int ret; |
832 | |
833 | static const enum AVSampleFormat sample_fmts[] = { AV_SAMPLE_FMT_DBL, AV_SAMPLE_FMT_NONE }; |
834 | static const int input_srate[] = {48000, -1}; // ITU-R BS.1770 provides coeff only for 48kHz |
835 | static const enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_RGB24, AV_PIX_FMT_NONE }; |
836 | |
837 | /* set optional output video format */ |
838 | if (ebur128->do_video) { |
839 | formats = ff_make_format_list(pix_fmts); |
840 | if ((ret = ff_formats_ref(formats, &outlink->in_formats)) < 0) |
841 | return ret; |
842 | outlink = ctx->outputs[1]; |
843 | } |
844 | |
845 | /* set input and output audio formats |
846 | * Note: ff_set_common_* functions are not used because they affect all the |
847 | * links, and thus break the video format negotiation */ |
848 | formats = ff_make_format_list(sample_fmts); |
849 | if ((ret = ff_formats_ref(formats, &inlink->out_formats)) < 0 || |
850 | (ret = ff_formats_ref(formats, &outlink->in_formats)) < 0) |
851 | return ret; |
852 | |
853 | layouts = ff_all_channel_layouts(); |
854 | if ((ret = ff_channel_layouts_ref(layouts, &inlink->out_channel_layouts)) < 0 || |
855 | (ret = ff_channel_layouts_ref(layouts, &outlink->in_channel_layouts)) < 0) |
856 | return ret; |
857 | |
858 | formats = ff_make_format_list(input_srate); |
859 | if ((ret = ff_formats_ref(formats, &inlink->out_samplerates)) < 0 || |
860 | (ret = ff_formats_ref(formats, &outlink->in_samplerates)) < 0) |
861 | return ret; |
862 | |
863 | return 0; |
864 | } |
865 | |
866 | static av_cold void uninit(AVFilterContext *ctx) |
867 | { |
868 | int i; |
869 | EBUR128Context *ebur128 = ctx->priv; |
870 | |
871 | /* dual-mono correction */ |
872 | if (ebur128->nb_channels == 1 && ebur128->dual_mono) { |
873 | ebur128->i400.rel_threshold -= ebur128->pan_law; |
874 | ebur128->i3000.rel_threshold -= ebur128->pan_law; |
875 | ebur128->lra_low -= ebur128->pan_law; |
876 | ebur128->lra_high -= ebur128->pan_law; |
877 | } |
878 | |
879 | av_log(ctx, AV_LOG_INFO, "Summary:\n\n" |
880 | " Integrated loudness:\n" |
881 | " I: %5.1f LUFS\n" |
882 | " Threshold: %5.1f LUFS\n\n" |
883 | " Loudness range:\n" |
884 | " LRA: %5.1f LU\n" |
885 | " Threshold: %5.1f LUFS\n" |
886 | " LRA low: %5.1f LUFS\n" |
887 | " LRA high: %5.1f LUFS", |
888 | ebur128->integrated_loudness, ebur128->i400.rel_threshold, |
889 | ebur128->loudness_range, ebur128->i3000.rel_threshold, |
890 | ebur128->lra_low, ebur128->lra_high); |
891 | |
892 | #define PRINT_PEAK_SUMMARY(str, sp, ptype) do { \ |
893 | int ch; \ |
894 | double maxpeak; \ |
895 | maxpeak = 0.0; \ |
896 | if (ebur128->peak_mode & PEAK_MODE_ ## ptype ## _PEAKS) { \ |
897 | for (ch = 0; ch < ebur128->nb_channels; ch++) \ |
898 | maxpeak = FFMAX(maxpeak, sp[ch]); \ |
899 | av_log(ctx, AV_LOG_INFO, "\n\n " str " peak:\n" \ |
900 | " Peak: %5.1f dBFS", \ |
901 | DBFS(maxpeak)); \ |
902 | } \ |
903 | } while (0) |
904 | |
905 | PRINT_PEAK_SUMMARY("Sample", ebur128->sample_peaks, SAMPLES); |
906 | PRINT_PEAK_SUMMARY("True", ebur128->true_peaks, TRUE); |
907 | av_log(ctx, AV_LOG_INFO, "\n"); |
908 | |
909 | av_freep(&ebur128->y_line_ref); |
910 | av_freep(&ebur128->ch_weighting); |
911 | av_freep(&ebur128->true_peaks); |
912 | av_freep(&ebur128->sample_peaks); |
913 | av_freep(&ebur128->true_peaks_per_frame); |
914 | av_freep(&ebur128->i400.histogram); |
915 | av_freep(&ebur128->i3000.histogram); |
916 | for (i = 0; i < ebur128->nb_channels; i++) { |
917 | av_freep(&ebur128->i400.cache[i]); |
918 | av_freep(&ebur128->i3000.cache[i]); |
919 | } |
920 | for (i = 0; i < ctx->nb_outputs; i++) |
921 | av_freep(&ctx->output_pads[i].name); |
922 | av_frame_free(&ebur128->outpicref); |
923 | #if CONFIG_SWRESAMPLE |
924 | av_freep(&ebur128->swr_buf); |
925 | swr_free(&ebur128->swr_ctx); |
926 | #endif |
927 | } |
928 | |
929 | static const AVFilterPad ebur128_inputs[] = { |
930 | { |
931 | .name = "default", |
932 | .type = AVMEDIA_TYPE_AUDIO, |
933 | .filter_frame = filter_frame, |
934 | .config_props = config_audio_input, |
935 | }, |
936 | { NULL } |
937 | }; |
938 | |
939 | AVFilter ff_af_ebur128 = { |
940 | .name = "ebur128", |
941 | .description = NULL_IF_CONFIG_SMALL("EBU R128 scanner."), |
942 | .priv_size = sizeof(EBUR128Context), |
943 | .init = init, |
944 | .uninit = uninit, |
945 | .query_formats = query_formats, |
946 | .inputs = ebur128_inputs, |
947 | .outputs = NULL, |
948 | .priv_class = &ebur128_class, |
949 | .flags = AVFILTER_FLAG_DYNAMIC_OUTPUTS, |
950 | }; |
951 |