[FFmpeg-devel] [PATCH] ALSA: use a different time filter.

Nicolas George nicolas.george at normalesup.org
Wed Dec 28 20:35:22 CET 2011


The time filter used so far was designed for JACK, which returns data in
small packets with constant size. Reads from ALSA have varying size,
especially in non-blocking mode, and can be much bigger with some plugins.
With these parameters, the filter is likely to diverge.

The new filter is based on a least-square linear fit maintained with an
exponential weighted moving average. It converges somewhat slower but can
handle larger and varying periods and can not diverge under normal
circumstances.

Signed-off-by: Nicolas George <nicolas.george at normalesup.org>
---


This is one of two options I have found. The other is this: use an arbitrary
period size, maybe sample_rate / 256, and use the current time filter with
interpolated values. That would probably make less code overall, but I am
not comfortable with the arbitrary values and the possible divergence.

Also, is someone currently maintaining ALSA?

Regards,

-- 
  Nicolas George


 libavdevice/Makefile            |    2 +-
 libavdevice/alsa-audio-common.c |    2 -
 libavdevice/alsa-audio-dec.c    |   61 +++++++++++++++++++++++++++++---------
 libavdevice/alsa-audio.h        |   11 ++++++-
 4 files changed, 57 insertions(+), 19 deletions(-)

diff --git a/libavdevice/Makefile b/libavdevice/Makefile
index d7806ea..1797885 100644
--- a/libavdevice/Makefile
+++ b/libavdevice/Makefile
@@ -10,7 +10,7 @@ OBJS    = alldevices.o avdevice.o
 
 # input/output devices
 OBJS-$(CONFIG_ALSA_INDEV)                += alsa-audio-common.o \
-                                            alsa-audio-dec.o timefilter.o
+                                            alsa-audio-dec.o
 OBJS-$(CONFIG_ALSA_OUTDEV)               += alsa-audio-common.o \
                                             alsa-audio-enc.o
 OBJS-$(CONFIG_BKTR_INDEV)                += bktr.o
diff --git a/libavdevice/alsa-audio-common.c b/libavdevice/alsa-audio-common.c
index cb7ba66..0f5629b 100644
--- a/libavdevice/alsa-audio-common.c
+++ b/libavdevice/alsa-audio-common.c
@@ -320,8 +320,6 @@ av_cold int ff_alsa_close(AVFormatContext *s1)
     AlsaData *s = s1->priv_data;
 
     av_freep(&s->reorder_buf);
-    if (CONFIG_ALSA_INDEV)
-        ff_timefilter_destroy(s->timefilter);
     snd_pcm_close(s->h);
     return 0;
 }
diff --git a/libavdevice/alsa-audio-dec.c b/libavdevice/alsa-audio-dec.c
index bb9d233..aa3c9d0 100644
--- a/libavdevice/alsa-audio-dec.c
+++ b/libavdevice/alsa-audio-dec.c
@@ -53,6 +53,36 @@
 #include "avdevice.h"
 #include "alsa-audio.h"
 
