OSDN Git Service

The rate plugin was redesigned. Now only whole periods are resampled to
authorJaroslav Kysela <perex@perex.cz>
Wed, 4 Feb 2004 09:21:11 +0000 (09:21 +0000)
committerJaroslav Kysela <perex@perex.cz>
Wed, 4 Feb 2004 09:21:11 +0000 (09:21 +0000)
avoid rounding problems and to allow using other "block" algorithms.

src/pcm/pcm_plugin.c
src/pcm/pcm_rate.c

index d7d7d81..5ffb50c 100644 (file)
@@ -134,6 +134,7 @@ void snd_pcm_plugin_init(snd_pcm_plugin_t *plugin)
        memset(plugin, 0, sizeof(snd_pcm_plugin_t));
        plugin->undo_read = snd_pcm_plugin_undo_read;
        plugin->undo_write = snd_pcm_plugin_undo_write;
+       snd_atomic_write_init(&plugin->watom);
 }
 
 int snd_pcm_plugin_close(snd_pcm_t *pcm)
index f9806b0..be9a5b2 100644 (file)
@@ -3,11 +3,13 @@
  * \ingroup PCM_Plugins
  * \brief PCM Rate Plugin Interface
  * \author Abramo Bagnara <abramo@alsa-project.org>
- * \date 2000-2001
+ * \author Jaroslav Kysela <perex@suse.cz>
+ * \date 2000-2004
  */
 /*
  *  PCM - Rate conversion
  *  Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
+ *                2004 by Jaroslav Kysela <perex@suse.cz>
  *
  *
  *   This library is free software; you can redistribute it and/or modify
@@ -30,6 +32,7 @@
 #include <byteswap.h>
 #include "pcm_local.h"
 #include "pcm_plugin.h"
+#include "iatomic.h"
 
 #ifndef PIC
 /* entry for static linking */
@@ -38,7 +41,7 @@ const char *_snd_module_pcm_rate = "";
 
 #ifndef DOC_HIDDEN
 
-#define DIV (1<<16)
+#define LINEAR_DIV (1<<16)
 
 enum rate_type {
        RATE_TYPE_LINEAR,               /* linear interpolation */
@@ -52,25 +55,26 @@ typedef struct {
                        int init;
                        int16_t old_sample, new_sample;
                        int sum;
-                       unsigned int pos;
                } linear;
        } u;
 } snd_pcm_rate_state_t;
  
-typedef snd_pcm_uframes_t (*rate_f)(const snd_pcm_channel_area_t *dst_areas,
-                                   snd_pcm_uframes_t dst_offset,
-                                   snd_pcm_uframes_t *dst_framesp,
-                                   const snd_pcm_channel_area_t *src_areas,
-                                   snd_pcm_uframes_t src_offset,
-                                   snd_pcm_uframes_t src_frames,
-                                   unsigned int channels,
-                                   unsigned int getidx, unsigned int putidx,
-                                   unsigned int arg,
-                                   snd_pcm_rate_state_t *states);
+typedef void (*rate_f)(const snd_pcm_channel_area_t *dst_areas,
+                      snd_pcm_uframes_t dst_offset,
+                      snd_pcm_uframes_t dst_frames,
+                      const snd_pcm_channel_area_t *src_areas,
+                      snd_pcm_uframes_t src_offset,
+                      snd_pcm_uframes_t src_frames,
+                      unsigned int channels,
+                      unsigned int getidx, unsigned int putidx,
+                      unsigned int arg,
+                      snd_pcm_rate_state_t *states);
 
 typedef struct {
-       /* This field need to be the first */
-       snd_pcm_plugin_t plug;
+       snd_pcm_t *slave;
+       int close_slave;
+       snd_atomic_write_t watom;
+       snd_pcm_uframes_t appl_ptr, hw_ptr;
        enum rate_type type;
        unsigned int get_idx;
        unsigned int put_idx;
@@ -79,6 +83,8 @@ typedef struct {
        snd_pcm_format_t sformat;
        unsigned int srate;
        snd_pcm_rate_state_t *states;
+       snd_pcm_channel_area_t *pareas; /* areas for splitted period (rate pcm) */
+       snd_pcm_channel_area_t *sareas; /* areas for splitted period (slave pcm) */
 } snd_pcm_rate_t;
 
 static int16_t initial_sample(const char *src, unsigned int getidx)
@@ -97,14 +103,14 @@ static int16_t initial_sample(const char *src, unsigned int getidx)
        return sample;
 }
 
