alsa-lib/src/pcm/pcm_rate.c
Takashi Iwai ffdb04c3ea Fix buffer allocation and mmap with plugins
Fixed the bug producing silent tones with some combinations of plugins.
The internal buffer handling is now better (cleaner) integrated with
snd_pcm_generic_*().
2005-09-02 16:36:40 +00:00

1545 lines
47 KiB
C

/**
* \file pcm/pcm_rate.c
* \ingroup PCM_Plugins
* \brief PCM Rate Plugin Interface
* \author Abramo Bagnara <abramo@alsa-project.org>
* \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
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <inttypes.h>
#include <byteswap.h>
#include "pcm_local.h"
#include "pcm_plugin.h"
#include "iatomic.h"
#if 0
#define DEBUG_REFINE
#endif
#ifndef PIC
/* entry for static linking */
const char *_snd_module_pcm_rate = "";
#endif
#ifndef DOC_HIDDEN
/* LINEAR_DIV needs to be large enough to handle resampling from 192000 -> 8000 */
#define LINEAR_DIV_SHIFT 19
#define LINEAR_DIV (1<<LINEAR_DIV_SHIFT)
enum rate_type {
RATE_TYPE_LINEAR, /* linear interpolation */
RATE_TYPE_BANDLIMIT, /* bandlimited interpolation */
RATE_TYPE_POLYPHASE, /* polyphase resampling */
};
typedef struct _snd_pcm_rate snd_pcm_rate_t;
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,
snd_pcm_rate_t *rate);
struct _snd_pcm_rate {
snd_pcm_generic_t gen;
snd_atomic_write_t watom;
snd_pcm_uframes_t appl_ptr, hw_ptr;
snd_pcm_uframes_t last_commit_ptr;
snd_pcm_uframes_t orig_avail_min;
snd_pcm_sw_params_t sw_params;
enum rate_type type;
unsigned int get_idx;
unsigned int put_idx;
unsigned int pitch;
unsigned int pitch_shift; /* for expand interpolation */
rate_f func;
snd_pcm_format_t sformat;
unsigned int srate;
snd_pcm_channel_area_t *pareas; /* areas for splitted period (rate pcm) */
snd_pcm_channel_area_t *sareas; /* areas for splitted period (slave pcm) */
int16_t *old_sample;
};
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,
snd_pcm_rate_t *rate)
{
#define GET16_LABELS
#define PUT16_LABELS
#include "plugin_ops.h"
#undef GET16_LABELS
#undef PUT16_LABELS
void *get = get16_labels[rate->get_idx];
void *put = put16_labels[rate->put_idx];
unsigned int get_threshold = rate->pitch;
unsigned int channel;
snd_pcm_uframes_t src_frames1;
snd_pcm_uframes_t dst_frames1;
int16_t sample = 0;
unsigned int pos;
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];
const char *src;
char *dst;
int src_step, dst_step;
int16_t old_sample = 0;
int16_t new_sample;
int old_weight, new_weight;
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);
dst_step = snd_pcm_channel_area_step(dst_area);
src_frames1 = 0;
dst_frames1 = 0;
new_sample = rate->old_sample[channel];
pos = get_threshold;
while (dst_frames1 < dst_frames) {
if (pos >= get_threshold) {
pos -= get_threshold;
old_sample = new_sample;
if (src_frames1 < src_frames) {
goto *get;
#define GET16_END after_get
#include "plugin_ops.h"
#undef GET16_END
after_get:
new_sample = sample;
}
}
new_weight = (pos << (16 - rate->pitch_shift)) / (get_threshold >> rate->pitch_shift);
old_weight = 0x10000 - new_weight;
sample = (old_sample * old_weight + new_sample * new_weight) >> 16;
goto *put;
#define PUT16_END after_put
#include "plugin_ops.h"
#undef PUT16_END
after_put:
dst += dst_step;
dst_frames1++;
pos += LINEAR_DIV;
if (pos >= get_threshold) {
src += src_step;
src_frames1++;
}
}
rate->old_sample[channel] = new_sample;
}
}
/* optimized version for S16 format */
static void snd_pcm_rate_expand_s16(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,
snd_pcm_rate_t *rate)
{
unsigned int channel;
snd_pcm_uframes_t src_frames1;
snd_pcm_uframes_t dst_frames1;
unsigned int get_threshold = rate->pitch;
unsigned int pos;
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];
const int16_t *src;
int16_t *dst;
int src_step, dst_step;
int16_t old_sample = 0;
int16_t new_sample;
int old_weight, new_weight;
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) >> 1;
dst_step = snd_pcm_channel_area_step(dst_area) >> 1;
src_frames1 = 0;
dst_frames1 = 0;
new_sample = rate->old_sample[channel];
pos = get_threshold;
while (dst_frames1 < dst_frames) {
if (pos >= get_threshold) {
pos -= get_threshold;
old_sample = new_sample;
if (src_frames1 < src_frames)
new_sample = *src;
}
new_weight = (pos << (16 - rate->pitch_shift)) / (get_threshold >> rate->pitch_shift);
old_weight = 0x10000 - new_weight;
*dst = (old_sample * old_weight + new_sample * new_weight) >> 16;
dst += dst_step;
dst_frames1++;
pos += LINEAR_DIV;
if (pos >= get_threshold) {
src += src_step;
src_frames1++;
}
}
rate->old_sample[channel] = new_sample;
}
}
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,
snd_pcm_rate_t *rate)
{
#define GET16_LABELS
#define PUT16_LABELS
#include "plugin_ops.h"
#undef GET16_LABELS
#undef PUT16_LABELS
void *get = get16_labels[rate->get_idx];
void *put = put16_labels[rate->put_idx];
unsigned int get_increment = rate->pitch;
unsigned int channel;
snd_pcm_uframes_t src_frames1;
snd_pcm_uframes_t dst_frames1;
int16_t sample = 0;
unsigned int pos;
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];
const char *src;
char *dst;
int src_step, dst_step;
int16_t old_sample = 0;
int16_t new_sample = 0;
int old_weight, new_weight;
pos = LINEAR_DIV - get_increment; /* Force first sample to be copied */
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);
dst_step = snd_pcm_channel_area_step(dst_area);
src_frames1 = 0;
dst_frames1 = 0;
while (src_frames1 < src_frames) {
goto *get;
#define GET16_END after_get
#include "plugin_ops.h"
#undef GET16_END
after_get:
new_sample = sample;
src += src_step;
src_frames1++;
pos += get_increment;
if (pos >= LINEAR_DIV) {
pos -= LINEAR_DIV;
old_weight = (pos << (32 - LINEAR_DIV_SHIFT)) / (get_increment >> (LINEAR_DIV_SHIFT - 16));
new_weight = 0x10000 - old_weight;
sample = (old_sample * old_weight + new_sample * new_weight) >> 16;
goto *put;
#define PUT16_END after_put
#include "plugin_ops.h"
#undef PUT16_END
after_put:
dst += dst_step;
dst_frames1++;
if (CHECK_SANITY(dst_frames1 > dst_frames)) {
SNDERR("dst_frames overflow");
break;
}
}
old_sample = new_sample;
}
}
}
/* optimized version for S16 format */
static void snd_pcm_rate_shrink_s16(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,
snd_pcm_rate_t *rate)
{
unsigned int get_increment = rate->pitch;
unsigned int channel;
snd_pcm_uframes_t src_frames1;
snd_pcm_uframes_t dst_frames1;
unsigned int pos = 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];
const int16_t *src;
int16_t *dst;
int src_step, dst_step;
int16_t old_sample = 0;
int16_t new_sample = 0;
int old_weight, new_weight;
pos = LINEAR_DIV - get_increment; /* Force first sample to be copied */
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) >> 1;
dst_step = snd_pcm_channel_area_step(dst_area) >> 1 ;
src_frames1 = 0;
dst_frames1 = 0;
while (src_frames1 < src_frames) {
new_sample = *src;
src += src_step;
src_frames1++;
pos += get_increment;
if (pos >= LINEAR_DIV) {
pos -= LINEAR_DIV;
old_weight = (pos << (32 - LINEAR_DIV_SHIFT)) / (get_increment >> (LINEAR_DIV_SHIFT - 16));
new_weight = 0x10000 - old_weight;
*dst = (old_sample * old_weight + new_sample * new_weight) >> 16;
dst += dst_step;
dst_frames1++;
if (CHECK_SANITY(dst_frames1 > dst_frames)) {
SNDERR("dst_frames overflow");
break;
}
}
old_sample = new_sample;
}
}
}
#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_near(frames, LINEAR_DIV, rate->pitch);
else
return muldiv_near(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_near(frames, rate->pitch, LINEAR_DIV);
else
return muldiv_near(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;
snd_pcm_access_mask_t access_mask = { SND_PCM_ACCBIT_SHM };
snd_pcm_format_mask_t format_mask = { SND_PCM_FMTBIT_LINEAR };
err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_ACCESS,
&access_mask);
if (err < 0)
return err;
err = _snd_pcm_hw_param_set_mask(params, SND_PCM_HW_PARAM_FORMAT,
&format_mask);
if (err < 0)
return err;
err = _snd_pcm_hw_params_set_subformat(params, SND_PCM_SUBFORMAT_STD);
if (err < 0)
return err;
err = _snd_pcm_hw_param_set_min(params,
SND_PCM_HW_PARAM_RATE, SND_PCM_PLUGIN_RATE_MIN, 0);
if (err < 0)
return err;
err = _snd_pcm_hw_param_set_max(params,
SND_PCM_HW_PARAM_RATE, SND_PCM_PLUGIN_RATE_MAX, 0);
if (err < 0)
return err;
params->info &= ~(SND_PCM_INFO_MMAP | SND_PCM_INFO_MMAP_VALID);
return 0;
}
static int snd_pcm_rate_hw_refine_sprepare(snd_pcm_t *pcm, snd_pcm_hw_params_t *sparams)
{
snd_pcm_rate_t *rate = pcm->private_data;
snd_pcm_access_mask_t saccess_mask = { SND_PCM_ACCBIT_MMAP };
_snd_pcm_hw_params_any(sparams);
_snd_pcm_hw_param_set_mask(sparams, SND_PCM_HW_PARAM_ACCESS,
&saccess_mask);
if (rate->sformat != SND_PCM_FORMAT_UNKNOWN) {
_snd_pcm_hw_params_set_format(sparams, rate->sformat);
_snd_pcm_hw_params_set_subformat(sparams, SND_PCM_SUBFORMAT_STD);
}
_snd_pcm_hw_param_set_minmax(sparams, SND_PCM_HW_PARAM_RATE,
rate->srate, 0, rate->srate + 1, -1);
return 0;
}
static int snd_pcm_rate_hw_refine_schange(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
snd_pcm_hw_params_t *sparams)
{
snd_pcm_rate_t *rate = pcm->private_data;
snd_interval_t t, buffer_size;
const snd_interval_t *srate, *crate;
int err;
unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
SND_PCM_HW_PARBIT_PERIOD_TIME |
SND_PCM_HW_PARBIT_TICK_TIME);
if (rate->sformat == SND_PCM_FORMAT_UNKNOWN)
links |= (SND_PCM_HW_PARBIT_FORMAT |
SND_PCM_HW_PARBIT_SUBFORMAT |
SND_PCM_HW_PARBIT_SAMPLE_BITS |
SND_PCM_HW_PARBIT_FRAME_BITS);
snd_interval_copy(&buffer_size, snd_pcm_hw_param_get_interval(params, SND_PCM_HW_PARAM_BUFFER_SIZE));
snd_interval_unfloor(&buffer_size);
crate = snd_pcm_hw_param_get_interval(params, SND_PCM_HW_PARAM_RATE);
srate = snd_pcm_hw_param_get_interval(sparams, SND_PCM_HW_PARAM_RATE);
snd_interval_muldiv(&buffer_size, srate, crate, &t);
err = _snd_pcm_hw_param_set_interval(sparams, SND_PCM_HW_PARAM_BUFFER_SIZE, &t);
if (err < 0)
return err;
err = _snd_pcm_hw_params_refine(sparams, links, params);
if (err < 0)
return err;
return 0;
}
static int snd_pcm_rate_hw_refine_cchange(snd_pcm_t *pcm, snd_pcm_hw_params_t *params,
snd_pcm_hw_params_t *sparams)
{
snd_pcm_rate_t *rate = pcm->private_data;
snd_interval_t t;
#ifdef DEBUG_REFINE
snd_output_t *out;
#endif
const snd_interval_t *sbuffer_size, *buffer_size;
const snd_interval_t *srate, *crate;
int err;
unsigned int links = (SND_PCM_HW_PARBIT_CHANNELS |
SND_PCM_HW_PARBIT_PERIOD_TIME |
SND_PCM_HW_PARBIT_TICK_TIME);
if (rate->sformat == SND_PCM_FORMAT_UNKNOWN)
links |= (SND_PCM_HW_PARBIT_FORMAT |
SND_PCM_HW_PARBIT_SUBFORMAT |
SND_PCM_HW_PARBIT_SAMPLE_BITS |
SND_PCM_HW_PARBIT_FRAME_BITS);
sbuffer_size = snd_pcm_hw_param_get_interval(sparams, SND_PCM_HW_PARAM_BUFFER_SIZE);
crate = snd_pcm_hw_param_get_interval(params, SND_PCM_HW_PARAM_RATE);
srate = snd_pcm_hw_param_get_interval(sparams, SND_PCM_HW_PARAM_RATE);
snd_interval_muldiv(sbuffer_size, crate, srate, &t);
snd_interval_floor(&t);
err = _snd_pcm_hw_param_set_interval(params, SND_PCM_HW_PARAM_BUFFER_SIZE, &t);
if (err < 0)
return err;
buffer_size = snd_pcm_hw_param_get_interval(params, SND_PCM_HW_PARAM_BUFFER_SIZE);
/*
* this condition probably needs more work:
* in case when the buffer_size is known and we are looking
* for best period_size, we should prefer situation when
* (buffer_size / period_size) * period_size == buffer_size
*/
if (snd_interval_single(buffer_size) && buffer_size->integer) {
snd_interval_t *period_size;
period_size = (snd_interval_t *)snd_pcm_hw_param_get_interval(params, SND_PCM_HW_PARAM_PERIOD_SIZE);
if (!snd_interval_checkempty(period_size) &&
period_size->openmin && period_size->openmax &&
period_size->min + 1 == period_size->max) {
if ((buffer_size->min / period_size->min) * period_size->min == buffer_size->min) {
snd_interval_set_value(period_size, period_size->min);
} else if ((buffer_size->max / period_size->max) * period_size->max == buffer_size->max) {
snd_interval_set_value(period_size, period_size->max);
}
}
}
#ifdef DEBUG_REFINE
snd_output_stdio_attach(&out, stderr, 0);
snd_output_printf(out, "REFINE (params):\n");
snd_pcm_hw_params_dump(params, out);
snd_output_printf(out, "REFINE (slave params):\n");
snd_pcm_hw_params_dump(sparams, out);
snd_output_close(out);
#endif
err = _snd_pcm_hw_params_refine(params, links, sparams);
#ifdef DEBUG_REFINE
snd_output_stdio_attach(&out, stderr, 0);
snd_output_printf(out, "********************\n");
snd_output_printf(out, "REFINE (params) (%i):\n", err);
snd_pcm_hw_params_dump(params, out);
snd_output_printf(out, "REFINE (slave params):\n");
snd_pcm_hw_params_dump(sparams, out);
snd_output_close(out);
#endif
if (err < 0)
return err;
return 0;
}
static int snd_pcm_rate_hw_refine(snd_pcm_t *pcm,
snd_pcm_hw_params_t *params)
{
return snd_pcm_hw_refine_slave(pcm, params,
snd_pcm_rate_hw_refine_cprepare,
snd_pcm_rate_hw_refine_cchange,
snd_pcm_rate_hw_refine_sprepare,
snd_pcm_rate_hw_refine_schange,
snd_pcm_generic_hw_refine);
}
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->gen.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_generic_hw_params);
if (err < 0)
return err;
if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
err = INTERNAL(snd_pcm_hw_params_get_format)(params, &src_format);
if (err < 0)
return err;
pformat = src_format;
dst_format = slave->format;
sformat = dst_format;
dst_rate = slave->rate;
err = INTERNAL(snd_pcm_hw_params_get_rate)(params, &src_rate, 0);
} else {
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_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;
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) {
if (src_format == dst_format && src_format == SND_PCM_FORMAT_S16)
rate->func = snd_pcm_rate_expand_s16;
else
rate->func = snd_pcm_rate_expand;
/* pitch is get_threshold */
} else {
if (src_format == dst_format && src_format == SND_PCM_FORMAT_S16)
rate->func = snd_pcm_rate_shrink_s16;
else
rate->func = snd_pcm_rate_shrink;
/* pitch is get_increment */
}
rate->pitch = (((u_int64_t)dst_rate * LINEAR_DIV) + (src_rate / 2)) / src_rate;
if (CHECK_SANITY(rate->pareas)) {
SNDMSG("rate plugin already in use");
return -EBUSY;
}
#if 0
if ((buffer_size / period_size) * period_size == buffer_size &&
(slave->buffer_size / slave->period_size) * slave->period_size == slave->buffer_size)
return 0;
#endif
rate->pareas = malloc(2 * channels * sizeof(*rate->pareas));
if (rate->pareas == NULL)
return -ENOMEM;
free(rate->old_sample);
rate->old_sample = malloc(sizeof(*rate->old_sample) * channels);
if (rate->old_sample == 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;
}
free(rate->old_sample);
return snd_pcm_hw_free(rate->gen.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->gen.slave;
unsigned long div;
if (*val == pcm->buffer_size) {
*val = slave->buffer_size;
} else {
div = *val / pcm->period_size;
if (div * pcm->period_size == *val)
*val = div * slave->period_size;
else
*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->gen.slave;
snd_pcm_sw_params_t *sparams;
snd_pcm_uframes_t boundary1, boundary2;
rate->sw_params = *params;
sparams = &rate->sw_params;
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) {
boundary1 *= 2;
boundary2 *= 2;
}
} else {
boundary1 = pcm->buffer_size;
boundary2 = slave->buffer_size;
while (boundary1 * 2 <= LONG_MAX - pcm->buffer_size) {
boundary1 *= 2;
boundary2 *= 2;
}
}
params->boundary = boundary1;
sparams->boundary = boundary2;
if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
rate->pitch = (((u_int64_t)slave->period_size * LINEAR_DIV) + (pcm->period_size/2) ) / pcm->period_size;
do {
snd_pcm_uframes_t cframes,cframes_test;
cframes = snd_pcm_rate_client_frames(pcm, slave->period_size );
if (cframes == pcm->period_size )
break;
if (cframes > pcm->period_size ) {
rate->pitch++;
cframes_test = snd_pcm_rate_client_frames(pcm, slave->period_size );
if (cframes_test < pcm->period_size ) {
SNDERR("Unable to satisfy pitch condition (%i/%i - %li/%li)\n", slave->rate, pcm->rate, slave->period_size, pcm->period_size);
return -EIO;
}
} else {
rate->pitch--;
cframes_test = snd_pcm_rate_client_frames(pcm, slave->period_size );
if (cframes_test > pcm->period_size) {
SNDERR("Unable to satisfy pitch condition (%i/%i - %li/%li)\n", slave->rate, pcm->rate, slave->period_size, pcm->period_size);
return -EIO;
}
}
} while (1);
if ((snd_pcm_uframes_t)snd_pcm_rate_client_frames(pcm, slave->period_size ) != pcm->period_size) {
SNDERR("invalid slave period_size %ld for pcm period_size %ld",
slave->period_size, pcm->period_size);
return -EIO;
}
} else {
rate->pitch = (((u_int64_t)pcm->period_size * LINEAR_DIV) + (slave->period_size/2) ) / slave->period_size;
do {
snd_pcm_uframes_t cframes;
cframes = snd_pcm_rate_slave_frames(pcm, pcm->period_size );
if (cframes == slave->period_size )
break;
if (cframes > slave->period_size ) {
rate->pitch++;
if ((snd_pcm_uframes_t)snd_pcm_rate_slave_frames(pcm, pcm->period_size ) < slave->period_size ) {
SNDERR("Unable to satisfy pitch condition (%i/%i - %li/%li)\n", slave->rate, pcm->rate, slave->period_size, pcm->period_size);
return -EIO;
}
} else {
rate->pitch--;
if ((snd_pcm_uframes_t)snd_pcm_rate_slave_frames(pcm, pcm->period_size) > slave->period_size ) {
SNDERR("Unable to satisfy pitch condition (%i/%i - %li/%li)\n", slave->rate, pcm->rate, slave->period_size , pcm->period_size );
return -EIO;
}
}
} while (1);
if ((snd_pcm_uframes_t)snd_pcm_rate_slave_frames(pcm, pcm->period_size ) != slave->period_size) {
SNDERR("invalid pcm period_size %ld for slave period_size",
pcm->period_size, slave->period_size);
return -EIO;
}
}
if (rate->pitch >= LINEAR_DIV) {
/* shift for expand linear interpolation */
rate->pitch_shift = 0;
while ((rate->pitch >> rate->pitch_shift) >= (1 << 16))
rate->pitch_shift++;
}
recalc(pcm, &sparams->avail_min);
rate->orig_avail_min = sparams->avail_min;
recalc(pcm, &sparams->xfer_align);
recalc(pcm, &sparams->start_threshold);
if (sparams->avail_min < 1) sparams->avail_min = 1;
if (sparams->xfer_align < 1) sparams->xfer_align = 1;
if (sparams->start_threshold <= slave->buffer_size) {
if (sparams->start_threshold > (slave->buffer_size / sparams->avail_min) * sparams->avail_min)
sparams->start_threshold = (slave->buffer_size / sparams->avail_min) * sparams->avail_min;
if (sparams->start_threshold > (slave->buffer_size / sparams->xfer_align) * sparams->xfer_align)
sparams->start_threshold = (slave->buffer_size / sparams->xfer_align) * sparams->xfer_align;
}
if (sparams->stop_threshold >= sparams->boundary) {
sparams->stop_threshold = sparams->boundary;
} else {
recalc(pcm, &sparams->stop_threshold);
}
recalc(pcm, &sparams->silence_threshold);
recalc(pcm, &sparams->silence_size);
return snd_pcm_sw_params(slave, sparams);
}
static int snd_pcm_rate_init(snd_pcm_t *pcm)
{
snd_pcm_rate_t *rate = pcm->private_data;
switch (rate->type) {
case RATE_TYPE_LINEAR:
/* for expand */
if (rate->old_sample)
memset(rate->old_sample, 0, sizeof(*rate->old_sample) * pcm->channels);
break;
default:
assert(0);
}
rate->last_commit_ptr = 0;
return 0;
}
static inline int
snd_pcm_rate_write_areas1(snd_pcm_t *pcm,
const snd_pcm_channel_area_t *areas,
snd_pcm_uframes_t offset,
const snd_pcm_channel_area_t *slave_areas,
snd_pcm_uframes_t slave_offset)
{
snd_pcm_rate_t *rate = pcm->private_data;
rate->func(slave_areas, slave_offset, rate->gen.slave->period_size,
areas, offset, pcm->period_size,
pcm->channels, rate);
return 0;
}
static inline int
snd_pcm_rate_read_areas1(snd_pcm_t *pcm,
const snd_pcm_channel_area_t *areas,
snd_pcm_uframes_t offset,
const snd_pcm_channel_area_t *slave_areas,
snd_pcm_uframes_t slave_offset)
{
snd_pcm_rate_t *rate = pcm->private_data;
rate->func(areas, offset, pcm->period_size,
slave_areas, slave_offset, rate->gen.slave->period_size,
pcm->channels, rate);
return 0;
}
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;
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->gen.slave;
orig_appl_ptr = rate->appl_ptr;
if (frames > 0)
snd_pcm_mmap_appl_forward(pcm, frames);
else
snd_pcm_mmap_appl_backward(pcm, -frames);
slave_appl_ptr =
(appl_ptr / pcm->period_size) * rate->gen.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->gen.slave, diff);
} else {
ndiff = snd_pcm_rewind(rate->gen.slave, diff);
}
if (ndiff < 0)
return diff;
slave_appl_ptr = *slave->appl.ptr;
rate->appl_ptr =
(slave_appl_ptr / rate->gen.slave->period_size) * pcm->period_size +
snd_pcm_rate_client_frames(pcm, slave_appl_ptr % rate->gen.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)
diff = -diff;
rate->last_commit_ptr = rate->appl_ptr - rate->appl_ptr % pcm->period_size;
return diff;
}
static inline void snd_pcm_rate_sync_hwptr(snd_pcm_t *pcm)
{
snd_pcm_rate_t *rate = pcm->private_data;
snd_pcm_uframes_t slave_hw_ptr = *rate->gen.slave->hw.ptr;
if (pcm->stream != SND_PCM_STREAM_PLAYBACK)
return;
/* FIXME: boundary overlap of slave hw_ptr isn't evaluated here!
* e.g. if slave rate is small...
*/
rate->hw_ptr =
(slave_hw_ptr / rate->gen.slave->period_size) * pcm->period_size +
snd_pcm_rate_client_frames(pcm, slave_hw_ptr % rate->gen.slave->period_size);
}
static int snd_pcm_rate_hwsync(snd_pcm_t *pcm)
{
snd_pcm_rate_t *rate = pcm->private_data;
int err = snd_pcm_hwsync(rate->gen.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)
*delayp = snd_pcm_mmap_playback_hw_avail(pcm);
else
*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->gen.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->gen.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 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_poll_descriptors(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int space)
{
snd_pcm_rate_t *rate = pcm->private_data;
snd_pcm_uframes_t avail_min;
int ret, err;
ret = snd_pcm_generic_poll_descriptors(pcm, pfds, space);
if (ret < 0)
return ret;
avail_min = rate->appl_ptr % pcm->period_size;
if (avail_min > 0) {
recalc(pcm, &avail_min);
if (avail_min < rate->gen.slave->buffer_size &&
avail_min != rate->gen.slave->period_size)
avail_min++; /* 1st small little rounding correction */
if (avail_min < rate->gen.slave->buffer_size &&
avail_min != rate->gen.slave->period_size)
avail_min++; /* 2nd small little rounding correction */
avail_min += rate->orig_avail_min;
} else {
avail_min = rate->orig_avail_min;
}
if (rate->sw_params.avail_min == avail_min)
return ret;
rate->sw_params.avail_min = avail_min;
err = snd_pcm_sw_params(rate->gen.slave, &rate->sw_params);
if (err < 0)
return err;
return ret;
}
static int snd_pcm_rate_commit_area(snd_pcm_t *pcm, snd_pcm_rate_t *rate,
snd_pcm_uframes_t appl_offset,
snd_pcm_uframes_t size,
snd_pcm_uframes_t slave_size)
{
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 >= size) {
result = snd_pcm_mmap_begin(rate->gen.slave, &slave_areas, &slave_offset, &slave_frames);
if (result < 0)
return result;
if (slave_frames < slave_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->gen.slave, slave_offset, slave_size);
if (result < (snd_pcm_sframes_t)slave_size) {
if (result < 0)
return result;
result = snd_pcm_rewind(rate->gen.slave, result);
if (result < 0)
return result;
return 0;
}
} else {
snd_pcm_areas_copy(rate->pareas, 0,
areas, appl_offset,
pcm->channels, cont,
pcm->format);
snd_pcm_areas_copy(rate->pareas, cont,
areas, 0,
pcm->channels, size - 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->gen.slave, &slave_areas, &slave_offset, &slave_frames);
if (result < 0)
return result;
__partial:
xfer = 0;
cont = rate->gen.slave->buffer_size - slave_offset;
if (cont > slave_size)
cont = slave_size;
snd_pcm_areas_copy(slave_areas, slave_offset,
rate->sareas, 0,
pcm->channels, cont,
rate->gen.slave->format);
result = snd_pcm_mmap_commit(rate->gen.slave, slave_offset, cont);
if (result < (snd_pcm_sframes_t)cont) {
if (result < 0)
return result;
result = snd_pcm_rewind(rate->gen.slave, result);
if (result < 0)
return result;
return 0;
}
xfer = cont;
if (xfer == slave_size)
return 1;
/* commit second fragment */
cont = rate->gen.slave->period_size - cont;
slave_frames = cont;
result = snd_pcm_mmap_begin(rate->gen.slave, &slave_areas, &slave_offset, &slave_frames);
if (result < 0)
return result;
if (slave_offset) {
SNDERR("non-zero slave_offset %ld", slave_offset);
return -EIO;
}
snd_pcm_areas_copy(slave_areas, slave_offset,
rate->sareas, xfer,
pcm->channels, cont,
rate->gen.slave->format);
result = snd_pcm_mmap_commit(rate->gen.slave, slave_offset, cont);
if (result < (snd_pcm_sframes_t)cont) {
if (result < 0)
return result;
result = snd_pcm_rewind(rate->gen.slave, result + xfer);
if (result < 0)
return result;
return 0;
}
}
return 1;
}
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;
return snd_pcm_rate_commit_area(pcm, rate, appl_offset, pcm->period_size,
rate->gen.slave->period_size);
}
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, 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->gen.slave, &slave_areas, &slave_offset, &slave_frames);
if (result < 0)
return result;
if (slave_frames < rate->gen.slave->period_size)
goto __partial;
snd_pcm_rate_read_areas1(pcm, areas, hw_offset,
slave_areas, slave_offset);
result = snd_pcm_mmap_commit(rate->gen.slave, slave_offset, rate->gen.slave->period_size);
if (result < (snd_pcm_sframes_t)rate->gen.slave->period_size) {
if (result < 0)
return result;
result = snd_pcm_rewind(rate->gen.slave, result);
if (result < 0)
return result;
return 0;
}
} else {
/* ok, grab first fragment */
result = snd_pcm_mmap_begin(rate->gen.slave, &slave_areas, &slave_offset, &slave_frames);
if (result < 0)
return result;
__partial:
xfer = 0;
cont = rate->gen.slave->buffer_size - slave_offset;
if (cont > rate->gen.slave->period_size)
cont = rate->gen.slave->period_size;
snd_pcm_areas_copy(rate->sareas, 0,
slave_areas, slave_offset,
pcm->channels, cont,
rate->gen.slave->format);
result = snd_pcm_mmap_commit(rate->gen.slave, slave_offset, cont);
if (result < (snd_pcm_sframes_t)cont) {
if (result < 0)
return result;
result = snd_pcm_rewind(rate->gen.slave, result);
if (result < 0)
return result;
return 0;
}
xfer = cont;
if (xfer == rate->gen.slave->period_size)
goto __transfer;
/* grab second fragment */
cont = rate->gen.slave->period_size - cont;
slave_frames = cont;
result = snd_pcm_mmap_begin(rate->gen.slave, &slave_areas, &slave_offset, &slave_frames);
if (result < 0)
return result;
if (slave_offset) {
SNDERR("non-zero slave_offset %ld", slave_offset);
return -EIO;
}
snd_pcm_areas_copy(rate->sareas, xfer,
slave_areas, slave_offset,
pcm->channels, cont,
rate->gen.slave->format);
result = snd_pcm_mmap_commit(rate->gen.slave, slave_offset, cont);
if (result < (snd_pcm_sframes_t)cont) {
if (result < 0)
return result;
result = snd_pcm_rewind(rate->gen.slave, result + xfer);
if (result < 0)
return result;
return 0;
}
__transfer:
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 int snd_pcm_rate_sync_playback_area(snd_pcm_t *pcm, snd_pcm_uframes_t appl_ptr)
{
snd_pcm_rate_t *rate = pcm->private_data;
snd_pcm_t *slave = rate->gen.slave;
snd_pcm_uframes_t xfer;
snd_pcm_sframes_t slave_size;
int err;
slave_size = snd_pcm_avail_update(slave);
if (slave_size < 0)
return slave_size;
if (appl_ptr < rate->last_commit_ptr)
xfer = appl_ptr - rate->last_commit_ptr + pcm->boundary;
else
xfer = appl_ptr - rate->last_commit_ptr;
while (xfer >= pcm->period_size &&
(snd_pcm_uframes_t)slave_size >= rate->gen.slave->period_size) {
err = snd_pcm_rate_commit_next_period(pcm, rate->last_commit_ptr % pcm->buffer_size);
if (err == 0)
break;
if (err < 0)
return err;
xfer -= pcm->period_size;
slave_size -= rate->gen.slave->period_size;
rate->last_commit_ptr += pcm->period_size;
if (rate->last_commit_ptr >= pcm->boundary)
rate->last_commit_ptr = 0;
}
return 0;
}
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;
int err;
if (size == 0)
return 0;
if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
err = snd_pcm_rate_sync_playback_area(pcm, rate->appl_ptr + size);
if (err < 0)
return err;
}
snd_atomic_write_begin(&rate->watom);
snd_pcm_mmap_appl_forward(pcm, size);
snd_atomic_write_end(&rate->watom);
return size;
}
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->gen.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);
snd_pcm_rate_sync_playback_area(pcm, rate->appl_ptr);
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->gen.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->gen.slave->period_size;
hw_offset += pcm->period_size;
hw_offset %= pcm->buffer_size;
snd_pcm_mmap_hw_forward(pcm, pcm->period_size);
}
return (snd_pcm_sframes_t)xfer;
}
}
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;
if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
/* Try to sync as much as possible */
snd_pcm_rate_hwsync(pcm);
snd_pcm_rate_sync_playback_area(pcm, rate->appl_ptr);
}
return snd_pcm_poll_descriptors_revents(rate->gen.slave, pfds, nfds, revents);
}
static int snd_pcm_rate_drain(snd_pcm_t *pcm)
{
snd_pcm_rate_t *rate = pcm->private_data;
if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
/* commit the remaining fraction (if any) */
snd_pcm_uframes_t size, slave_size;
size = rate->appl_ptr - rate->last_commit_ptr;
if (size > 0) {
slave_size = snd_pcm_rate_slave_frames(pcm, size);
if (slave_size > 0)
snd_pcm_rate_commit_area(pcm, rate, rate->last_commit_ptr % pcm->buffer_size,
size, slave_size);
}
}
return snd_pcm_drain(rate->gen.slave);
}
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->gen.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_rate_t *rate = pcm->private_data;
if (rate->sformat == SND_PCM_FORMAT_UNKNOWN)
snd_output_printf(out, "Rate conversion PCM (%d)\n",
rate->srate);
else
snd_output_printf(out, "Rate conversion PCM (%d, sformat=%s)\n",
rate->srate,
snd_pcm_format_name(rate->sformat));
if (pcm->setup) {
snd_output_printf(out, "Its setup is:\n");
snd_pcm_dump_setup(pcm, out);
}
snd_output_printf(out, "Slave: ");
snd_pcm_dump(rate->gen.slave, out);
}
static snd_pcm_fast_ops_t snd_pcm_rate_fast_ops = {
.status = snd_pcm_rate_status,
.state = snd_pcm_generic_state,
.hwsync = snd_pcm_rate_hwsync,
.delay = snd_pcm_rate_delay,
.prepare = snd_pcm_rate_prepare,
.reset = snd_pcm_rate_reset,
.start = snd_pcm_generic_start,
.drop = snd_pcm_generic_drop,
.drain = snd_pcm_rate_drain,
.pause = snd_pcm_generic_pause,
.rewind = snd_pcm_rate_rewind,
.forward = snd_pcm_rate_forward,
.resume = snd_pcm_generic_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,
.poll_descriptors_count = snd_pcm_generic_poll_descriptors_count,
.poll_descriptors = snd_pcm_rate_poll_descriptors,
.poll_revents = snd_pcm_rate_poll_revents,
};
static snd_pcm_ops_t snd_pcm_rate_ops = {
.close = snd_pcm_generic_close,
.info = snd_pcm_generic_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_generic_channel_info,
.dump = snd_pcm_rate_dump,
.nonblock = snd_pcm_generic_nonblock,
.async = snd_pcm_generic_async,
.mmap = snd_pcm_generic_mmap,
.munmap = snd_pcm_generic_munmap,
};
/**
* \brief Creates a new rate PCM
* \param pcmp Returns created PCM handle
* \param name Name of PCM
* \param sformat Slave format
* \param srate Slave rate
* \param slave Slave PCM handle
* \param close_slave When set, the slave PCM handle is closed with copy PCM
* \retval zero on success otherwise a negative error code
* \warning Using of this function might be dangerous in the sense
* of compatibility reasons. The prototype might be freely
* changed in future.
*/
int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sformat, unsigned int srate, snd_pcm_t *slave, int close_slave)
{
snd_pcm_t *pcm;
snd_pcm_rate_t *rate;
int err;
assert(pcmp && slave);
if (sformat != SND_PCM_FORMAT_UNKNOWN &&
snd_pcm_format_linear(sformat) != 1)
return -EINVAL;
rate = calloc(1, sizeof(snd_pcm_rate_t));
if (!rate) {
return -ENOMEM;
}
rate->gen.slave = slave;
rate->gen.close_slave = close_slave;
rate->type = RATE_TYPE_LINEAR;
rate->srate = srate;
rate->sformat = sformat;
snd_atomic_write_init(&rate->watom);
err = snd_pcm_new(&pcm, SND_PCM_TYPE_RATE, name, slave->stream, slave->mode);
if (err < 0) {
free(rate);
return err;
}
pcm->ops = &snd_pcm_rate_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;
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;
}
/*! \page pcm_plugins
\section pcm_plugins_rate Plugin: Rate
This plugin converts a stream rate. The input and output formats must be linear.
\code
pcm.name {
type rate # Rate PCM
slave STR # Slave name
# or
slave { # Slave definition
pcm STR # Slave PCM name
# or
pcm { } # Slave PCM definition
rate INT # Slave rate
[format STR] # Slave format
}
}
\endcode
\subsection pcm_plugins_rate_funcref Function reference
<UL>
<LI>snd_pcm_rate_open()
<LI>_snd_pcm_rate_open()
</UL>
*/
/**
* \brief Creates a new rate PCM
* \param pcmp Returns created PCM handle
* \param name Name of PCM
* \param root Root configuration node
* \param conf Configuration node with rate PCM description
* \param stream Stream type
* \param mode Stream mode
* \retval zero on success otherwise a negative error code
* \warning Using of this function might be dangerous in the sense
* of compatibility reasons. The prototype might be freely
* changed in future.
*/
int _snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name,
snd_config_t *root, snd_config_t *conf,
snd_pcm_stream_t stream, int mode)
{
snd_config_iterator_t i, next;
int err;
snd_pcm_t *spcm;
snd_config_t *slave = NULL, *sconf;
snd_pcm_format_t sformat = SND_PCM_FORMAT_UNKNOWN;
int srate = -1;
snd_config_for_each(i, next, conf) {
snd_config_t *n = snd_config_iterator_entry(i);
const char *id;
if (snd_config_get_id(n, &id) < 0)
continue;
if (snd_pcm_conf_generic_id(id))
continue;
if (strcmp(id, "slave") == 0) {
slave = n;
continue;
}
SNDERR("Unknown field %s", id);
return -EINVAL;
}
if (!slave) {
SNDERR("slave is not defined");
return -EINVAL;
}
err = snd_pcm_slave_conf(root, slave, &sconf, 2,
SND_PCM_HW_PARAM_FORMAT, 0, &sformat,
SND_PCM_HW_PARAM_RATE, SCONF_MANDATORY, &srate);
if (err < 0)
return err;
if (sformat != SND_PCM_FORMAT_UNKNOWN &&
snd_pcm_format_linear(sformat) != 1) {
snd_config_delete(sconf);
SNDERR("slave format is not linear");
return -EINVAL;
}
err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode);
snd_config_delete(sconf);
if (err < 0)
return err;
err = snd_pcm_rate_open(pcmp, name,
sformat, (unsigned int) srate, spcm, 1);
if (err < 0)
snd_pcm_close(spcm);
return err;
}
#ifndef DOC_HIDDEN
SND_DLSYM_BUILD_VERSION(_snd_pcm_rate_open, SND_PCM_DLSYM_VERSION);
#endif