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