-static snd_pcm_uframes_t snd_pcm_rate_expand(const snd_pcm_channel_area_t *dst_areas,
-                                            snd_pcm_uframes_t dst_offset, snd_pcm_uframes_t *dst_framesp,
-                                            const snd_pcm_channel_area_t *src_areas,
-                                            snd_pcm_uframes_t src_offset, snd_pcm_uframes_t src_frames,
-                                            unsigned int channels,
-                                            unsigned int getidx, unsigned int putidx,
-                                            unsigned int get_threshold,
-                                            snd_pcm_rate_state_t *states)
+static void snd_pcm_rate_expand(const snd_pcm_channel_area_t *dst_areas,
+                               snd_pcm_uframes_t dst_offset, snd_pcm_uframes_t dst_frames,
+                               const snd_pcm_channel_area_t *src_areas,
+                               snd_pcm_uframes_t src_offset, snd_pcm_uframes_t src_frames,
+                               unsigned int channels,
+                               unsigned int getidx, unsigned int putidx,
+                               unsigned int get_threshold,
+                               snd_pcm_rate_state_t *states)
 {
 #define GET16_LABELS
 #define PUT16_LABELS
@@ -116,12 +122,8 @@ static snd_pcm_uframes_t snd_pcm_rate_expand(const snd_pcm_channel_area_t *dst_a
        unsigned int channel;
        snd_pcm_uframes_t src_frames1 = 0;
        snd_pcm_uframes_t dst_frames1 = 0;
-       snd_pcm_uframes_t dst_frames = *dst_framesp;
        int16_t sample = 0;
        
-       if (src_frames == 0 ||
-           dst_frames == 0)
-               return 0;
        for (channel = 0; channel < channels; ++channel) {
                const snd_pcm_channel_area_t *src_area = &src_areas[channel];
                const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
@@ -130,7 +132,7 @@ static snd_pcm_uframes_t snd_pcm_rate_expand(const snd_pcm_channel_area_t *dst_a
                int src_step, dst_step;
                int16_t old_sample = states->u.linear.old_sample;
                int16_t new_sample = states->u.linear.new_sample;
-               unsigned int pos = states->u.linear.pos;
+               unsigned int pos = 0;
                src = snd_pcm_channel_area_addr(src_area, src_offset);
                dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
                src_step = snd_pcm_channel_area_step(src_area);
@@ -140,9 +142,7 @@ static snd_pcm_uframes_t snd_pcm_rate_expand(const snd_pcm_channel_area_t *dst_a
                if (! states->u.linear.init) {
                        sample = initial_sample(src, getidx);
                        old_sample = new_sample = sample;
-                       src += src_step;
-                       src_frames1++;
-                       states->u.linear.init = 2; /* get a new sample */
+                       states->u.linear.init = 1;
                }
                while (dst_frames1 < dst_frames) {
                        if (states->u.linear.init == 2) {
@@ -163,33 +163,29 @@ static snd_pcm_uframes_t snd_pcm_rate_expand(const snd_pcm_channel_area_t *dst_a
                after_put:
                        dst += dst_step;
                        dst_frames1++;
-                       pos += DIV;
+                       pos += LINEAR_DIV;
                        if (pos >= get_threshold) {
                                pos -= get_threshold;
                                src += src_step;
                                src_frames1++;
                                states->u.linear.init = 2; /* get a new sample */
-                               if (src_frames1 >= src_frames)
-                                       break;
+                               assert(src_frames1 <= src_frames);
                        }
                } 
                states->u.linear.old_sample = old_sample;
                states->u.linear.new_sample = new_sample;
-               states->u.linear.pos = pos;
                states++;
        }
-       *dst_framesp = dst_frames1;
-       return src_frames1;
 }
 
-static snd_pcm_uframes_t snd_pcm_rate_shrink(const snd_pcm_channel_area_t *dst_areas,
-                                            snd_pcm_uframes_t dst_offset, snd_pcm_uframes_t *dst_framesp,
-                                            const snd_pcm_channel_area_t *src_areas,
-                                            snd_pcm_uframes_t src_offset, snd_pcm_uframes_t src_frames,
-                                            unsigned int channels,
-                                            unsigned int getidx, unsigned int putidx,
-                                            unsigned int get_increment,
-                                            snd_pcm_rate_state_t *states)
+static void snd_pcm_rate_shrink(const snd_pcm_channel_area_t *dst_areas,
+                               snd_pcm_uframes_t dst_offset, snd_pcm_uframes_t dst_frames,
+                               const snd_pcm_channel_area_t *src_areas,
+                               snd_pcm_uframes_t src_offset, snd_pcm_uframes_t src_frames,
+                               unsigned int channels,
+                               unsigned int getidx, unsigned int putidx,
+                               unsigned int get_increment,
+                               snd_pcm_rate_state_t *states)
 {
 #define GET16_LABELS
 #define PUT16_LABELS
@@ -201,12 +197,8 @@ static snd_pcm_uframes_t snd_pcm_rate_shrink(const snd_pcm_channel_area_t *dst_a
        unsigned int channel;
        snd_pcm_uframes_t src_frames1 = 0;
        snd_pcm_uframes_t dst_frames1 = 0;
-       snd_pcm_uframes_t dst_frames = *dst_framesp;
        int16_t sample = 0;
 
-       if (src_frames == 0 ||
-           dst_frames == 0)
-               return 0;
        for (channel = 0; channel < channels; ++channel) {
                const snd_pcm_channel_area_t *src_area = &src_areas[channel];
                const snd_pcm_channel_area_t *dst_area = &dst_areas[channel];
@@ -216,7 +208,7 @@ static snd_pcm_uframes_t snd_pcm_rate_shrink(const snd_pcm_channel_area_t *dst_a
                char *dst;
                int src_step, dst_step;
                sum = states->u.linear.sum;
-               pos = states->u.linear.pos;
+               pos = 0;
                states->u.linear.init = 0;
                src = snd_pcm_channel_area_addr(src_area, src_offset);
                dst = snd_pcm_channel_area_addr(dst_area, dst_offset);
@@ -234,11 +226,11 @@ static snd_pcm_uframes_t snd_pcm_rate_shrink(const snd_pcm_channel_area_t *dst_a
                        src += src_step;
                        src_frames1++;
                        pos += get_increment;
-                       if (pos >= DIV) {
+                       if (pos >= LINEAR_DIV) {
                                int s = sample;
-                               pos -= DIV;
+                               pos -= LINEAR_DIV;
                                sum += s * (get_increment - pos);
-                               sum /= DIV;
+                               sum /= LINEAR_DIV;
                                sample = sum;
                                goto *put;
 #define PUT16_END after_put
@@ -248,21 +240,39 @@ static snd_pcm_uframes_t snd_pcm_rate_shrink(const snd_pcm_channel_area_t *dst_a
                                dst += dst_step;
                                sum = s * pos;
                                dst_frames1++;
-                               if (dst_frames1 == dst_frames)
-                                       break;
+                               assert(dst_frames1 <= dst_frames);
                        } else
                                sum += sample * get_increment;
                }
                states->u.linear.sum = sum;
-               states->u.linear.pos = pos;
                states++;
        }
-       *dst_framesp = dst_frames1;
-       return src_frames1;
 }
 
 #endif /* DOC_HIDDEN */
 
+static snd_pcm_sframes_t snd_pcm_rate_client_frames(snd_pcm_t *pcm, snd_pcm_sframes_t frames)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       if (frames == 0)
+               return 0;
+       /* Round toward zero */
+       if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
+               return muldiv_down(frames, LINEAR_DIV, rate->pitch);
+       else
+               return muldiv_down(frames, rate->pitch, LINEAR_DIV);
+}
+
+static snd_pcm_sframes_t snd_pcm_rate_slave_frames(snd_pcm_t *pcm, snd_pcm_sframes_t frames)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       /* Round toward zero */
+       if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
+               return muldiv_down(frames, rate->pitch, LINEAR_DIV);
+       else
+               return muldiv_down(frames, LINEAR_DIV, rate->pitch);
+}
+
 static int snd_pcm_rate_hw_refine_cprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params)
 {
        int err;
@@ -366,6 +376,18 @@ static int snd_pcm_rate_hw_refine_cchange(snd_pcm_t *pcm, snd_pcm_hw_params_t *p
        return 0;
 }
 
+static int snd_pcm_rate_hw_refine_slave(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       return snd_pcm_hw_refine(rate->slave, params);
+}
+
+static int snd_pcm_rate_hw_params_slave(snd_pcm_t *pcm, snd_pcm_hw_params_t *params)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       return _snd_pcm_hw_params(rate->slave, params);
+}
+
 static int snd_pcm_rate_hw_refine(snd_pcm_t *pcm, 
                                  snd_pcm_hw_params_t *params)
 {
@@ -374,40 +396,55 @@ static int snd_pcm_rate_hw_refine(snd_pcm_t *pcm,
                                       snd_pcm_rate_hw_refine_cchange,
                                       snd_pcm_rate_hw_refine_sprepare,
                                       snd_pcm_rate_hw_refine_schange,
-                                      snd_pcm_plugin_hw_refine_slave);
+                                      snd_pcm_rate_hw_refine_slave);
 }
 
 static int snd_pcm_rate_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
 {
        snd_pcm_rate_t *rate = pcm->private_data;
-       snd_pcm_t *slave = rate->plug.slave;
-       snd_pcm_format_t src_format, dst_format;
-       unsigned int src_rate, dst_rate;
+       snd_pcm_t *slave = rate->slave;
+       snd_pcm_format_t src_format, dst_format, pformat, sformat;
+       unsigned int src_rate, dst_rate, channels, pwidth, swidth, chn;
+       snd_pcm_uframes_t period_size, buffer_size;
        int err = snd_pcm_hw_params_slave(pcm, params,
                                          snd_pcm_rate_hw_refine_cchange,
                                          snd_pcm_rate_hw_refine_sprepare,
                                          snd_pcm_rate_hw_refine_schange,
-                                         snd_pcm_plugin_hw_params_slave);
+                                         snd_pcm_rate_hw_params_slave);
        if (err < 0)
                return err;
 
        if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+               period_size = slave->period_size;
+               buffer_size = slave->buffer_size;
                err = INTERNAL(snd_pcm_hw_params_get_format)(params, &src_format);
                if (err < 0)
                        return err;
+               pformat = src_format;
                dst_format = slave->format;
-               err = INTERNAL(snd_pcm_hw_params_get_rate)(params, &src_rate, 0);
+               sformat = dst_format;
                dst_rate = slave->rate;
+               err = INTERNAL(snd_pcm_hw_params_get_rate)(params, &src_rate, 0);
        } else {
-               src_format = slave->format;
+               err = INTERNAL(snd_pcm_hw_params_get_period_size)(params, &period_size, 0);
+               if (err < 0)
+                       return err;
+               err = INTERNAL(snd_pcm_hw_params_get_buffer_size)(params, &buffer_size);
+               if (err < 0)
+                       return err;
+               sformat = src_format = slave->format;
                err = INTERNAL(snd_pcm_hw_params_get_format)(params, &dst_format);
                if (err < 0)
                        return err;
+               pformat = dst_format;
                src_rate = slave->rate;
                err = INTERNAL(snd_pcm_hw_params_get_rate)(params, &dst_rate, 0);
        }
        if (err < 0)
                return err;
+       err = INTERNAL(snd_pcm_hw_params_get_channels)(params, &channels);
+       if (err < 0)
+               return err;
        rate->get_idx = snd_pcm_linear_get_index(src_format, SND_PCM_FORMAT_S16);
        rate->put_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S16, dst_format);
        if (src_rate < dst_rate) {
@@ -417,26 +454,58 @@ static int snd_pcm_rate_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params)
                rate->func = snd_pcm_rate_shrink;
                /* pitch is get_increment */
        }
-       rate->pitch = (((u_int64_t)dst_rate * DIV) + src_rate / 2) / src_rate;
+       rate->pitch = (((u_int64_t)dst_rate * LINEAR_DIV) + src_rate - 1) / src_rate;
        assert(!rate->states);
-       rate->states = malloc(rate->plug.slave->channels * sizeof(*rate->states));
+       assert(!rate->pareas);
+       rate->states = malloc(channels * sizeof(*rate->states));
+       if (rate->states == NULL)
+               return -ENOMEM;
+       if ((buffer_size / period_size) * period_size == buffer_size)
+               return 0;
+       rate->pareas = malloc(2 * channels * sizeof(*rate->pareas));
+       if (rate->pareas == NULL)
+               return -ENOMEM;
+       pwidth = snd_pcm_format_physical_width(pformat);
+       swidth = snd_pcm_format_physical_width(sformat);
+       rate->pareas[0].addr = malloc(((pwidth * channels * period_size) / 8) +
+                                     ((swidth * channels * slave->period_size) / 8));
+       if (rate->pareas[0].addr == NULL) {
+               free(rate->pareas);
+               return -ENOMEM;
+       }
+       rate->sareas = rate->pareas + channels;
+       rate->sareas[0].addr = (char *)rate->pareas[0].addr + ((pwidth * channels * period_size) / 8);
+       for (chn = 0; chn < channels; chn++) {
+               rate->pareas[chn].addr = rate->pareas[0].addr + (pwidth * chn * period_size) / 8;
+               rate->pareas[chn].first = 0;
+               rate->pareas[chn].step = pwidth;
+               rate->sareas[chn].addr = rate->sareas[0].addr + (swidth * chn * slave->period_size) / 8;
+               rate->sareas[chn].first = 0;
+               rate->sareas[chn].step = swidth;
+       }
        return 0;
 }
 
 static int snd_pcm_rate_hw_free(snd_pcm_t *pcm)
 {
        snd_pcm_rate_t *rate = pcm->private_data;
+       if (rate->pareas) {
+               free(rate->pareas[0].addr);
+               free(rate->pareas);
+               rate->pareas = NULL;
+               rate->sareas = NULL;
+       }
        if (rate->states) {
                free(rate->states);
                rate->states = NULL;
        }
-       return snd_pcm_hw_free(rate->plug.slave);
+       return snd_pcm_hw_free(rate->slave);
 }
 
 static void recalc(snd_pcm_t *pcm, snd_pcm_uframes_t *val)
 {
        snd_pcm_rate_t *rate = pcm->private_data;
-       snd_pcm_t *slave = rate->plug.slave;
+       snd_pcm_t *slave = rate->slave;
        unsigned long div;
 
        if (*val == pcm->buffer_size) {
@@ -446,19 +515,19 @@ static void recalc(snd_pcm_t *pcm, snd_pcm_uframes_t *val)
                if (div * pcm->period_size == *val)
                        *val = div * slave->period_size;
                else
-                       *val = muldiv_near(*val, slave->rate, pcm->rate);
+                       *val = muldiv_near(*val, slave->period_size, pcm->period_size);
        }
 }
 
 static int snd_pcm_rate_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t * params)
 {
        snd_pcm_rate_t *rate = pcm->private_data;
-       snd_pcm_t *slave = rate->plug.slave;
+       snd_pcm_t *slave = rate->slave;
        snd_pcm_sw_params_t sparams;
        snd_pcm_uframes_t boundary1, boundary2;
 
        sparams = *params;
-       if ((rate->pitch >= DIV ? 1 : 0) ^ (pcm->stream == SND_PCM_STREAM_CAPTURE ? 1 : 0)) {
+       if ((rate->pitch >= LINEAR_DIV ? 1 : 0) ^ (pcm->stream == SND_PCM_STREAM_CAPTURE ? 1 : 0)) {
                boundary1 = pcm->buffer_size;
                boundary2 = slave->buffer_size;
                while (boundary2 * 2 <= LONG_MAX - slave->buffer_size) {
@@ -475,13 +544,53 @@ static int snd_pcm_rate_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t * params)
        }
        params->boundary = boundary1;
        sparams.boundary = boundary2;
-#if 0
        if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
-               rate->pitch = (((u_int64_t)boundary2 * DIV) + boundary1 / 2) / boundary1;
+               rate->pitch = (((u_int64_t)slave->period_size * LINEAR_DIV) + pcm->period_size - 1) / pcm->period_size;
+               do {
+                       snd_pcm_uframes_t cframes;
+                       
+                       cframes = snd_pcm_rate_client_frames(pcm, slave->period_size - 1);
+                       if (cframes == pcm->period_size - 1)
+                               break;
+                       if (cframes > pcm->period_size - 1) {
+                               rate->pitch++;
+                               if ((snd_pcm_uframes_t)snd_pcm_rate_client_frames(pcm, slave->period_size - 1) < pcm->period_size - 1) {
+                                       SNDERR("Unable to satisfy pitch condition (%i/%i - %li/%li)\n", slave->rate, pcm->rate, slave->period_size - 1, pcm->period_size - 1);
+                                       return -EIO;
+                               }
+                       } else {
+                               rate->pitch--;
+                               if ((snd_pcm_uframes_t)snd_pcm_rate_client_frames(pcm, slave->period_size - 1) > pcm->period_size - 1) {
+                                       SNDERR("Unable to satisfy pitch condition (%i/%i - %li/%li)\n", slave->rate, pcm->rate, slave->period_size - 1, pcm->period_size - 1);
+                                       return -EIO;
+                               }
+                       }
+               } while (1);
+               assert((snd_pcm_uframes_t)snd_pcm_rate_client_frames(pcm, slave->period_size - 1) == pcm->period_size - 1);
        } else {
-               rate->pitch = (((u_int64_t)boundary1 * DIV) + boundary2 / 2) / boundary2;
+               rate->pitch = (((u_int64_t)pcm->period_size * LINEAR_DIV) + slave->period_size - 1) / slave->period_size;
+               do {
+                       snd_pcm_uframes_t cframes;
+                       
+                       cframes = snd_pcm_rate_slave_frames(pcm, pcm->period_size - 1);
+                       if (cframes == slave->period_size - 1)
+                               break;
+                       if (cframes > slave->period_size - 1) {
+                               rate->pitch++;
+                               if ((snd_pcm_uframes_t)snd_pcm_rate_slave_frames(pcm, pcm->period_size - 1) < slave->period_size - 1) {
+                                       SNDERR("Unable to satisfy pitch condition (%i/%i - %li/%li)\n", slave->rate, pcm->rate, slave->period_size - 1, pcm->period_size - 1);
+                                       return -EIO;
+                               }
+                       } else {
+                               rate->pitch--;
+                               if ((snd_pcm_uframes_t)snd_pcm_rate_slave_frames(pcm, pcm->period_size - 1) > slave->period_size - 1) {
+                                       SNDERR("Unable to satisfy pitch condition (%i/%i - %li/%li)\n", slave->rate, pcm->rate, slave->period_size - 1, pcm->period_size - 1);
+                                       return -EIO;
+                               }
+                       }
+               } while (1);
+               assert((snd_pcm_uframes_t)snd_pcm_rate_slave_frames(pcm, pcm->period_size - 1) == slave->period_size - 1);
        }
-#endif
        recalc(pcm, &sparams.avail_min);
        recalc(pcm, &sparams.xfer_align);
        recalc(pcm, &sparams.start_threshold);
@@ -505,7 +614,6 @@ static int snd_pcm_rate_init(snd_pcm_t *pcm)
                        rate->states[k].u.linear.sum = 0;
                        rate->states[k].u.linear.old_sample = 0;
                        rate->states[k].u.linear.new_sample = 0;
-                       rate->states[k].u.linear.pos = 0;
                        rate->states[k].u.linear.init = 0;
                }
                break;
