alsa-lib/src/pcm/pcm_rate.c

1469 lines
42 KiB
C
Raw Normal View History

/**
* \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
*/
2000-09-24 09:57:26 +00:00
/*
* PCM - Rate conversion
* Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org>
* 2004 by Jaroslav Kysela <perex@perex.cz>
2000-09-24 09:57:26 +00:00
*
*
* 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
2000-09-24 09:57:26 +00:00
* 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.
2000-09-24 09:57:26 +00:00
*
* You should have received a copy of the GNU Lesser General Public
2000-09-24 09:57:26 +00:00
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
2000-09-24 09:57:26 +00:00
*
*/
#include <inttypes.h>
#include "bswap.h"
2000-09-24 09:57:26 +00:00
#include "pcm_local.h"
#include "pcm_plugin.h"
#include "pcm_rate.h"
2000-09-24 09:57:26 +00:00
#include "plugin_ops.h"
#if 0
#define DEBUG_REFINE
#endif
2001-10-24 14:14:11 +00:00
#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;
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;
2001-03-29 17:50:28 +00:00
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;
};
2000-09-24 09:57:26 +00:00
#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)
2000-09-24 09:57:26 +00:00
{
snd_pcm_rate_t *rate = pcm->private_data;
2000-09-24 09:57:26 +00:00
int err;
2001-03-21 16:31:31 +00:00
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);
2000-12-21 20:44:10 +00:00
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);
2000-12-21 20:44:10 +00:00
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)
2000-12-21 20:44:10 +00:00
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);
2000-09-24 09:57:26 +00:00
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)
2000-09-24 09:57:26 +00:00
{
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;
2000-12-21 20:44:10 +00:00
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)
2000-12-21 20:44:10 +00:00
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);
2000-09-24 09:57:26 +00:00
if (err < 0)
return err;
2000-09-24 09:57:26 +00:00
if (pcm->stream == SND_PCM_STREAM_PLAYBACK) {
cinfo = &rate->info.in;
sinfo = &rate->info.out;
2000-09-24 09:57:26 +00:00
} else {
sinfo = &rate->info.in;
cinfo = &rate->info.out;
2000-09-24 09:57:26 +00:00
}
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);
2004-02-04 18:29:41 +00:00
if (err < 0)
return err;
err = INTERNAL(snd_pcm_hw_params_get_period_size)(params, &cinfo->period_size, 0);
2004-02-04 18:29:41 +00:00
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;
}
2000-09-24 09:57:26 +00:00
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;
2000-09-24 09:57:26 +00:00
}
2001-01-19 13:10:50 +00:00
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);
2001-01-19 13:10:50 +00:00
}
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);
}
}
2000-11-20 20:10:46 +00:00
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);
2000-11-20 20:10:46 +00:00
}
2000-09-24 09:57:26 +00:00
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;
2000-09-24 09:57:26 +00:00
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)
2000-09-24 09:57:26 +00:00
{
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);
2000-09-24 09:57:26 +00:00
}
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)
2000-09-24 09:57:26 +00:00
{
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);
2000-09-24 09:57:26 +00:00
}
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);
rate->hw_ptr %= pcm->boundary;
}
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 int snd_pcm_rate_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp)
{
snd_pcm_rate_hwsync(pcm);
*delayp = snd_pcm_mmap_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;
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;
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_uframes_t slave_size;
slave_size = snd_pcm_avail_update(slave);
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 &&
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);
}
pcm: Add thread-safety to PCM API Traditionally, many of ALSA library functions are supposed to be thread-unsafe, and applications are required to take care of thread safety by themselves. However, people never be careful enough, and almost all applications fail in this regard. This patch is an attempt to harden the thread safety in exported PCM functions in a simplistic way: just wrap some of exported functions with the pthread mutex of each PCM object. Not all API functions are wrapped by the mutex since it doesn't make sense. Instead, the patchset covers only the functions that may be likely called concurrently. The supposedly thread-safe API functions are marked in the document. For achieving the feature, two new fields are added snd_pcm_t when the option is enabled: thread_safe and lock. The former indicates that the plugin is thread-safe that doesn't need this workaround and the latter is the pthread mutex. Currently only hw plugin have thread_safe=1. So, the most of real-time sensitive apps won't be influenced by this patchset. Although the patch covers most of PCM ops, a few snd_pcm_fast_ops are left without the extra mutex locking: namely, the ones that may have blocking behavior, i.e. resume, drain, readi, writei, readn and writen. These are supposed to handle own locking in the callbacks. Also, if anyone wants to disable this new thread-safe API feature, it can be still turned off via --disable-thread-safety configure option. Signed-off-by: Takashi Iwai <tiwai@suse.de>
2016-06-30 15:32:40 +02:00
/* 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;
pcm: Add thread-safety to PCM API Traditionally, many of ALSA library functions are supposed to be thread-unsafe, and applications are required to take care of thread safety by themselves. However, people never be careful enough, and almost all applications fail in this regard. This patch is an attempt to harden the thread safety in exported PCM functions in a simplistic way: just wrap some of exported functions with the pthread mutex of each PCM object. Not all API functions are wrapped by the mutex since it doesn't make sense. Instead, the patchset covers only the functions that may be likely called concurrently. The supposedly thread-safe API functions are marked in the document. For achieving the feature, two new fields are added snd_pcm_t when the option is enabled: thread_safe and lock. The former indicates that the plugin is thread-safe that doesn't need this workaround and the latter is the pthread mutex. Currently only hw plugin have thread_safe=1. So, the most of real-time sensitive apps won't be influenced by this patchset. Although the patch covers most of PCM ops, a few snd_pcm_fast_ops are left without the extra mutex locking: namely, the ones that may have blocking behavior, i.e. resume, drain, readi, writei, readn and writen. These are supposed to handle own locking in the callbacks. Also, if anyone wants to disable this new thread-safe API feature, it can be still turned off via --disable-thread-safety configure option. Signed-off-by: Takashi Iwai <tiwai@suse.de>
2016-06-30 15:32:40 +02:00
__snd_pcm_lock(pcm);
2007-09-21 10:40:55 +02:00
/* temporarily set avail_min to one */
sw_params = rate->sw_params;
saved_avail_min = sw_params.avail_min;
2007-09-21 10:40:55 +02:00
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;
pcm: Add thread-safety to PCM API Traditionally, many of ALSA library functions are supposed to be thread-unsafe, and applications are required to take care of thread safety by themselves. However, people never be careful enough, and almost all applications fail in this regard. This patch is an attempt to harden the thread safety in exported PCM functions in a simplistic way: just wrap some of exported functions with the pthread mutex of each PCM object. Not all API functions are wrapped by the mutex since it doesn't make sense. Instead, the patchset covers only the functions that may be likely called concurrently. The supposedly thread-safe API functions are marked in the document. For achieving the feature, two new fields are added snd_pcm_t when the option is enabled: thread_safe and lock. The former indicates that the plugin is thread-safe that doesn't need this workaround and the latter is the pthread mutex. Currently only hw plugin have thread_safe=1. So, the most of real-time sensitive apps won't be influenced by this patchset. Although the patch covers most of PCM ops, a few snd_pcm_fast_ops are left without the extra mutex locking: namely, the ones that may have blocking behavior, i.e. resume, drain, readi, writei, readn and writen. These are supposed to handle own locking in the callbacks. Also, if anyone wants to disable this new thread-safe API feature, it can be still turned off via --disable-thread-safety configure option. Signed-off-by: Takashi Iwai <tiwai@suse.de>
2016-06-30 15:32:40 +02:00
int err;
pcm: Add thread-safety to PCM API Traditionally, many of ALSA library functions are supposed to be thread-unsafe, and applications are required to take care of thread safety by themselves. However, people never be careful enough, and almost all applications fail in this regard. This patch is an attempt to harden the thread safety in exported PCM functions in a simplistic way: just wrap some of exported functions with the pthread mutex of each PCM object. Not all API functions are wrapped by the mutex since it doesn't make sense. Instead, the patchset covers only the functions that may be likely called concurrently. The supposedly thread-safe API functions are marked in the document. For achieving the feature, two new fields are added snd_pcm_t when the option is enabled: thread_safe and lock. The former indicates that the plugin is thread-safe that doesn't need this workaround and the latter is the pthread mutex. Currently only hw plugin have thread_safe=1. So, the most of real-time sensitive apps won't be influenced by this patchset. Although the patch covers most of PCM ops, a few snd_pcm_fast_ops are left without the extra mutex locking: namely, the ones that may have blocking behavior, i.e. resume, drain, readi, writei, readn and writen. These are supposed to handle own locking in the callbacks. Also, if anyone wants to disable this new thread-safe API feature, it can be still turned off via --disable-thread-safety configure option. Signed-off-by: Takashi Iwai <tiwai@suse.de>
2016-06-30 15:32:40 +02:00
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);
pcm: Add thread-safety to PCM API Traditionally, many of ALSA library functions are supposed to be thread-unsafe, and applications are required to take care of thread safety by themselves. However, people never be careful enough, and almost all applications fail in this regard. This patch is an attempt to harden the thread safety in exported PCM functions in a simplistic way: just wrap some of exported functions with the pthread mutex of each PCM object. Not all API functions are wrapped by the mutex since it doesn't make sense. Instead, the patchset covers only the functions that may be likely called concurrently. The supposedly thread-safe API functions are marked in the document. For achieving the feature, two new fields are added snd_pcm_t when the option is enabled: thread_safe and lock. The former indicates that the plugin is thread-safe that doesn't need this workaround and the latter is the pthread mutex. Currently only hw plugin have thread_safe=1. So, the most of real-time sensitive apps won't be influenced by this patchset. Although the patch covers most of PCM ops, a few snd_pcm_fast_ops are left without the extra mutex locking: namely, the ones that may have blocking behavior, i.e. resume, drain, readi, writei, readn and writen. These are supposed to handle own locking in the callbacks. Also, if anyone wants to disable this new thread-safe API feature, it can be still turned off via --disable-thread-safety configure option. Signed-off-by: Takashi Iwai <tiwai@suse.de>
2016-06-30 15:32:40 +02:00
__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_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);
}
return 0;
}
2001-01-17 11:00:32 +00:00
static void snd_pcm_rate_dump(snd_pcm_t *pcm, snd_output_t *out)
2000-09-24 09:57:26 +00:00
{
snd_pcm_rate_t *rate = pcm->private_data;
if (rate->sformat == SND_PCM_FORMAT_UNKNOWN)
2001-01-17 11:00:32 +00:00
snd_output_printf(out, "Rate conversion PCM (%d)\n",
2000-11-20 20:10:46 +00:00
rate->srate);
2000-09-24 09:57:26 +00:00
else
2001-01-17 11:00:32 +00:00
snd_output_printf(out, "Rate conversion PCM (%d, sformat=%s)\n",
2000-11-20 20:10:46 +00:00
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);
2000-11-20 20:10:46 +00:00
if (pcm->setup) {
2001-01-17 11:00:32 +00:00
snd_output_printf(out, "Its setup is:\n");
snd_pcm_dump_setup(pcm, out);
2000-09-24 09:57:26 +00:00
}
2001-01-17 11:00:32 +00:00
snd_output_printf(out, "Slave: ");
snd_pcm_dump(rate->gen.slave, out);
2000-09-24 09:57:26 +00:00
}
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_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,
2000-09-24 09:57:26 +00:00
};
/**
* \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)
2000-09-24 09:57:26 +00:00
{
snd_pcm_t *pcm;
2000-09-24 09:57:26 +00:00
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)
2000-09-24 09:57:26 +00:00
return -EINVAL;
rate = calloc(1, sizeof(snd_pcm_rate_t));
if (!rate) {
return -ENOMEM;
}
rate->gen.slave = slave;
rate->gen.close_slave = close_slave;
2000-11-20 20:10:46 +00:00
rate->srate = srate;
rate->sformat = sformat;
2000-09-24 09:57:26 +00:00
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;
2000-09-24 09:57:26 +00:00
}
#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;
2000-09-24 09:57:26 +00:00
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
2004-04-26 07:40:12 +00:00
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)
2000-09-24 09:57:26 +00:00
{
snd_config_iterator_t i, next;
2000-09-24 09:57:26 +00:00
int err;
snd_pcm_t *spcm;
snd_config_t *slave = NULL, *sconf;
snd_pcm_format_t sformat = SND_PCM_FORMAT_UNKNOWN;
2001-03-17 16:34:43 +00:00
int srate = -1;
const snd_config_t *converter = NULL;
snd_config_for_each(i, next, conf) {
2001-02-07 11:34:33 +00:00
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))
2000-09-24 09:57:26 +00:00
continue;
2001-03-17 16:34:43 +00:00
if (strcmp(id, "slave") == 0) {
slave = n;
2000-09-24 09:57:26 +00:00
continue;
}
if (strcmp(id, "converter") == 0) {
converter = n;
continue;
}
SNDERR("Unknown field %s", id);
2000-09-24 09:57:26 +00:00
return -EINVAL;
}
2001-03-17 16:34:43 +00:00
if (!slave) {
SNDERR("slave is not defined");
2000-09-24 09:57:26 +00:00
return -EINVAL;
2000-11-30 09:40:50 +00:00
}
err = snd_pcm_slave_conf(root, slave, &sconf, 2,
2001-03-17 16:34:43 +00:00
SND_PCM_HW_PARAM_FORMAT, 0, &sformat,
SND_PCM_HW_PARAM_RATE, SCONF_MANDATORY, &srate);
2001-03-17 16:34:43 +00:00
if (err < 0)
return err;
if (sformat != SND_PCM_FORMAT_UNKNOWN &&
snd_pcm_format_linear(sformat) != 1) {
snd_config_delete(sconf);
2001-03-17 16:34:43 +00:00
SNDERR("slave format is not linear");
2000-11-30 09:40:50 +00:00
return -EINVAL;
}
err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf);
snd_config_delete(sconf);
2000-09-24 09:57:26 +00:00
if (err < 0)
return err;
err = snd_pcm_rate_open(pcmp, name, sformat, (unsigned int) srate,
converter, spcm, 1);
2000-09-24 09:57:26 +00:00
if (err < 0)
snd_pcm_close(spcm);
return err;
}
#ifndef DOC_HIDDEN
2001-10-24 14:14:11 +00:00
SND_DLSYM_BUILD_VERSION(_snd_pcm_rate_open, SND_PCM_DLSYM_VERSION);
#endif