diff options
author | Nicolas Dechesne <nicolas.dechesne@linaro.org> | 2014-05-06 23:33:35 +0000 |
---|---|---|
committer | Nicolas Dechesne <nicolas.dechesne@linaro.org> | 2014-05-06 23:33:35 +0000 |
commit | 7786e88c56b91bcac3295c58b7a4ad6642990b8c (patch) | |
tree | f0c71068de1a47b81a82057ea3661062a1ab937e /src/pcm/pcm_rate.c |
Imported Upstream version 1.0.27.2upstream/1.0.27.2
Diffstat (limited to 'src/pcm/pcm_rate.c')
-rw-r--r-- | src/pcm/pcm_rate.c | 1547 |
1 files changed, 1547 insertions, 0 deletions
diff --git a/src/pcm/pcm_rate.c b/src/pcm/pcm_rate.c new file mode 100644 index 00000000..54a3e670 --- /dev/null +++ b/src/pcm/pcm_rate.c @@ -0,0 +1,1547 @@ +/** + * \file pcm/pcm_rate.c + * \ingroup PCM_Plugins + * \brief PCM Rate Plugin Interface + * \author Abramo Bagnara <abramo@alsa-project.org> + * \author Jaroslav Kysela <perex@perex.cz> + * \date 2000-2004 + */ +/* + * PCM - Rate conversion + * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org> + * 2004 by Jaroslav Kysela <perex@perex.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 "pcm_rate.h" +#include "iatomic.h" + +#include "plugin_ops.h" + +#if 0 +#define DEBUG_REFINE +#endif + +#ifndef PIC +/* entry for static linking */ +const char *_snd_module_pcm_rate = ""; +#endif + +#ifndef DOC_HIDDEN + +typedef struct _snd_pcm_rate snd_pcm_rate_t; + +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; + 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) */ + snd_pcm_rate_info_t info; + void *open_func; + void *obj; + snd_pcm_rate_ops_t ops; + unsigned int get_idx; + unsigned int put_idx; + int16_t *src_buf; + int16_t *dst_buf; + int start_pending; /* start is triggered but not commited to slave */ + snd_htimestamp_t trigger_tstamp; + unsigned int plugin_version; + unsigned int rate_min, rate_max; +}; + +#define SND_PCM_RATE_PLUGIN_VERSION_OLD 0x010001 /* old rate plugin */ + +#endif /* DOC_HIDDEN */ + +static int snd_pcm_rate_hw_refine_cprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params) +{ + snd_pcm_rate_t *rate = pcm->private_data; + 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; + if (rate->rate_min) { + err = _snd_pcm_hw_param_set_min(params, SND_PCM_HW_PARAM_RATE, + rate->rate_min, 0); + if (err < 0) + return err; + } + if (rate->rate_max) { + err = _snd_pcm_hw_param_set_max(params, SND_PCM_HW_PARAM_RATE, + rate->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 (period_size->min > 0 && (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_rate_side_info_t *sinfo, *cinfo; + unsigned int channels, cwidth, swidth, chn; + 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) { + cinfo = &rate->info.in; + sinfo = &rate->info.out; + } else { + sinfo = &rate->info.in; + cinfo = &rate->info.out; + } + err = INTERNAL(snd_pcm_hw_params_get_format)(params, &cinfo->format); + if (err < 0) + return err; + err = INTERNAL(snd_pcm_hw_params_get_rate)(params, &cinfo->rate, 0); + if (err < 0) + return err; + err = INTERNAL(snd_pcm_hw_params_get_period_size)(params, &cinfo->period_size, 0); + if (err < 0) + return err; + err = INTERNAL(snd_pcm_hw_params_get_buffer_size)(params, &cinfo->buffer_size); + if (err < 0) + return err; + err = INTERNAL(snd_pcm_hw_params_get_channels)(params, &channels); + if (err < 0) + return err; + + rate->info.channels = channels; + sinfo->format = slave->format; + sinfo->rate = slave->rate; + sinfo->buffer_size = slave->buffer_size; + sinfo->period_size = slave->period_size; + + if (CHECK_SANITY(rate->pareas)) { + SNDMSG("rate plugin already in use"); + return -EBUSY; + } + err = rate->ops.init(rate->obj, &rate->info); + if (err < 0) + return err; + + rate->pareas = malloc(2 * channels * sizeof(*rate->pareas)); + if (rate->pareas == NULL) + goto error; + + cwidth = snd_pcm_format_physical_width(cinfo->format); + swidth = snd_pcm_format_physical_width(sinfo->format); + rate->pareas[0].addr = malloc(((cwidth * channels * cinfo->period_size) / 8) + + ((swidth * channels * sinfo->period_size) / 8)); + if (rate->pareas[0].addr == NULL) + goto error; + + rate->sareas = rate->pareas + channels; + rate->sareas[0].addr = (char *)rate->pareas[0].addr + ((cwidth * channels * cinfo->period_size) / 8); + for (chn = 0; chn < channels; chn++) { + rate->pareas[chn].addr = rate->pareas[0].addr + (cwidth * chn * cinfo->period_size) / 8; + rate->pareas[chn].first = 0; + rate->pareas[chn].step = cwidth; + rate->sareas[chn].addr = rate->sareas[0].addr + (swidth * chn * sinfo->period_size) / 8; + rate->sareas[chn].first = 0; + rate->sareas[chn].step = swidth; + } + + if (rate->ops.convert_s16) { + rate->get_idx = snd_pcm_linear_get_index(rate->info.in.format, SND_PCM_FORMAT_S16); + rate->put_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S16, rate->info.out.format); + free(rate->src_buf); + rate->src_buf = malloc(channels * rate->info.in.period_size * 2); + free(rate->dst_buf); + rate->dst_buf = malloc(channels * rate->info.out.period_size * 2); + if (! rate->src_buf || ! rate->dst_buf) + goto error; + } + + return 0; + + error: + if (rate->pareas) { + free(rate->pareas[0].addr); + free(rate->pareas); + rate->pareas = NULL; + } + if (rate->ops.free) + rate->ops.free(rate->obj); + return -ENOMEM; +} + +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->ops.free) + rate->ops.free(rate->obj); + free(rate->src_buf); + free(rate->dst_buf); + rate->src_buf = rate->dst_buf = NULL; + 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, sboundary; + int err; + + sparams = &rate->sw_params; + err = snd_pcm_sw_params_current(slave, sparams); + if (err < 0) + return err; + sboundary = sparams->boundary; + *sparams = *params; + boundary1 = pcm->buffer_size; + boundary2 = slave->buffer_size; + while (boundary1 * 2 <= LONG_MAX - pcm->buffer_size && + boundary2 * 2 <= LONG_MAX - slave->buffer_size) { + boundary1 *= 2; + boundary2 *= 2; + } + params->boundary = boundary1; + sparams->boundary = sboundary; + + if (rate->ops.adjust_pitch) + rate->ops.adjust_pitch(rate->obj, &rate->info); + + recalc(pcm, &sparams->avail_min); + rate->orig_avail_min = sparams->avail_min; + recalc(pcm, &sparams->start_threshold); + if (sparams->avail_min < 1) sparams->avail_min = 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->stop_threshold >= params->boundary) { + sparams->stop_threshold = sparams->boundary; + } else { + recalc(pcm, &sparams->stop_threshold); + } + recalc(pcm, &sparams->silence_threshold); + if (sparams->silence_size >= params->boundary) { + sparams->silence_size = sparams->boundary; + } else { + 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; + + if (rate->ops.reset) + rate->ops.reset(rate->obj); + rate->last_commit_ptr = 0; + rate->start_pending = 0; + return 0; +} + +static void convert_to_s16(snd_pcm_rate_t *rate, int16_t *buf, + const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t offset, unsigned int frames, + unsigned int channels) +{ +#ifndef DOC_HIDDEN +#define GET16_LABELS +#include "plugin_ops.h" +#undef GET16_LABELS +#endif /* DOC_HIDDEN */ + void *get = get16_labels[rate->get_idx]; + const char *src; + int16_t sample; + const char *srcs[channels]; + int src_step[channels]; + unsigned int c; + + for (c = 0; c < channels; c++) { + srcs[c] = snd_pcm_channel_area_addr(areas + c, offset); + src_step[c] = snd_pcm_channel_area_step(areas + c); + } + + while (frames--) { + for (c = 0; c < channels; c++) { + src = srcs[c]; + goto *get; +#ifndef DOC_HIDDEN +#define GET16_END after_get +#include "plugin_ops.h" +#undef GET16_END +#endif /* DOC_HIDDEN */ + after_get: + *buf++ = sample; + srcs[c] += src_step[c]; + } + } +} + +static void convert_from_s16(snd_pcm_rate_t *rate, const int16_t *buf, + const snd_pcm_channel_area_t *areas, + snd_pcm_uframes_t offset, unsigned int frames, + unsigned int channels) +{ +#ifndef DOC_HIDDEN +#define PUT16_LABELS +#include "plugin_ops.h" +#undef PUT16_LABELS +#endif /* DOC_HIDDEN */ + void *put = put16_labels[rate->put_idx]; + char *dst; + int16_t sample; + char *dsts[channels]; + int dst_step[channels]; + unsigned int c; + + for (c = 0; c < channels; c++) { + dsts[c] = snd_pcm_channel_area_addr(areas + c, offset); + dst_step[c] = snd_pcm_channel_area_step(areas + c); + } + + while (frames--) { + for (c = 0; c < channels; c++) { + dst = dsts[c]; + sample = *buf++; + goto *put; +#ifndef DOC_HIDDEN +#define PUT16_END after_put +#include "plugin_ops.h" +#undef PUT16_END +#endif /* DOC_HIDDEN */ + after_put: + dsts[c] += dst_step[c]; + } + } +} + +static void do_convert(const snd_pcm_channel_area_t *dst_areas, + snd_pcm_uframes_t dst_offset, unsigned int dst_frames, + const snd_pcm_channel_area_t *src_areas, + snd_pcm_uframes_t src_offset, unsigned int src_frames, + unsigned int channels, + snd_pcm_rate_t *rate) +{ + if (rate->ops.convert_s16) { + const int16_t *src; + int16_t *dst; + if (! rate->src_buf) + src = src_areas->addr + src_offset * 2 * channels; + else { + convert_to_s16(rate, rate->src_buf, src_areas, src_offset, + src_frames, channels); + src = rate->src_buf; + } + if (! rate->dst_buf) + dst = dst_areas->addr + dst_offset * 2 * channels; + else + dst = rate->dst_buf; + rate->ops.convert_s16(rate->obj, dst, dst_frames, src, src_frames); + if (dst == rate->dst_buf) + convert_from_s16(rate, rate->dst_buf, dst_areas, dst_offset, + dst_frames, channels); + } else { + rate->ops.convert(rate->obj, dst_areas, dst_offset, dst_frames, + src_areas, src_offset, src_frames); + } +} + +static inline void +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; + do_convert(slave_areas, slave_offset, rate->gen.slave->period_size, + areas, offset, pcm->period_size, + pcm->channels, rate); +} + +static inline void +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; + do_convert(areas, offset, pcm->period_size, + slave_areas, slave_offset, rate->gen.slave->period_size, + pcm->channels, rate); +} + +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 + + orig_appl_ptr % pcm->period_size; + if (pcm->stream == SND_PCM_STREAM_PLAYBACK) + rate->appl_ptr += rate->ops.input_frames(rate->obj, slave_appl_ptr % rate->gen.slave->period_size); + else + rate->appl_ptr += rate->ops.output_frames(rate->obj, slave_appl_ptr % rate->gen.slave->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 + + rate->ops.input_frames(rate->obj, 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_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 = slave_frames; + 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) + goto commit_done; + + /* commit second fragment */ + cont = slave_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 0 + if (slave_offset) { + SNDERR("non-zero slave_offset %ld", slave_offset); + return -EIO; + } +#endif + 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; + } + } + + commit_done: + if (rate->start_pending) { + /* we have pending start-trigger. let's issue it now */ + snd_pcm_start(rate->gen.slave); + rate->start_pending = 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 = slave_frames; + 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 0 + if (slave_offset) { + SNDERR("non-zero slave_offset %ld", slave_offset); + return -EIO; + } +#endif + 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_htimestamp(snd_pcm_t *pcm, + snd_pcm_uframes_t *avail, + snd_htimestamp_t *tstamp) +{ + snd_pcm_rate_t *rate = pcm->private_data; + snd_pcm_sframes_t avail1; + snd_pcm_uframes_t tmp; + int ok = 0, err; + + while (1) { + /* the position is from this plugin itself */ + avail1 = snd_pcm_avail_update(pcm); + if (avail1 < 0) + return avail1; + if (ok && (snd_pcm_uframes_t)avail1 == *avail) + break; + *avail = avail1; + /* timestamp is taken from the slave PCM */ + err = snd_pcm_htimestamp(rate->gen.slave, &tmp, tstamp); + if (err < 0) + return err; + ok = 1; + } + return 0; +} + +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, ofs, saved_avail_min; + snd_pcm_sw_params_t sw_params; + + /* temporarily set avail_min to one */ + sw_params = rate->sw_params; + saved_avail_min = sw_params.avail_min; + sw_params.avail_min = 1; + snd_pcm_sw_params(rate->gen.slave, &sw_params); + + size = rate->appl_ptr - rate->last_commit_ptr; + ofs = rate->last_commit_ptr % pcm->buffer_size; + while (size > 0) { + snd_pcm_uframes_t psize, spsize; + + if (snd_pcm_wait(rate->gen.slave, -1) < 0) + break; + if (size > pcm->period_size) { + psize = pcm->period_size; + spsize = rate->gen.slave->period_size; + } else { + psize = size; + spsize = rate->ops.output_frames(rate->obj, size); + if (! spsize) + break; + } + snd_pcm_rate_commit_area(pcm, rate, ofs, + psize, spsize); + ofs = (ofs + psize) % pcm->buffer_size; + size -= psize; + } + sw_params.avail_min = saved_avail_min; + snd_pcm_sw_params(rate->gen.slave, &sw_params); + } + return snd_pcm_drain(rate->gen.slave); +} + +static snd_pcm_state_t snd_pcm_rate_state(snd_pcm_t *pcm) +{ + snd_pcm_rate_t *rate = pcm->private_data; + if (rate->start_pending) /* pseudo-state */ + return SND_PCM_STATE_RUNNING; + return snd_pcm_state(rate->gen.slave); +} + + +static int snd_pcm_rate_start(snd_pcm_t *pcm) +{ + snd_pcm_rate_t *rate = pcm->private_data; + snd_pcm_uframes_t avail; + + if (pcm->stream == SND_PCM_STREAM_CAPTURE) + return snd_pcm_start(rate->gen.slave); + + if (snd_pcm_state(rate->gen.slave) != SND_PCM_STATE_PREPARED) + return -EBADFD; + + gettimestamp(&rate->trigger_tstamp, pcm->monotonic); + + avail = snd_pcm_mmap_playback_hw_avail(rate->gen.slave); + if (avail == 0) { + /* postpone the trigger since we have no data committed yet */ + rate->start_pending = 1; + return 0; + } + rate->start_pending = 0; + return snd_pcm_start(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; + } + if (pcm->stream == SND_PCM_STREAM_PLAYBACK) { + if (rate->start_pending) + status->state = SND_PCM_STATE_RUNNING; + status->trigger_tstamp = rate->trigger_tstamp; + } + 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); + status->avail_max = rate->ops.input_frames(rate->obj, status->avail_max); + } else { + status->delay = snd_pcm_mmap_capture_hw_avail(pcm); + status->avail = snd_pcm_mmap_capture_avail(pcm); + status->avail_max = rate->ops.output_frames(rate->obj, status->avail_max); + } + if (!snd_atomic_read_ok(&ratom)) { + snd_atomic_read_wait(&ratom); + goto _again; + } + 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 (rate->ops.dump) + rate->ops.dump(rate->obj, out); + snd_output_printf(out, "Protocol version: %x\n", rate->plugin_version); + 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 int snd_pcm_rate_close(snd_pcm_t *pcm) +{ + snd_pcm_rate_t *rate = pcm->private_data; + + if (rate->ops.close) + rate->ops.close(rate->obj); + if (rate->open_func) + snd_dlobj_cache_put(rate->open_func); + return snd_pcm_generic_close(pcm); +} + +static const 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_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, + .htimestamp = snd_pcm_rate_htimestamp, + .poll_descriptors_count = snd_pcm_generic_poll_descriptors_count, + .poll_descriptors = snd_pcm_generic_poll_descriptors, + .poll_revents = snd_pcm_rate_poll_revents, + .may_wait_for_avail_min = snd_pcm_generic_may_wait_for_avail_min, +}; + +static const snd_pcm_ops_t snd_pcm_rate_ops = { + .close = snd_pcm_rate_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, + .query_chmaps = snd_pcm_generic_query_chmaps, + .get_chmap = snd_pcm_generic_get_chmap, + .set_chmap = snd_pcm_generic_set_chmap, +}; + +/** + * \brief Get a default converter string + * \param root Root configuration node + * \retval A const config item if found, or NULL + */ +const snd_config_t *snd_pcm_rate_get_default_converter(snd_config_t *root) +{ + snd_config_t *n; + /* look for default definition */ + if (snd_config_search(root, "defaults.pcm.rate_converter", &n) >= 0) + return n; + return NULL; +} + +#ifdef PIC +static int is_builtin_plugin(const char *type) +{ + return strcmp(type, "linear") == 0; +} + +static const char *const default_rate_plugins[] = { + "speexrate", "linear", NULL +}; + +static int rate_open_func(snd_pcm_rate_t *rate, const char *type, int verbose) +{ + char open_name[64], lib_name[128], *lib = NULL; + snd_pcm_rate_open_func_t open_func; + int err; + + snprintf(open_name, sizeof(open_name), "_snd_pcm_rate_%s_open", type); + if (!is_builtin_plugin(type)) { + snprintf(lib_name, sizeof(lib_name), + "%s/libasound_module_rate_%s.so", ALSA_PLUGIN_DIR, type); + lib = lib_name; + } + open_func = snd_dlobj_cache_get(lib, open_name, NULL, verbose); + if (!open_func) + return -ENOENT; + + rate->open_func = open_func; + rate->rate_min = SND_PCM_PLUGIN_RATE_MIN; + rate->rate_max = SND_PCM_PLUGIN_RATE_MAX; + rate->plugin_version = SND_PCM_RATE_PLUGIN_VERSION; + + err = open_func(SND_PCM_RATE_PLUGIN_VERSION, &rate->obj, &rate->ops); + if (!err) { + rate->plugin_version = rate->ops.version; + if (rate->ops.get_supported_rates) + rate->ops.get_supported_rates(rate->obj, + &rate->rate_min, + &rate->rate_max); + return 0; + } + + /* try to open with the old protocol version */ + rate->plugin_version = SND_PCM_RATE_PLUGIN_VERSION_OLD; + err = open_func(SND_PCM_RATE_PLUGIN_VERSION_OLD, + &rate->obj, &rate->ops); + if (err) { + snd_dlobj_cache_put(open_func); + rate->open_func = NULL; + } + return err; +} +#endif + +/** + * \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 converter SRC type string node + * \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, + const snd_config_t *converter, + snd_pcm_t *slave, int close_slave) +{ + snd_pcm_t *pcm; + snd_pcm_rate_t *rate; + const char *type = NULL; + int err; +#ifndef PIC + snd_pcm_rate_open_func_t open_func; + extern int SND_PCM_RATE_PLUGIN_ENTRY(linear) (unsigned int version, void **objp, snd_pcm_rate_ops_t *ops); +#endif + + 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->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; + } + +#ifdef PIC + err = -ENOENT; + if (!converter) { + const char *const *types; + for (types = default_rate_plugins; *types; types++) { + err = rate_open_func(rate, *types, 0); + if (!err) { + type = *types; + break; + } + } + } else if (!snd_config_get_string(converter, &type)) + err = rate_open_func(rate, type, 1); + else if (snd_config_get_type(converter) == SND_CONFIG_TYPE_COMPOUND) { + snd_config_iterator_t i, next; + snd_config_for_each(i, next, converter) { + snd_config_t *n = snd_config_iterator_entry(i); + if (snd_config_get_string(n, &type) < 0) + break; + err = rate_open_func(rate, type, 0); + if (!err) + break; + } + } else { + SNDERR("Invalid type for rate converter"); + snd_pcm_free(pcm); + free(rate); + return -EINVAL; + } + if (err < 0) { + SNDERR("Cannot find rate converter"); + snd_pcm_free(pcm); + free(rate); + return -ENOENT; + } +#else + type = "linear"; + open_func = SND_PCM_RATE_PLUGIN_ENTRY(linear); + err = open_func(SND_PCM_RATE_PLUGIN_VERSION, &rate->obj, &rate->ops); + if (err < 0) { + snd_pcm_free(pcm); + free(rate); + return err; + } +#endif + + if (! rate->ops.init || ! (rate->ops.convert || rate->ops.convert_s16) || + ! rate->ops.input_frames || ! rate->ops.output_frames) { + SNDERR("Inproper rate plugin %s initialization", type); + snd_pcm_free(pcm); + 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; + pcm->monotonic = slave->monotonic; + 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 + } + converter STR # optional + # or + converter [ STR1 STR2 ... ] # optional + # Converter type, default is taken from + # defaults.pcm.rate_converter +} +\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; + const snd_config_t *converter = NULL; + + 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; + } + if (strcmp(id, "converter") == 0) { + converter = 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, conf); + snd_config_delete(sconf); + if (err < 0) + return err; + err = snd_pcm_rate_open(pcmp, name, sformat, (unsigned int) srate, + converter, 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 |