[MPlayer-dev-eng] [PATCH] af_scaletempo

Robert Juliano juliano.1 at osu.edu
Wed Sep 12 23:44:46 CEST 2007


Here's a new patch.  Changes include:
  - fixed memory allocation bug that caused crashes
  - reworked variable names
  - cached some calculations used in the main loops
  - switched from array notation to pointers, and
  - implemented an integer version (which I've commented out
    since it benchmarks slower).

usage: mplayer -af scaletempo [-speed scale]

It's linked to playback_speed, so the audio scales to match
speed whenever the speed changes (by -speed, keyboard, menu,
command, or property).

Since this filter exposes a bug in lib/af.c:af_calc_insize_constrained
you'll also want the af_calc_insize_constrained_overflow_quickfix from:
http://lists.mplayerhq.hu/pipermail/mplayer-dev-eng/2007-June/052315.html

Let me know if you have any issues.
robert
-------------- next part --------------
diff --git a/libaf/Makefile b/libaf/Makefile
index a4a7b14..e11643e 100644
--- a/libaf/Makefile
+++ b/libaf/Makefile
@@ -16,6 +16,7 @@ SRCS_COMMON = af.c \
               af_karaoke.c \
               af_pan.c \
               af_resample.c \
+              af_scaletempo.c \
               af_sinesuppress.c \
               af_sub.c \
               af_surround.c \
diff --git a/libaf/af.c b/libaf/af.c
index d95a3c5..fc9d5fa 100644
--- a/libaf/af.c
+++ b/libaf/af.c
@@ -31,6 +31,7 @@ extern af_info_t af_info_ladspa;
 extern af_info_t af_info_center;
 extern af_info_t af_info_sinesuppress;
 extern af_info_t af_info_karaoke;