@@ -515,61 +623,596 @@ static int snd_pcm_rate_init(snd_pcm_t *pcm)
        return 0;
 }
 
-static snd_pcm_uframes_t
-snd_pcm_rate_write_areas(snd_pcm_t *pcm,
+static inline int
+snd_pcm_rate_write_areas1(snd_pcm_t *pcm,
                         const snd_pcm_channel_area_t *areas,
                         snd_pcm_uframes_t offset,
-                        snd_pcm_uframes_t size,
                         const snd_pcm_channel_area_t *slave_areas,
-                        snd_pcm_uframes_t slave_offset,
-                        snd_pcm_uframes_t *slave_sizep)
+                        snd_pcm_uframes_t slave_offset)
 {
        snd_pcm_rate_t *rate = pcm->private_data;
-       return rate->func(slave_areas, slave_offset, slave_sizep, 
-                         areas, offset, size,
-                         pcm->channels,
-                         rate->get_idx, rate->put_idx,
-                         rate->pitch, rate->states);
+       rate->func(slave_areas, slave_offset, rate->slave->period_size,
+                  areas, offset, pcm->period_size,
+                  pcm->channels,
+                  rate->get_idx, rate->put_idx,
+                  rate->pitch, rate->states);
+       return 0;
 }
 
-static snd_pcm_uframes_t
-snd_pcm_rate_read_areas(snd_pcm_t *pcm,
+static inline int
+snd_pcm_rate_read_areas1(snd_pcm_t *pcm,
                         const snd_pcm_channel_area_t *areas,
                         snd_pcm_uframes_t offset,
-                        snd_pcm_uframes_t size,
                         const snd_pcm_channel_area_t *slave_areas,
-                        snd_pcm_uframes_t slave_offset,
-                        snd_pcm_uframes_t *slave_sizep)
+                        snd_pcm_uframes_t slave_offset)
 {
        snd_pcm_rate_t *rate = pcm->private_data;
-       *slave_sizep = rate->func(areas, offset, &size,
-                                 slave_areas, slave_offset, *slave_sizep,
-                                 pcm->channels,
-                                 rate->get_idx, rate->put_idx,
-                                 rate->pitch, rate->states);
-       return size;
+       rate->func(areas, offset, pcm->period_size,
+                  slave_areas, slave_offset, rate->slave->period_size,
+                  pcm->channels,
+                  rate->get_idx, rate->put_idx,
+                  rate->pitch, rate->states);
+       return 0;
 }
 
