From 7d76294ce006692d341beedada7a5cde017f1697 Mon Sep 17 00:00:00 2001 From: Paul B Mahol Date: Tue, 29 Dec 2015 21:22:26 +0100 Subject: avfilter: add ahistogram multimedia filter Signed-off-by: Paul B Mahol --- libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/avf_ahistogram.c | 411 +++++++++++++++++++++++++++++++++++++++++++ libavfilter/version.h | 2 +- 4 files changed, 414 insertions(+), 1 deletion(-) create mode 100644 libavfilter/avf_ahistogram.c (limited to 'libavfilter') diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 9257a92284..e3e3561933 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -280,6 +280,7 @@ OBJS-$(CONFIG_NULLSINK_FILTER) += vsink_nullsink.o # multimedia filters OBJS-$(CONFIG_ADRAWGRAPH_FILTER) += f_drawgraph.o +OBJS-$(CONFIG_AHISTOGRAM_FILTER) += avf_ahistogram.o OBJS-$(CONFIG_APHASEMETER_FILTER) += avf_aphasemeter.o OBJS-$(CONFIG_AVECTORSCOPE_FILTER) += avf_avectorscope.o OBJS-$(CONFIG_CONCAT_FILTER) += avf_concat.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index d4815d6585..1faf39301a 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -300,6 +300,7 @@ void avfilter_register_all(void) /* multimedia filters */ REGISTER_FILTER(ADRAWGRAPH, adrawgraph, avf); + REGISTER_FILTER(AHISTOGRAM, ahistogram, avf); REGISTER_FILTER(APHASEMETER, aphasemeter, avf); REGISTER_FILTER(AVECTORSCOPE, avectorscope, avf); REGISTER_FILTER(CONCAT, concat, avf); diff --git a/libavfilter/avf_ahistogram.c b/libavfilter/avf_ahistogram.c new file mode 100644 index 0000000000..55f7462676 --- /dev/null +++ b/libavfilter/avf_ahistogram.c @@ -0,0 +1,411 @@ +/* + * Copyright (c) 2015 Paul B Mahol + * + * 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 + */ + +#include "libavutil/avassert.h" +#include "libavutil/opt.h" +#include "libavutil/parseutils.h" +#include "avfilter.h" +#include "formats.h" +#include "audio.h" +#include "video.h" +#include "internal.h" + +enum DisplayScale { LINEAR, SQRT, CBRT, LOG, RLOG, NB_SCALES }; +enum AmplitudeScale { ALINEAR, ALOG, NB_ASCALES }; +enum SlideMode { REPLACE, SCROLL, NB_SLIDES }; +enum DisplayMode { SINGLE, SEPARATE, NB_DMODES }; +enum HistogramMode { ACCUMULATE, CURRENT, NB_HMODES }; + +typedef struct AudioHistogramContext { + const AVClass *class; + AVFrame *out; + int w, h; + AVRational frame_rate; + uint64_t *achistogram; + uint64_t *shistogram; + int ascale; + int scale; + float phisto; + int histogram_h; + int apos; + int ypos; + int slide; + int dmode; + int dchannels; + int count; + int frame_count; + float *combine_buffer; + AVFrame *in[101]; + int first; +} AudioHistogramContext; + +#define OFFSET(x) offsetof(AudioHistogramContext, x) +#define FLAGS AV_OPT_FLAG_FILTERING_PARAM|AV_OPT_FLAG_VIDEO_PARAM + +static const AVOption ahistogram_options[] = { + { "dmode", "set method to display channels", OFFSET(dmode), AV_OPT_TYPE_INT, {.i64=SINGLE}, 0, NB_DMODES-1, FLAGS, "dmode" }, + { "single", "all channels use single histogram", 0, AV_OPT_TYPE_CONST, {.i64=SINGLE}, 0, 0, FLAGS, "dmode" }, + { "separate", "each channel have own histogram", 0, AV_OPT_TYPE_CONST, {.i64=SEPARATE}, 0, 0, FLAGS, "dmode" }, + { "rate", "set video rate", OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str="25"}, 0, 0, FLAGS }, + { "r", "set video rate", OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, {.str="25"}, 0, 0, FLAGS }, + { "size", "set video size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str="hd720"}, 0, 0, FLAGS }, + { "s", "set video size", OFFSET(w), AV_OPT_TYPE_IMAGE_SIZE, {.str="hd720"}, 0, 0, FLAGS }, + { "scale", "set display scale", OFFSET(scale), AV_OPT_TYPE_INT, {.i64=LOG}, LINEAR, NB_SCALES-1, FLAGS, "scale" }, + { "log", "logarithmic", 0, AV_OPT_TYPE_CONST, {.i64=LOG}, 0, 0, FLAGS, "scale" }, + { "sqrt", "square root", 0, AV_OPT_TYPE_CONST, {.i64=SQRT}, 0, 0, FLAGS, "scale" }, + { "cbrt", "cubic root", 0, AV_OPT_TYPE_CONST, {.i64=CBRT}, 0, 0, FLAGS, "scale" }, + { "lin", "linear", 0, AV_OPT_TYPE_CONST, {.i64=LINEAR}, 0, 0, FLAGS, "scale" }, + { "rlog", "reverse logarithmic", 0, AV_OPT_TYPE_CONST, {.i64=RLOG}, 0, 0, FLAGS, "scale" }, + { "ascale", "set amplitude scale", OFFSET(ascale), AV_OPT_TYPE_INT, {.i64=ALOG}, LINEAR, NB_ASCALES-1, FLAGS, "ascale" }, + { "log", "logarithmic", 0, AV_OPT_TYPE_CONST, {.i64=ALOG}, 0, 0, FLAGS, "ascale" }, + { "lin", "linear", 0, AV_OPT_TYPE_CONST, {.i64=ALINEAR}, 0, 0, FLAGS, "ascale" }, + { "acount", "how much frames to accumulate", OFFSET(count), AV_OPT_TYPE_INT, {.i64=1}, -1, 100, FLAGS }, + { "rheight", "set histogram ratio of window height", OFFSET(phisto), AV_OPT_TYPE_FLOAT, {.dbl=0.10}, 0, 1, FLAGS }, + { "slide", "set sonogram sliding", OFFSET(slide), AV_OPT_TYPE_INT, {.i64=REPLACE}, 0, NB_SLIDES-1, FLAGS, "slide" }, + { "replace", "replace old rows with new", 0, AV_OPT_TYPE_CONST, {.i64=REPLACE}, 0, 0, FLAGS, "slide" }, + { "scroll", "scroll from top to bottom", 0, AV_OPT_TYPE_CONST, {.i64=SCROLL}, 0, 0, FLAGS, "slide" }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(ahistogram); + +static int query_formats(AVFilterContext *ctx) +{ + AVFilterFormats *formats = NULL; + AVFilterChannelLayouts *layouts = NULL; + AVFilterLink *inlink = ctx->inputs[0]; + AVFilterLink *outlink = ctx->outputs[0]; + static const enum AVSampleFormat sample_fmts[] = { AV_SAMPLE_FMT_FLTP, AV_SAMPLE_FMT_NONE }; + static const enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_YUVA444P, AV_PIX_FMT_NONE }; + int ret = AVERROR(EINVAL); + + formats = ff_make_format_list(sample_fmts); + if ((ret = ff_formats_ref (formats, &inlink->out_formats )) < 0 || + (layouts = ff_all_channel_counts()) == NULL || + (ret = ff_channel_layouts_ref (layouts, &inlink->out_channel_layouts)) < 0) + return ret; + + formats = ff_all_samplerates(); + if ((ret = ff_formats_ref(formats, &inlink->out_samplerates)) < 0) + return ret; + + formats = ff_make_format_list(pix_fmts); + if ((ret = ff_formats_ref(formats, &outlink->in_formats)) < 0) + return ret; + + return 0; +} + +static int config_input(AVFilterLink *inlink) +{ + AVFilterContext *ctx = inlink->dst; + AudioHistogramContext *s = ctx->priv; + int nb_samples; + + nb_samples = FFMAX(1024, ((double)inlink->sample_rate / av_q2d(s->frame_rate)) + 0.5); + inlink->partial_buf_size = + inlink->min_samples = + inlink->max_samples = nb_samples; + + s->dchannels = s->dmode == SINGLE ? 1 : inlink->channels; + s->shistogram = av_calloc(s->w, s->dchannels * sizeof(*s->shistogram)); + if (!s->shistogram) + return AVERROR(ENOMEM); + + s->achistogram = av_calloc(s->w, s->dchannels * sizeof(*s->achistogram)); + if (!s->achistogram) + return AVERROR(ENOMEM); + + return 0; +} + +static int config_output(AVFilterLink *outlink) +{ + AudioHistogramContext *s = outlink->src->priv; + + outlink->w = s->w; + outlink->h = s->h; + outlink->sample_aspect_ratio = (AVRational){1,1}; + outlink->frame_rate = s->frame_rate; + + s->histogram_h = s->h * s->phisto; + s->ypos = s->h * s->phisto; + + if (s->dmode == SEPARATE) { + s->combine_buffer = av_malloc_array(outlink->w * 3, sizeof(*s->combine_buffer)); + if (!s->combine_buffer) + return AVERROR(ENOMEM); + } + + return 0; +} + +static int filter_frame(AVFilterLink *inlink, AVFrame *in) +{ + AVFilterContext *ctx = inlink->dst; + AVFilterLink *outlink = ctx->outputs[0]; + AudioHistogramContext *s = ctx->priv; + const int H = s->histogram_h; + const int w = s->w; + int c, y, n, p, bin; + uint64_t acmax = 0; + + if (!s->out || s->out->width != outlink->w || + s->out->height != outlink->h) { + av_frame_free(&s->out); + s->out = ff_get_video_buffer(outlink, outlink->w, outlink->h); + if (!s->out) { + av_frame_free(&in); + return AVERROR(ENOMEM); + } + for (n = H; n < s->h; n++) { + memset(s->out->data[0] + n * s->out->linesize[0], 0, w); + memset(s->out->data[1] + n * s->out->linesize[0], 127, w); + memset(s->out->data[2] + n * s->out->linesize[0], 127, w); + memset(s->out->data[3] + n * s->out->linesize[0], 0, w); + } + } + + if (s->dmode == SEPARATE) { + for (y = 0; y < w; y++) { + s->combine_buffer[3 * y ] = 0; + s->combine_buffer[3 * y + 1] = 127.5; + s->combine_buffer[3 * y + 2] = 127.5; + } + } + + for (n = 0; n < H; n++) { + memset(s->out->data[0] + n * s->out->linesize[0], 0, w); + memset(s->out->data[1] + n * s->out->linesize[0], 127, w); + memset(s->out->data[2] + n * s->out->linesize[0], 127, w); + memset(s->out->data[3] + n * s->out->linesize[0], 0, w); + } + s->out->pts = in->pts; + + s->first = s->frame_count; + + switch (s->ascale) { + case ALINEAR: + for (c = 0; c < inlink->channels; c++) { + const float *src = (const float *)in->extended_data[c]; + uint64_t *achistogram = &s->achistogram[(s->dmode == SINGLE ? 0: c) * w]; + + for (n = 0; n < in->nb_samples; n++) { + bin = lrint(av_clipf(fabsf(src[n]), 0, 1) * (w - 1)); + + achistogram[bin]++; + } + + if (s->in[s->first] && s->count >= 0) { + uint64_t *shistogram = &s->shistogram[(s->dmode == SINGLE ? 0: c) * w]; + const float *src2 = (const float *)s->in[s->first]->extended_data[c]; + + for (n = 0; n < in->nb_samples; n++) { + bin = lrint(av_clipf(fabsf(src2[n]), 0, 1) * (w - 1)); + + shistogram[bin]++; + } + } + } + break; + case ALOG: + for (c = 0; c < inlink->channels; c++) { + const float *src = (const float *)in->extended_data[c]; + uint64_t *achistogram = &s->achistogram[(s->dmode == SINGLE ? 0: c) * w]; + + for (n = 0; n < in->nb_samples; n++) { + bin = lrint(av_clipf(1 + log10(fabsf(src[n])) / 6, 0, 1) * (w - 1)); + + achistogram[bin]++; + } + + if (s->in[s->first] && s->count >= 0) { + uint64_t *shistogram = &s->shistogram[(s->dmode == SINGLE ? 0: c) * w]; + const float *src2 = (const float *)s->in[s->first]->extended_data[c]; + + for (n = 0; n < in->nb_samples; n++) { + bin = lrint(av_clipf(1 + log10(fabsf(src2[n])) / 6, 0, 1) * (w - 1)); + + shistogram[bin]++; + } + } + } + break; + } + + av_frame_free(&s->in[s->frame_count]); + s->in[s->frame_count] = in; + s->frame_count++; + if (s->frame_count > s->count) + s->frame_count = 0; + + for (n = 0; n < w * s->dchannels; n++) { + acmax = FFMAX(s->achistogram[n] - s->shistogram[n], acmax); + } + + for (c = 0; c < s->dchannels; c++) { + uint64_t *shistogram = &s->shistogram[c * w]; + uint64_t *achistogram = &s->achistogram[c * w]; + float yf, uf, vf; + + if (s->dmode == SEPARATE) { + yf = 256.0f / s->dchannels; + uf = yf * M_PI; + vf = yf * M_PI; + uf *= 0.5 * sin((2 * M_PI * c) / s->dchannels); + vf *= 0.5 * cos((2 * M_PI * c) / s->dchannels); + } + + for (n = 0; n < w; n++) { + double a, aa; + int h; + + a = achistogram[n] - shistogram[n]; + + switch (s->scale) { + case LINEAR: + aa = a / (double)acmax; + break; + case SQRT: + aa = sqrt(a) / sqrt(acmax); + break; + case CBRT: + aa = cbrt(a) / cbrt(acmax); + break; + case LOG: + aa = log2(a + 1) / log2(acmax + 1); + break; + case RLOG: + aa = 1. - log2(a + 1) / log2(acmax + 1); + if (aa == 1.) + aa = 0; + break; + } + + h = aa * (H - 1); + + if (s->dmode == SINGLE) { + + for (y = H - h; y < H; y++) { + s->out->data[0][y * s->out->linesize[0] + n] = 255; + s->out->data[3][y * s->out->linesize[0] + n] = 255; + } + + if (s->h - H > 0) { + h = aa * 255; + + s->out->data[0][s->ypos * s->out->linesize[0] + n] = h; + s->out->data[1][s->ypos * s->out->linesize[1] + n] = 127; + s->out->data[2][s->ypos * s->out->linesize[2] + n] = 127; + s->out->data[3][s->ypos * s->out->linesize[3] + n] = 255; + } + } else if (s->dmode == SEPARATE) { + float *out = &s->combine_buffer[3 * n]; + int old; + + old = s->out->data[0][(H - h) * s->out->linesize[0] + n]; + for (y = H - h; y < H; y++) { + if (s->out->data[0][y * s->out->linesize[0] + n] != old) + break; + old = s->out->data[0][y * s->out->linesize[0] + n]; + s->out->data[0][y * s->out->linesize[0] + n] = yf; + s->out->data[1][y * s->out->linesize[1] + n] = 128+uf; + s->out->data[2][y * s->out->linesize[2] + n] = 128+vf; + s->out->data[3][y * s->out->linesize[3] + n] = 255; + } + + out[0] += aa * yf; + out[1] += aa * uf; + out[2] += aa * vf; + } + } + } + + if (s->h - H > 0) { + if (s->dmode == SEPARATE) { + for (n = 0; n < w; n++) { + float *cb = &s->combine_buffer[3 * n]; + + s->out->data[0][s->ypos * s->out->linesize[0] + n] = cb[0]; + s->out->data[1][s->ypos * s->out->linesize[1] + n] = cb[1]; + s->out->data[2][s->ypos * s->out->linesize[2] + n] = cb[2]; + s->out->data[3][s->ypos * s->out->linesize[3] + n] = 255; + } + } + + if (s->slide == SCROLL) { + for (p = 0; p < 4; p++) { + for (y = s->h; y >= H + 1; y--) { + memmove(s->out->data[p] + (y ) * s->out->linesize[p], + s->out->data[p] + (y-1) * s->out->linesize[p], w); + } + } + } + + s->ypos++; + if (s->slide == SCROLL || s->ypos >= s->h) + s->ypos = H; + } + + return ff_filter_frame(outlink, av_frame_clone(s->out)); +} + +static av_cold void uninit(AVFilterContext *ctx) +{ + AudioHistogramContext *s = ctx->priv; + int i; + + av_frame_free(&s->out); + av_freep(&s->shistogram); + av_freep(&s->achistogram); + av_freep(&s->combine_buffer); + for (i = 0; i < 101; i++) + av_frame_free(&s->in[i]); +} + +static const AVFilterPad audiovectorscope_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_AUDIO, + .config_props = config_input, + .filter_frame = filter_frame, + }, + { NULL } +}; + +static const AVFilterPad audiovectorscope_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .config_props = config_output, + }, + { NULL } +}; + +AVFilter ff_avf_ahistogram = { + .name = "ahistogram", + .description = NULL_IF_CONFIG_SMALL("Convert input audio to histogram video output."), + .uninit = uninit, + .query_formats = query_formats, + .priv_size = sizeof(AudioHistogramContext), + .inputs = audiovectorscope_inputs, + .outputs = audiovectorscope_outputs, + .priv_class = &ahistogram_class, +}; diff --git a/libavfilter/version.h b/libavfilter/version.h index d13929ed33..d1f3802b68 100644 --- a/libavfilter/version.h +++ b/libavfilter/version.h @@ -30,7 +30,7 @@ #include "libavutil/version.h" #define LIBAVFILTER_VERSION_MAJOR 6 -#define LIBAVFILTER_VERSION_MINOR 24 +#define LIBAVFILTER_VERSION_MINOR 25 #define LIBAVFILTER_VERSION_MICRO 100 #define LIBAVFILTER_VERSION_INT AV_VERSION_INT(LIBAVFILTER_VERSION_MAJOR, \ -- cgit v1.2.3