diff --git a/doc/filters.texi b/doc/filters.texi index 80a1bda322..e49dd9ef0d 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -5914,6 +5914,11 @@ Options are true or false. Default is false. @item print_format Set print format for stats. Options are summary, json, or none. Default value is none. + +@item stats_file +Write stats to specified file. Format is controlled by @option{print_format}, +which must be set. Specify @code{-} to write to standard output. +Default is unset. @end table @section lowpass diff --git a/libavfilter/af_loudnorm.c b/libavfilter/af_loudnorm.c index 2636f2245e..e9cc40baa6 100644 --- a/libavfilter/af_loudnorm.c +++ b/libavfilter/af_loudnorm.c @@ -20,6 +20,8 @@ /* http://k.ylo.ph/2016/04/04/loudnorm.html */ +#include "libavutil/avstring.h" +#include "libavutil/file_open.h" #include "libavutil/mem.h" #include "libavutil/opt.h" #include "avfilter.h" @@ -64,6 +66,7 @@ typedef struct LoudNormContext { int linear; int dual_mono; enum PrintFormat print_format; + char *stats_file_str; double *buf; int buf_size; @@ -121,6 +124,7 @@ static const AVOption loudnorm_options[] = { { "none", 0, 0, AV_OPT_TYPE_CONST, {.i64 = NONE}, 0, 0, FLAGS, .unit = "print_format" }, { "json", 0, 0, AV_OPT_TYPE_CONST, {.i64 = JSON}, 0, 0, FLAGS, .unit = "print_format" }, { "summary", 0, 0, AV_OPT_TYPE_CONST, {.i64 = SUMMARY}, 0, 0, FLAGS, .unit = "print_format" }, + { "stats_file", "set stats output file", OFFSET(stats_file_str), AV_OPT_TYPE_STRING, {.str = NULL}, 0, 0, FLAGS }, { NULL } }; @@ -803,6 +807,11 @@ static av_cold int init(AVFilterContext *ctx) LoudNormContext *s = ctx->priv; s->frame_type = FIRST_FRAME; + if (s->stats_file_str && s->print_format == NONE) { + av_log(ctx, AV_LOG_ERROR, "stats_file requested but print_format not specified\n"); + return AVERROR(EINVAL); + } + if (s->linear) { double offset, offset_tp; offset = s->target_i - s->measured_i; @@ -824,6 +833,8 @@ static av_cold void uninit(AVFilterContext *ctx) LoudNormContext *s = ctx->priv; double i_in, i_out, lra_in, lra_out, thresh_in, thresh_out, tp_in, tp_out; int c; + FILE *stats_file = NULL; + char *stats = NULL; if (!s->r128_in || !s->r128_out) goto end; @@ -848,13 +859,28 @@ static av_cold void uninit(AVFilterContext *ctx) tp_out = tmp; } + + if (s->stats_file_str) { + if (!strcmp(s->stats_file_str, "-")) { + stats_file = stdout; + } else { + stats_file = avpriv_fopen_utf8(s->stats_file_str, "w"); + if (!stats_file) { + int err = AVERROR(errno); + av_log(ctx, AV_LOG_ERROR, "Could not open stats file %s: %s\n", + s->stats_file_str, av_err2str(err)); + goto end; + } + } + } + switch(s->print_format) { case NONE: break; case JSON: - av_log(ctx, AV_LOG_INFO, - "\n{\n" + stats = av_asprintf( + "{\n" "\t\"input_i\" : \"%.2f\",\n" "\t\"input_tp\" : \"%.2f\",\n" "\t\"input_lra\" : \"%.2f\",\n" @@ -877,11 +903,13 @@ static av_cold void uninit(AVFilterContext *ctx) s->frame_type == LINEAR_MODE ? "linear" : "dynamic", s->target_i - i_out ); + av_log(ctx, AV_LOG_INFO, "\n%s", stats); + if (stats_file) + fprintf(stats_file, "%s", stats); break; case SUMMARY: - av_log(ctx, AV_LOG_INFO, - "\n" + stats = av_asprintf( "Input Integrated: %+6.1f LUFS\n" "Input True Peak: %+6.1f dBTP\n" "Input LRA: %6.1f LU\n" @@ -905,10 +933,17 @@ static av_cold void uninit(AVFilterContext *ctx) s->frame_type == LINEAR_MODE ? "Linear" : "Dynamic", s->target_i - i_out ); + av_log(ctx, AV_LOG_INFO, "\n%s", stats); + if (stats_file) + fprintf(stats_file, "%s", stats); break; } end: + if (stats) + av_free(stats); + if (stats_file && stats_file != stdout) + fclose(stats_file); if (s->r128_in) ff_ebur128_destroy(&s->r128_in); if (s->r128_out)