avfilter/af_atempo: Make independent of the channel layout
[ffmpeg.git] / libavfilter / af_atempo.c
index 4d1c68a..d5d29b3 100644 (file)
@@ -123,8 +123,9 @@ typedef struct {
     // tempo scaling factor:
     double tempo;
 
-    // cumulative alignment drift:
-    int drift;
+    // a snapshot of previous fragment input and output position values
+    // captured when the tempo scale factor was set most recently:
+    int64_t origin[2];
 
     // current/previous fragment ring-buffer:
     AudioFragment frag[2];
@@ -159,6 +160,16 @@ static const AVOption atempo_options[] = {
 
 AVFILTER_DEFINE_CLASS(atempo);
 
+inline static AudioFragment *yae_curr_frag(ATempoContext *atempo)
+{
+    return &atempo->frag[atempo->nfrag % 2];
+}
+
+inline static AudioFragment *yae_prev_frag(ATempoContext *atempo)
+{
+    return &atempo->frag[(atempo->nfrag + 1) % 2];
+}
+
 /**
  * Reset filter to initial state, do not deallocate existing local buffers.
  */
@@ -168,13 +179,15 @@ static void yae_clear(ATempoContext *atempo)
     atempo->head = 0;
     atempo->tail = 0;
 
-    atempo->drift = 0;
     atempo->nfrag = 0;
     atempo->state = YAE_LOAD_FRAGMENT;
 
     atempo->position[0] = 0;
     atempo->position[1] = 0;
 
+    atempo->origin[0] = 0;
+    atempo->origin[1] = 0;
+
     atempo->frag[0].position[0] = 0;
     atempo->frag[0].position[1] = 0;
     atempo->frag[0].nsamples    = 0;
@@ -308,6 +321,7 @@ static int yae_reset(ATempoContext *atempo,
 
 static int yae_set_tempo(AVFilterContext *ctx, const char *arg_tempo)
 {
+    const AudioFragment *prev;
     ATempoContext *atempo = ctx->priv;
     char   *tail = NULL;
     double tempo = av_strtod(arg_tempo, &tail);
@@ -323,20 +337,13 @@ static int yae_set_tempo(AVFilterContext *ctx, const char *arg_tempo)
         return AVERROR(EINVAL);
     }
 
+    prev = yae_prev_frag(atempo);
+    atempo->origin[0] = prev->position[0] + atempo->window / 2;
+    atempo->origin[1] = prev->position[1] + atempo->window / 2;
     atempo->tempo = tempo;
     return 0;
 }
 
-inline static AudioFragment *yae_curr_frag(ATempoContext *atempo)
-{
-    return &atempo->frag[atempo->nfrag % 2];
-}
-
-inline static AudioFragment *yae_prev_frag(ATempoContext *atempo)
-{
-    return &atempo->frag[(atempo->nfrag + 1) % 2];
-}
-
 /**
  * A helper macro for initializing complex data buffer with scalar data
  * of a given type.
@@ -689,12 +696,21 @@ static int yae_adjust_position(ATempoContext *atempo)
     const AudioFragment *prev = yae_prev_frag(atempo);
     AudioFragment       *frag = yae_curr_frag(atempo);
 
+    const double prev_output_position =
+        (double)(prev->position[1] - atempo->origin[1] + atempo->window / 2);
+
+    const double ideal_output_position =
+        (double)(prev->position[0] - atempo->origin[0] + atempo->window / 2) /
+        atempo->tempo;
+
+    const int drift = (int)(prev_output_position - ideal_output_position);
+
     const int delta_max  = atempo->window / 2;
     const int correction = yae_align(frag,
                                      prev,
                                      atempo->window,
                                      delta_max,
-                                     atempo->drift,
+                                     drift,
                                      atempo->correlation,
                                      atempo->complex_to_real);
 
@@ -704,9 +720,6 @@ static int yae_adjust_position(ATempoContext *atempo)
 
         // clear so that the fragment can be reloaded:
         frag->nsamples = 0;
-
-        // update cumulative correction drift counter:
-        atempo->drift += correction;
     }
 
     return correction;
@@ -936,7 +949,13 @@ static int yae_flush(ATempoContext *atempo,
         }
     }
 
-    // flush the remaininder of the current fragment:
+    // check whether all of the input samples have been consumed:
+    if (frag->position[0] + frag->nsamples < atempo->position[0]) {
+        yae_advance_to_next_frag(atempo);
+        return AVERROR(EAGAIN);
+    }
+
+    // flush the remainder of the current fragment:
     start_here = FFMAX(atempo->position[1], overlap_end);
     stop_here  = frag->position[1] + frag->nsamples;
     offset     = start_here - frag->position[1];
@@ -993,26 +1012,29 @@ static int query_formats(AVFilterContext *ctx)
         AV_SAMPLE_FMT_DBL,
         AV_SAMPLE_FMT_NONE
     };
+    int ret;
 
     layouts = ff_all_channel_layouts();
     if (!layouts) {
         return AVERROR(ENOMEM);
     }
-    ff_set_common_channel_layouts(ctx, layouts);
+    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);
     }
-    ff_set_common_formats(ctx, formats);
+    ret = ff_set_common_formats(ctx, formats);
+    if (ret < 0)
+        return ret;
 
     formats = ff_all_samplerates();
     if (!formats) {
         return AVERROR(ENOMEM);
     }
-    ff_set_common_samplerates(ctx, formats);
-
-    return 0;
+    return ff_set_common_samplerates(ctx, formats);
 }
 
 static int config_props(AVFilterLink *inlink)
@@ -1022,11 +1044,8 @@ static int config_props(AVFilterLink *inlink)
 
     enum AVSampleFormat format = inlink->format;
     int sample_rate = (int)inlink->sample_rate;
-    int channels = av_get_channel_layout_nb_channels(inlink->channel_layout);
 
-    ctx->outputs[0]->flags |= FF_LINK_FLAG_REQUEST_LOOP;
-
-    return yae_reset(atempo, format, sample_rate, channels);
+    return yae_reset(atempo, format, sample_rate, inlink->channels);
 }
 
 static int push_samples(ATempoContext *atempo,
@@ -1045,11 +1064,11 @@ static int push_samples(ATempoContext *atempo,
                      outlink->time_base);
 
     ret = ff_filter_frame(outlink, atempo->dst_buffer);
-    if (ret < 0)
-        return ret;
     atempo->dst_buffer = NULL;
     atempo->dst        = NULL;
     atempo->dst_end    = NULL;
+    if (ret < 0)
+        return ret;
 
     atempo->nsamples_out += n_out;
     return 0;
@@ -1169,7 +1188,7 @@ static const AVFilterPad atempo_outputs[] = {
     { NULL }
 };
 
-AVFilter avfilter_af_atempo = {
+AVFilter ff_af_atempo = {
     .name            = "atempo",
     .description     = NULL_IF_CONFIG_SMALL("Adjust audio tempo."),
     .init            = init,