/** * \file pcm/pcm_rate.c * \ingroup PCM_Plugins * \brief PCM Rate Plugin Interface * \author Abramo Bagnara * \author Jaroslav Kysela * \date 2000-2004 */ /* * PCM - Rate conversion * Copyright (c) 2000 by Abramo Bagnara * 2004 by Jaroslav Kysela * * * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include "bswap.h" #include "pcm_local.h" #include "pcm_plugin.h" #include "pcm_rate.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_pcm_uframes_t appl_ptr, hw_ptr, last_slave_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 = (char *)rate->pareas[0].addr + (cwidth * chn * cinfo->period_size) / 8; rate->pareas[chn].first = 0; rate->pareas[chn].step = cwidth; rate->sareas[chn].addr = (char *)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 = (int16_t *)src_areas->addr + src_offset * 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 = (int16_t *)dst_areas->addr + dst_offset * 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 void snd_pcm_rate_sync_hwptr0(snd_pcm_t *pcm, snd_pcm_uframes_t slave_hw_ptr) { snd_pcm_rate_t *rate = pcm->private_data; snd_pcm_sframes_t slave_hw_ptr_diff = slave_hw_ptr - rate->last_slave_hw_ptr; snd_pcm_sframes_t last_slave_hw_ptr_frac; if (pcm->stream != SND_PCM_STREAM_PLAYBACK) return; if (slave_hw_ptr_diff < 0) slave_hw_ptr_diff += rate->gen.slave->boundary; /* slave boundary wraparound */ else if (slave_hw_ptr_diff == 0) return; last_slave_hw_ptr_frac = rate->last_slave_hw_ptr % rate->gen.slave->period_size; /* While handling fraction part fo slave period, rounded value will be * introduced by input_frames(). * To eliminate rounding issue on rate->hw_ptr, subtract last rounded * value from rate->hw_ptr and add new rounded value of present * slave_hw_ptr fraction part to rate->hw_ptr. Hence, * rate->hw_ptr += [ (no. of updated slave periods * pcm rate period size) - * fractional part of last_slave_hw_ptr rounded value + * fractional part of updated slave hw ptr's rounded value ] */ rate->hw_ptr += ( (((last_slave_hw_ptr_frac + slave_hw_ptr_diff) / rate->gen.slave->period_size) * pcm->period_size) - rate->ops.input_frames(rate->obj, last_slave_hw_ptr_frac) + rate->ops.input_frames(rate->obj, (last_slave_hw_ptr_frac + slave_hw_ptr_diff) % rate->gen.slave->period_size)); rate->last_slave_hw_ptr = slave_hw_ptr; rate->hw_ptr %= pcm->boundary; } static inline void snd_pcm_rate_sync_hwptr(snd_pcm_t *pcm) { snd_pcm_rate_t *rate = pcm->private_data; snd_pcm_rate_sync_hwptr0(pcm, *rate->gen.slave->hw.ptr); } 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_pcm_rate_sync_hwptr(pcm); return 0; } static snd_pcm_uframes_t snd_pcm_rate_playback_internal_delay(snd_pcm_t *pcm) { snd_pcm_rate_t *rate = pcm->private_data; if (rate->appl_ptr < rate->last_commit_ptr) { return rate->appl_ptr - rate->last_commit_ptr + pcm->boundary; } else { return rate->appl_ptr - rate->last_commit_ptr; } } static int snd_pcm_rate_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) { snd_pcm_rate_t *rate = pcm->private_data; snd_pcm_sframes_t slave_delay; int err; snd_pcm_rate_hwsync(pcm); err = snd_pcm_delay(rate->gen.slave, &slave_delay); if (err < 0) { return err; } if (pcm->stream == SND_PCM_STREAM_PLAYBACK) { *delayp = rate->ops.input_frames(rate->obj, slave_delay) + snd_pcm_rate_playback_internal_delay(pcm); } else { *delayp = rate->ops.output_frames(rate->obj, slave_delay) + 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; err = snd_pcm_prepare(rate->gen.slave); if (err < 0) return err; *pcm->hw.ptr = 0; *pcm->appl.ptr = 0; rate->last_slave_hw_ptr = 0; 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; err = snd_pcm_reset(rate->gen.slave); if (err < 0) return err; *pcm->hw.ptr = 0; *pcm->appl.ptr = 0; rate->last_slave_hw_ptr = 0; err = snd_pcm_rate_init(pcm); if (err < 0) return err; return 0; } static snd_pcm_sframes_t snd_pcm_rate_rewindable(snd_pcm_t *pcm ATTRIBUTE_UNUSED) { return 0; } static snd_pcm_sframes_t snd_pcm_rate_forwardable(snd_pcm_t *pcm ATTRIBUTE_UNUSED) { return 0; } static snd_pcm_sframes_t snd_pcm_rate_rewind(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_uframes_t frames ATTRIBUTE_UNUSED) { return 0; } static snd_pcm_sframes_t snd_pcm_rate_forward(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_uframes_t frames ATTRIBUTE_UNUSED) { return 0; } 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_pcm_mmap_appl_forward(pcm, size); 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_sframes_t slave_size; slave_size = snd_pcm_avail_update(slave); if (slave_size < 0) return slave_size; if (pcm->stream == SND_PCM_STREAM_CAPTURE) goto _capture; snd_pcm_rate_sync_hwptr(pcm); 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 && (snd_pcm_uframes_t)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); } /* locking */ 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; __snd_pcm_lock(pcm); /* 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; int err; err = __snd_pcm_wait_in_lock(rate->gen.slave, -1); if (err < 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); __snd_pcm_unlock(pcm); } 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_sframes_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->tstamp_type); avail = snd_pcm_mmap_playback_hw_avail(rate->gen.slave); if (avail < 0) /* can't happen on healthy drivers */ return -EBADFD; 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; err = snd_pcm_status(rate->gen.slave, status); if (err < 0) 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_hwptr0(pcm, status->hw_ptr); status->appl_ptr = *pcm->appl.ptr; status->hw_ptr = *pcm->hw.ptr; if (pcm->stream == SND_PCM_STREAM_PLAYBACK) { status->delay = rate->ops.input_frames(rate->obj, status->delay) + snd_pcm_rate_playback_internal_delay(pcm); status->avail = snd_pcm_mmap_playback_avail(pcm); status->avail_max = rate->ops.input_frames(rate->obj, status->avail_max); } else { /* FIXME: Maybe possible to somthing similar to * snd_pcm_rate_playback_internal_delay() * for the capture case. */ status->delay = rate->ops.output_frames(rate->obj, 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); } 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, .rewindable = snd_pcm_rate_rewindable, .rewind = snd_pcm_rate_rewind, .forwardable = snd_pcm_rate_forwardable, .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_plugin_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, const snd_config_t *converter_conf, int verbose) { char open_name[64], open_conf_name[64], lib_name[128], *lib = NULL; snd_pcm_rate_open_func_t open_func; snd_pcm_rate_open_conf_func_t open_conf_func; int err; snprintf(open_name, sizeof(open_name), "_snd_pcm_rate_%s_open", type); snprintf(open_conf_name, sizeof(open_conf_name), "_snd_pcm_rate_%s_open_conf", 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; } rate->rate_min = SND_PCM_PLUGIN_RATE_MIN; rate->rate_max = SND_PCM_PLUGIN_RATE_MAX; rate->plugin_version = SND_PCM_RATE_PLUGIN_VERSION; open_conf_func = snd_dlobj_cache_get(lib, open_conf_name, NULL, verbose && converter_conf != NULL); if (open_conf_func) { err = open_conf_func(SND_PCM_RATE_PLUGIN_VERSION, &rate->obj, &rate->ops, converter_conf); 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); rate->open_func = open_conf_func; return 0; } else { snd_dlobj_cache_put(open_conf_func); return err; } } open_func = snd_dlobj_cache_get(lib, open_name, NULL, verbose); if (!open_func) return -ENOENT; rate->open_func = open_func; 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 /* * If the conf is an array of alternatives then the id of * the first element will be "0" (or maybe NULL). Otherwise assume it is * a structure. */ static int is_string_array(const snd_config_t *conf) { snd_config_iterator_t i; if (snd_config_get_type(conf) != SND_CONFIG_TYPE_COMPOUND) return 0; i = snd_config_iterator_first(conf); if (i && i != snd_config_iterator_end(conf)) { snd_config_t *n = snd_config_iterator_entry(i); const char *id; if (snd_config_get_id(n, &id) < 0) return 0; if (id && strcmp(id, "0") != 0) return 0; } return 1; } /** * \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; 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, NULL, 0); if (!err) { type = *types; break; } } } else if (!snd_config_get_string(converter, &type)) err = rate_open_func(rate, type, NULL, 1); else if (is_string_array(converter)) { 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, NULL, 0); if (!err) break; } } 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); const char *id; if (snd_config_get_id(n, &id) < 0) continue; if (strcmp(id, "name") != 0) continue; snd_config_get_string(n, &type); break; } if (!type) { SNDERR("No name given for rate converter"); snd_pcm_free(pcm); free(rate); return -EINVAL; } err = rate_open_func(rate, type, converter, 1); } 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->tstamp_type = slave->tstamp_type; 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 # or converter { # optional name STR # Convertor type xxx yyy # optional convertor-specific configuration } } \endcode \subsection pcm_plugins_rate_funcref Function reference
  • snd_pcm_rate_open()
  • _snd_pcm_rate_open()
*/ /** * \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