History

avfilter: add Audio Video Sync Test filter

Paul B Mahol committed 9 years ago

Commit 1047fed1 parent 74439b66 files · +443 −1Browse files

Unified · Split

6 files
  1. Changelog +1 −0
  2. doc/filters.texi +37 −0
  3. libavfilter/Makefile +1 −0
  4. libavfilter/allfilters.c +1 −0
  5. libavfilter/src_avsynctest.c +402 −0
  6. libavfilter/version.h +1 −1
Changelog+1−0
@@ -8,6 +8,7 @@ version 5.1: · 7 unmodified lines
88 - DFPWM audio encoder/decoder and raw muxer/demuxer
99 - SITI filter
1010 - Vizrt Binary Image encoder/decoder
11+- avsynctest source filter
1112
1213
1314 version 5.0:
doc/filters.texi+37−0
@@ -29282,6 +29282,43 @@ Below is a description of the currently available multimedia sources. · 29281 unmodified lines
2928229282 This is the same as @ref{movie} source, except it selects an audio
2928329283 stream by default.
2928429284
29285+@section avsynctest
29286+Generate an Audio/Video Sync Test.
29287+
29288+Generated stream periodically shows flash video frame and emits beep in audio.
29289+Useful to inspect A/V sync issues.
29290+
29291+It accepts the following options:
29292+
29293+@table @option
29294+@item size, s
29295+Set output video size. Default value is @code{hd720}.
29296+
29297+@item framerate, fr
29298+Set output video frame rate. Default value is @code{30}.
29299+
29300+@item samplerate, sr
29301+Set output audio sample rate. Default value is @code{44100}.
29302+
29303+@item amplitude, a
29304+Set output audio beep amplitude. Default value is @code{0.7}.
29305+
29306+@item period, p
29307+Set output audio beep period in seconds. Default value is @code{3}.
29308+
29309+@item delay, dl
29310+Set output video flash delay in number of frames. Default value is @code{0}.
29311+
29312+@item cycle, c
29313+Enable cycling of video delays, by default is disabled.
29314+
29315+@item duration, d
29316+Set stream output duration. By default duration is unlimited.
29317+
29318+@item fg, bg, ag
29319+Set foreground/background/additional color.
29320+@end table
29321+
2928529322 @anchor{movie}
2928629323 @section movie
2928729324
libavfilter/Makefile+1−0
@@ -579,6 +579,7 @@ OBJS-$(CONFIG_SHOWWAVESPIC_FILTER) += avf_showwaves.o · 578 unmodified lines
579579 OBJS-$(CONFIG_SPECTRUMSYNTH_FILTER) += vaf_spectrumsynth.o
580580
581581 # multimedia sources
582+OBJS-$(CONFIG_AVSYNCTEST_FILTER) += src_avsynctest.o
582583 OBJS-$(CONFIG_AMOVIE_FILTER) += src_movie.o
583584 OBJS-$(CONFIG_MOVIE_FILTER) += src_movie.o
584585
libavfilter/allfilters.c+1−0
@@ -550,6 +550,7 @@ extern const AVFilter ff_avf_showwavespic; · 549 unmodified lines
550550 extern const AVFilter ff_vaf_spectrumsynth;
551551
552552 /* multimedia sources */
553+extern const AVFilter ff_avsrc_avsynctest;
553554 extern const AVFilter ff_avsrc_amovie;
554555 extern const AVFilter ff_avsrc_movie;
555556
libavfilter/src_avsynctest.c+402−0
@@ -0,0 +1,402 @@
1+/*
2+ * Copyright (c) 2017-2022 Paul B Mahol
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+#include "libavutil/avassert.h"
22+#include "libavutil/common.h"
23+#include "libavutil/channel_layout.h"
24+#include "libavutil/ffmath.h"
25+#include "libavutil/opt.h"
26+#include "libavutil/imgutils.h"
27+#include "libavutil/intreadwrite.h"
28+#include "libavutil/parseutils.h"
29+#include "libavutil/timestamp.h"
30+#include "libavutil/xga_font_data.h"
31+#include "avfilter.h"
32+#include "drawutils.h"
33+#include "filters.h"
34+#include "formats.h"
35+#include "internal.h"
36+#include "audio.h"
37+#include "video.h"
38+
39+typedef struct AVSyncTestContext {
40+ const AVClass *class;
41+
42+ int w, h;
43+ AVRational frame_rate;
44+ int sample_rate;
45+ int64_t duration;
46+ int64_t apts, vpts;
47+ float amplitude;
48+ int period;
49+ int delay;
50+ int cycle;
51+
52+ int beep;
53+ int beep_duration;
54+ int flash;
55+ int dir;
56+ AVRational vdelay, delay_max, delay_min;
57+ AVRational delay_range;
58+ int64_t prev_intpart;
59+
60+ uint8_t rgba[3][4];
61+ FFDrawContext draw;
62+ FFDrawColor fg;
63+ FFDrawColor bg;
64+ FFDrawColor ag;
65+} AVSyncTestContext;
66+
67+#define OFFSET(x) offsetof(AVSyncTestContext, x)
68+#define A AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
69+#define V AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
70+
71+static const AVOption avsynctest_options[] = {
72+ {"size", "set frame size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str="hd720"}, 0, 0, V },
73+ {"s", "set frame size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str="hd720"}, 0, 0, V },
74+ {"framerate", "set frame rate", OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str="30"}, 0,INT_MAX, V },
75+ {"fr", "set frame rate", OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str="30"}, 0,INT_MAX, V },
76+ {"samplerate", "set sample rate", OFFSET(sample_rate), AV_OPT_TYPE_INT, {.i64=44100},8000,384000, A },
77+ {"sr", "set sample rate", OFFSET(sample_rate), AV_OPT_TYPE_INT, {.i64=44100},8000,384000, A },
78+ {"amplitude", "set beep amplitude", OFFSET(amplitude), AV_OPT_TYPE_FLOAT, {.dbl=.7}, 0., 1., A },
79+ {"a", "set beep amplitude", OFFSET(amplitude), AV_OPT_TYPE_FLOAT, {.dbl=.7}, 0., 1., A },
80+ {"period", "set beep period", OFFSET(period), AV_OPT_TYPE_INT, {.i64=3}, 1, 99., A },
81+ {"p", "set beep period", OFFSET(period), AV_OPT_TYPE_INT, {.i64=3}, 1, 99., A },
82+ {"delay", "set flash delay", OFFSET(delay), AV_OPT_TYPE_INT, {.i64=0}, -30, 30, V },
83+ {"dl", "set flash delay", OFFSET(delay), AV_OPT_TYPE_INT, {.i64=0}, -30, 30, V },
84+ {"cycle", "set delay cycle", OFFSET(cycle), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, V },
85+ {"c", "set delay cycle", OFFSET(cycle), AV_OPT_TYPE_BOOL, {.i64=0}, 0, 1, V },
86+ {"duration", "set duration", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64=0}, 0, INT64_MAX, V|A },
87+ {"d", "set duration", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64=0}, 0, INT64_MAX, V|A },
88+ {"fg", "set foreground color", OFFSET(rgba[0]), AV_OPT_TYPE_COLOR, {.str="white"}, 0, 0, V },
89+ {"bg", "set background color", OFFSET(rgba[1]), AV_OPT_TYPE_COLOR, {.str="black"}, 0, 0, V },
90+ {"ag", "set additional color", OFFSET(rgba[2]), AV_OPT_TYPE_COLOR, {.str="gray"}, 0, 0, V },
91+ {NULL},
92+};
93+
94+AVFILTER_DEFINE_CLASS(avsynctest);
95+
96+static av_cold int query_formats(AVFilterContext *ctx)
97+{
98+ AVSyncTestContext *s = ctx->priv;
99+ AVFilterChannelLayouts *chlayout = NULL;
100+ int sample_rates[] = { s->sample_rate, -1 };
101+ static const enum AVSampleFormat sample_fmts[] = {
102+ AV_SAMPLE_FMT_S32, AV_SAMPLE_FMT_NONE
103+ };
104+ AVFilterFormats *formats;
105+ int ret;
106+
107+ formats = ff_make_format_list(sample_fmts);
108+ if (!formats)
109+ return AVERROR(ENOMEM);
110+ if ((ret = ff_formats_ref(formats, &ctx->outputs[0]->incfg.formats)) < 0)
111+ return ret;
112+
113+ formats = ff_draw_supported_pixel_formats(0);
114+ if (!formats)
115+ return AVERROR(ENOMEM);
116+ if ((ret = ff_formats_ref(formats, &ctx->outputs[1]->incfg.formats)) < 0)
117+ return ret;
118+
119+ if ((ret = ff_add_channel_layout(&chlayout, &(AVChannelLayout)AV_CHANNEL_LAYOUT_MONO)) < 0)
120+ return ret;
121+ ret = ff_set_common_channel_layouts(ctx, chlayout);
122+ if (ret < 0)
123+ return ret;
124+
125+ formats = ff_make_format_list(sample_rates);
126+ if (!formats)
127+ return AVERROR(ENOMEM);
128+ return ff_set_common_samplerates(ctx, formats);
129+}
130+
131+static av_cold int aconfig_props(AVFilterLink *outlink)
132+{
133+ AVFilterContext *ctx = outlink->src;
134+ AVSyncTestContext *s = ctx->priv;
135+
136+ outlink->sample_rate = s->sample_rate;
137+ outlink->time_base = (AVRational){1, s->sample_rate};
138+
139+ s->beep_duration = av_rescale(s->sample_rate, s->frame_rate.den, s->frame_rate.num);
140+ s->duration = av_rescale(s->duration, s->sample_rate, AV_TIME_BASE);
141+
142+ return 0;
143+}
144+
145+static av_cold int config_props(AVFilterLink *outlink)
146+{
147+ AVFilterContext *ctx = outlink->src;
148+ AVSyncTestContext *s = ctx->priv;
149+
150+ outlink->w = s->w;
151+ outlink->h = s->h;
152+ outlink->time_base = av_inv_q(s->frame_rate);
153+ outlink->frame_rate = s->frame_rate;
154+ outlink->sample_aspect_ratio = (AVRational) {1, 1};
155+ s->delay_min = av_mul_q(s->frame_rate, av_make_q(-1, 2));
156+ s->delay_max = av_mul_q(s->delay_min, av_make_q(-1, 1));
157+ s->delay_range = av_sub_q(s->delay_max, s->delay_min);
158+ s->vdelay = av_make_q(s->delay, 1);
159+ s->dir = 1;
160+ s->prev_intpart = INT64_MIN;
161+
162+ ff_draw_init(&s->draw, outlink->format, 0);
163+
164+ ff_draw_color(&s->draw, &s->fg, s->rgba[0]);
165+ ff_draw_color(&s->draw, &s->bg, s->rgba[1]);
166+ ff_draw_color(&s->draw, &s->ag, s->rgba[2] );
167+
168+ return 0;
169+}
170+
171+#define FPI 0x8000
172+
173+static int32_t sin32(int32_t x, int shift)
174+{
175+ const double pi = M_PI;
176+ const int32_t a = ((2.0 * pi) * (1 << 24));
177+ const int32_t b = (1 << 7) * (12.0 / pi - 1.0 - pi) * (1 << 24);
178+ const int32_t c = (1 << 9) * 3.0 * (2.0 + pi - 16.0 / pi) * (1 << 24);
179+ int64_t x2, result;
180+ int32_t t1, t2;
181+
182+ x &= 2 * FPI - 1;
183+
184+ if (x >= (3 * FPI / 2))
185+ x = x - 2 * FPI;
186+ else if (x > FPI / 2)
187+ x = FPI - x;
188+
189+ x2 = x * x;
190+ t1 = (x2 * c) >> 32;
191+ t2 = ((b + t1) * x2) >> 32;
192+ x = x << 8;
193+
194+ result = a + t2;
195+ result *= x;
196+ result += (1U << 31);
197+ result >>= (32 - shift);
198+
199+ return result;
200+}
201+
202+static int audio_frame(AVFilterLink *outlink)
203+{
204+ AVFilterContext *ctx = outlink->src;
205+ AVSyncTestContext *s = ctx->priv;
206+ const int a = lrintf(s->amplitude * 15);
207+ int64_t duration[2];
208+ int64_t delta;
209+ AVFrame *out;
210+ int32_t *dst;
211+
212+ delta = av_rescale_q(s->vpts, av_make_q(s->sample_rate, 1), s->frame_rate) - s->apts;
213+ if (delta < 0)
214+ return 1;
215+
216+ duration[0] = av_rescale_rnd(s->sample_rate, s->frame_rate.den, s->frame_rate.num, AV_ROUND_DOWN);
217+ duration[1] = av_rescale_rnd(s->sample_rate, s->frame_rate.den, s->frame_rate.num, AV_ROUND_UP);
218+
219+ delta = duration[delta > 0];
220+ out = ff_get_audio_buffer(outlink, delta);
221+ if (!out)
222+ return AVERROR(ENOMEM);
223+
224+ out->pts = s->apts;
225+ dst = (int32_t *)out->data[0];
226+
227+ for (int i = 0; i < delta; i++) {
228+ if (((s->apts + i) % (s->period * s->sample_rate)) == 0)
229+ s->beep = 1;
230+ if (s->beep) {
231+ dst[i] = sin32(av_rescale_q(800LL * 2LL * FPI, outlink->time_base, av_make_q(1, s->apts + i)), a);
232+ s->beep++;
233+ } else {
234+ dst[i] = 0;
235+ }
236+ if (s->beep >= s->beep_duration) {
237+ s->beep = 0;
238+ }
239+ }
240+ s->apts += out->nb_samples;
241+
242+ return ff_filter_frame(outlink, out);
243+}
244+
245+static void draw_text(FFDrawContext *draw, AVFrame *out, FFDrawColor *color,
246+ int x0, int y0, const uint8_t *text)
247+{
248+ int x = x0;
249+
250+ for (; *text; text++) {
251+ if (*text == '\n') {
252+ x = x0;
253+ y0 += 8;
254+ continue;
255+ }
256+ ff_blend_mask(draw, color, out->data, out->linesize,
257+ out->width, out->height,
258+ avpriv_cga_font + *text * 8, 1, 8, 8, 0, 0, x, y0);
259+ x += 8;
260+ }
261+}
262+
263+static int offset(int x, int num, int den)
264+{
265+ return av_rescale_rnd(x, num, den, AV_ROUND_UP);
266+}
267+
268+static int video_frame(AVFilterLink *outlink)
269+{
270+ AVFilterContext *ctx = outlink->src;
271+ AVSyncTestContext *s = ctx->priv;
272+ const int w = outlink->w;
273+ const int h = outlink->h;
274+ const int step = av_rescale_rnd(w, s->delay_range.den, s->delay_range.num, AV_ROUND_DOWN);
275+ char text[128];
276+ int new_offset;
277+ int64_t delta, temp, intpart;
278+ AVFrame *out;
279+
280+ delta = av_rescale_q(s->apts, s->frame_rate, av_make_q(s->sample_rate, 1)) - s->vpts;
281+ if (delta < 0)
282+ return 1;
283+
284+ out = ff_get_video_buffer(outlink, w, h);
285+ if (!out)
286+ return AVERROR(ENOMEM);
287+
288+ ff_fill_rectangle(&s->draw, &s->bg, out->data, out->linesize, 0, 0, w, h);
289+
290+ snprintf(text, sizeof(text), "FRN: %"PRId64"", s->vpts);
291+ draw_text(&s->draw, out, &s->fg, offset(w, 1, 10), offset(h, 1, 10), text);
292+
293+ snprintf(text, sizeof(text), "SEC: %s", av_ts2timestr(s->vpts, &outlink->time_base));
294+ draw_text(&s->draw, out, &s->fg, offset(w, 1, 10), offset(h, 9, 10), text);
295+
296+ snprintf(text, sizeof(text), "DLY: %d", s->vdelay.num);
297+ draw_text(&s->draw, out, &s->fg, offset(w, 9, 10) - strlen(text) * 8, offset(h, 9, 10), text);
298+
299+ snprintf(text, sizeof(text), "FPS: %d/%d", s->frame_rate.num, s->frame_rate.den);
300+ draw_text(&s->draw, out, &s->fg, offset(w, 9, 10) - strlen(text) * 8, offset(h, 1, 10), text);
301+
302+ snprintf(text, sizeof(text), "P: %d", s->period);
303+ draw_text(&s->draw, out, &s->ag, offset(w, 1, 2) - strlen(text) * 4, offset(h, 9, 10), text);
304+
305+ snprintf(text, sizeof(text), "SR: %d", s->sample_rate);
306+ draw_text(&s->draw, out, &s->ag, offset(w, 1, 2) - strlen(text) * 4, offset(h, 1, 10), text);
307+
308+ snprintf(text, sizeof(text), "A: %1.2f", s->amplitude);
309+ draw_text(&s->draw, out, &s->ag, offset(w, 1, 10), offset(h, 1, 2), text);
310+
311+ snprintf(text, sizeof(text), "WxH: %dx%d", w, h);
312+ draw_text(&s->draw, out, &s->ag, offset(w, 9, 10) - strlen(text) * 8, offset(h, 1, 2), text);
313+
314+ temp = s->vpts + s->vdelay.num;
315+ intpart = av_rescale_rnd(temp, outlink->time_base.num, outlink->time_base.den, AV_ROUND_NEAR_INF);
316+ intpart = temp - av_rescale_rnd(intpart, outlink->time_base.den, outlink->time_base.num, AV_ROUND_NEAR_INF);
317+
318+ new_offset = offset(w, 1, 2);
319+ ff_fill_rectangle(&s->draw, &s->fg, out->data, out->linesize,
320+ av_clip(new_offset + step * intpart, 0, w - 2),
321+ offset(h, 141, 200), offset(step, 2, 3), offset(h, 1, 25));
322+
323+ if (intpart == 0 && s->prev_intpart != intpart) {
324+ if (s->flash >= s->period) {
325+ int result;
326+
327+ if (s->cycle)
328+ s->vdelay = av_add_q(s->vdelay, av_make_q(s->dir, 1));
329+ result = av_cmp_q(s->vdelay, s->delay_max);
330+ if (result >= 0)
331+ s->dir = -1;
332+ result = av_cmp_q(s->vdelay, s->delay_min);
333+ if (result <= 0)
334+ s->dir = 1;
335+ ff_fill_rectangle(&s->draw, &s->fg, out->data, out->linesize,
336+ offset(w, 1, 3), offset(h, 1, 3), offset(w, 1, 3), offset(h, 1, 4));
337+ s->flash = 0;
338+ }
339+ s->flash++;
340+ }
341+ s->prev_intpart = intpart;
342+
343+ for (int i = av_rescale(s->delay_min.num, 1, s->delay_min.den);
344+ i < av_rescale(s->delay_max.num, 1, s->delay_max.den); i++) {
345+ ff_fill_rectangle(&s->draw, &s->fg, out->data, out->linesize,
346+ av_clip(new_offset + step * i, 0, w - 2),
347+ offset(h, 7, 10), 1, offset(h, 1, 20));
348+ }
349+
350+ out->pts = s->vpts++;
351+
352+ return ff_filter_frame(outlink, out);
353+}
354+
355+static int activate(AVFilterContext *ctx)
356+{
357+ AVSyncTestContext *s = ctx->priv;
358+ AVFilterLink *aoutlink = ctx->outputs[0];
359+ AVFilterLink *voutlink = ctx->outputs[1];
360+ int ret = FFERROR_NOT_READY;
361+
362+ if (!ff_outlink_frame_wanted(aoutlink) &&
363+ !ff_outlink_frame_wanted(voutlink))
364+ return ret;
365+
366+ if (s->duration > 0 && s->apts >= s->duration) {
367+ ff_outlink_set_status(aoutlink, AVERROR_EOF, s->apts);
368+ ff_outlink_set_status(voutlink, AVERROR_EOF, s->vpts);
369+ return 0;
370+ }
371+
372+ ret = audio_frame(aoutlink);
373+ if (ret < 0)
374+ return ret;
375+ ret = video_frame(voutlink);
376+
377+ return ret;
378+}
379+
380+static const AVFilterPad avsynctest_outputs[] = {
381+ {
382+ .name = "audio",
383+ .type = AVMEDIA_TYPE_AUDIO,
384+ .config_props = aconfig_props,
385+ },
386+ {
387+ .name = "video",
388+ .type = AVMEDIA_TYPE_VIDEO,
389+ .config_props = config_props,
390+ },
391+};
392+
393+const AVFilter ff_avsrc_avsynctest = {
394+ .name = "avsynctest",
395+ .description = NULL_IF_CONFIG_SMALL("Generate an Audio Video Sync Test."),
396+ .priv_size = sizeof(AVSyncTestContext),
397+ .priv_class = &avsynctest_class,
398+ .inputs = NULL,
399+ .activate = activate,
400+ FILTER_OUTPUTS(avsynctest_outputs),
401+ FILTER_QUERY_FUNC(query_formats),
402+};
libavfilter/version.h+1−1
@@ -31,7 +31,7 @@ · 30 unmodified lines
3131
3232 #include "version_major.h"
3333
34−#define LIBAVFILTER_VERSION_MINOR 30
34+#define LIBAVFILTER_VERSION_MINOR 31
3535 #define LIBAVFILTER_VERSION_MICRO 100
3636
3737

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t