* \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
#include <byteswap.h>
#include "pcm_local.h"
#include "pcm_plugin.h"
+#include "iatomic.h"
#ifndef PIC
/* entry for static linking */
#ifndef DOC_HIDDEN
-#define DIV (1<<16)
+#define LINEAR_DIV (1<<16)
enum rate_type {
RATE_TYPE_LINEAR, /* linear interpolation */
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;
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)
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
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];
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);
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) {
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
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];
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);
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
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;
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)
{
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) {
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) {
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) {
}
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);
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;
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)
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,
};
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) {
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;