| @@ -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 | +}; |