X-Git-Url: https://git.sesse.net/?a=blobdiff_plain;f=libavfilter%2Ff_ebur128.c;h=a9a7820db84a2cff6ba521ae69d4ae7bab25ef69;hb=3a3e8c35b63a40c4d59161097dc8652c15d13779;hp=1e8b90fa2fadb836e81b5e9c5b45b3b8854e1ae0;hpb=03210fe138f3b3bd7f5272fe29aca810cf517329;p=ffmpeg diff --git a/libavfilter/f_ebur128.c b/libavfilter/f_ebur128.c index 1e8b90fa2fa..a9a7820db84 100644 --- a/libavfilter/f_ebur128.c +++ b/libavfilter/f_ebur128.c @@ -114,6 +114,8 @@ typedef struct EBUR128Context { int meter; ///< select a EBU mode between +9 and +18 int scale_range; ///< the range of LU values according to the meter int y_zero_lu; ///< the y value (pixel position) for 0 LU + int y_opt_max; ///< the y value (pixel position) for 1 LU + int y_opt_min; ///< the y value (pixel position) for -1 LU int *y_line_ref; ///< y reference values for drawing the LU lines in the graph and the gauge /* audio */ @@ -142,6 +144,9 @@ typedef struct EBUR128Context { int metadata; ///< whether or not to inject loudness results in frames int dual_mono; ///< whether or not to treat single channel input files as dual-mono double pan_law; ///< pan law value used to calculate dual-mono measurements + int target; ///< target level in LUFS used to set relative zero LU in visualization + int gauge_type; ///< whether gauge shows momentary or short + int scale; ///< display scale type of statistics } EBUR128Context; enum { @@ -150,6 +155,16 @@ enum { PEAK_MODE_TRUE_PEAKS = 1<<2, }; +enum { + GAUGE_TYPE_MOMENTARY = 0, + GAUGE_TYPE_SHORTTERM = 1, +}; + +enum { + SCALE_TYPE_ABSOLUTE = 0, + SCALE_TYPE_RELATIVE = 1, +}; + #define OFFSET(x) offsetof(EBUR128Context, x) #define A AV_OPT_FLAG_AUDIO_PARAM #define V AV_OPT_FLAG_VIDEO_PARAM @@ -168,28 +183,48 @@ static const AVOption ebur128_options[] = { { "true", "enable true-peak mode", 0, AV_OPT_TYPE_CONST, {.i64 = PEAK_MODE_TRUE_PEAKS}, INT_MIN, INT_MAX, A|F, "mode" }, { "dualmono", "treat mono input files as dual-mono", OFFSET(dual_mono), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, A|F }, { "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 }, + { "target", "set a specific target level in LUFS (-23 to 0)", OFFSET(target), AV_OPT_TYPE_INT, {.i64 = -23}, -23, 0, V|F }, + { "gauge", "set gauge display type", OFFSET(gauge_type), AV_OPT_TYPE_INT, {.i64 = 0 }, GAUGE_TYPE_MOMENTARY, GAUGE_TYPE_SHORTTERM, V|F, "gaugetype" }, + { "momentary", "display momentary value", 0, AV_OPT_TYPE_CONST, {.i64 = GAUGE_TYPE_MOMENTARY}, INT_MIN, INT_MAX, V|F, "gaugetype" }, + { "m", "display momentary value", 0, AV_OPT_TYPE_CONST, {.i64 = GAUGE_TYPE_MOMENTARY}, INT_MIN, INT_MAX, V|F, "gaugetype" }, + { "shortterm", "display short-term value", 0, AV_OPT_TYPE_CONST, {.i64 = GAUGE_TYPE_SHORTTERM}, INT_MIN, INT_MAX, V|F, "gaugetype" }, + { "s", "display short-term value", 0, AV_OPT_TYPE_CONST, {.i64 = GAUGE_TYPE_SHORTTERM}, INT_MIN, INT_MAX, V|F, "gaugetype" }, + { "scale", "sets display method for the stats", OFFSET(scale), AV_OPT_TYPE_INT, {.i64 = 0}, SCALE_TYPE_ABSOLUTE, SCALE_TYPE_RELATIVE, V|F, "scaletype" }, + { "absolute", "display absolute values (LUFS)", 0, AV_OPT_TYPE_CONST, {.i64 = SCALE_TYPE_ABSOLUTE}, INT_MIN, INT_MAX, V|F, "scaletype" }, + { "LUFS", "display absolute values (LUFS)", 0, AV_OPT_TYPE_CONST, {.i64 = SCALE_TYPE_ABSOLUTE}, INT_MIN, INT_MAX, V|F, "scaletype" }, + { "relative", "display values relative to target (LU)", 0, AV_OPT_TYPE_CONST, {.i64 = SCALE_TYPE_RELATIVE}, INT_MIN, INT_MAX, V|F, "scaletype" }, + { "LU", "display values relative to target (LU)", 0, AV_OPT_TYPE_CONST, {.i64 = SCALE_TYPE_RELATIVE}, INT_MIN, INT_MAX, V|F, "scaletype" }, { NULL }, }; AVFILTER_DEFINE_CLASS(ebur128); static const uint8_t graph_colors[] = { - 0xdd, 0x66, 0x66, // value above 0LU non reached - 0x66, 0x66, 0xdd, // value below 0LU non reached - 0x96, 0x33, 0x33, // value above 0LU reached - 0x33, 0x33, 0x96, // value below 0LU reached - 0xdd, 0x96, 0x96, // value above 0LU line non reached - 0x96, 0x96, 0xdd, // value below 0LU line non reached - 0xdd, 0x33, 0x33, // value above 0LU line reached - 0x33, 0x33, 0xdd, // value below 0LU line reached + 0xdd, 0x66, 0x66, // value above 1LU non reached below -1LU (impossible) + 0x66, 0x66, 0xdd, // value below 1LU non reached below -1LU + 0x96, 0x33, 0x33, // value above 1LU reached below -1LU (impossible) + 0x33, 0x33, 0x96, // value below 1LU reached below -1LU + 0xdd, 0x96, 0x96, // value above 1LU line non reached below -1LU (impossible) + 0x96, 0x96, 0xdd, // value below 1LU line non reached below -1LU + 0xdd, 0x33, 0x33, // value above 1LU line reached below -1LU (impossible) + 0x33, 0x33, 0xdd, // value below 1LU line reached below -1LU + 0xdd, 0x66, 0x66, // value above 1LU non reached above -1LU + 0x66, 0xdd, 0x66, // value below 1LU non reached above -1LU + 0x96, 0x33, 0x33, // value above 1LU reached above -1LU + 0x33, 0x96, 0x33, // value below 1LU reached above -1LU + 0xdd, 0x96, 0x96, // value above 1LU line non reached above -1LU + 0x96, 0xdd, 0x96, // value below 1LU line non reached above -1LU + 0xdd, 0x33, 0x33, // value above 1LU line reached above -1LU + 0x33, 0xdd, 0x33, // value below 1LU line reached above -1LU }; static const uint8_t *get_graph_color(const EBUR128Context *ebur128, int v, int y) { - const int below0 = y > ebur128->y_zero_lu; + const int above_opt_max = y > ebur128->y_opt_max; + const int below_opt_min = y < ebur128->y_opt_min; const int reached = y >= v; const int line = ebur128->y_line_ref[y] || y == ebur128->y_zero_lu; - const int colorid = 4*line + 2*reached + below0; + const int colorid = 8*below_opt_min+ 4*line + 2*reached + above_opt_max; return graph_colors + 3*colorid; } @@ -323,6 +358,8 @@ static int config_video_output(AVFilterLink *outlink) /* draw graph */ ebur128->y_zero_lu = lu_to_y(ebur128, 0); + ebur128->y_opt_max = lu_to_y(ebur128, 1); + ebur128->y_opt_min = lu_to_y(ebur128, -1); p = outpicref->data[0] + ebur128->graph.y * outpicref->linesize[0] + ebur128->graph.x * 3; for (y = 0; y < ebur128->graph.h; y++) { @@ -383,7 +420,7 @@ static int config_audio_output(AVFilterLink *outlink) for (i = 0; i < nb_channels; i++) { /* channel weighting */ - const uint16_t chl = av_channel_layout_extract_channel(outlink->channel_layout, i); + const uint64_t chl = av_channel_layout_extract_channel(outlink->channel_layout, i); if (chl & (AV_CH_LOW_FREQUENCY|AV_CH_LOW_FREQUENCY_2)) { ebur128->ch_weighting[i] = 0; } else if (chl & BACK_MASK) { @@ -459,6 +496,7 @@ static av_cold int init(AVFilterContext *ctx) { EBUR128Context *ebur128 = ctx->priv; AVFilterPad pad; + int ret; if (ebur128->loglevel != AV_LOG_INFO && ebur128->loglevel != AV_LOG_VERBOSE) { @@ -489,22 +527,22 @@ static av_cold int init(AVFilterContext *ctx) /* insert output pads */ if (ebur128->do_video) { pad = (AVFilterPad){ - .name = av_strdup("out0"), + .name = "out0", .type = AVMEDIA_TYPE_VIDEO, .config_props = config_video_output, }; - if (!pad.name) - return AVERROR(ENOMEM); - ff_insert_outpad(ctx, 0, &pad); + ret = ff_insert_outpad(ctx, 0, &pad); + if (ret < 0) + return ret; } pad = (AVFilterPad){ - .name = av_asprintf("out%d", ebur128->do_video), + .name = ebur128->do_video ? "out1" : "out0", .type = AVMEDIA_TYPE_AUDIO, .config_props = config_audio_output, }; - if (!pad.name) - return AVERROR(ENOMEM); - ff_insert_outpad(ctx, ebur128->do_video, &pad); + ret = ff_insert_outpad(ctx, ebur128->do_video, &pad); + if (ret < 0) + return ret; /* summary */ av_log(ctx, AV_LOG_VERBOSE, "EBU +%d scale\n", ebur128->meter); @@ -724,15 +762,24 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *insamples) loudness_3000 -= ebur128->pan_law; } -#define LOG_FMT "M:%6.1f S:%6.1f I:%6.1f LUFS LRA:%6.1f LU" +#define LOG_FMT "TARGET:%d LUFS M:%6.1f S:%6.1f I:%6.1f %s LRA:%6.1f LU" /* push one video frame */ if (ebur128->do_video) { + AVFrame *clone; int x, y, ret; uint8_t *p; + double gauge_value; + int y_loudness_lu_graph, y_loudness_lu_gauge; - const int y_loudness_lu_graph = lu_to_y(ebur128, loudness_3000 + 23); - const int y_loudness_lu_gauge = lu_to_y(ebur128, loudness_400 + 23); + if (ebur128->gauge_type == GAUGE_TYPE_MOMENTARY) { + gauge_value = loudness_400 - ebur128->target; + } else { + gauge_value = loudness_3000 - ebur128->target; + } + + y_loudness_lu_graph = lu_to_y(ebur128, loudness_3000 - ebur128->target); + y_loudness_lu_gauge = lu_to_y(ebur128, gauge_value); /* draw the graph using the short-term loudness */ p = pic->data[0] + ebur128->graph.y*pic->linesize[0] + ebur128->graph.x*3; @@ -744,7 +791,7 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *insamples) p += pic->linesize[0]; } - /* draw the gauge using the momentary loudness */ + /* draw the gauge using either momentary or short-term loudness */ p = pic->data[0] + ebur128->gauge.y*pic->linesize[0] + ebur128->gauge.x*3; for (y = 0; y < ebur128->gauge.h; y++) { const uint8_t *c = get_graph_color(ebur128, y_loudness_lu_gauge, y); @@ -755,14 +802,24 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *insamples) } /* draw textual info */ - drawtext(pic, PAD, PAD - PAD/2, FONT16, font_colors, - LOG_FMT " ", // padding to erase trailing characters - loudness_400, loudness_3000, - ebur128->integrated_loudness, ebur128->loudness_range); + if (ebur128->scale == SCALE_TYPE_ABSOLUTE) { + drawtext(pic, PAD, PAD - PAD/2, FONT16, font_colors, + LOG_FMT " ", // padding to erase trailing characters + ebur128->target, loudness_400, loudness_3000, + ebur128->integrated_loudness, "LUFS", ebur128->loudness_range); + } else { + drawtext(pic, PAD, PAD - PAD/2, FONT16, font_colors, + LOG_FMT " ", // padding to erase trailing characters + ebur128->target, loudness_400-ebur128->target, loudness_3000-ebur128->target, + ebur128->integrated_loudness-ebur128->target, "LU", ebur128->loudness_range); + } /* set pts and push frame */ pic->pts = pts; - ret = ff_filter_frame(outlink, av_frame_clone(pic)); + clone = av_frame_clone(pic); + if (!clone) + return AVERROR(ENOMEM); + ret = ff_filter_frame(outlink, clone); if (ret < 0) return ret; } @@ -798,10 +855,17 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *insamples) SET_META_PEAK(true, TRUE); } - av_log(ctx, ebur128->loglevel, "t: %-10s " LOG_FMT, - av_ts2timestr(pts, &outlink->time_base), - loudness_400, loudness_3000, - ebur128->integrated_loudness, ebur128->loudness_range); + if (ebur128->scale == SCALE_TYPE_ABSOLUTE) { + av_log(ctx, ebur128->loglevel, "t: %-10s " LOG_FMT, + av_ts2timestr(pts, &outlink->time_base), + ebur128->target, loudness_400, loudness_3000, + ebur128->integrated_loudness, "LUFS", ebur128->loudness_range); + } else { + av_log(ctx, ebur128->loglevel, "t: %-10s " LOG_FMT, + av_ts2timestr(pts, &outlink->time_base), + ebur128->target, loudness_400-ebur128->target, loudness_3000-ebur128->target, + ebur128->integrated_loudness-ebur128->target, "LU", ebur128->loudness_range); + } #define PRINT_PEAKS(str, sp, ptype) do { \ if (ebur128->peak_mode & PEAK_MODE_ ## ptype ## _PEAKS) { \ @@ -838,7 +902,7 @@ static int query_formats(AVFilterContext *ctx) /* set optional output video format */ if (ebur128->do_video) { formats = ff_make_format_list(pix_fmts); - if ((ret = ff_formats_ref(formats, &outlink->in_formats)) < 0) + if ((ret = ff_formats_ref(formats, &outlink->incfg.formats)) < 0) return ret; outlink = ctx->outputs[1]; } @@ -847,18 +911,18 @@ static int query_formats(AVFilterContext *ctx) * Note: ff_set_common_* functions are not used because they affect all the * links, and thus break the video format negotiation */ formats = ff_make_format_list(sample_fmts); - if ((ret = ff_formats_ref(formats, &inlink->out_formats)) < 0 || - (ret = ff_formats_ref(formats, &outlink->in_formats)) < 0) + if ((ret = ff_formats_ref(formats, &inlink->outcfg.formats)) < 0 || + (ret = ff_formats_ref(formats, &outlink->incfg.formats)) < 0) return ret; layouts = ff_all_channel_layouts(); - if ((ret = ff_channel_layouts_ref(layouts, &inlink->out_channel_layouts)) < 0 || - (ret = ff_channel_layouts_ref(layouts, &outlink->in_channel_layouts)) < 0) + if ((ret = ff_channel_layouts_ref(layouts, &inlink->outcfg.channel_layouts)) < 0 || + (ret = ff_channel_layouts_ref(layouts, &outlink->incfg.channel_layouts)) < 0) return ret; formats = ff_make_format_list(input_srate); - if ((ret = ff_formats_ref(formats, &inlink->out_samplerates)) < 0 || - (ret = ff_formats_ref(formats, &outlink->in_samplerates)) < 0) + if ((ret = ff_formats_ref(formats, &inlink->outcfg.samplerates)) < 0 || + (ret = ff_formats_ref(formats, &outlink->incfg.samplerates)) < 0) return ret; return 0; @@ -918,8 +982,6 @@ static av_cold void uninit(AVFilterContext *ctx) av_freep(&ebur128->i400.cache[i]); av_freep(&ebur128->i3000.cache[i]); } - for (i = 0; i < ctx->nb_outputs; i++) - av_freep(&ctx->output_pads[i].name); av_frame_free(&ebur128->outpicref); #if CONFIG_SWRESAMPLE av_freep(&ebur128->swr_buf); @@ -937,7 +999,7 @@ static const AVFilterPad ebur128_inputs[] = { { NULL } }; -AVFilter ff_af_ebur128 = { +const AVFilter ff_af_ebur128 = { .name = "ebur128", .description = NULL_IF_CONFIG_SMALL("EBU R128 scanner."), .priv_size = sizeof(EBUR128Context),