+extern af_info_t af_info_scaletempo;
 
 static af_info_t* filter_list[]={ 
    &af_info_dummy,
@@ -61,6 +62,7 @@ static af_info_t* filter_list[]={
    &af_info_center,
    &af_info_sinesuppress,
    &af_info_karaoke,
+   &af_info_scaletempo,
    NULL 
 };
 
diff --git a/libaf/af_scaletempo.c b/libaf/af_scaletempo.c
new file mode 100644
index 0000000..ce75d57
--- /dev/null
+++ b/libaf/af_scaletempo.c
@@ -0,0 +1,355 @@
+/*
+ * scaletempo audio filter
+ * (cc) GPL 2007 MPlayer / Robert Juliano
+ *
+ * This file is part of MPlayer.
+ *
+ * MPlayer is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * MPlayer 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MPlayer; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * 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*speed input samples per loop
+ *
+ * to produce smoother transitions between strides, blend next ns_overlap
+ * samples from last stride with correlated samples of current input
+ *
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include "af.h"
+
+//#define USE_INT
+#ifdef USE_INT
+#define SAMPLETYPE int16_t
+#define LONG_SAMPLETYPE int32_t
+#else
+#define SAMPLETYPE float
+#define LONG_SAMPLETYPE float
+#endif
+
+// Data for specific instances of this filter
+typedef struct af_scaletempo_s
+{
+  float   scale;
+  int     samples_overlap;
+  int     frames_seek;
+  float   frames_stride_scaled;
+  float   frames_stride_error;
+  int     bytes_stride;
+  SAMPLETYPE * window;
+  SAMPLETYPE * overlap;
+  SAMPLETYPE * pre_corr;
+  int     bytes_window;
+  int     bytes_filled;
+  int     bytes_to_slide;
+  int     bytes_per_frame;
+  int     num_channels;
+  // cached values
+  int     bytes_overlap;
+  int     bytes_standing;
+  float   bytes_stride_scaled;
+  int     samples_standing;
+  float * blend_overlap;
+  float * blend_pre_corr;
+  int     sloping_divider;
+} af_scaletempo_t;
+
+int fill_window(struct af_instance_s* af, af_data_t* data, int offset) {
+  af_scaletempo_t* s = af->setup;
+  int bytes_in = data->len - offset;
+  int offset_unchanged = offset;
+
+  if (s->bytes_to_slide > 0) {
+    if (s->bytes_to_slide < s->bytes_filled) {
+      int bytes_move = s->bytes_filled - s->bytes_to_slide;
+      memmove(s->window,
+              (int8_t *)s->window + s->bytes_to_slide,
+              bytes_move);
+      s->bytes_to_slide = 0;
+      s->bytes_filled = bytes_move;
+    } else {
+      int bytes_skip;
+      s->bytes_to_slide -= s->bytes_filled;
+      bytes_skip = min(s->bytes_to_slide, bytes_in);
+      s->bytes_filled = 0;
+      s->bytes_to_slide -= bytes_skip;
+      offset += bytes_skip;
+      bytes_in -= bytes_skip;
+    }
+  }
+
+  if (bytes_in > 0) {
+    int bytes_copy = min(s->bytes_window - s->bytes_filled, bytes_in);
+    memcpy((int8_t *)s->window + s->bytes_filled,
+           (int8_t *)data->audio + offset,
+           bytes_copy);
+    s->bytes_filled += bytes_copy;
+    offset += bytes_copy;
+  }
+
+  return offset - offset_unchanged;
+}
+
+int best_overlap_off(struct af_instance_s* af) {
+  af_scaletempo_t* s = af->setup;
+  SAMPLETYPE *po, *pc, *seek_start;
+  float * pb;
+  LONG_SAMPLETYPE best_corr = INT_MIN;
+  int best_off = 0;
+  int len, off;
+
+  po = s->overlap;
+  pc = s->pre_corr;
+  pb = s->blend_pre_corr;
+  len = s->samples_overlap;
+  while (len--) {
+    *pc = *po * *pb;
+    pc++; po++; pb++;
+  }
+
+  seek_start = s->window;
+  for (off=0; off<s->frames_seek; off++) {
+    LONG_SAMPLETYPE corr = 0;
+    SAMPLETYPE * pw = seek_start;
+    pc = s->pre_corr;
+    len = s->samples_overlap;
+    while (len--) {
+#ifdef USE_INT
+      corr += ( *pc * *pw ) / s->sloping_divider;
+#else
+      corr += *pc * *pw;
+#endif
+      pc++; pw++;
+    }
+    if (corr > best_corr) {
+      best_corr = corr;
+      best_off  = off;
+    }
+    seek_start += s->num_channels;
+  }
+
+  return best_off;
+}
+
+// Filter data through filter
+static af_data_t* play(struct af_instance_s* af, af_data_t* io_data)
+{
+  af_scaletempo_t* s = af->setup;
+  int offset_in;
+  int max_bytes_out;
+  SAMPLETYPE * pout;
+
+  // RESIZE_LOCAL_BUFFER - can't use macro
+  max_bytes_out = ((int)(io_data->len / s->bytes_stride_scaled) + 1) * s->bytes_stride;
+  if (max_bytes_out > af->data->len) {
+    af_msg(AF_MSG_VERBOSE,"[libaf] Reallocating memory in module %s, "
+          "old len = %i, new len = %i\n",af->info->name,af->data->len,max_bytes_out);
+    af->data->audio = realloc(af->data->audio, max_bytes_out);
+    if (!af->data->audio) {
+      af_msg(AF_MSG_FATAL, "[libaf] Could not allocate memory\n");
+      return NULL;
+    }
+    af->data->len = max_bytes_out;
+  }
+
+  offset_in = fill_window(af, io_data, 0);
+  pout = af->data->audio;
+  while (s->bytes_filled >= s->bytes_window) {
+    SAMPLETYPE * po = s->overlap;
+    SAMPLETYPE * pw = s->window;
+    float * pb = s->blend_overlap;
+    int len, ti;
+    float tf;
+
+    pw += best_overlap_off(af);
+    len = s->samples_overlap;
+    while (len--) {
+      *pout = (1 - *pb) * *po + *pb * *pw;
+      pout++; pb++; po++; pw++;
+    }
+    memcpy(pout, pw, s->bytes_standing);
+    pout += s->samples_standing;
+
+    memcpy(s->overlap, pw + s->samples_standing, 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_window(af, io_data, offset_in);
+  }
+
+  io_data->audio = af->data->audio;
+  io_data->len   = (int)pout - (int)af->data->audio;
+  return io_data;
+}
+
+// Initialization and runtime control
+static int control(struct af_instance_s* af, int cmd, void* arg)
+{
+  af_scaletempo_t* s = af->setup;
+  switch(cmd){
+  case AF_CONTROL_REINIT:{
+    af_data_t* io_data = (af_data_t*)arg;
+    int rate = io_data->rate;
+    int nch  = io_data->nch;
+    int bps  = sizeof(SAMPLETYPE);
+    int frames_window, frames_overlap;
+    int i;
+    float * pb;
+
+    frames_window  = rate * 82 / 1000;
+    frames_overlap = rate * 1 / 1000;
+    s->frames_seek = rate * 14 / 1000;
+
+    s->bytes_window     = (frames_window + s->frames_seek) * bps * nch;
+    s->samples_overlap  = frames_overlap * nch;
+    s->bytes_overlap    = frames_overlap * nch * bps;
+    s->bytes_stride     = (frames_window - frames_overlap) * bps * nch;
+    s->bytes_standing   = s->bytes_stride - s->bytes_overlap;
+    s->samples_standing = (int)(s->bytes_standing / bps);
+
+    s->bytes_stride_scaled  = s->bytes_stride * s->scale;
+    s->frames_stride_scaled = (frames_window - frames_overlap) * s->scale;
+    s->frames_stride_error  = 0;
+    af->mul.n = s->bytes_stride;
+    af->mul.d = s->bytes_stride_scaled;
+    af_frac_cancel(&af->mul);
+
+    s->bytes_per_frame  = bps * nch;
+    s->num_channels     = nch;
+
+    s->window   = realloc(s->window,   s->bytes_window);
+    s->overlap  = realloc(s->overlap,  s->bytes_overlap);
+    s->pre_corr = realloc(s->pre_corr, s->bytes_overlap);
+    s->blend_overlap  = realloc(s->blend_overlap, frames_overlap * sizeof(float) * nch);
+    s->blend_pre_corr = realloc(s->blend_pre_corr, frames_overlap * sizeof(float) * nch);
+    if(!(s->window && s->overlap && s->pre_corr && s->blend_overlap && s->blend_pre_corr)) {
+      af_msg(AF_MSG_FATAL, "[scaletempo] Out of memory\n");
+      return AF_ERROR;
+    }
+
+    pb = s->blend_overlap;
+    for (i=0; i<frames_overlap; i++) {
+      float t = i / (float)frames_overlap;
+      int ch = nch;
+      while (ch--) {
+        *pb = t;
+        pb++;
+      }
+    }
+
+    pb = s->blend_pre_corr;
+    for (i=0; i<frames_overlap; i++) {
+      SAMPLETYPE t = i * (frames_overlap - 1);
+      int ch = nch;
+      while (ch--) {
+        *pb = t;
+        pb++;
+      }
+    }
+
+    af->data->rate      = io_data->rate;
+    af->data->nch       = io_data->nch;
+#ifdef USE_INT
+    af->data->format    = AF_FORMAT_S16_NE;
+    s->sloping_divider  = nch * (frames_overlap * frames_overlap) / 6;
+#else
+    af->data->format    = AF_FORMAT_FLOAT_NE;
+#endif
+    af->data->bps       = sizeof(SAMPLETYPE);
+
+    af_msg (AF_MSG_DEBUG0,
+            "%4i seek, "
+            "%5i window_size, "
+            "%4i overlap, "
+            "%5i standing, "
+            "%5i stride, "
+            "%6.2f stride_scaled\n",
+            s->frames_seek,
+            (int)(s->bytes_window / nch / bps),
+            (int)(s->bytes_overlap / nch / bps),
+            (int)(s->bytes_standing / nch / bps),
+            (int)(s->bytes_stride / nch / bps),
+            s->frames_stride_scaled);
+
+    return af_test_output(af,(af_data_t*)arg);
+  }
+  case AF_CONTROL_PLAYBACK_SPEED | AF_CONTROL_SET:
+  case AF_CONTROL_SCALETEMPO_AMOUNT | AF_CONTROL_SET:
+    s->scale = *(float*)arg;
+    return AF_OK;
+  case AF_CONTROL_SCALETEMPO_AMOUNT | AF_CONTROL_GET:
+    *(float*)arg = s->scale;
+    return AF_OK;
+  case AF_CONTROL_COMMAND_LINE:{
+    float f;
+    sscanf((char*)arg,"%f", &f);
+    return control(af, AF_CONTROL_SCALETEMPO_AMOUNT | AF_CONTROL_SET, &f);
+  }
+  }
+  return AF_UNKNOWN;
+}
+
+// Deallocate memory
+static void uninit(struct af_instance_s* af)
+{
+  af_scaletempo_t* s = af->setup;
+  free(af->data->audio);
+  free(af->data);
+  free(s->window);
+  free(s->overlap);
+  free(s->pre_corr);
+  free(s->blend_overlap);
+  free(s->blend_pre_corr);
+  free(af->setup);
+}
+
+// Allocate memory and set function pointers
+static int af_open(af_instance_t* af){
+  af_scaletempo_t* s;
+
+  af->control   = control;
+  af->uninit    = uninit;
+  af->play      = play;
+  af->mul.n     = 1;
+  af->mul.d     = 1;
+  af->data      = calloc(1,sizeof(af_data_t));
+  af->setup     = calloc(1,sizeof(af_scaletempo_t));
+  if(af->data == NULL || af->setup == NULL)
+    return AF_ERROR;
+
+  s = af->setup;
+  s->scale = 1.0;
+
+  return AF_OK;
+}
+
+// Description of this filter
+af_info_t af_info_scaletempo = {
+  "Scale audio tempo while maintaining pitch",
+  "scaletempo",
+  "Robert Juliano",
+  "",
+  AF_FLAGS_REENTRANT,
+  af_open
+};
diff --git a/libaf/control.h b/libaf/control.h
index 80eff7c..7c6a8dd 100644
--- a/libaf/control.h
+++ b/libaf/control.h
@@ -231,4 +231,7 @@ typedef struct af_control_ext_s{
 #define AF_CONTROL_SS_FREQ		0x00002300 | AF_CONTROL_FILTER_SPECIFIC
 #define AF_CONTROL_SS_DECAY		0x00002400 | AF_CONTROL_FILTER_SPECIFIC
 
+#define AF_CONTROL_PLAYBACK_SPEED	0x00002500 | AF_CONTROL_FILTER_SPECIFIC
+#define AF_CONTROL_SCALETEMPO_AMOUNT	0x00002600 | AF_CONTROL_FILTER_SPECIFIC
+
 #endif /*__af_control_h */
diff --git a/mplayer.c b/mplayer.c
index c0ca8d5..5847712 100644
--- a/mplayer.c
+++ b/mplayer.c
@@ -1203,14 +1203,12 @@ int build_afilter_chain(sh_audio_t *sh_audio, ao_data_t *ao_data)
     mpctx->mixer.afilter = NULL;
     return 0;
   }