-static snd_pcm_sframes_t snd_pcm_rate_client_frames(snd_pcm_t *pcm, snd_pcm_sframes_t frames)
+static inline snd_pcm_sframes_t snd_pcm_rate_move_applptr(snd_pcm_t *pcm, snd_pcm_sframes_t frames)
 {
        snd_pcm_rate_t *rate = pcm->private_data;
-       if (frames == 0)
-               return 0;
-       /* Round toward zero */
-       if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
-               return muldiv_down(frames, DIV, rate->pitch);
+       snd_pcm_uframes_t orig_appl_ptr, appl_ptr = rate->appl_ptr, slave_appl_ptr;
+       snd_pcm_sframes_t diff, ndiff;
+       snd_pcm_t *slave = rate->slave;
+
+       orig_appl_ptr = rate->appl_ptr;
+       if (frames > 0)
+               snd_pcm_mmap_appl_forward(pcm, frames);
        else
-               return muldiv_down(frames, rate->pitch, DIV);
+               snd_pcm_mmap_appl_backward(pcm, -frames);
+       slave_appl_ptr =
+               (appl_ptr / pcm->period_size) * rate->slave->period_size;
+       diff = slave_appl_ptr - *slave->appl.ptr;
+       if (diff < -(snd_pcm_sframes_t)(slave->boundary / 2)) {
+               diff = (slave->boundary - *slave->appl.ptr) + slave_appl_ptr;
+       } else if (diff > (snd_pcm_sframes_t)(slave->boundary / 2)) {
+               diff = -((slave->boundary - slave_appl_ptr) + *slave->appl.ptr);
+       }
+       if (diff == 0)
+               return frames;
+       if (diff > 0) {
+               ndiff = snd_pcm_forward(rate->slave, diff);
+       } else {
+               ndiff = snd_pcm_rewind(rate->slave, diff);
+       }
+       if (ndiff < 0)
+               return diff;
+       slave_appl_ptr = *slave->appl.ptr;
+       rate->appl_ptr =
+               (slave_appl_ptr / rate->slave->period_size) * pcm->period_size +
+               snd_pcm_rate_client_frames(pcm, slave_appl_ptr % rate->slave->period_size) +
+               orig_appl_ptr % pcm->period_size;
+       diff = orig_appl_ptr - rate->appl_ptr;
+       if (diff < -(snd_pcm_sframes_t)(slave->boundary / 2)) {
+               diff = (slave->boundary - rate->appl_ptr) + orig_appl_ptr;
+       } else if (diff > (snd_pcm_sframes_t)(slave->boundary / 2)) {
+               diff = -((slave->boundary - orig_appl_ptr) + rate->appl_ptr);
+       }
+       if (frames < 0)
+               return -diff;
+       return diff;
 }
 