+static double fast_exp(double x)
+{
+    return 1 / (1 - x * (1 - 0.5 * x * (1 - (1 / 3.0) * x)));
+}
+
+static void linreg_update(struct linreg *l, double x, double y)
+{
+    double k = fast_exp(l->conv_factor * (l->xlast - x));
+    double mean_x2 = k * l->mean_x + (1 - k) * x;
+    double mean_y2 = k * l->mean_y + (1 - k) * y;
+
+    l->xlast = x;
+    if (l->mean_x == 0) { /* exactly 0 => reset */
+        l->mean_x = x;
+        l->mean_y = y;
+        return;
+    }
+    /* Note: the numbers quickly become too big: computing var_xx as
+       mean(x*x) - mean(x)*mean(x) loses precision. */
+    l->var_xx += (mean_x2 - l->mean_x) * (mean_x2 - l->mean_x);
+    l->var_xy += (mean_x2 - l->mean_x) * (mean_y2 - l->mean_y);
+    l->mean_x = mean_x2;
+    l->mean_y = mean_y2;
+    l->var_xx = k * l->var_xx + (1 - k) * (x - l->mean_x) * (x - l->mean_x);
+    l->var_xy = k * l->var_xy + (1 - k) * (x - l->mean_x) * (y - l->mean_y);
+    if (l->var_xx >= l->var_xx_min)
+        l->a = l->var_xy / l->var_xx;
+
+}
+
 static av_cold int audio_read_header(AVFormatContext *s1,
                                      AVFormatParameters *ap)
 {
@@ -60,7 +90,6 @@ static av_cold int audio_read_header(AVFormatContext *s1,
     AVStream *st;
     int ret;
     enum CodecID codec_id;
-    double o;
 
     st = avformat_new_stream(s1, NULL);
     if (!st) {
@@ -82,24 +111,23 @@ static av_cold int audio_read_header(AVFormatContext *s1,
     st->codec->sample_rate = s->sample_rate;
     st->codec->channels    = s->channels;
     avpriv_set_pts_info(st, 64, 1, 1000000);  /* 64 bits pts in us */
-    o = 2 * M_PI * s->period_size / s->sample_rate * 1.5; // bandwidth: 1.5Hz
-    s->timefilter = ff_timefilter_new(1000000.0 / s->sample_rate,
-                                      sqrt(2 * o), o * o);
-    if (!s->timefilter)
-        goto fail;
 
-    return 0;
+    /* init linear regression */
+    s->linreg.conv_factor = 0.5 * 2 * M_PI / s->sample_rate;
+    s->linreg.a           = (double)AV_TIME_BASE / s->sample_rate;
+    s->linreg.var_xx      = 1 / (s->linreg.conv_factor * s->linreg.conv_factor);
+    s->linreg.var_xy      = s->linreg.var_xx * s->linreg.a;
+    s->linreg.var_xx_min  = s->linreg.var_xx / 100;
+    s->time_origin = av_gettime();
 
-fail:
-    snd_pcm_close(s->h);
-    return AVERROR(EIO);
+    return 0;
 }
 
 static int audio_read_packet(AVFormatContext *s1, AVPacket *pkt)
 {
     AlsaData *s  = s1->priv_data;
     int res;
-    int64_t dts;
+    int64_t endts;
     snd_pcm_sframes_t delay = 0;
 
     if (av_new_packet(pkt, s->period_size * s->frame_size) < 0) {
@@ -119,13 +147,16 @@ static int audio_read_packet(AVFormatContext *s1, AVPacket *pkt)
 
             return AVERROR(EIO);
         }
-        ff_timefilter_reset(s->timefilter);
+        /* reset linear regression */
+        s->linreg.mean_x = s->linreg.mean_y = 0;
     }
 
-    dts = av_gettime();
+    endts = av_gettime() - s->time_origin;
     snd_pcm_delay(s->h, &delay);
-    dts -= av_rescale(delay + res, 1000000, s->sample_rate);
-    pkt->pts = ff_timefilter_update(s->timefilter, dts, res);
+    linreg_update(&s->linreg, s->samples + res + delay, endts);
+    pkt->pts = s->time_origin + s->linreg.mean_y +
+               s->linreg.a * (s->samples - s->linreg.mean_x);
+    s->samples += res;
 
     pkt->size = res * s->frame_size;
 
diff --git a/libavdevice/alsa-audio.h b/libavdevice/alsa-audio.h
index e453a20..c635e45 100644
--- a/libavdevice/alsa-audio.h
+++ b/libavdevice/alsa-audio.h
@@ -52,10 +52,19 @@ typedef struct {
     int period_size; ///< preferred size for reads and writes, in frames
     int sample_rate; ///< sample rate set by user
     int channels;    ///< number of channels set by user
-    TimeFilter *timefilter;
     void (*reorder_func)(const void *, void *, int);
     void *reorder_buf;
     int reorder_buf_size; ///< in frames
+    int64_t samples; ///< number of samples returned so far
+    int64_t time_origin; ///< initial timestamp
+    struct linreg {  ///< exponential weighted moving linear regression
+        double conv_factor;
+        double var_xx_min;
+        double xlast;
+        double mean_x, mean_y;
+        double var_xx, var_xy;
+        double a;
+    } linreg;
 } AlsaData;
 
 /**
-- 
1.7.2.5



More information about the ffmpeg-devel mailing list