From patchwork Sat Dec 2 19:13:42 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Paul B Mahol X-Patchwork-Id: 6513 Delivered-To: ffmpegpatchwork@gmail.com Received: by 10.2.161.94 with SMTP id m30csp2635061jah; Sat, 2 Dec 2017 11:14:10 -0800 (PST) X-Google-Smtp-Source: AGs4zMYi36Qe8HylH+A6DEvWbqUjZN0hdXht41CNHtHgOfX/sU6U04ZYmULJULtUj1/C7N19L/vV X-Received: by 10.28.249.26 with SMTP id x26mr4229428wmh.36.1512242050047; Sat, 02 Dec 2017 11:14:10 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1512242050; cv=none; d=google.com; s=arc-20160816; b=snTL+baBvI9pUsU3UV5+kvyD99T783WyZpJmSgay8IjcmUnR2PxZ3DCAKHHauoXSo3 VKFQjKbNSf/uJfX7Ce0dgQlogzUvQKzolXW6Ogb0Y2A/LLkgShlAjQo1kVbfzeqRdHc6 iqOyuyoUi0OfAtmurYhKJVoaXi+gPP5nfUDUrDBWsFl3RflvqffZ6jseLM9/RWB573x7 11T7fAGohd07PoQuw2Fi4TYCct041Euap/zmVxvcmMD9Lw6ilHXx5owxT1/yA35Am5UK gsT1n+Vtu5hV2TJ66C+Sm0/9OXkVSBkICd3BnYr2tLia1jEP4RRaIYjrDQTuoJv3ZOaS BNsA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=sender:errors-to:content-transfer-encoding:mime-version:reply-to :list-subscribe:list-help:list-post:list-archive:list-unsubscribe :list-id:precedence:subject:message-id:date:to:from:dkim-signature :delivered-to:arc-authentication-results; bh=R7uspnRtmlozgqkUkWdHkobo9/uyDRWzYVGHoFKUjW4=; b=XYFxgYceWDfcDKzo7DVp5+jf5sJjjwvdEUoAVJ+2q59YOtJcBngpmPJSq7AKyFtsug r3FWa65w3YLWxDLfN2EcTT1ZRyjH0iRgyeLzSwwrrq+MxuHQlNCfDbviV3u+Fn5nVlfG cQc0mKS/eBlNmr8yTVN6RC0DhQDJPj85i4y61/18XEmp1yV/dGPZdEnCp7NZha0RINv+ QirTZA0Qhz9UNEZjPxRDnZXawIFPumQPSkOzhbO3EUa2cQ3Xn87nlSTbeF+whgL6bsEp gS241U6mYVQJ7MFY+UdHtSjU8imfODzLAl6Bc4Wrn6/7PxoNGGsByzJpXsZYYfzKPbX0 LcnQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20161025 header.b=GE/y1yvc; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=gmail.com Return-Path: Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org. [79.124.17.100]) by mx.google.com with ESMTP id v65si2505801wma.263.2017.12.02.11.14.09; Sat, 02 Dec 2017 11:14:09 -0800 (PST) Received-SPF: pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) client-ip=79.124.17.100; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@gmail.com header.s=20161025 header.b=GE/y1yvc; spf=pass (google.com: domain of ffmpeg-devel-bounces@ffmpeg.org designates 79.124.17.100 as permitted sender) smtp.mailfrom=ffmpeg-devel-bounces@ffmpeg.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=gmail.com Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 25FE868A41E; Sat, 2 Dec 2017 21:14:04 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from mail-wr0-f176.google.com (mail-wr0-f176.google.com [209.85.128.176]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTPS id 737E068A327 for ; Sat, 2 Dec 2017 21:13:57 +0200 (EET) Received: by mail-wr0-f176.google.com with SMTP id y21so13223516wrc.1 for ; Sat, 02 Dec 2017 11:14:01 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:subject:date:message-id; bh=4u0jv0zc4hiIqv0qGgBnwYZ9g4MlFP6V1pl9zXUlIzc=; b=GE/y1yvcOTdBBc7jDJPd5f+nUvSqg95jJyPQwUGv2jKJYy8awl7oC8GJK5YNnQOCu8 xI7aXkkpwPj/fq2obzs3b2M9qPgEcaeQsohLzi2d70yQ0VyPZWM9wmc01DAdaLzzDIAo rtXsiIC2BpJRtDBTZnTzAzKdXK/tLXq8dic17+y+qh16geu4/6OBVeJuFwjW841WH++r 90ZHJLAoxRp5XvK4vnRMfvSYsIkQFxUJQR+RYAe8yRsJ4kql1WG1//bXns0JWPqcSxYs eAhEM8pP42GK8QuFGoINm0PfwzuRaivIZSt8sYpypzw7GSVzzDj/goZu+He0/n3XHnKt fO1Q== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id; bh=4u0jv0zc4hiIqv0qGgBnwYZ9g4MlFP6V1pl9zXUlIzc=; b=GfyiaqqEsfYqPxtkha1X1N37UDiJ/Zd+HgMd1DoWfeV+y7PsxRobHqT91bjUi6JZd2 m5m5dR1kA8l/cEOB1YCRPyBK+TUuiJ0WsnIIF9NFUYflDG/VEllX5vuKtpNldesJ4bzi uuGNilFCVC52wHY5zFHKgZ1KuQyEI8X6g9XfIp4eXP2zjJY9yGYv3QgpnzLby1fIsEqB Jt0deaqJJ3tYqvEMTWjVIvOxr3V9GsjnU2DnnQObwNG+en7mVW/6BCXIpclcC4clnaGC XJ9IZfhBi93MxQlRa59PIG0w7Q39eU96oi0H2Gr9lhD8lONUcwXxUsJdKEdlgeSZ/msP DJZA== X-Gm-Message-State: AJaThX4V87zpLzG3Pi68dDzuqt5z59Cu9QJ9akR86yl5UqWe1U5+ZHIb UJR/s9bSfeTO22yqDFuxvyTW+g== X-Received: by 10.223.135.169 with SMTP id b38mr8407337wrb.278.1512242040679; Sat, 02 Dec 2017 11:14:00 -0800 (PST) Received: from localhost.localdomain ([94.250.174.60]) by smtp.gmail.com with ESMTPSA id j10sm9522845wrh.32.2017.12.02.11.13.56 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sat, 02 Dec 2017 11:14:00 -0800 (PST) From: Paul B Mahol To: ffmpeg-devel@ffmpeg.org Date: Sat, 2 Dec 2017 20:13:42 +0100 Message-Id: <20171202191342.25358-1-onemda@gmail.com> X-Mailer: git-send-email 2.11.0 Subject: [FFmpeg-devel] [PATCH] avfilter: port scaletempo filter from mpv X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches MIME-Version: 1.0 Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" Signed-off-by: Paul B Mahol --- libavfilter/Makefile | 1 + libavfilter/af_scaletempo.c | 529 ++++++++++++++++++++++++++++++++++++++++++++ libavfilter/allfilters.c | 1 + 3 files changed, 531 insertions(+) create mode 100644 libavfilter/af_scaletempo.c diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 1c0cc1da80..4c025c8d07 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -107,6 +107,7 @@ OBJS-$(CONFIG_PAN_FILTER) += af_pan.o OBJS-$(CONFIG_REPLAYGAIN_FILTER) += af_replaygain.o OBJS-$(CONFIG_RESAMPLE_FILTER) += af_resample.o OBJS-$(CONFIG_RUBBERBAND_FILTER) += af_rubberband.o +OBJS-$(CONFIG_SCALETEMPO_FILTER) += af_scaletempo.o OBJS-$(CONFIG_SIDECHAINCOMPRESS_FILTER) += af_sidechaincompress.o OBJS-$(CONFIG_SIDECHAINGATE_FILTER) += af_agate.o OBJS-$(CONFIG_SILENCEDETECT_FILTER) += af_silencedetect.o diff --git a/libavfilter/af_scaletempo.c b/libavfilter/af_scaletempo.c new file mode 100644 index 0000000000..1e673d3e34 --- /dev/null +++ b/libavfilter/af_scaletempo.c @@ -0,0 +1,529 @@ +/* + * scaletempo audio filter + * + * scale tempo while maintaining pitch + * (WSOLA technique with cross correlation) + * inspired by SoundTouch library by Olli Parviainen + * + * basic algorithm + * - produce 'stride' output samples per loop + * - consume stride*scale input samples per loop + * + * to produce smoother transitions between strides, blend next overlap + * samples from last stride with correlated samples of current input + * + * Copyright (c) 2007 Robert Juliano + * + * 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, see . + */ + +#include +#include +#include +#include + +#include "libavutil/channel_layout.h" +#include "libavutil/opt.h" +#include "libavutil/samplefmt.h" + +#include "avfilter.h" +#include "audio.h" +#include "internal.h" + +typedef struct ScaleTempoContext +{ + AVClass *class; + + // stride + float scale; + float speed; + int frames_stride; + float frames_stride_scaled; + float frames_stride_error; + int bytes_per_frame; + int bytes_stride; + int bytes_queue; + int bytes_queued; + int bytes_to_slide; + int8_t *buf_queue; + // overlap + int samples_overlap; + int samples_standing; + int bytes_overlap; + int bytes_standing; + void *buf_overlap; + void *table_blend; + void (*output_overlap)(struct ScaleTempoContext *s, void *out_buf, + int bytes_off); + // best overlap + int frames_search; + int num_channels; + void *buf_pre_corr; + void *table_window; + int (*best_overlap_offset)(struct ScaleTempoContext *s); + // command line + float scale_nominal; + float ms_stride; + float percent_overlap; + float ms_search; +#define SCALE_TEMPO 1 +#define SCALE_PITCH 2 + int speed_opt; + + int64_t pts; +} ScaleTempoContext; + +static int query_formats(AVFilterContext *ctx) +{ + AVFilterChannelLayouts *layouts = NULL; + AVFilterFormats *formats = NULL; + static const enum AVSampleFormat sample_fmts[] = { + AV_SAMPLE_FMT_S16, + AV_SAMPLE_FMT_FLT, + AV_SAMPLE_FMT_NONE + }; + int ret; + + layouts = ff_all_channel_counts(); + if (!layouts) { + return AVERROR(ENOMEM); + } + ret = ff_set_common_channel_layouts(ctx, layouts); + if (ret < 0) + return ret; + + formats = ff_make_format_list(sample_fmts); + if (!formats) { + return AVERROR(ENOMEM); + } + ret = ff_set_common_formats(ctx, formats); + if (ret < 0) + return ret; + + formats = ff_all_samplerates(); + if (!formats) { + return AVERROR(ENOMEM); + } + return ff_set_common_samplerates(ctx, formats); +} + +static int fill_queue(AVFilterContext *ctx, AVFrame *in, int offset) +{ + ScaleTempoContext *s = ctx->priv; + int bytes_in = in->nb_samples * s->bytes_per_frame - offset; + int offset_unchanged = offset; + + if (s->bytes_to_slide > 0) { + if (s->bytes_to_slide < s->bytes_queued) { + int bytes_move = s->bytes_queued - s->bytes_to_slide; + + memmove(s->buf_queue, s->buf_queue + s->bytes_to_slide, bytes_move); + s->bytes_to_slide = 0; + s->bytes_queued = bytes_move; + } else { + int bytes_skip; + + s->bytes_to_slide -= s->bytes_queued; + bytes_skip = FFMIN(s->bytes_to_slide, bytes_in); + s->bytes_queued = 0; + s->bytes_to_slide -= bytes_skip; + offset += bytes_skip; + bytes_in -= bytes_skip; + } + } + + if (bytes_in > 0) { + int bytes_copy = FFMIN(s->bytes_queue - s->bytes_queued, bytes_in); + + memcpy(s->buf_queue + s->bytes_queued, in->data[0] + offset, bytes_copy); + s->bytes_queued += bytes_copy; + offset += bytes_copy; + } + + return offset - offset_unchanged; +} + +#define UNROLL_PADDING (4 * 4) + +static int best_overlap_offset_float(ScaleTempoContext *s) +{ + float best_corr = INT_MIN; + int i, off, best_off = 0; + float *ppc; + float *pw = s->table_window; + float *po = s->buf_overlap; + float *search_start; + + po += s->num_channels; + ppc = s->buf_pre_corr; + for (i = s->num_channels; i < s->samples_overlap; i++) + *ppc++ = *pw++ **po++; + + search_start = (float *)s->buf_queue + s->num_channels; + for (off = 0; off < s->frames_search; off++) { + float corr = 0; + float *ps = search_start; + ppc = s->buf_pre_corr; + for (i = s->num_channels; i < s->samples_overlap; i++) + corr += *ppc++ **ps++; + if (corr > best_corr) { + best_corr = corr; + best_off = off; + } + search_start += s->num_channels; + } + + return best_off * 4 * s->num_channels; +} + +static int best_overlap_offset_s16(ScaleTempoContext *s) +{ + int64_t best_corr = INT64_MIN; + int i, off, best_off = 0; + int32_t *ppc; + int16_t *search_start; + + int32_t *pw = s->table_window; + int16_t *po = s->buf_overlap; + po += s->num_channels; + ppc = s->buf_pre_corr; + for (i = s->num_channels; i < s->samples_overlap; i++) + *ppc++ = (*pw++ **po++) >> 15; + + search_start = (int16_t *)s->buf_queue + s->num_channels; + for (off = 0; off < s->frames_search; off++) { + int64_t corr = 0; + int16_t *ps = search_start; + long i; + + ppc = s->buf_pre_corr; + ppc += s->samples_overlap - s->num_channels; + ps += s->samples_overlap - s->num_channels; + i = -(s->samples_overlap - s->num_channels); + do { + corr += ppc[i + 0] * ps[i + 0]; + corr += ppc[i + 1] * ps[i + 1]; + corr += ppc[i + 2] * ps[i + 2]; + corr += ppc[i + 3] * ps[i + 3]; + i += 4; + } while (i < 0); + if (corr > best_corr) { + best_corr = corr; + best_off = off; + } + search_start += s->num_channels; + } + + return best_off * 2 * s->num_channels; +} + +static void output_overlap_float(ScaleTempoContext *s, void *buf_out, + int bytes_off) +{ + float *pout = buf_out; + float *pb = s->table_blend; + float *po = s->buf_overlap; + float *pin = (float *)(s->buf_queue + bytes_off); + int i; + + for (i = 0; i < s->samples_overlap; i++) { + *pout++ = *po - *pb++ *(*po - *pin++); + po++; + } +} + +static void output_overlap_s16(ScaleTempoContext *s, void *buf_out, + int bytes_off) +{ + int16_t *pout = buf_out; + int32_t *pb = s->table_blend; + int16_t *po = s->buf_overlap; + int16_t *pin = (int16_t *)(s->buf_queue + bytes_off); + int i; + + for (i = 0; i < s->samples_overlap; i++) { + *pout++ = *po - ((*pb++ *(*po - *pin++)) >> 16); + po++; + } +} + +static int filter_frame(AVFilterLink *inlink, AVFrame *in) +{ + AVFilterContext *ctx = inlink->dst; + AVFilterLink *outlink = ctx->outputs[0]; + ScaleTempoContext *s = ctx->priv; + int offset_in, in_samples, ret = 0; + uint8_t *pout; + AVFrame *out; + + if (s->scale == 1.0) { + return ff_filter_frame(outlink, in); + } + + if (s->pts == AV_NOPTS_VALUE) + s->pts = in->pts; + + in_samples = in->nb_samples; + out = ff_get_audio_buffer(outlink, ((int)(in_samples / s->frames_stride_scaled) + 1) * s->frames_stride); + if (!out) { + av_frame_free(&in); + return AVERROR(ENOMEM); + } + av_frame_copy_props(out, in); + + offset_in = fill_queue(ctx, in, 0); + pout = out->data[0]; + while (s->bytes_queued >= s->bytes_queue) { + int bytes_off = 0, ti; + float tf; + + // output stride + if (s->output_overlap) { + if (s->best_overlap_offset) + bytes_off = s->best_overlap_offset(s); + s->output_overlap(s, pout, bytes_off); + } + memcpy(pout + s->bytes_overlap, + s->buf_queue + bytes_off + s->bytes_overlap, + s->bytes_standing); + pout += s->bytes_stride; + + // input stride + memcpy(s->buf_overlap, + s->buf_queue + bytes_off + s->bytes_stride, + s->bytes_overlap); + tf = s->frames_stride_scaled + s->frames_stride_error; + ti = (int)tf; + s->frames_stride_error = tf - ti; + s->bytes_to_slide = ti * s->bytes_per_frame; + + offset_in += fill_queue(ctx, in, offset_in); + } + + out->nb_samples = (pout - (uint8_t *)out->data[0]) / (s->bytes_per_frame); + av_frame_free(&in); + if (out->nb_samples) { + out->pts = s->pts; + s->pts += av_rescale_q(out->nb_samples, + (AVRational){1, outlink->sample_rate}, + outlink->time_base); + + ret = ff_filter_frame(outlink, out); + } else { + av_frame_free(&out); + } + return ret; +} + +static void update_speed(AVFilterContext *ctx, float speed) +{ + ScaleTempoContext *s = ctx->priv; + double factor; + + s->speed = speed; + + factor = (s->speed_opt & SCALE_PITCH) ? 1.0 / s->speed : s->speed; + s->scale = factor * s->scale_nominal; + + s->frames_stride_scaled = s->scale * s->frames_stride; + s->frames_stride_error = FFMIN(s->frames_stride_error, s->frames_stride_scaled); +} + +static int config_input(AVFilterLink *inlink) +{ + AVFilterContext *ctx = inlink->dst; + ScaleTempoContext *s = ctx->priv; + float srate = inlink->sample_rate / 1000.0; + int frames_overlap, nch = inlink->channels; + int bps, use_int = 0; + + if (inlink->format == AV_SAMPLE_FMT_S16) { + use_int = 1; + bps = 2; + } else { + bps = 4; + } + + s->frames_stride = srate * s->ms_stride; + s->bytes_stride = s->frames_stride * bps * nch; + s->speed = 1.f; + s->pts = AV_NOPTS_VALUE; + + update_speed(ctx, s->speed); + + frames_overlap = s->frames_stride * s->percent_overlap; + if (frames_overlap <= 0) { + s->bytes_standing = s->bytes_stride; + s->samples_standing = s->bytes_standing / bps; + s->output_overlap = NULL; + s->bytes_overlap = 0; + } else { + s->samples_overlap = frames_overlap * nch; + s->bytes_overlap = frames_overlap * nch * bps; + s->bytes_standing = s->bytes_stride - s->bytes_overlap; + s->samples_standing = s->bytes_standing / bps; + s->buf_overlap = av_realloc(s->buf_overlap, s->bytes_overlap); + s->table_blend = av_realloc(s->table_blend, s->bytes_overlap * 4); + if (!s->buf_overlap || !s->table_blend) { + return AVERROR(ENOMEM); + } + memset(s->buf_overlap, 0, s->bytes_overlap); + if (use_int) { + int32_t *pb = s->table_blend; + int64_t blend = 0; + int i, j; + + for (i = 0; i < frames_overlap; i++) { + int32_t v = blend / frames_overlap; + for (j = 0; j < nch; j++) + *pb++ = v; + blend += 65536; // 2^16 + } + s->output_overlap = output_overlap_s16; + } else { + float *pb = s->table_blend; + int i; + + for (i = 0; i < frames_overlap; i++) { + float v = i / (float)frames_overlap; + int j; + + for (j = 0; j < nch; j++) + *pb++ = v; + } + s->output_overlap = output_overlap_float; + } + } + + s->frames_search = (frames_overlap > 1) ? srate * s->ms_search : 0; + if (s->frames_search <= 0) { + s->best_overlap_offset = NULL; + } else { + if (use_int) { + int64_t t = frames_overlap; + int32_t n = 8589934588LL / (t * t); // 4 * (2^31 - 1) / t^2 + int32_t *pw; + int i, j; + + s->buf_pre_corr = av_realloc(s->buf_pre_corr, + s->bytes_overlap * 2 + UNROLL_PADDING); + s->table_window = av_realloc(s->table_window, + s->bytes_overlap * 2 - nch * bps * 2); + if (!s->buf_pre_corr || !s->table_window) { + return AVERROR(ENOMEM); + } + memset((char *)s->buf_pre_corr + s->bytes_overlap * 2, 0, + UNROLL_PADDING); + pw = s->table_window; + for (i = 1; i < frames_overlap; i++) { + int32_t v = (i * (t - i) * n) >> 15; + for (j = 0; j < nch; j++) + *pw++ = v; + } + s->best_overlap_offset = best_overlap_offset_s16; + } else { + float *pw; + int i, j; + + s->buf_pre_corr = av_realloc(s->buf_pre_corr, s->bytes_overlap); + s->table_window = av_realloc(s->table_window, + s->bytes_overlap - nch * bps); + if (!s->buf_pre_corr || !s->table_window) { + return AVERROR(ENOMEM); + } + pw = s->table_window; + for (i = 1; i < frames_overlap; i++) { + float v = i * (frames_overlap - i); + for (j = 0; j < nch; j++) + *pw++ = v; + } + s->best_overlap_offset = best_overlap_offset_float; + } + } + + s->bytes_per_frame = bps * nch; + s->num_channels = nch; + + s->bytes_queue = (s->frames_search + s->frames_stride + frames_overlap) + * bps * nch; + s->buf_queue = av_realloc(s->buf_queue, s->bytes_queue + UNROLL_PADDING); + if (!s->buf_queue) { + return AVERROR(ENOMEM); + } + + s->bytes_queued = 0; + s->bytes_to_slide = 0; + + return 0; +} + +static void uninit(AVFilterContext *ctx) +{ + ScaleTempoContext *s = ctx->priv; + + av_freep(&s->buf_queue); + av_freep(&s->buf_overlap); + av_freep(&s->buf_pre_corr); + av_freep(&s->table_blend); + av_freep(&s->table_window); +} + +#define OFFSET(x) offsetof(ScaleTempoContext, x) +#define AF AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_FILTERING_PARAM + +static const AVOption scaletempo_options[] = { + { "scale", "set nominal amount to scale tempo", OFFSET(scale_nominal), AV_OPT_TYPE_FLOAT, {.dbl=1.0}, 0.01, 10, AF }, + { "stride", "set length in ms to output each stride", OFFSET(ms_stride), AV_OPT_TYPE_FLOAT, {.dbl= 60}, 0.01, 1000, AF }, + { "overlap", "set percentage of stride to overlap", OFFSET(percent_overlap), AV_OPT_TYPE_FLOAT, {.dbl=.2}, 0, 1, AF }, + { "search", "set length in ms to search for best overlap position", OFFSET(ms_search), AV_OPT_TYPE_FLOAT, {.dbl=14}, 0.01, 1000, AF }, + { "speed", "set response to tempo change", OFFSET(speed_opt), AV_OPT_TYPE_INT, {.i64=1}, 0, 3, AF, "speed" }, + { "none", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 0}, 0, 0, AF, "speed" }, + { "tempo", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 1}, 0, 0, AF, "speed" }, + { "pitch", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 2}, 0, 0, AF, "speed" }, + { "both", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 3}, 0, 0, AF, "speed" }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(scaletempo); + +static const AVFilterPad scaletempo_inputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_AUDIO, + .filter_frame = filter_frame, + .config_props = config_input, + }, + { NULL } +}; + +static const AVFilterPad scaletempo_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_AUDIO, + }, + { NULL } +}; + +AVFilter ff_af_scaletempo = { + .name = "scaletempo", + .description = NULL_IF_CONFIG_SMALL("Scale audio tempo while maintaining pitch."), + .uninit = uninit, + .query_formats = query_formats, + .priv_size = sizeof(ScaleTempoContext), + .priv_class = &scaletempo_class, + .inputs = scaletempo_inputs, + .outputs = scaletempo_outputs, +}; diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index fc212e58db..5e622b9ad4 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -118,6 +118,7 @@ static void register_all(void) REGISTER_FILTER(REPLAYGAIN, replaygain, af); REGISTER_FILTER(RESAMPLE, resample, af); REGISTER_FILTER(RUBBERBAND, rubberband, af); + REGISTER_FILTER(SCALETEMPO, scaletempo, af); REGISTER_FILTER(SIDECHAINCOMPRESS, sidechaincompress, af); REGISTER_FILTER(SIDECHAINGATE, sidechaingate, af); REGISTER_FILTER(SILENCEDETECT, silencedetect, af);