-static snd_pcm_sframes_t snd_pcm_rate_slave_frames(snd_pcm_t *pcm, snd_pcm_sframes_t frames)
+static inline void snd_pcm_rate_sync_hwptr(snd_pcm_t *pcm)
 {
        snd_pcm_rate_t *rate = pcm->private_data;
-       /* Round toward zero */
+       snd_pcm_uframes_t slave_hw_ptr = *rate->slave->hw.ptr;
+
+       if (pcm->stream != SND_PCM_STREAM_PLAYBACK)
+               return;
+       rate->hw_ptr =
+               (slave_hw_ptr / rate->slave->period_size) * pcm->period_size +
+               snd_pcm_rate_client_frames(pcm, slave_hw_ptr % rate->slave->period_size);
+}
+
+static int snd_pcm_rate_close(snd_pcm_t *pcm)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       int err = 0;
+       if (rate->close_slave)
+               err = snd_pcm_close(rate->slave);
+       free(rate);
+       return 0;
+}
+
+static int snd_pcm_rate_nonblock(snd_pcm_t *pcm, int nonblock)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       return snd_pcm_nonblock(rate->slave, nonblock);
+}
+
+static int snd_pcm_rate_async(snd_pcm_t *pcm, int sig, pid_t pid)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       return snd_pcm_async(rate->slave, sig, pid);
+}
+
+static int snd_pcm_rate_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       return snd_pcm_poll_descriptors_revents(rate->slave, pfds, nfds, revents);
+}
+
+static int snd_pcm_rate_info(snd_pcm_t *pcm, snd_pcm_info_t * info)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       return snd_pcm_info(rate->slave, info);
+}
+
+static int snd_pcm_rate_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t *info)
+{
+       return snd_pcm_channel_info_shm(pcm, info, -1);
+}
+
+static snd_pcm_state_t snd_pcm_rate_state(snd_pcm_t *pcm)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       return snd_pcm_state(rate->slave);
+}
+
+static int snd_pcm_rate_hwsync(snd_pcm_t *pcm)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       int err = snd_pcm_hwsync(rate->slave);
+       if (err < 0)
+               return err;
+       snd_atomic_write_begin(&rate->watom);
+       snd_pcm_rate_sync_hwptr(pcm);
+       snd_atomic_write_end(&rate->watom);
+       return 0;
+}
+
+static int snd_pcm_rate_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp)
+{
+       snd_pcm_rate_hwsync(pcm);
        if (pcm->stream == SND_PCM_STREAM_PLAYBACK)
-               return muldiv_down(frames, rate->pitch, DIV);
+               *delayp = snd_pcm_mmap_playback_hw_avail(pcm);
        else
-               return muldiv_down(frames, DIV, rate->pitch);
+               *delayp = snd_pcm_mmap_capture_hw_avail(pcm);
+       return 0;
+}
+
+static int snd_pcm_rate_prepare(snd_pcm_t *pcm)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       int err;
+
+       snd_atomic_write_begin(&rate->watom);
+       err = snd_pcm_prepare(rate->slave);
+       if (err < 0) {
+               snd_atomic_write_end(&rate->watom);
+               return err;
+       }
+       *pcm->hw.ptr = 0;
+       *pcm->appl.ptr = 0;
+       snd_atomic_write_end(&rate->watom);
+       err = snd_pcm_rate_init(pcm);
+       if (err < 0)
+               return err;
+       return 0;
+}
+
+static int snd_pcm_rate_reset(snd_pcm_t *pcm)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       int err;
+       snd_atomic_write_begin(&rate->watom);
+       err = snd_pcm_reset(rate->slave);
+       if (err < 0) {
+               snd_atomic_write_end(&rate->watom);
+               return err;
+       }
+       *pcm->hw.ptr = 0;
+       *pcm->appl.ptr = 0;
+       snd_atomic_write_end(&rate->watom);
+       err = snd_pcm_rate_init(pcm);
+       if (err < 0)
+               return err;
+       return 0;
+}
+
+static int snd_pcm_rate_start(snd_pcm_t *pcm)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       return snd_pcm_start(rate->slave);
+}
+
+static int snd_pcm_rate_drop(snd_pcm_t *pcm)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       return snd_pcm_drop(rate->slave);
+}
+
+static int snd_pcm_rate_drain(snd_pcm_t *pcm)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       return snd_pcm_drain(rate->slave);
+}
+
+static int snd_pcm_rate_pause(snd_pcm_t *pcm, int enable)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       return snd_pcm_pause(rate->slave, enable);
+}
+
+static snd_pcm_sframes_t snd_pcm_rate_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       snd_pcm_sframes_t n = snd_pcm_mmap_hw_avail(pcm);
+
+       if ((snd_pcm_uframes_t)n > frames)
+               frames = n;
+       if (frames == 0)
+               return 0;
+       
+       snd_atomic_write_begin(&rate->watom);
+       n = snd_pcm_rate_move_applptr(pcm, -frames);
+       snd_atomic_write_end(&rate->watom);
+       return n;
+}
+
+static snd_pcm_sframes_t snd_pcm_rate_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       snd_pcm_sframes_t n = snd_pcm_mmap_avail(pcm);
+
+       if ((snd_pcm_uframes_t)n > frames)
+               frames = n;
+       if (frames == 0)
+               return 0;
+       
+       snd_atomic_write_begin(&rate->watom);
+       n = snd_pcm_rate_move_applptr(pcm, frames);
+       snd_atomic_write_end(&rate->watom);
+       return n;
+}
+
+static int snd_pcm_rate_resume(snd_pcm_t *pcm)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       return snd_pcm_resume(rate->slave);
+}
+
+static int snd_pcm_rate_commit_next_period(snd_pcm_t *pcm, snd_pcm_uframes_t appl_offset)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       snd_pcm_uframes_t cont = pcm->buffer_size - appl_offset;
+       const snd_pcm_channel_area_t *areas;
+       const snd_pcm_channel_area_t *slave_areas;
+       snd_pcm_uframes_t slave_offset, xfer;
+       snd_pcm_uframes_t slave_frames = ULONG_MAX;
+       snd_pcm_sframes_t result;
+
+       areas = snd_pcm_mmap_areas(pcm);
+       if (cont >= pcm->period_size) {
+               result = snd_pcm_mmap_begin(rate->slave, &slave_areas, &slave_offset, &slave_frames);
+               if (result < 0)
+                       return result;
+               if (slave_frames < rate->slave->period_size) {
+                       snd_pcm_rate_write_areas1(pcm, areas, appl_offset, rate->sareas, 0);
+                       goto __partial;
+               }
+               snd_pcm_rate_write_areas1(pcm, areas, appl_offset,
+                                         slave_areas, slave_offset);
+               result = snd_pcm_mmap_commit(rate->slave, slave_offset, rate->slave->period_size);
+               if (result < (snd_pcm_sframes_t)rate->slave->period_size) {
+                       if (result < 0)
+                               return result;
+                       result = snd_pcm_rewind(rate->slave, result);
+                       if (result < 0)
+                               return result;
+                       return 0;
+               }
+       } else {
+               snd_pcm_areas_copy(rate->pareas, cont,
+                                  areas, 0,
+                                  pcm->channels, pcm->period_size - cont,
+                                  pcm->format);
+               snd_pcm_areas_copy(rate->pareas, 0,
+                                  areas, appl_offset,
+                                  pcm->channels, cont,
+                                  pcm->format);
+
+               snd_pcm_rate_write_areas1(pcm, rate->pareas, 0, rate->sareas, 0);
+
+               /* ok, commit first fragment */
+               result = snd_pcm_mmap_begin(rate->slave, &slave_areas, &slave_offset, &slave_frames);
+               if (result < 0)
+                       return result;
+             __partial:
+               xfer = 0;
+               cont = rate->slave->buffer_size - slave_offset;
+               if (cont > rate->slave->period_size)
+                       cont = rate->slave->period_size;
+               snd_pcm_areas_copy(slave_areas, slave_offset,
+                                  rate->sareas, 0,
+                                  pcm->channels, cont,
+                                  rate->slave->format);
+               result = snd_pcm_mmap_commit(rate->slave, slave_offset, cont);
+               if (result < (snd_pcm_sframes_t)cont) {
+                       if (result < 0)
+                               return result;
+                       result = snd_pcm_rewind(rate->slave, result);
+                       if (result < 0)
+                               return result;
+                       return 0;
+               }
+               xfer = cont;
+
+               if (xfer == rate->slave->period_size)
+                       return 1;
+               
+               /* commit second fragment */
+               cont = rate->slave->period_size - cont;
+               slave_frames = cont;
+               result = snd_pcm_mmap_begin(rate->slave, &slave_areas, &slave_offset, &slave_frames);
+               if (result < 0)
+                       return result;
+               assert(slave_offset == 0);
+               snd_pcm_areas_copy(slave_areas, slave_offset,
+                                  rate->sareas, xfer,
+                                  pcm->channels, cont,
+                                  rate->slave->format);
+               result = snd_pcm_mmap_commit(rate->slave, slave_offset, cont);
+               if (result < (snd_pcm_sframes_t)cont) {
+                       if (result < 0)
+                               return result;
+                       result = snd_pcm_rewind(rate->slave, result + xfer);
+                       if (result < 0)
+                               return result;
+                       return 0;
+               }
+       }
+       return 1;
+}
+
+static int snd_pcm_rate_grab_next_period(snd_pcm_t *pcm, snd_pcm_uframes_t hw_offset)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       snd_pcm_uframes_t cont = pcm->buffer_size - hw_offset;
+       const snd_pcm_channel_area_t *areas;
+       const snd_pcm_channel_area_t *slave_areas;
+       snd_pcm_uframes_t slave_offset;
+       snd_pcm_uframes_t slave_frames = ULONG_MAX;
+       snd_pcm_sframes_t result;
+
+       areas = snd_pcm_mmap_areas(pcm);
+       if (cont >= pcm->period_size) {
+               result = snd_pcm_mmap_begin(rate->slave, &slave_areas, &slave_offset, &slave_frames);
+               if (result < 0)
+                       return result;
+               if (slave_frames < rate->slave->period_size)
+                       goto __partial;
+               snd_pcm_rate_read_areas1(pcm, areas, hw_offset,
+                                        slave_areas, slave_offset);
+               result = snd_pcm_mmap_commit(rate->slave, slave_offset, rate->slave->period_size);
+               if (result < (snd_pcm_sframes_t)rate->slave->period_size) {
+                       if (result < 0)
+                               return result;
+                       result = snd_pcm_rewind(rate->slave, result);
+                       if (result < 0)
+                               return result;
+                       return 0;
+               }
+       } else {
+               snd_pcm_rate_t *rate = pcm->private_data;
+               snd_pcm_uframes_t xfer;
+
+               /* ok, grab first fragment */
+               result = snd_pcm_mmap_begin(rate->slave, &slave_areas, &slave_offset, &slave_frames);
+               if (result < 0)
+                       return result;
+             __partial:
+               xfer = 0;
+               cont = rate->slave->buffer_size - slave_offset;
+               snd_pcm_areas_copy(rate->sareas, 0,
+                                  slave_areas, slave_offset,
+                                  pcm->channels, cont,
+                                  rate->slave->format);
+               result = snd_pcm_mmap_commit(rate->slave, slave_offset, cont);
+               if (result < (snd_pcm_sframes_t)cont) {
+                       if (result < 0)
+                               return result;
+                       result = snd_pcm_rewind(rate->slave, result);
+                       if (result < 0)
+                               return result;
+                       return 0;
+               }
+               xfer = cont;
+
+               /* grab second fragment */
+               cont = rate->slave->period_size - cont;
+               slave_frames = cont;
+               result = snd_pcm_mmap_begin(rate->slave, &slave_areas, &slave_offset, &slave_frames);
+               if (result < 0)
+                       return result;
+               assert(slave_offset == 0);
+               snd_pcm_areas_copy(rate->sareas, xfer,
+                                  slave_areas, slave_offset,
+                                  pcm->channels, cont,
+                                  rate->slave->format);
+               result = snd_pcm_mmap_commit(rate->slave, slave_offset, cont);
+               if (result < (snd_pcm_sframes_t)cont) {
+                       if (result < 0)
+                               return result;
+                       result = snd_pcm_rewind(rate->slave, result + xfer);
+                       if (result < 0)
+                               return result;
+                       return 0;
+               }
+
+               cont = pcm->buffer_size - hw_offset;
+               if (cont >= pcm->period_size) {
+                       snd_pcm_rate_read_areas1(pcm, areas, hw_offset,
+                                                rate->sareas, 0);
+               } else {
+                       snd_pcm_rate_read_areas1(pcm,
+                                                rate->pareas, 0,
+                                                rate->sareas, 0);
+                       snd_pcm_areas_copy(areas, hw_offset,
+                                          rate->pareas, 0,
+                                          pcm->channels, cont,
+                                          pcm->format);
+                       snd_pcm_areas_copy(areas, 0,
+                                          rate->pareas, cont,
+                                          pcm->channels, pcm->period_size - cont,
+                                          pcm->format);
+               }
+       }
+       return 1;
+}
+
+static snd_pcm_sframes_t snd_pcm_rate_mmap_commit(snd_pcm_t *pcm,
+                                                 snd_pcm_uframes_t offset ATTRIBUTE_UNUSED,
+                                                 snd_pcm_uframes_t size)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       snd_pcm_t *slave = rate->slave;
+       snd_pcm_uframes_t appl_offset, xfer;
+       snd_pcm_sframes_t slave_size;
+       int err;
+
+       if (size == 0)
+               return 0;
+       if (pcm->stream == SND_PCM_STREAM_CAPTURE) {
+               snd_atomic_write_begin(&rate->watom);
+               snd_pcm_mmap_appl_forward(pcm, size);
+               snd_atomic_write_end(&rate->watom);
+               return size;
+       }
+       slave_size = snd_pcm_avail_update(slave);
+       if (slave_size < 0)
+               return slave_size;
+       xfer = rate->appl_ptr % pcm->period_size;
+       appl_offset = (rate->appl_ptr - xfer) % pcm->buffer_size;
+       xfer = pcm->period_size - xfer;
+       if (xfer >= size) {
+               if (xfer == size) {
+                       err = snd_pcm_rate_commit_next_period(pcm, appl_offset);
+                       if (err < 0)
+                               return err;
+                       if (err == 0)
+                               return 0;
+               }
+               snd_atomic_write_begin(&rate->watom);
+               snd_pcm_mmap_appl_forward(pcm, size);
+               snd_atomic_write_end(&rate->watom);
+               return size;
+       } else {
+               size -= xfer;
+               err = snd_pcm_rate_commit_next_period(pcm, appl_offset);
+               if (err < 0)
+                       return err;
+               if (err == 0)
+                       return 0;
+               snd_atomic_write_begin(&rate->watom);
+               snd_pcm_mmap_appl_forward(pcm, xfer);
+               snd_atomic_write_end(&rate->watom);
+       }
+       while ((snd_pcm_uframes_t)size >= pcm->period_size &&
+              (snd_pcm_uframes_t)slave_size >= rate->slave->period_size) {
+               err = snd_pcm_rate_commit_next_period(pcm, appl_offset);
+               if (err == 0)
+                       return xfer;
+               if (err < 0)
+                       return xfer > 0 ? (snd_pcm_sframes_t)xfer : err;
+               xfer += pcm->period_size;
+               size -= pcm->period_size;
+               slave_size -= rate->slave->period_size;
+               snd_atomic_write_begin(&rate->watom);
+               snd_pcm_mmap_appl_forward(pcm, pcm->period_size);
+               snd_atomic_write_end(&rate->watom);
+       }
+       if (size > 0) {
+               snd_atomic_write_begin(&rate->watom);
+               snd_pcm_mmap_appl_forward(pcm, size);
+               snd_atomic_write_end(&rate->watom);
+               xfer += size;
+       }
+       return xfer;
+}
+
+static snd_pcm_sframes_t snd_pcm_rate_avail_update(snd_pcm_t *pcm)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       snd_pcm_t *slave = rate->slave;
+       snd_pcm_uframes_t slave_size;
+
+       slave_size = snd_pcm_avail_update(slave);
+       if (pcm->stream == SND_PCM_STREAM_CAPTURE)
+               goto _capture;
+       snd_atomic_write_begin(&rate->watom);
+       snd_pcm_rate_sync_hwptr(pcm);
+       snd_atomic_write_end(&rate->watom);
+       return snd_pcm_mmap_avail(pcm);
+ _capture: {
+       snd_pcm_uframes_t xfer, hw_offset, size;
+       
+       xfer = snd_pcm_mmap_capture_avail(pcm);
+       size = pcm->buffer_size - xfer;
+       hw_offset = snd_pcm_mmap_hw_offset(pcm);
+       while (size >= pcm->period_size &&
+              slave_size >= rate->slave->period_size) {
+               int err = snd_pcm_rate_grab_next_period(pcm, hw_offset);
+               if (err < 0)
+                       return err;
+               if (err == 0)
+                       return (snd_pcm_sframes_t)xfer;
+               xfer += pcm->period_size;
+               size -= pcm->period_size;
+               slave_size -= rate->slave->period_size;
+               snd_pcm_mmap_hw_forward(pcm, pcm->period_size);
+       }
+       return (snd_pcm_sframes_t)xfer;
+ }
+}
+
+static int snd_pcm_rate_mmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
+{
+       return 0;
+}
+
+static int snd_pcm_rate_munmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED)
+{
+       return 0;
+}
+
+static int snd_pcm_rate_status(snd_pcm_t *pcm, snd_pcm_status_t * status)
+{
+       snd_pcm_rate_t *rate = pcm->private_data;
+       snd_pcm_sframes_t err;
+       snd_atomic_read_t ratom;
+       snd_atomic_read_init(&ratom, &rate->watom);
+ _again:
+       snd_atomic_read_begin(&ratom);
+       err = snd_pcm_status(rate->slave, status);
+       if (err < 0) {
+               snd_atomic_read_ok(&ratom);
+               return err;
+       }
+       snd_pcm_rate_sync_hwptr(pcm);
+       status->appl_ptr = *pcm->appl.ptr;
+       status->hw_ptr = *pcm->hw.ptr;
+       if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
+               status->delay = snd_pcm_mmap_playback_hw_avail(pcm);
+               status->avail = snd_pcm_mmap_playback_avail(pcm);
+       } else {
+               status->delay = snd_pcm_mmap_capture_hw_avail(pcm);
+               status->avail = snd_pcm_mmap_capture_avail(pcm);
+       }
+       if (!snd_atomic_read_ok(&ratom)) {
+               snd_atomic_read_wait(&ratom);
+               goto _again;
+       }
+       status->avail_max = snd_pcm_rate_client_frames(pcm, (snd_pcm_sframes_t) status->avail_max);
+       return 0;
 }
 
 static void snd_pcm_rate_dump(snd_pcm_t *pcm, snd_output_t *out)
