diff mbox

[FFmpeg-devel] avfilter: add dumpwave filter.

Message ID 20180108003648.99878-2-dmitry.gumenyuk@gmail.com
State Withdrawn
Headers show

Commit Message

dmitry.gumenyuk@gmail.com Jan. 8, 2018, 12:36 a.m. UTC
From: Dmytro Humeniuk <dmitry.gumenyuk@gmail.com>

Signed-off-by: Dmytro Humeniuk <dmitry.gumenyuk@gmail.com>
---
 Changelog                      |   1 +
 libavfilter/Makefile           |   1 +
 libavfilter/af_dumpwave.c      | 273 +++++++++++++++++++++++++++++++++++++++++
 libavfilter/allfilters.c       |   1 +
 libavfilter/version.h          |   4 +-
 tests/fate/filter-audio.mak    |   5 +
 tests/ref/fate/filter-dumpwave |   1 +
 7 files changed, 284 insertions(+), 2 deletions(-)
 create mode 100644 libavfilter/af_dumpwave.c
 create mode 100644 tests/ref/fate/filter-dumpwave

Comments

Kyle Swanson Jan. 8, 2018, 10:58 p.m. UTC | #1
Hi,

Can you provide some context for this? Please add docs as well.
Can't really test this because it segfaults for me:  ./ffmpeg -f lavfi
-i anoisesrc -af dumpwave -f null -

