libavfilter/af_rubberband.c
2a0fc559
 /*
  * 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 <rubberband/rubberband-c.h>
 
 #include "libavutil/channel_layout.h"
 #include "libavutil/common.h"
 #include "libavutil/opt.h"
 
 #include "audio.h"
 #include "avfilter.h"
 #include "formats.h"
 #include "internal.h"
 
 typedef struct RubberBandContext {
     const AVClass *class;
     RubberBandState rbs;
 
     double tempo, pitch;
     int transients, detector, phase, window,
         smoothing, formant, opitch, channels;
     int64_t nb_samples_out;
     int64_t nb_samples_in;
     int flushed;
 } RubberBandContext;
 
 #define OFFSET(x) offsetof(RubberBandContext, x)
 #define A AV_OPT_FLAG_AUDIO_PARAM|AV_OPT_FLAG_FILTERING_PARAM
 
 static const AVOption rubberband_options[] = {
     { "tempo",      "set tempo scale factor", OFFSET(tempo), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0.01, 100, A },
     { "pitch",      "set pitch scale factor", OFFSET(pitch), AV_OPT_TYPE_DOUBLE, {.dbl=1}, 0.01, 100, A },
     { "transients", "set transients", OFFSET(transients), AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, A, "transients" },
         { "crisp",  0,                0,                  AV_OPT_TYPE_CONST, {.i64=RubberBandOptionTransientsCrisp},  0, 0, A, "transients" },
         { "mixed",  0,                0,                  AV_OPT_TYPE_CONST, {.i64=RubberBandOptionTransientsMixed},  0, 0, A, "transients" },
         { "smooth", 0,                0,                  AV_OPT_TYPE_CONST, {.i64=RubberBandOptionTransientsSmooth}, 0, 0, A, "transients" },
     { "detector",   "set detector",   OFFSET(detector),   AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, A, "detector" },
         { "compound",   0,            0,                  AV_OPT_TYPE_CONST, {.i64=RubberBandOptionDetectorCompound},   0, 0, A, "detector" },
         { "percussive", 0,            0,                  AV_OPT_TYPE_CONST, {.i64=RubberBandOptionDetectorPercussive}, 0, 0, A, "detector" },
         { "soft",       0,            0,                  AV_OPT_TYPE_CONST, {.i64=RubberBandOptionDetectorSoft},       0, 0, A, "detector" },
     { "phase",      "set phase",      OFFSET(phase),      AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, A, "phase" },
         { "laminar",     0,           0,                  AV_OPT_TYPE_CONST, {.i64=RubberBandOptionPhaseLaminar},     0, 0, A, "phase" },
         { "independent", 0,           0,                  AV_OPT_TYPE_CONST, {.i64=RubberBandOptionPhaseIndependent}, 0, 0, A, "phase" },
     { "window",     "set window",     OFFSET(window),     AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, A, "window" },
         { "standard", 0,              0,                  AV_OPT_TYPE_CONST, {.i64=RubberBandOptionWindowStandard}, 0, 0, A, "window" },
         { "short",    0,              0,                  AV_OPT_TYPE_CONST, {.i64=RubberBandOptionWindowShort},    0, 0, A, "window" },
         { "long",     0,              0,                  AV_OPT_TYPE_CONST, {.i64=RubberBandOptionWindowLong},     0, 0, A, "window" },
     { "smoothing",  "set smoothing",  OFFSET(smoothing),  AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, A, "smoothing" },
         { "off",    0,                0,                  AV_OPT_TYPE_CONST, {.i64=RubberBandOptionSmoothingOff}, 0, 0, A, "smoothing" },
         { "on",     0,                0,                  AV_OPT_TYPE_CONST, {.i64=RubberBandOptionSmoothingOn},  0, 0, A, "smoothing" },
     { "formant",    "set formant",    OFFSET(formant),    AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, A, "formant" },
         { "shifted",    0,            0,                  AV_OPT_TYPE_CONST, {.i64=RubberBandOptionFormantShifted},   0, 0, A, "formant" },
         { "preserved",  0,            0,                  AV_OPT_TYPE_CONST, {.i64=RubberBandOptionFormantPreserved}, 0, 0, A, "formant" },
43f0b1d3
     { "pitchq",     "set pitch quality", OFFSET(opitch),  AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, A, "pitch" },
2a0fc559
         { "quality",     0,           0,                  AV_OPT_TYPE_CONST, {.i64=RubberBandOptionPitchHighQuality},     0, 0, A, "pitch" },
         { "speed",       0,           0,                  AV_OPT_TYPE_CONST, {.i64=RubberBandOptionPitchHighSpeed},       0, 0, A, "pitch" },
         { "consistency", 0,           0,                  AV_OPT_TYPE_CONST, {.i64=RubberBandOptionPitchHighConsistency}, 0, 0, A, "pitch" },
     { "channels",   "set channels",   OFFSET(channels),   AV_OPT_TYPE_INT, {.i64=0}, 0, INT_MAX, A, "channels" },
         { "apart",    0,              0,                  AV_OPT_TYPE_CONST, {.i64=RubberBandOptionChannelsApart},    0, 0, A, "channels" },
         { "together", 0,              0,                  AV_OPT_TYPE_CONST, {.i64=RubberBandOptionChannelsTogether}, 0, 0, A, "channels" },
     { NULL },
 };
 
 AVFILTER_DEFINE_CLASS(rubberband);
 
 static av_cold void uninit(AVFilterContext *ctx)
 {
     RubberBandContext *s = ctx->priv;
 
     if (s->rbs)
         rubberband_delete(s->rbs);
 }
 
 static int query_formats(AVFilterContext *ctx)
 {
     AVFilterFormats *formats = NULL;
     AVFilterChannelLayouts *layouts = NULL;
     static const enum AVSampleFormat sample_fmts[] = {
         AV_SAMPLE_FMT_FLTP,
         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 filter_frame(AVFilterLink *inlink, AVFrame *in)
 {
     RubberBandContext *s = inlink->dst->priv;
     AVFilterLink *outlink = inlink->dst->outputs[0];
     AVFrame *out;
     int ret = 0, nb_samples;
 
     rubberband_process(s->rbs, (const float *const *)in->data, in->nb_samples, 0);
     s->nb_samples_in += in->nb_samples;
 
     nb_samples = rubberband_available(s->rbs);
     if (nb_samples > 0) {
         out = ff_get_audio_buffer(inlink, nb_samples);
         if (!out) {
             av_frame_free(&in);
             return AVERROR(ENOMEM);
         }
         out->pts = av_rescale_q(s->nb_samples_out,
                      (AVRational){ 1, outlink->sample_rate },
                      outlink->time_base);
         nb_samples = rubberband_retrieve(s->rbs, (float *const *)out->data, nb_samples);
         out->nb_samples = nb_samples;
         ret = ff_filter_frame(outlink, out);
         s->nb_samples_out += nb_samples;
     }
 
     av_frame_free(&in);
     return ret;
 }
 
 static int config_input(AVFilterLink *inlink)
 {
     AVFilterContext *ctx = inlink->dst;
     RubberBandContext *s = ctx->priv;
     int opts = s->transients|s->detector|s->phase|s->window|
                s->smoothing|s->formant|s->opitch|s->channels|
                RubberBandOptionProcessRealTime;
 
     if (s->rbs)
         rubberband_delete(s->rbs);
     s->rbs = rubberband_new(inlink->sample_rate, inlink->channels, opts, 1. / s->tempo, s->pitch);
 
     inlink->partial_buf_size =
     inlink->min_samples =
     inlink->max_samples = rubberband_get_samples_required(s->rbs);
 
     return 0;
 }
 
 static int request_frame(AVFilterLink *outlink)
 {
     AVFilterContext *ctx = outlink->src;
     RubberBandContext *s = ctx->priv;
     AVFilterLink *inlink = ctx->inputs[0];
     int ret = 0;
 
     ret = ff_request_frame(ctx->inputs[0]);
 
     if (ret == AVERROR_EOF && !s->flushed) {
6ce02126
         if (rubberband_available(s->rbs) > 0) {
             AVFrame *out = ff_get_audio_buffer(inlink, 1);
             int nb_samples;
2a0fc559
 
             if (!out)
                 return AVERROR(ENOMEM);
6ce02126
 
             rubberband_process(s->rbs, (const float *const *)out->data, 1, 1);
             av_frame_free(&out);
             nb_samples = rubberband_available(s->rbs);
 
             if (nb_samples > 0) {
                 out = ff_get_audio_buffer(inlink, nb_samples);
                 if (!out)
                     return AVERROR(ENOMEM);
                 out->pts = av_rescale_q(s->nb_samples_out,
                              (AVRational){ 1, outlink->sample_rate },
                              outlink->time_base);
                 nb_samples = rubberband_retrieve(s->rbs, (float *const *)out->data, nb_samples);
                 out->nb_samples = nb_samples;
                 ret = ff_filter_frame(outlink, out);
                 s->nb_samples_out += nb_samples;
             }
2a0fc559
         }
6ce02126
         s->flushed = 1;
2a0fc559
         av_log(ctx, AV_LOG_DEBUG, "nb_samples_in %"PRId64" nb_samples_out %"PRId64"\n",
                                    s->nb_samples_in, s->nb_samples_out);
     }
 
     return ret;
 }
 
1d7d8244
 static int process_command(AVFilterContext *ctx, const char *cmd, const char *args,
                            char *res, int res_len, int flags)
 {
     RubberBandContext *s = ctx->priv;
 
     if (!strcmp(cmd, "tempo")) {
         double arg;
 
         sscanf(args, "%lf", &arg);
         if (arg < 0.01 || arg > 100) {
             av_log(ctx, AV_LOG_ERROR,
                    "Tempo scale factor '%f' out of range\n", arg);
             return AVERROR(EINVAL);
         }
         rubberband_set_time_ratio(s->rbs, 1. / arg);
     }
 
     if (!strcmp(cmd, "pitch")) {
         double arg;
 
         sscanf(args, "%lf", &arg);
         if (arg < 0.01 || arg > 100) {
             av_log(ctx, AV_LOG_ERROR,
                    "Pitch scale factor '%f' out of range\n", arg);
             return AVERROR(EINVAL);
         }
         rubberband_set_pitch_scale(s->rbs, arg);
     }
 
     return 0;
 }
 
2a0fc559
 static const AVFilterPad rubberband_inputs[] = {
     {
         .name          = "default",
         .type          = AVMEDIA_TYPE_AUDIO,
         .config_props  = config_input,
         .filter_frame  = filter_frame,
     },
     { NULL }
 };
 
 static const AVFilterPad rubberband_outputs[] = {
     {
         .name          = "default",
         .type          = AVMEDIA_TYPE_AUDIO,
         .request_frame = request_frame,
     },
     { NULL }
 };
 
 AVFilter ff_af_rubberband = {
     .name          = "rubberband",
     .description   = NULL_IF_CONFIG_SMALL("Apply time-stretching and pitch-shifting."),
     .query_formats = query_formats,
     .priv_size     = sizeof(RubberBandContext),
     .priv_class    = &rubberband_class,
     .uninit        = uninit,
     .inputs        = rubberband_inputs,
     .outputs       = rubberband_outputs,
1d7d8244
     .process_command = process_command,
2a0fc559
 };