@@ -587,23 +1230,45 @@ static void snd_pcm_rate_dump(snd_pcm_t *pcm, snd_output_t *out)
                snd_pcm_dump_setup(pcm, out);
        }
        snd_output_printf(out, "Slave: ");
-       snd_pcm_dump(rate->plug.slave, out);
+       snd_pcm_dump(rate->slave, out);
 }
 
+static snd_pcm_fast_ops_t snd_pcm_rate_fast_ops = {
+       .status = snd_pcm_rate_status,
+       .state = snd_pcm_rate_state,
+       .hwsync = snd_pcm_rate_hwsync,
+       .delay = snd_pcm_rate_delay,
+       .prepare = snd_pcm_rate_prepare,
+       .reset = snd_pcm_rate_reset,
+       .start = snd_pcm_rate_start,
+       .drop = snd_pcm_rate_drop,
+       .drain = snd_pcm_rate_drain,
+       .pause = snd_pcm_rate_pause,
+       .rewind = snd_pcm_rate_rewind,
+       .forward = snd_pcm_rate_forward,
+       .resume = snd_pcm_rate_resume,
+       .writei = snd_pcm_mmap_writei,
+       .writen = snd_pcm_mmap_writen,
+       .readi = snd_pcm_mmap_readi,
+       .readn = snd_pcm_mmap_readn,
+       .avail_update = snd_pcm_rate_avail_update,
+       .mmap_commit = snd_pcm_rate_mmap_commit,
+};
+
 static snd_pcm_ops_t snd_pcm_rate_ops = {
-       .close = snd_pcm_plugin_close,
-       .info = snd_pcm_plugin_info,
+       .close = snd_pcm_rate_close,
+       .info = snd_pcm_rate_info,
        .hw_refine = snd_pcm_rate_hw_refine,
        .hw_params = snd_pcm_rate_hw_params,
        .hw_free = snd_pcm_rate_hw_free,
        .sw_params = snd_pcm_rate_sw_params,
-       .channel_info = snd_pcm_plugin_channel_info,
+       .channel_info = snd_pcm_rate_channel_info,
        .dump = snd_pcm_rate_dump,
-       .nonblock = snd_pcm_plugin_nonblock,
-       .async = snd_pcm_plugin_async,
-       .poll_revents = snd_pcm_plugin_poll_revents,
-       .mmap = snd_pcm_plugin_mmap,
-       .munmap = snd_pcm_plugin_munmap,
+       .nonblock = snd_pcm_rate_nonblock,
+       .async = snd_pcm_rate_async,
+       .poll_revents = snd_pcm_rate_poll_revents,
+       .mmap = snd_pcm_rate_mmap,
+       .munmap = snd_pcm_rate_munmap,
 };
 
 