-  new_srate = sh_audio->samplerate * playback_speed;
-  if (new_srate != ao_data->samplerate) {
-    // limits are taken from libaf/af_resample.c
-    if (new_srate < 8000)
-      new_srate = 8000;
-    if (new_srate > 192000)
-      new_srate = 192000;
-    playback_speed = (float)new_srate / (float)sh_audio->samplerate;
+  if(af_control_any_rev(sh_audio->afilter,
+                        AF_CONTROL_PLAYBACK_SPEED | AF_CONTROL_SET,
+                        &playback_speed)) {
+    new_srate = sh_audio->samplerate;
+  } else {
+    new_srate = sh_audio->samplerate * playback_speed;
   }
   result =  init_audio_filters(sh_audio, new_srate,
            sh_audio->channels, sh_audio->sample_format,
@@ -1497,7 +1495,7 @@ if(mpctx->sh_audio){
   // first init to detect best values
   if(!preinit_audio_filters(mpctx->sh_audio,
         // input:
-        (int)(mpctx->sh_audio->samplerate*playback_speed),
+        mpctx->sh_audio->samplerate,
 	mpctx->sh_audio->channels, mpctx->sh_audio->sample_format,
 	// output:
 	&ao_data.samplerate, &ao_data.channels, &ao_data.format)){
-- 
1.5.1.6



More information about the MPlayer-dev-eng mailing list