On Sun, Jan 7, 2018 at 4:36 PM, <dmitry.gumenyuk@gmail.com> wrote:
>
> From: Dmytro Humeniuk <dmitry.gumenyuk@gmail.com>
>
> Signed-off-by: Dmytro Humeniuk <dmitry.gumenyuk@gmail.com>
> ---
>  Changelog                      |   1 +
>  libavfilter/Makefile           |   1 +
>  libavfilter/af_dumpwave.c      | 273 +++++++++++++++++++++++++++++++++++++++++
>  libavfilter/allfilters.c       |   1 +
>  libavfilter/version.h          |   4 +-
>  tests/fate/filter-audio.mak    |   5 +
>  tests/ref/fate/filter-dumpwave |   1 +
>  7 files changed, 284 insertions(+), 2 deletions(-)
>  create mode 100644 libavfilter/af_dumpwave.c
>  create mode 100644 tests/ref/fate/filter-dumpwave
>
> diff --git a/Changelog b/Changelog
> index 61075b3392..40fd624449 100644
> --- a/Changelog
> +++ b/Changelog
> @@ -38,6 +38,7 @@ version <next>:
>  - Removed the ffserver program
>  - Removed the ffmenc and ffmdec muxer and demuxer
>  - VideoToolbox HEVC encoder and hwaccel
> +- dumpwave audio filter
>
>
>  version 3.4:
> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> index 256dfabd66..020d90ee01 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -122,6 +122,7 @@ OBJS-$(CONFIG_TREMOLO_FILTER)                += af_tremolo.o
>  OBJS-$(CONFIG_VIBRATO_FILTER)                += af_vibrato.o generate_wave_table.o
>  OBJS-$(CONFIG_VOLUME_FILTER)                 += af_volume.o
>  OBJS-$(CONFIG_VOLUMEDETECT_FILTER)           += af_volumedetect.o
> +OBJS-$(CONFIG_DUMPWAVE_FILTER)               += af_dumpwave.o
>
>  OBJS-$(CONFIG_AEVALSRC_FILTER)               += aeval.o
>  OBJS-$(CONFIG_ANOISESRC_FILTER)              += asrc_anoisesrc.o
> diff --git a/libavfilter/af_dumpwave.c b/libavfilter/af_dumpwave.c
> new file mode 100644
> index 0000000000..493b5b7ff2
> --- /dev/null
> +++ b/libavfilter/af_dumpwave.c
> @@ -0,0 +1,273 @@
> +/*
> + * Copyright (c) 2017 Dmytro Humeniuk
> + *
> + * This file is part of FFmpeg.
> + *
> + * FFmpeg is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.
> + *
> + * FFmpeg is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with FFmpeg; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
> + */
> +
> +/**
> + * @file
> + * waveform audio filter – dumps RMS amplitude to JSON file like SoundCloud does
> + */
> +
> +#include "libavutil/avassert.h"
> +#include "libavutil/avstring.h"
> +#include "libavutil/channel_layout.h"
> +#include "libavutil/opt.h"
> +#include "libavutil/parseutils.h"
> +#include "avfilter.h"
> +#include "formats.h"
> +#include "audio.h"
> +#include "internal.h"
> +
> +typedef struct DumpWaveContext {
> +    const AVClass *class;
> +    int w, h;
> +    AVRational rate;
> +    int col;
> +    char *json;
> +    char *str;
> +    double *values;
> +    int64_t c, n, max_samples;
> +    double sum; /* sum of the squared samples per segment */
> +} DumpWaveContext;
> +
> +#define OFFSET(x) offsetof(DumpWaveContext, x)
> +#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
> +
> +static const AVOption dumpwave_options[] = {
> +    { "s",    "set dump size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str = "600x240"}, 0, 0, FLAGS },
> +    { "c", "set number of samples per item",  OFFSET(c), AV_OPT_TYPE_INT64,  {.i64 = 0}, 0, INT64_MAX, FLAGS },

Use more descriptive parameter names. Also, per item? What is an item?

> +    { "json", "set dump file", OFFSET(json), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, FLAGS },
> +    { NULL }
> +};
> +
> +AVFILTER_DEFINE_CLASS(dumpwave);
> +
> +static int config_output(AVFilterLink *outlink)
> +{
> +    DumpWaveContext *dumpwave = outlink->src->priv;
> +    const int ch_width = dumpwave->w;
> +    dumpwave->values = av_malloc(ch_width * sizeof(double));
> +    dumpwave->str = av_malloc(ch_width*sizeof(int));
> +    dumpwave->max_samples = dumpwave->c * outlink->channels;
> +
> +    return 0;
> +}
> +
> +static av_cold void uninit(AVFilterContext *ctx)
> +{
> +    DumpWaveContext *dumpwave = ctx->priv;
> +    const int ch_height = dumpwave->h;
> +    const int ch_width = dumpwave->w;
> +    char *result = dumpwave->str;
> +    FILE *dump_fp = NULL;
> +
> +    if (dumpwave->json && !(dump_fp = av_fopen_utf8(dumpwave->json, "w")))
> +        av_log(ctx, AV_LOG_WARNING, "dump failed.\n");
> +
> +    if (dump_fp) {
> +        fprintf(dump_fp, "{\"width\":%d,\"height\":%d,\"samples\":[%s]}", ch_width, ch_height, result);
> +        fclose(dump_fp);
> +    }
> +    av_freep(&dumpwave->str);
> +    av_freep(&dumpwave->values);
> +}
> +
> +static int dumpwave_request_frame(AVFilterLink *outlink)
> +{
> +    AVFilterContext *ctx = outlink->src;
> +    DumpWaveContext *dumpwave = ctx->priv;
> +    const int ch_height = dumpwave->h;
> +    const int ch_width = dumpwave->w;
> +    const double *values = dumpwave->values;
> +    char *result = dumpwave->str;
> +
> +    AVFilterLink *inlink = ctx->inputs[0];
> +    int ret, val;
> +
> +    ret = ff_request_frame(inlink);
> +    if (ret == AVERROR_EOF) {
> +
> +        char *pos = result;
> +
> +        for(int i = 0; i < ch_width; i++) {
> +            val = ch_height * values[i];
> +            if (val < 0 ) val = 0;
> +            pos += sprintf(pos, "%d,", val);
> +        }
> +        pos[-1] = '\0'; //removing trailing comma
> +    }
> +
> +    return ret;
> +}
> +
> +static inline void calculate(DumpWaveContext *dumpwave, double smpl)
> +{
> +    double *sum = &dumpwave->sum;
> +    double *values = dumpwave->values;
> +    const int64_t max_samples = dumpwave->max_samples;
> +    int *col = &dumpwave->col;
> +    int64_t *n = &dumpwave->n;
> +
> +    if (smpl != 0)
> +        smpl = (20 * log10(fabs(smpl)) + 60) / 60;
> +
> +    *sum += smpl*smpl;
> +
> +    if ((*n)++ == max_samples) {
> +        values[(*col)++] = sqrt(*sum / max_samples);
> +        *sum = *n = 0;
> +    }
> +}
> +
> +static int dumpwave_filter_frame(AVFilterLink *inlink, AVFrame *buf)
> +{
> +    AVFilterContext *ctx = inlink->dst;
> +    DumpWaveContext *dumpwave = ctx->priv;
> +    const int channels = inlink->channels;
> +
> +    int i, c;
> +
> +    switch (inlink->format) {
> +        case AV_SAMPLE_FMT_DBLP:
> +            for (c = 0; c < channels; c++) {
> +                const double *src = (const double *)buf->extended_data[c];
> +
> +                for (i = 0; i < buf->nb_samples; i++, src++)
> +                    calculate(dumpwave, *src);
> +            }
> +            break;
> +        case AV_SAMPLE_FMT_DBL: {
> +            const double *src = (const double *)buf->extended_data[0];
> +
> +            for (i = 0; i < buf->nb_samples; i++) {
> +                for (c = 0; c < channels; c++, src++)
> +                    calculate(dumpwave, *src);
> +            }}
> +            break;
> +        case AV_SAMPLE_FMT_FLTP:
> +            for (c = 0; c < channels; c++) {
> +                const float *src = (const float *)buf->extended_data[c];
> +
> +                for (i = 0; i < buf->nb_samples; i++, src++)
> +                    calculate(dumpwave, *src);
> +            }
> +            break;
> +        case AV_SAMPLE_FMT_FLT: {
> +            const float *src = (const float *)buf->extended_data[0];
> +
> +            for (i = 0; i < buf->nb_samples; i++) {
> +                for (c = 0; c < channels; c++, src++)
> +                    calculate(dumpwave, *src);
> +            }}
> +            break;
> +        case AV_SAMPLE_FMT_S64P:
> +            for (c = 0; c < channels; c++) {
> +                const int64_t *src = (const int64_t *)buf->extended_data[c];
> +
> +                for (i = 0; i < buf->nb_samples; i++, src++)
> +                    calculate(dumpwave, *src / (double)INT64_MAX);
> +            }
> +            break;
> +        case AV_SAMPLE_FMT_S64: {
> +            const int64_t *src = (const int64_t *)buf->extended_data[0];
> +
> +            for (i = 0; i < buf->nb_samples; i++) {
> +                for (c = 0; c < channels; c++, src++)
> +                    calculate(dumpwave, *src / (double)INT64_MAX);
> +            }}
> +            break;
> +        case AV_SAMPLE_FMT_S32P:
> +            for (c = 0; c < channels; c++) {
> +                const int32_t *src = (const int32_t *)buf->extended_data[c];
> +
> +                for (i = 0; i < buf->nb_samples; i++, src++)
> +                    calculate(dumpwave, *src / (double)INT32_MAX);
> +            }
> +            break;
> +        case AV_SAMPLE_FMT_S32: {
> +            const int32_t *src = (const int32_t *)buf->extended_data[0];
> +
> +            for (i = 0; i < buf->nb_samples; i++) {
> +                for (c = 0; c < channels; c++, src++)
> +                    calculate(dumpwave, *src / (double)INT32_MAX);
> +            }}
> +            break;
> +        case AV_SAMPLE_FMT_S16P:
> +            for (c = 0; c < channels; c++) {
> +                const int16_t *src = (const int16_t *)buf->extended_data[c];
> +
> +                for (i = 0; i < buf->nb_samples; i++, src++)
> +                    calculate(dumpwave, *src / (double)INT16_MAX);
> +            }
> +            break;
> +        case AV_SAMPLE_FMT_S16: {
> +            const int16_t *src = (const int16_t *)buf->extended_data[0];
> +
> +            for (i = 0; i < buf->nb_samples; i++) {
> +                for (c = 0; c < channels; c++, src++)
> +                    calculate(dumpwave, *src / (double)INT16_MAX);
> +            }}
> +            break;
> +        case AV_SAMPLE_FMT_U8P:
> +            for (c = 0; c < channels; c++) {
> +                const int8_t *src = (const int8_t *)buf->extended_data[c];
> +
> +                for (i = 0; i < buf->nb_samples; i++, src++)
> +                    calculate(dumpwave, *src / (double)INT8_MAX);
> +            }
> +            break;
> +        case AV_SAMPLE_FMT_U8: {
> +            const int8_t *src = (const int8_t *)buf->extended_data[0];
> +
> +            for (i = 0; i < buf->nb_samples; i++) {
> +                for (c = 0; c < channels; c++, src++)
> +                    calculate(dumpwave, *src / (double)INT8_MAX);
> +            }}
> +            break;
> +    }

Just set the sample format to something like AV_SAMPLE_FMT_DBL in
query_format. There's no need for this big switch statement.

> +    return ff_filter_frame(ctx->outputs[0], buf);
> +}
> +
> +static const AVFilterPad dumpwave_inputs[] = {
> +    {
> +        .name         = "default",
> +        .type         = AVMEDIA_TYPE_AUDIO,
> +        .filter_frame = dumpwave_filter_frame,
> +    },
> +    { NULL }
> +};
> +
> +static const AVFilterPad dumpwave_outputs[] = {
> +    {
> +        .name          = "default",
> +        .type          = AVMEDIA_TYPE_AUDIO,
> +        .request_frame = dumpwave_request_frame,
> +        .config_props = config_output
> +    },
> +    { NULL }
> +};
> +
> +AVFilter ff_af_dumpwave = {
> +    .name          = "dumpwave",
> +    .description   = NULL_IF_CONFIG_SMALL("Dumps RMS audio peaks to a json file"),
> +    .uninit        = uninit,
> +    .priv_size     = sizeof(DumpWaveContext),
> +    .inputs        = dumpwave_inputs,
> +    .outputs       = dumpwave_outputs,
> +    .priv_class    = &dumpwave_class,
> +};
> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> index 753ae968aa..887c80c706 100644
> --- a/libavfilter/allfilters.c
> +++ b/libavfilter/allfilters.c
> @@ -133,6 +133,7 @@ static void register_all(void)
>      REGISTER_FILTER(VIBRATO,        vibrato,        af);
>      REGISTER_FILTER(VOLUME,         volume,         af);
>      REGISTER_FILTER(VOLUMEDETECT,   volumedetect,   af);
> +    REGISTER_FILTER(DUMPWAVE,       dumpwave,       af);
>
>      REGISTER_FILTER(AEVALSRC,       aevalsrc,       asrc);
>      REGISTER_FILTER(ANOISESRC,      anoisesrc,      asrc);
> diff --git a/libavfilter/version.h b/libavfilter/version.h
> index 0f11721822..ca096962bb 100644
> --- a/libavfilter/version.h
> +++ b/libavfilter/version.h
> @@ -30,8 +30,8 @@
>  #include "libavutil/version.h"
>
>  #define LIBAVFILTER_VERSION_MAJOR   7
> -#define LIBAVFILTER_VERSION_MINOR  11
> -#define LIBAVFILTER_VERSION_MICRO 101
> +#define LIBAVFILTER_VERSION_MINOR  12
> +#define LIBAVFILTER_VERSION_MICRO 100
>
>  #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
>                                                 LIBAVFILTER_VERSION_MINOR, \
> diff --git a/tests/fate/filter-audio.mak b/tests/fate/filter-audio.mak
> index bd8b3d3c35..2da644a66f 100644
> --- a/tests/fate/filter-audio.mak
> +++ b/tests/fate/filter-audio.mak
> @@ -347,3 +347,8 @@ fate-filter-formats: CMD = run libavfilter/tests/formats
>  FATE_SAMPLES_AVCONV += $(FATE_AFILTER_SAMPLES-yes)
>  FATE_FFMPEG += $(FATE_AFILTER-yes)
>  fate-afilter: $(FATE_AFILTER-yes) $(FATE_AFILTER_SAMPLES-yes)
> +
> +FATE_AFILTER-$(call FILTERDEMDEC, DUMPWAVE, WAV, PCM_S16LE) += fate-filter-dumpwave
> +fate-filter-dumpwave: SRC = $(TARGET_PATH)/tests/data/asynth-44100-2.wav
> +fate-filter-dumpwave: CMD = ffmpeg -i $(SRC) -af dumpwave=s=1800x140:c=147:json=tests/data/fate/filter-dumpwave.out -f null - && cat tests/data/fate/filter-dumpwave.out
> +
> diff --git a/tests/ref/fate/filter-dumpwave b/tests/ref/fate/filter-dumpwave
> new file mode 100644
> index 0000000000..bd07098ef8
> --- /dev/null
> +++ b/tests/ref/fate/filter-dumpwave
> @@ -0,0 +1 @@
> +{"width":1800,"height":140,"samples":[103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,102,104,103,102,104,104,102,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,102,104,103,102,104,104,102,103,104,102,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,102,104,104,102,103,104,102,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,102,103,104,102,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,102,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,102,104,103,102,104,103,102,103,104,102,103,104,103,101,104,105,104,103,102,104,104,102,102,103,104,104,103,103,104,104,102,103,103,103,104,103,103,103,104,102,104,103,103,104,103,103,103,103,103,103,103,103,103,104,103,103,103,104,103,103,103,103,104,103,103,103,103,103,103,103,104,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,104,104,103,103,103,103,103,103,103,103,103,103,103,102,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,104,103,103,105,103,103,103,103,103,103,104,103,103,103,104,103,103,103,104,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,106,106,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,100,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,96,98,100,97,99,98,97,97,97,96,98,97,98,97,99,98,97,95,98,97,98,101,96,95,96,94,97,99,99,95,98,96,98,98,98,98,98,98,99,98,96,97,99,97,98,99,99,99,96,98,97,98,98,99,99,97,100,95,98,97,99,94,98,96,99,98,97,99,98,97,96,98,95,96,97,100,99,96,99,97,97,97,99,98,97,96,97,97,99,99,100,95,99,98,95,96,99,97,99,99,95,98,96,97,96,99,96,97,98,96,97,95,97,99,99,96,99,96,98,98,96,96,97,96,99,98,97,98,100,98,100,96,98,98,99,97,99,99,99,97,99,97,99,99,98,96,100,97,95,112,119,121,121,118,122,122,120,121,123,121,120,118,121,120,121,123,124,122,120,124,121,122,121,120,121,122,122,120,119,118,122,121,122,121,120,123,120,121,122,121,121,119,120,119,120,121,121,120,122,120,122,123,122,124,122,120,122,121,121,119,122,123,123,122,120,121,119,123,121,125,119,121,119,120,121,121,121,123,122,120,122,123,120,120,123,121,119,122,120,122,123,121,123,121,121,123,120,120,121,123,123,120,122,122,119,120,122,122,120,122,122,120,123,120,121,120,121,121,123,120,121,119,122,117,124,122,122,119,120,122,121,121,123,121,120,121,122,120,121,123,121,119,123,120,123,125,121,120,121,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,104,103,104,103,103,104,103,104,103,104,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,104,103,104,102,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,104,103,103,103,104,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,104,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,102,81,75,74,74,75,76,78,78,79,81,81,82,84,84,84,86,86,86,87,88,88,88,90,88,89,90,89,89,90,89,89,89,89,88,88,89,87,87,87,85,85,85,83,83,82,81,80,79,78,76,75,75,73,75,80,76,79,75,74,74,76,77,77,79,81,80,82,84,83,84,86,86,86,88,87,87,89,89,88,89,90,89,89,90,89,89,90,89,88,89,88,87,87,87,86,85,86,84,83,83,81,80,80,78,77,76,75,73,74,76,81,81,76,73,74,75,76,76,79,79,80,82,83,83,84,85,85,86,87,87,87,89,88,88,90,89,89,89,90,89,89,90,89,89,89,88,87,88,87,86,86,86,84,84,84,82,81,81,78,77,77,75,74,74,74,79,76,79,74,74,75,75,76,78,78,80,81,82,82,84,84,85,86,87,86,87,89,88,88,90,89,89,90,89,89,90,89,89,89,89,88,88,89,87,86,87,85,84,85,83,82,82,81,79,79,78,75,75,75,73,75,81,80,76,74,74,75,76,77,78,79,81,81,82,84,84,84,86,86,86,88,88,87,88,89,88,89,90,89,89,90,89,89,90,89,88,89,88,87,87,87,85,85,85,83,83,83,81,80,80,78,76,75,75,73,74,78,76,79,75,74,74,75,77,77,79,80,80,82,84,83,84,86,85,86,88,87,87,89,89,88,89,90,89,89,90,89,89,90,89,88,89,88,87,88,87,86,86,85,84,83,83,81,80,80,78,77,76,75,74,74,75,81,80,77,73,74,75,75,76,79,79,80,82,82,83,84,85,85,86,87,87,87,89,88,88,90,89,89,90,90,89,89,90,89,89,89,88,88,88,87,86,86,85,84,84,83,82,81,81,79,78,78,75,74,75,74,77,76,80,75,74,74,75,76,78,78,80,81,81,82,84,84,85,86,87,86,87,88,88,88,90,89,89,90,89,89,90,89,89,89,89,88,88,89,87,87,87,85,84,85,83,82,82,81,79,79,78,76,75,75,73,75,80,81,76,75,74,75,76,77,78,79,81,81,82,84,84,84,86,86,86,88,88,87,89,89,88,89,90,89,89,90,89,89,90,89,88,89,88,87,87,87,85,85,86,83,83,83,81,80,80,78,77,76,75,73,74,77,78,79,76,74,74,75,76,77,79,80,80,82,83,83,84,86,85,86,88,87,87,89,88,88,90,89,89,90,90,89,89,90,89,88,89,88,87,88,87,86,86,85,84,84,83,81,81,80,78,77,77,75,74,74,75,80,81,78,74,74,75,75,76,79,79,80,82,82,82,85,85,85,86,87,86,87,89,88,88,90,89,89,90,89,89,90,90,89,89,89,88,88,88,87,86,87,85,84,0,0,0,0,0,0,0]}
> --
> 2.14.3 (Apple Git-98)
>
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel

Thanks,
Kyle
dmitry.gumenyuk@gmail.com Jan. 10, 2018, 12:14 a.m. UTC | #2
Hi,
>Can you provide some context for this? Please add docs as well.
filter converts samples to decibels and calculates RMS
(Root-Mean-Square) audio power scaled to desired values,
it supposed to generate something like SoundCloud does
http://plnkr.co/edit/WeppGcEPQiOyKDsA0C4Y
I added docs.
>Can't really test this because it segfaults for me:  ./ffmpeg -f lavfi
>-i anoisesrc -af dumpwave -f null -
it expected some config - fixed

>Just set the sample format to something like AV_SAMPLE_FMT_DBL in
>query_format. There's no need for this big switch statement.
it handles samples in different way, you may look at the astats filter

2018-01-08 23:58 GMT+01:00 Kyle Swanson <k@ylo.ph>:
> Hi,
>
> Can you provide some context for this? Please add docs as well.
> Can't really test this because it segfaults for me:  ./ffmpeg -f lavfi
> -i anoisesrc -af dumpwave -f null -
>
> On Sun, Jan 7, 2018 at 4:36 PM, <dmitry.gumenyuk@gmail.com> wrote:
>>
>> From: Dmytro Humeniuk <dmitry.gumenyuk@gmail.com>
>>
>> Signed-off-by: Dmytro Humeniuk <dmitry.gumenyuk@gmail.com>
>> ---
>>  Changelog                      |   1 +
>>  libavfilter/Makefile           |   1 +
>>  libavfilter/af_dumpwave.c      | 273 +++++++++++++++++++++++++++++++++++++++++
>>  libavfilter/allfilters.c       |   1 +
>>  libavfilter/version.h          |   4 +-
>>  tests/fate/filter-audio.mak    |   5 +
>>  tests/ref/fate/filter-dumpwave |   1 +
>>  7 files changed, 284 insertions(+), 2 deletions(-)
>>  create mode 100644 libavfilter/af_dumpwave.c
>>  create mode 100644 tests/ref/fate/filter-dumpwave
>>
>> diff --git a/Changelog b/Changelog
>> index 61075b3392..40fd624449 100644
>> --- a/Changelog
>> +++ b/Changelog
>> @@ -38,6 +38,7 @@ version <next>:
>>  - Removed the ffserver program
>>  - Removed the ffmenc and ffmdec muxer and demuxer
>>  - VideoToolbox HEVC encoder and hwaccel
>> +- dumpwave audio filter
>>
>>
>>  version 3.4:
>> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
>> index 256dfabd66..020d90ee01 100644
>> --- a/libavfilter/Makefile
>> +++ b/libavfilter/Makefile
>> @@ -122,6 +122,7 @@ OBJS-$(CONFIG_TREMOLO_FILTER)                += af_tremolo.o
>>  OBJS-$(CONFIG_VIBRATO_FILTER)                += af_vibrato.o generate_wave_table.o
>>  OBJS-$(CONFIG_VOLUME_FILTER)                 += af_volume.o
>>  OBJS-$(CONFIG_VOLUMEDETECT_FILTER)           += af_volumedetect.o
>> +OBJS-$(CONFIG_DUMPWAVE_FILTER)               += af_dumpwave.o
>>
>>  OBJS-$(CONFIG_AEVALSRC_FILTER)               += aeval.o
>>  OBJS-$(CONFIG_ANOISESRC_FILTER)              += asrc_anoisesrc.o
>> diff --git a/libavfilter/af_dumpwave.c b/libavfilter/af_dumpwave.c
>> new file mode 100644
>> index 0000000000..493b5b7ff2
>> --- /dev/null
>> +++ b/libavfilter/af_dumpwave.c
>> @@ -0,0 +1,273 @@
>> +/*
>> + * Copyright (c) 2017 Dmytro Humeniuk
>> + *
>> + * This file is part of FFmpeg.
>> + *
>> + * FFmpeg is free software; you can redistribute it and/or
>> + * modify it under the terms of the GNU Lesser General Public
>> + * License as published by the Free Software Foundation; either
>> + * version 2.1 of the License, or (at your option) any later version.
>> + *
>> + * FFmpeg is distributed in the hope that it will be useful,
>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>> + * Lesser General Public License for more details.
>> + *
>> + * You should have received a copy of the GNU Lesser General Public
>> + * License along with FFmpeg; if not, write to the Free Software
>> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
>> + */
>> +
>> +/**
>> + * @file
>> + * waveform audio filter – dumps RMS amplitude to JSON file like SoundCloud does
>> + */
>> +
>> +#include "libavutil/avassert.h"
>> +#include "libavutil/avstring.h"
>> +#include "libavutil/channel_layout.h"
>> +#include "libavutil/opt.h"
>> +#include "libavutil/parseutils.h"
>> +#include "avfilter.h"
>> +#include "formats.h"
>> +#include "audio.h"
>> +#include "internal.h"
>> +
>> +typedef struct DumpWaveContext {
>> +    const AVClass *class;
>> +    int w, h;
>> +    AVRational rate;
>> +    int col;
>> +    char *json;
>> +    char *str;
>> +    double *values;
>> +    int64_t c, n, max_samples;
>> +    double sum; /* sum of the squared samples per segment */
>> +} DumpWaveContext;
>> +
>> +#define OFFSET(x) offsetof(DumpWaveContext, x)
>> +#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
>> +
>> +static const AVOption dumpwave_options[] = {
>> +    { "s",    "set dump size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str = "600x240"}, 0, 0, FLAGS },
>> +    { "c", "set number of samples per item",  OFFSET(c), AV_OPT_TYPE_INT64,  {.i64 = 0}, 0, INT64_MAX, FLAGS },
>
> Use more descriptive parameter names. Also, per item? What is an item?
>
>> +    { "json", "set dump file", OFFSET(json), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, FLAGS },
>> +    { NULL }
>> +};
>> +
>> +AVFILTER_DEFINE_CLASS(dumpwave);
>> +
>> +static int config_output(AVFilterLink *outlink)
>> +{
>> +    DumpWaveContext *dumpwave = outlink->src->priv;
>> +    const int ch_width = dumpwave->w;
>> +    dumpwave->values = av_malloc(ch_width * sizeof(double));
>> +    dumpwave->str = av_malloc(ch_width*sizeof(int));
>> +    dumpwave->max_samples = dumpwave->c * outlink->channels;
>> +
>> +    return 0;
>> +}
>> +
>> +static av_cold void uninit(AVFilterContext *ctx)
>> +{
>> +    DumpWaveContext *dumpwave = ctx->priv;
>> +    const int ch_height = dumpwave->h;
>> +    const int ch_width = dumpwave->w;
>> +    char *result = dumpwave->str;
>> +    FILE *dump_fp = NULL;
>> +
>> +    if (dumpwave->json && !(dump_fp = av_fopen_utf8(dumpwave->json, "w")))
>> +        av_log(ctx, AV_LOG_WARNING, "dump failed.\n");
>> +
>> +    if (dump_fp) {
>> +        fprintf(dump_fp, "{\"width\":%d,\"height\":%d,\"samples\":[%s]}", ch_width, ch_height, result);
>> +        fclose(dump_fp);
>> +    }
>> +    av_freep(&dumpwave->str);
>> +    av_freep(&dumpwave->values);
>> +}
>> +
>> +static int dumpwave_request_frame(AVFilterLink *outlink)
>> +{
>> +    AVFilterContext *ctx = outlink->src;
>> +    DumpWaveContext *dumpwave = ctx->priv;
>> +    const int ch_height = dumpwave->h;
>> +    const int ch_width = dumpwave->w;
>> +    const double *values = dumpwave->values;
>> +    char *result = dumpwave->str;
>> +
>> +    AVFilterLink *inlink = ctx->inputs[0];
>> +    int ret, val;
>> +
>> +    ret = ff_request_frame(inlink);
>> +    if (ret == AVERROR_EOF) {
>> +
>> +        char *pos = result;
>> +
>> +        for(int i = 0; i < ch_width; i++) {
>> +            val = ch_height * values[i];
>> +            if (val < 0 ) val = 0;
>> +            pos += sprintf(pos, "%d,", val);
>> +        }
>> +        pos[-1] = '\0'; //removing trailing comma
>> +    }
>> +
>> +    return ret;
>> +}
>> +
>> +static inline void calculate(DumpWaveContext *dumpwave, double smpl)
>> +{
>> +    double *sum = &dumpwave->sum;
>> +    double *values = dumpwave->values;
>> +    const int64_t max_samples = dumpwave->max_samples;
>> +    int *col = &dumpwave->col;
>> +    int64_t *n = &dumpwave->n;
>> +
>> +    if (smpl != 0)
>> +        smpl = (20 * log10(fabs(smpl)) + 60) / 60;
>> +
>> +    *sum += smpl*smpl;
>> +
>> +    if ((*n)++ == max_samples) {
>> +        values[(*col)++] = sqrt(*sum / max_samples);
>> +        *sum = *n = 0;
>> +    }
>> +}
>> +
>> +static int dumpwave_filter_frame(AVFilterLink *inlink, AVFrame *buf)
>> +{
>> +    AVFilterContext *ctx = inlink->dst;
>> +    DumpWaveContext *dumpwave = ctx->priv;
>> +    const int channels = inlink->channels;
>> +
>> +    int i, c;
>> +
>> +    switch (inlink->format) {
>> +        case AV_SAMPLE_FMT_DBLP:
>> +            for (c = 0; c < channels; c++) {
>> +                const double *src = (const double *)buf->extended_data[c];
>> +
>> +                for (i = 0; i < buf->nb_samples; i++, src++)
>> +                    calculate(dumpwave, *src);
>> +            }
>> +            break;
>> +        case AV_SAMPLE_FMT_DBL: {
>> +            const double *src = (const double *)buf->extended_data[0];
>> +
>> +            for (i = 0; i < buf->nb_samples; i++) {
>> +                for (c = 0; c < channels; c++, src++)
>> +                    calculate(dumpwave, *src);
>> +            }}
>> +            break;
>> +        case AV_SAMPLE_FMT_FLTP:
>> +            for (c = 0; c < channels; c++) {
>> +                const float *src = (const float *)buf->extended_data[c];
>> +
>> +                for (i = 0; i < buf->nb_samples; i++, src++)
>> +                    calculate(dumpwave, *src);
>> +            }
>> +            break;
>> +        case AV_SAMPLE_FMT_FLT: {
>> +            const float *src = (const float *)buf->extended_data[0];
>> +
>> +            for (i = 0; i < buf->nb_samples; i++) {
>> +                for (c = 0; c < channels; c++, src++)
>> +                    calculate(dumpwave, *src);
>> +            }}
>> +            break;
>> +        case AV_SAMPLE_FMT_S64P:
>> +            for (c = 0; c < channels; c++) {
>> +                const int64_t *src = (const int64_t *)buf->extended_data[c];
>> +
>> +                for (i = 0; i < buf->nb_samples; i++, src++)
>> +                    calculate(dumpwave, *src / (double)INT64_MAX);
>> +            }
>> +            break;
>> +        case AV_SAMPLE_FMT_S64: {
>> +            const int64_t *src = (const int64_t *)buf->extended_data[0];
>> +
>> +            for (i = 0; i < buf->nb_samples; i++) {
>> +                for (c = 0; c < channels; c++, src++)
>> +                    calculate(dumpwave, *src / (double)INT64_MAX);
>> +            }}
>> +            break;
>> +        case AV_SAMPLE_FMT_S32P:
>> +            for (c = 0; c < channels; c++) {
>> +                const int32_t *src = (const int32_t *)buf->extended_data[c];
>> +
>> +                for (i = 0; i < buf->nb_samples; i++, src++)
>> +                    calculate(dumpwave, *src / (double)INT32_MAX);
>> +            }
>> +            break;
>> +        case AV_SAMPLE_FMT_S32: {
>> +            const int32_t *src = (const int32_t *)buf->extended_data[0];
>> +
>> +            for (i = 0; i < buf->nb_samples; i++) {
>> +                for (c = 0; c < channels; c++, src++)
>> +                    calculate(dumpwave, *src / (double)INT32_MAX);
>> +            }}
>> +            break;
>> +        case AV_SAMPLE_FMT_S16P:
>> +            for (c = 0; c < channels; c++) {
>> +                const int16_t *src = (const int16_t *)buf->extended_data[c];
>> +
>> +                for (i = 0; i < buf->nb_samples; i++, src++)
>> +                    calculate(dumpwave, *src / (double)INT16_MAX);
>> +            }
>> +            break;
>> +        case AV_SAMPLE_FMT_S16: {
>> +            const int16_t *src = (const int16_t *)buf->extended_data[0];
>> +
>> +            for (i = 0; i < buf->nb_samples; i++) {
>> +                for (c = 0; c < channels; c++, src++)
>> +                    calculate(dumpwave, *src / (double)INT16_MAX);
>> +            }}
>> +            break;
>> +        case AV_SAMPLE_FMT_U8P:
>> +            for (c = 0; c < channels; c++) {
>> +                const int8_t *src = (const int8_t *)buf->extended_data[c];
>> +
>> +                for (i = 0; i < buf->nb_samples; i++, src++)
>> +                    calculate(dumpwave, *src / (double)INT8_MAX);
>> +            }
>> +            break;
>> +        case AV_SAMPLE_FMT_U8: {
>> +            const int8_t *src = (const int8_t *)buf->extended_data[0];
>> +
>> +            for (i = 0; i < buf->nb_samples; i++) {
>> +                for (c = 0; c < channels; c++, src++)
>> +                    calculate(dumpwave, *src / (double)INT8_MAX);
>> +            }}
>> +            break;
>> +    }
>
> Just set the sample format to something like AV_SAMPLE_FMT_DBL in
> query_format. There's no need for this big switch statement.
>
>> +    return ff_filter_frame(ctx->outputs[0], buf);
>> +}
>> +
>> +static const AVFilterPad dumpwave_inputs[] = {
>> +    {
>> +        .name         = "default",
>> +        .type         = AVMEDIA_TYPE_AUDIO,
>> +        .filter_frame = dumpwave_filter_frame,
>> +    },
>> +    { NULL }
>> +};
>> +
>> +static const AVFilterPad dumpwave_outputs[] = {
>> +    {
>> +        .name          = "default",
>> +        .type          = AVMEDIA_TYPE_AUDIO,
>> +        .request_frame = dumpwave_request_frame,
>> +        .config_props = config_output
>> +    },
>> +    { NULL }
>> +};
>> +
>> +AVFilter ff_af_dumpwave = {
>> +    .name          = "dumpwave",
>> +    .description   = NULL_IF_CONFIG_SMALL("Dumps RMS audio peaks to a json file"),
>> +    .uninit        = uninit,
>> +    .priv_size     = sizeof(DumpWaveContext),
>> +    .inputs        = dumpwave_inputs,
>> +    .outputs       = dumpwave_outputs,
>> +    .priv_class    = &dumpwave_class,
>> +};
>> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
>> index 753ae968aa..887c80c706 100644
>> --- a/libavfilter/allfilters.c
>> +++ b/libavfilter/allfilters.c
>> @@ -133,6 +133,7 @@ static void register_all(void)
>>      REGISTER_FILTER(VIBRATO,        vibrato,        af);
>>      REGISTER_FILTER(VOLUME,         volume,         af);
>>      REGISTER_FILTER(VOLUMEDETECT,   volumedetect,   af);
>> +    REGISTER_FILTER(DUMPWAVE,       dumpwave,       af);
>>
>>      REGISTER_FILTER(AEVALSRC,       aevalsrc,       asrc);
>>      REGISTER_FILTER(ANOISESRC,      anoisesrc,      asrc);
>> diff --git a/libavfilter/version.h b/libavfilter/version.h
>> index 0f11721822..ca096962bb 100644
>> --- a/libavfilter/version.h
>> +++ b/libavfilter/version.h
>> @@ -30,8 +30,8 @@
>>  #include "libavutil/version.h"
>>
>>  #define LIBAVFILTER_VERSION_MAJOR   7
>> -#define LIBAVFILTER_VERSION_MINOR  11
>> -#define LIBAVFILTER_VERSION_MICRO 101
>> +#define LIBAVFILTER_VERSION_MINOR  12
>> +#define LIBAVFILTER_VERSION_MICRO 100
>>
>>  #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
>>                                                 LIBAVFILTER_VERSION_MINOR, \
>> diff --git a/tests/fate/filter-audio.mak b/tests/fate/filter-audio.mak
>> index bd8b3d3c35..2da644a66f 100644
>> --- a/tests/fate/filter-audio.mak
>> +++ b/tests/fate/filter-audio.mak
>> @@ -347,3 +347,8 @@ fate-filter-formats: CMD = run libavfilter/tests/formats
>>  FATE_SAMPLES_AVCONV += $(FATE_AFILTER_SAMPLES-yes)
>>  FATE_FFMPEG += $(FATE_AFILTER-yes)
>>  fate-afilter: $(FATE_AFILTER-yes) $(FATE_AFILTER_SAMPLES-yes)
>> +
>> +FATE_AFILTER-$(call FILTERDEMDEC, DUMPWAVE, WAV, PCM_S16LE) += fate-filter-dumpwave
>> +fate-filter-dumpwave: SRC = $(TARGET_PATH)/tests/data/asynth-44100-2.wav
>> +fate-filter-dumpwave: CMD = ffmpeg -i $(SRC) -af dumpwave=s=1800x140:c=147:json=tests/data/fate/filter-dumpwave.out -f null - && cat tests/data/fate/filter-dumpwave.out
>> +
>> diff --git a/tests/ref/fate/filter-dumpwave b/tests/ref/fate/filter-dumpwave
>> new file mode 100644
>> index 0000000000..bd07098ef8
>> --- /dev/null
>> +++ b/tests/ref/fate/filter-dumpwave
>> @@ -0,0 +1 @@
>> +{"width":1800,"height":140,"samples":[103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,102,104,103,102,104,104,102,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,102,104,103,102,104,104,102,103,104,102,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,102,104,104,102,103,104,102,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,102,103,104,102,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,102,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,102,104,103,102,104,103,102,103,104,102,103,104,103,101,104,105,104,103,102,104,104,102,102,103,104,104,103,103,104,104,102,103,103,103,104,103,103,103,104,102,104,103,103,104,103,103,103,103,103,103,103,103,103,104,103,103,103,104,103,103,103,103,104,103,103,103,103,103,103,103,104,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,104,104,103,103,103,103,103,103,103,103,103,103,103,102,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,104,103,103,105,103,103,103,103,103,103,104,103,103,103,104,103,103,103,104,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,106,106,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,100,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,96,98,100,97,99,98,97,97,97,96,98,97,98,97,99,98,97,95,98,97,98,101,96,95,96,94,97,99,99,95,98,96,98,98,98,98,98,98,99,98,96,97,99,97,98,99,99,99,96,98,97,98,98,99,99,97,100,95,98,97,99,94,98,96,99,98,97,99,98,97,96,98,95,96,97,100,99,96,99,97,97,97,99,98,97,96,97,97,99,99,100,95,99,98,95,96,99,97,99,99,95,98,96,97,96,99,96,97,98,96,97,95,97,99,99,96,99,96,98,98,96,96,97,96,99,98,97,98,100,98,100,96,98,98,99,97,99,99,99,97,99,97,99,99,98,96,100,97,95,112,119,121,121,118,122,122,120,121,123,121,120,118,121,120,121,123,124,122,120,124,121,122,121,120,121,122,122,120,119,118,122,121,122,121,120,123,120,121,122,121,121,119,120,119,120,121,121,120,122,120,122,123,122,124,122,120,122,121,121,119,122,123,123,122,120,121,119,123,121,125,119,121,119,120,121,121,121,123,122,120,122,123,120,120,123,121,119,122,120,122,123,121,123,121,121,123,120,120,121,123,123,120,122,122,119,120,122,122,120,122,122,120,123,120,121,120,121,121,123,120,121,119,122,117,124,122,122,119,120,122,121,121,123,121,120,121,122,120,121,123,121,119,123,120,123,125,121,120,121,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,104,103,104,103,103,104,103,104,103,104,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,104,103,104,102,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,104,103,103,103,104,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,104,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,102,81,75,74,74,75,76,78,78,79,81,81,82,84,84,84,86,86,86,87,88,88,88,90,88,89,90,89,89,90,89,89,89,89,88,88,89,87,87,87,85,85,85,83,83,82,81,80,79,78,76,75,75,73,75,80,76,79,75,74,74,76,77,77,79,81,80,82,84,83,84,86,86,86,88,87,87,89,89,88,89,90,89,89,90,89,89,90,89,88,89,88,87,87,87,86,85,86,84,83,83,81,80,80,78,77,76,75,73,74,76,81,81,76,73,74,75,76,76,79,79,80,82,83,83,84,85,85,86,87,87,87,89,88,88,90,89,89,89,90,89,89,90,89,89,89,88,87,88,87,86,86,86,84,84,84,82,81,81,78,77,77,75,74,74,74,79,76,79,74,74,75,75,76,78,78,80,81,82,82,84,84,85,86,87,86,87,89,88,88,90,89,89,90,89,89,90,89,89,89,89,88,88,89,87,86,87,85,84,85,83,82,82,81,79,79,78,75,75,75,73,75,81,80,76,74,74,75,76,77,78,79,81,81,82,84,84,84,86,86,86,88,88,87,88,89,88,89,90,89,89,90,89,89,90,89,88,89,88,87,87,87,85,85,85,83,83,83,81,80,80,78,76,75,75,73,74,78,76,79,75,74,74,75,77,77,79,80,80,82,84,83,84,86,85,86,88,87,87,89,89,88,89,90,89,89,90,89,89,90,89,88,89,88,87,88,87,86,86,85,84,83,83,81,80,80,78,77,76,75,74,74,75,81,80,77,73,74,75,75,76,79,79,80,82,82,83,84,85,85,86,87,87,87,89,88,88,90,89,89,90,90,89,89,90,89,89,89,88,88,88,87,86,86,85,84,84,83,82,81,81,79,78,78,75,74,75,74,77,76,80,75,74,74,75,76,78,78,80,81,81,82,84,84,85,86,87,86,87,88,88,88,90,89,89,90,89,89,90,89,89,89,89,88,88,89,87,87,87,85,84,85,83,82,82,81,79,79,78,76,75,75,73,75,80,81,76,75,74,75,76,77,78,79,81,81,82,84,84,84,86,86,86,88,88,87,89,89,88,89,90,89,89,90,89,89,90,89,88,89,88,87,87,87,85,85,86,83,83,83,81,80,80,78,77,76,75,73,74,77,78,79,76,74,74,75,76,77,79,80,80,82,83,83,84,86,85,86,88,87,87,89,88,88,90,89,89,90,90,89,89,90,89,88,89,88,87,88,87,86,86,85,84,84,83,81,81,80,78,77,77,75,74,74,75,80,81,78,74,74,75,75,76,79,79,80,82,82,82,85,85,85,86,87,86,87,89,88,88,90,89,89,90,89,89,90,90,89,89,89,88,88,88,87,86,87,85,84,0,0,0,0,0,0,0]}
>> --
>> 2.14.3 (Apple Git-98)
>>
>> _______________________________________________
>> ffmpeg-devel mailing list
>> ffmpeg-devel@ffmpeg.org
>> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
>
> Thanks,
> Kyle
> _______________________________________________
> ffmpeg-devel mailing list
> ffmpeg-devel@ffmpeg.org
> http://ffmpeg.org/mailman/listinfo/ffmpeg-devel
Tobias Rapp Jan. 10, 2018, 8:15 a.m. UTC | #3
On 08.01.2018 01:36, dmitry.gumenyuk@gmail.com wrote:
> From: Dmytro Humeniuk <dmitry.gumenyuk@gmail.com>
> 
> Signed-off-by: Dmytro Humeniuk <dmitry.gumenyuk@gmail.com>
> ---
>   Changelog                      |   1 +
>   libavfilter/Makefile           |   1 +
>   libavfilter/af_dumpwave.c      | 273 +++++++++++++++++++++++++++++++++++++++++
>   libavfilter/allfilters.c       |   1 +
>   libavfilter/version.h          |   4 +-
>   tests/fate/filter-audio.mak    |   5 +
>   tests/ref/fate/filter-dumpwave |   1 +
>   7 files changed, 284 insertions(+), 2 deletions(-)
>   create mode 100644 libavfilter/af_dumpwave.c
>   create mode 100644 tests/ref/fate/filter-dumpwave
> 
> [...]

As far as I can see the filter reads audio and writes an envelope curve 
using JSON data format. The JSON data is then rendered into an image 
outside of FFmpeg.

In my opinion it would be better to allow connecting the filter with 
other filters by directly rendering the image within FFmpeg. So the 
filter should have a generic video output instead of the JSON output. 
This would be similar to the existing "showvolume" filter.

If you just want to get the raw envelope data from FFmpeg I suggest to 
take a look at the "write_peak" option of the WAV muxer.

Regards,
Tobias
Tobias Rapp Jan. 10, 2018, 8:27 a.m. UTC | #4
On 10.01.2018 09:11, Дмитрий Гуменюк wrote:
> Hi,
> In my opinion JSON is more flexible, so envelope can be rendered in web app or mobile app
> This filter is useful when you’d like to generate visual representation of audio while doing transcode

I see, but my point is: If you do the image rendering external anyway, 
why not use an existing envelope dumping format? Or alternatively 
connect the rendering application via the av* library interface in C?

Filters into the codebase should be as generic as possible to allow 
re-usage by other filters.

> 
>> On 10 Jan 2018, at 09:00, Tobias Rapp <t.rapp@noa-archive.com> wrote:
>>
>> On 08.01.2018 01:36, dmitry.gumenyuk@gmail.com wrote:
>>> From: Dmytro Humeniuk <dmitry.gumenyuk@gmail.com>
>>> Signed-off-by: Dmytro Humeniuk <dmitry.gumenyuk@gmail.com>
>>> ---
>>>   Changelog                      |   1 +
>>>   libavfilter/Makefile           |   1 +
>>>   libavfilter/af_dumpwave.c      | 273 +++++++++++++++++++++++++++++++++++++++++
>>>   libavfilter/allfilters.c       |   1 +
>>>   libavfilter/version.h          |   4 +-
>>>   tests/fate/filter-audio.mak    |   5 +
>>>   tests/ref/fate/filter-dumpwave |   1 +
>>>   7 files changed, 284 insertions(+), 2 deletions(-)
>>>   create mode 100644 libavfilter/af_dumpwave.c
>>>   create mode 100644 tests/ref/fate/filter-dumpwave
>>> [...]
>>
>> As far as I can see the filter reads audio and writes an envelope curve using JSON data format. The JSON data is then rendered into an image outside of FFmpeg.
>>
>> In my opinion it would be better to allow connecting the filter with other filters by directly rendering the image within FFmpeg. So the filter should have a generic video output instead of the JSON output. This would be similar to the existing "showvolume" filter.
>>
>> If you just want to get the raw envelope data from FFmpeg I suggest to take a look at the "write_peak" option of the WAV muxer.
>>
>> Regards,
>> Tobias
>>
> 

BTW: Top-posting is unpopular on this list.

Regards,
Tobias
Tobias Rapp Jan. 10, 2018, 9:17 a.m. UTC | #5
On 10.01.2018 09:34, Дмитрий Гуменюк wrote:
> There is no existing dumping format for RMS. How to make it generic?

Some of my applications use the following pattern to dump audio envelope 
data during transcoding:

ffmpeg -i input-file -f [...] output-file \
   -f wav -write_peak only peak-file

The data format of "peak-file" follows EBU Tech 3285 Supplement 3 (BWF 
Peak Envelope Chunk).

Regards,
Tobias
diff mbox

Patch

diff --git a/Changelog b/Changelog
index 61075b3392..40fd624449 100644
--- a/Changelog
+++ b/Changelog
@@ -38,6 +38,7 @@  version <next>:
 - Removed the ffserver program
 - Removed the ffmenc and ffmdec muxer and demuxer
 - VideoToolbox HEVC encoder and hwaccel
+- dumpwave audio filter
 
 
 version 3.4:
diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 256dfabd66..020d90ee01 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -122,6 +122,7 @@  OBJS-$(CONFIG_TREMOLO_FILTER)                += af_tremolo.o
 OBJS-$(CONFIG_VIBRATO_FILTER)                += af_vibrato.o generate_wave_table.o
 OBJS-$(CONFIG_VOLUME_FILTER)                 += af_volume.o
 OBJS-$(CONFIG_VOLUMEDETECT_FILTER)           += af_volumedetect.o
+OBJS-$(CONFIG_DUMPWAVE_FILTER)               += af_dumpwave.o
 
 OBJS-$(CONFIG_AEVALSRC_FILTER)               += aeval.o
 OBJS-$(CONFIG_ANOISESRC_FILTER)              += asrc_anoisesrc.o
diff --git a/libavfilter/af_dumpwave.c b/libavfilter/af_dumpwave.c
new file mode 100644
index 0000000000..493b5b7ff2
--- /dev/null
+++ b/libavfilter/af_dumpwave.c
@@ -0,0 +1,273 @@ 
+/*
+ * Copyright (c) 2017 Dmytro Humeniuk
+ *
+ * This file is part of FFmpeg.
+ *
+ * FFmpeg is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * FFmpeg is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with FFmpeg; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * waveform audio filter – dumps RMS amplitude to JSON file like SoundCloud does
+ */
+
+#include "libavutil/avassert.h"
+#include "libavutil/avstring.h"
+#include "libavutil/channel_layout.h"
+#include "libavutil/opt.h"
+#include "libavutil/parseutils.h"
+#include "avfilter.h"
+#include "formats.h"
+#include "audio.h"
+#include "internal.h"
+
+typedef struct DumpWaveContext {
+    const AVClass *class;
+    int w, h;
+    AVRational rate;
+    int col;
+    char *json;
+    char *str;
+    double *values;
+    int64_t c, n, max_samples;
+    double sum; /* sum of the squared samples per segment */
+} DumpWaveContext;
+
+#define OFFSET(x) offsetof(DumpWaveContext, x)
+#define FLAGS AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
+
+static const AVOption dumpwave_options[] = {
+    { "s",    "set dump size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str = "600x240"}, 0, 0, FLAGS },
+    { "c", "set number of samples per item",  OFFSET(c), AV_OPT_TYPE_INT64,  {.i64 = 0}, 0, INT64_MAX, FLAGS },
+    { "json", "set dump file", OFFSET(json), AV_OPT_TYPE_STRING, { .str = NULL }, 0, 0, FLAGS },
+    { NULL }
+};
+
+AVFILTER_DEFINE_CLASS(dumpwave);
+
+static int config_output(AVFilterLink *outlink)
+{
+    DumpWaveContext *dumpwave = outlink->src->priv;
+    const int ch_width = dumpwave->w;
+    dumpwave->values = av_malloc(ch_width * sizeof(double));
+    dumpwave->str = av_malloc(ch_width*sizeof(int));
+    dumpwave->max_samples = dumpwave->c * outlink->channels;
+
+    return 0;
+}
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    DumpWaveContext *dumpwave = ctx->priv;
+    const int ch_height = dumpwave->h;
+    const int ch_width = dumpwave->w;
+    char *result = dumpwave->str;
+    FILE *dump_fp = NULL;
+    
+    if (dumpwave->json && !(dump_fp = av_fopen_utf8(dumpwave->json, "w")))
+        av_log(ctx, AV_LOG_WARNING, "dump failed.\n");
+    
+    if (dump_fp) {
+        fprintf(dump_fp, "{\"width\":%d,\"height\":%d,\"samples\":[%s]}", ch_width, ch_height, result);
+        fclose(dump_fp);
+    }
+    av_freep(&dumpwave->str);
+    av_freep(&dumpwave->values);
+}
+
+static int dumpwave_request_frame(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = outlink->src;
+    DumpWaveContext *dumpwave = ctx->priv;
+    const int ch_height = dumpwave->h;
+    const int ch_width = dumpwave->w;
+    const double *values = dumpwave->values;
+    char *result = dumpwave->str;
+    
+    AVFilterLink *inlink = ctx->inputs[0];
+    int ret, val;
+
+    ret = ff_request_frame(inlink);
+    if (ret == AVERROR_EOF) {
+
+        char *pos = result;
+
+        for(int i = 0; i < ch_width; i++) {
+            val = ch_height * values[i];
+            if (val < 0 ) val = 0;
+            pos += sprintf(pos, "%d,", val);
+        }
+        pos[-1] = '\0'; //removing trailing comma
+    }
+
+    return ret;
+}
+
+static inline void calculate(DumpWaveContext *dumpwave, double smpl)
+{
+    double *sum = &dumpwave->sum;
+    double *values = dumpwave->values;
+    const int64_t max_samples = dumpwave->max_samples;
+    int *col = &dumpwave->col;
+    int64_t *n = &dumpwave->n;
+
+    if (smpl != 0)
+        smpl = (20 * log10(fabs(smpl)) + 60) / 60;
+
+    *sum += smpl*smpl;
+    
+    if ((*n)++ == max_samples) {
+        values[(*col)++] = sqrt(*sum / max_samples);
+        *sum = *n = 0;
+    }
+}
+
+static int dumpwave_filter_frame(AVFilterLink *inlink, AVFrame *buf)
+{
+    AVFilterContext *ctx = inlink->dst;
+    DumpWaveContext *dumpwave = ctx->priv;
+    const int channels = inlink->channels;
+
+    int i, c;
+    
+    switch (inlink->format) {
+        case AV_SAMPLE_FMT_DBLP:
+            for (c = 0; c < channels; c++) {
+                const double *src = (const double *)buf->extended_data[c];
+                
+                for (i = 0; i < buf->nb_samples; i++, src++)
+                    calculate(dumpwave, *src);
+            }
+            break;
+        case AV_SAMPLE_FMT_DBL: {
+            const double *src = (const double *)buf->extended_data[0];
+            
+            for (i = 0; i < buf->nb_samples; i++) {
+                for (c = 0; c < channels; c++, src++)
+                    calculate(dumpwave, *src);
+            }}
+            break;
+        case AV_SAMPLE_FMT_FLTP:
+            for (c = 0; c < channels; c++) {
+                const float *src = (const float *)buf->extended_data[c];
+                
+                for (i = 0; i < buf->nb_samples; i++, src++)
+                    calculate(dumpwave, *src);
+            }
+            break;
+        case AV_SAMPLE_FMT_FLT: {
+            const float *src = (const float *)buf->extended_data[0];
+            
+            for (i = 0; i < buf->nb_samples; i++) {
+                for (c = 0; c < channels; c++, src++)
+                    calculate(dumpwave, *src);
+            }}
+            break;
+        case AV_SAMPLE_FMT_S64P:
+            for (c = 0; c < channels; c++) {
+                const int64_t *src = (const int64_t *)buf->extended_data[c];
+                
+                for (i = 0; i < buf->nb_samples; i++, src++)
+                    calculate(dumpwave, *src / (double)INT64_MAX);
+            }
+            break;
+        case AV_SAMPLE_FMT_S64: {
+            const int64_t *src = (const int64_t *)buf->extended_data[0];
+            
+            for (i = 0; i < buf->nb_samples; i++) {
+                for (c = 0; c < channels; c++, src++)
+                    calculate(dumpwave, *src / (double)INT64_MAX);
+            }}
+            break;
+        case AV_SAMPLE_FMT_S32P:
+            for (c = 0; c < channels; c++) {
+                const int32_t *src = (const int32_t *)buf->extended_data[c];
+                
+                for (i = 0; i < buf->nb_samples; i++, src++)
+                    calculate(dumpwave, *src / (double)INT32_MAX);
+            }
+            break;
+        case AV_SAMPLE_FMT_S32: {
+            const int32_t *src = (const int32_t *)buf->extended_data[0];
+            
+            for (i = 0; i < buf->nb_samples; i++) {
+                for (c = 0; c < channels; c++, src++)
+                    calculate(dumpwave, *src / (double)INT32_MAX);
+            }}
+            break;
+        case AV_SAMPLE_FMT_S16P:
+            for (c = 0; c < channels; c++) {
+                const int16_t *src = (const int16_t *)buf->extended_data[c];
+                
+                for (i = 0; i < buf->nb_samples; i++, src++)
+                    calculate(dumpwave, *src / (double)INT16_MAX);
+            }
+            break;
+        case AV_SAMPLE_FMT_S16: {
+            const int16_t *src = (const int16_t *)buf->extended_data[0];
+            
+            for (i = 0; i < buf->nb_samples; i++) {
+                for (c = 0; c < channels; c++, src++)
+                    calculate(dumpwave, *src / (double)INT16_MAX);
+            }}
+            break;
+        case AV_SAMPLE_FMT_U8P:
+            for (c = 0; c < channels; c++) {
+                const int8_t *src = (const int8_t *)buf->extended_data[c];
+                
+                for (i = 0; i < buf->nb_samples; i++, src++)
+                    calculate(dumpwave, *src / (double)INT8_MAX);
+            }
+            break;
+        case AV_SAMPLE_FMT_U8: {
+            const int8_t *src = (const int8_t *)buf->extended_data[0];
+            
+            for (i = 0; i < buf->nb_samples; i++) {
+                for (c = 0; c < channels; c++, src++)
+                    calculate(dumpwave, *src / (double)INT8_MAX);
+            }}
+            break;
+    }
+    return ff_filter_frame(ctx->outputs[0], buf);
+}
+
+static const AVFilterPad dumpwave_inputs[] = {
+    {
+        .name         = "default",
+        .type         = AVMEDIA_TYPE_AUDIO,
+        .filter_frame = dumpwave_filter_frame,
+    },
+    { NULL }
+};
+
+static const AVFilterPad dumpwave_outputs[] = {
+    {
+        .name          = "default",
+        .type          = AVMEDIA_TYPE_AUDIO,
+        .request_frame = dumpwave_request_frame,
+        .config_props = config_output
+    },
+    { NULL }
+};
+
+AVFilter ff_af_dumpwave = {
+    .name          = "dumpwave",
+    .description   = NULL_IF_CONFIG_SMALL("Dumps RMS audio peaks to a json file"),
+    .uninit        = uninit,
+    .priv_size     = sizeof(DumpWaveContext),
+    .inputs        = dumpwave_inputs,
+    .outputs       = dumpwave_outputs,
+    .priv_class    = &dumpwave_class,
+};
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 753ae968aa..887c80c706 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -133,6 +133,7 @@  static void register_all(void)
     REGISTER_FILTER(VIBRATO,        vibrato,        af);
     REGISTER_FILTER(VOLUME,         volume,         af);
     REGISTER_FILTER(VOLUMEDETECT,   volumedetect,   af);
+    REGISTER_FILTER(DUMPWAVE,       dumpwave,       af);
 
     REGISTER_FILTER(AEVALSRC,       aevalsrc,       asrc);
     REGISTER_FILTER(ANOISESRC,      anoisesrc,      asrc);
diff --git a/libavfilter/version.h b/libavfilter/version.h
index 0f11721822..ca096962bb 100644
--- a/libavfilter/version.h
+++ b/libavfilter/version.h
@@ -30,8 +30,8 @@ 
 #include "libavutil/version.h"
 
 #define LIBAVFILTER_VERSION_MAJOR   7
-#define LIBAVFILTER_VERSION_MINOR  11
-#define LIBAVFILTER_VERSION_MICRO 101
+#define LIBAVFILTER_VERSION_MINOR  12
+#define LIBAVFILTER_VERSION_MICRO 100
 
 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \
                                                LIBAVFILTER_VERSION_MINOR, \
diff --git a/tests/fate/filter-audio.mak b/tests/fate/filter-audio.mak
index bd8b3d3c35..2da644a66f 100644
--- a/tests/fate/filter-audio.mak
+++ b/tests/fate/filter-audio.mak
@@ -347,3 +347,8 @@  fate-filter-formats: CMD = run libavfilter/tests/formats
 FATE_SAMPLES_AVCONV += $(FATE_AFILTER_SAMPLES-yes)
 FATE_FFMPEG += $(FATE_AFILTER-yes)
 fate-afilter: $(FATE_AFILTER-yes) $(FATE_AFILTER_SAMPLES-yes)
+
+FATE_AFILTER-$(call FILTERDEMDEC, DUMPWAVE, WAV, PCM_S16LE) += fate-filter-dumpwave
+fate-filter-dumpwave: SRC = $(TARGET_PATH)/tests/data/asynth-44100-2.wav
+fate-filter-dumpwave: CMD = ffmpeg -i $(SRC) -af dumpwave=s=1800x140:c=147:json=tests/data/fate/filter-dumpwave.out -f null - && cat tests/data/fate/filter-dumpwave.out
+
diff --git a/tests/ref/fate/filter-dumpwave b/tests/ref/fate/filter-dumpwave
new file mode 100644
index 0000000000..bd07098ef8
--- /dev/null
+++ b/tests/ref/fate/filter-dumpwave
@@ -0,0 +1 @@ 
+{"width":1800,"height":140,"samples":[103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,102,104,103,102,104,104,102,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,102,104,103,102,104,104,102,103,104,102,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,102,104,104,102,103,104,102,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,102,103,104,102,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,102,104,103,103,103,104,103,103,104,103,103,104,103,103,104,103,103,103,104,103,103,104,103,103,104,103,102,104,103,102,104,103,102,103,104,102,103,104,103,101,104,105,104,103,102,104,104,102,102,103,104,104,103,103,104,104,102,103,103,103,104,103,103,103,104,102,104,103,103,104,103,103,103,103,103,103,103,103,103,104,103,103,103,104,103,103,103,103,104,103,103,103,103,103,103,103,104,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,104,104,103,103,103,103,103,103,103,103,103,103,103,102,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,104,103,103,105,103,103,103,103,103,103,104,103,103,103,104,103,103,103,104,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,106,106,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,100,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,96,98,100,97,99,98,97,97,97,96,98,97,98,97,99,98,97,95,98,97,98,101,96,95,96,94,97,99,99,95,98,96,98,98,98,98,98,98,99,98,96,97,99,97,98,99,99,99,96,98,97,98,98,99,99,97,100,95,98,97,99,94,98,96,99,98,97,99,98,97,96,98,95,96,97,100,99,96,99,97,97,97,99,98,97,96,97,97,99,99,100,95,99,98,95,96,99,97,99,99,95,98,96,97,96,99,96,97,98,96,97,95,97,99,99,96,99,96,98,98,96,96,97,96,99,98,97,98,100,98,100,96,98,98,99,97,99,99,99,97,99,97,99,99,98,96,100,97,95,112,119,121,121,118,122,122,120,121,123,121,120,118,121,120,121,123,124,122,120,124,121,122,121,120,121,122,122,120,119,118,122,121,122,121,120,123,120,121,122,121,121,119,120,119,120,121,121,120,122,120,122,123,122,124,122,120,122,121,121,119,122,123,123,122,120,121,119,123,121,125,119,121,119,120,121,121,121,123,122,120,122,123,120,120,123,121,119,122,120,122,123,121,123,121,121,123,120,120,121,123,123,120,122,122,119,120,122,122,120,122,122,120,123,120,121,120,121,121,123,120,121,119,122,117,124,122,122,119,120,122,121,121,123,121,120,121,122,120,121,123,121,119,123,120,123,125,121,120,121,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,104,103,104,103,103,104,103,104,103,104,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,104,103,104,102,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,104,103,103,103,104,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,104,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,103,104,103,103,103,103,103,103,103,102,81,75,74,74,75,76,78,78,79,81,81,82,84,84,84,86,86,86,87,88,88,88,90,88,89,90,89,89,90,89,89,89,89,88,88,89,87,87,87,85,85,85,83,83,82,81,80,79,78,76,75,75,73,75,80,76,79,75,74,74,76,77,77,79,81,80,82,84,83,84,86,86,86,88,87,87,89,89,88,89,90,89,89,90,89,89,90,89,88,89,88,87,87,87,86,85,86,84,83,83,81,80,80,78,77,76,75,73,74,76,81,81,76,73,74,75,76,76,79,79,80,82,83,83,84,85,85,86,87,87,87,89,88,88,90,89,89,89,90,89,89,90,89,89,89,88,87,88,87,86,86,86,84,84,84,82,81,81,78,77,77,75,74,74,74,79,76,79,74,74,75,75,76,78,78,80,81,82,82,84,84,85,86,87,86,87,89,88,88,90,89,89,90,89,89,90,89,89,89,89,88,88,89,87,86,87,85,84,85,83,82,82,81,79,79,78,75,75,75,73,75,81,80,76,74,74,75,76,77,78,79,81,81,82,84,84,84,86,86,86,88,88,87,88,89,88,89,90,89,89,90,89,89,90,89,88,89,88,87,87,87,85,85,85,83,83,83,81,80,80,78,76,75,75,73,74,78,76,79,75,74,74,75,77,77,79,80,80,82,84,83,84,86,85,86,88,87,87,89,89,88,89,90,89,89,90,89,89,90,89,88,89,88,87,88,87,86,86,85,84,83,83,81,80,80,78,77,76,75,74,74,75,81,80,77,73,74,75,75,76,79,79,80,82,82,83,84,85,85,86,87,87,87,89,88,88,90,89,89,90,90,89,89,90,89,89,89,88,88,88,87,86,86,85,84,84,83,82,81,81,79,78,78,75,74,75,74,77,76,80,75,74,74,75,76,78,78,80,81,81,82,84,84,85,86,87,86,87,88,88,88,90,89,89,90,89,89,90,89,89,89,89,88,88,89,87,87,87,85,84,85,83,82,82,81,79,79,78,76,75,75,73,75,80,81,76,75,74,75,76,77,78,79,81,81,82,84,84,84,86,86,86,88,88,87,89,89,88,89,90,89,89,90,89,89,90,89,88,89,88,87,87,87,85,85,86,83,83,83,81,80,80,78,77,76,75,73,74,77,78,79,76,74,74,75,76,77,79,80,80,82,83,83,84,86,85,86,88,87,87,89,88,88,90,89,89,90,90,89,89,90,89,88,89,88,87,88,87,86,86,85,84,84,83,81,81,80,78,77,77,75,74,74,75,80,81,78,74,74,75,75,76,79,79,80,82,82,82,85,85,85,86,87,86,87,89,88,88,90,89,89,90,89,89,90,90,89,89,89,88,88,88,87,86,87,85,84,0,0,0,0,0,0,0]}