@@ -633,17 +1298,12 @@ int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sform
        if (!rate) {
                return -ENOMEM;
        }
-       snd_pcm_plugin_init(&rate->plug);
+       rate->slave = slave;
+       rate->close_slave = close_slave;
        rate->type = RATE_TYPE_LINEAR;
        rate->srate = srate;
        rate->sformat = sformat;
-       rate->plug.read = snd_pcm_rate_read_areas;
-       rate->plug.write = snd_pcm_rate_write_areas;
-       rate->plug.client_frames = snd_pcm_rate_client_frames;
-       rate->plug.slave_frames = snd_pcm_rate_slave_frames;
-       rate->plug.init = snd_pcm_rate_init;
-       rate->plug.slave = slave;
-       rate->plug.close_slave = close_slave;
+       snd_atomic_write_init(&rate->watom);
 
        err = snd_pcm_new(&pcm, SND_PCM_TYPE_RATE, name, slave->stream, slave->mode);
        if (err < 0) {
@@ -651,12 +1311,13 @@ int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sform
                return err;
        }
        pcm->ops = &snd_pcm_rate_ops;
-       pcm->fast_ops = &snd_pcm_plugin_fast_ops;
+       pcm->fast_ops = &snd_pcm_rate_fast_ops;
        pcm->private_data = rate;
        pcm->poll_fd = slave->poll_fd;
        pcm->poll_events = slave->poll_events;
-       snd_pcm_set_hw_ptr(pcm, &rate->plug.hw_ptr, -1, 0);
-       snd_pcm_set_appl_ptr(pcm, &rate->plug.appl_ptr, -1, 0);
+       pcm->mmap_rw = 1;
+       snd_pcm_set_hw_ptr(pcm, &rate->hw_ptr, -1, 0);
+       snd_pcm_set_appl_ptr(pcm, &rate->appl_ptr, -1, 0);
        *pcmp = pcm;
 
        return 0;