From 4a4cc2a3014ea698c590364d026e276d93a85345 Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Wed, 4 Feb 2004 09:21:11 +0000 Subject: [PATCH] The rate plugin was redesigned. Now only whole periods are resampled to avoid rounding problems and to allow using other "block" algorithms. --- src/pcm/pcm_plugin.c | 1 + src/pcm/pcm_rate.c | 919 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 791 insertions(+), 129 deletions(-) diff --git a/src/pcm/pcm_plugin.c b/src/pcm/pcm_plugin.c index d7d7d810..5ffb50c9 100644 --- a/src/pcm/pcm_plugin.c +++ b/src/pcm/pcm_plugin.c @@ -134,6 +134,7 @@ void snd_pcm_plugin_init(snd_pcm_plugin_t *plugin) memset(plugin, 0, sizeof(snd_pcm_plugin_t)); plugin->undo_read = snd_pcm_plugin_undo_read; plugin->undo_write = snd_pcm_plugin_undo_write; + snd_atomic_write_init(&plugin->watom); } int snd_pcm_plugin_close(snd_pcm_t *pcm) diff --git a/src/pcm/pcm_rate.c b/src/pcm/pcm_rate.c index f9806b0c..be9a5b23 100644 --- a/src/pcm/pcm_rate.c +++ b/src/pcm/pcm_rate.c @@ -3,11 +3,13 @@ * \ingroup PCM_Plugins * \brief PCM Rate Plugin Interface * \author Abramo Bagnara - * \date 2000-2001 + * \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 @@ -30,6 +32,7 @@ #include #include "pcm_local.h" #include "pcm_plugin.h" +#include "iatomic.h" #ifndef PIC /* entry for static linking */ @@ -38,7 +41,7 @@ const char *_snd_module_pcm_rate = ""; #ifndef DOC_HIDDEN -#define DIV (1<<16) +#define LINEAR_DIV (1<<16) enum rate_type { RATE_TYPE_LINEAR, /* linear interpolation */ @@ -52,25 +55,26 @@ typedef struct { int init; int16_t old_sample, new_sample; int sum; - unsigned int pos; } linear; } u; } snd_pcm_rate_state_t; -typedef snd_pcm_uframes_t (*rate_f)(const snd_pcm_channel_area_t *dst_areas, - snd_pcm_uframes_t dst_offset, - snd_pcm_uframes_t *dst_framesp, - const snd_pcm_channel_area_t *src_areas, - snd_pcm_uframes_t src_offset, - snd_pcm_uframes_t src_frames, - unsigned int channels, - unsigned int getidx, unsigned int putidx, - unsigned int arg, - snd_pcm_rate_state_t *states); +typedef void (*rate_f)(const snd_pcm_channel_area_t *dst_areas, + snd_pcm_uframes_t dst_offset, + snd_pcm_uframes_t dst_frames, + const snd_pcm_channel_area_t *src_areas, + snd_pcm_uframes_t src_offset, + snd_pcm_uframes_t src_frames, + unsigned int channels, + unsigned int getidx, unsigned int putidx, + unsigned int arg, + snd_pcm_rate_state_t *states); typedef struct { - /* This field need to be the first */ - snd_pcm_plugin_t plug; + snd_pcm_t *slave; + int close_slave; + snd_atomic_write_t watom; + snd_pcm_uframes_t appl_ptr, hw_ptr; enum rate_type type; unsigned int get_idx; unsigned int put_idx; @@ -79,6 +83,8 @@ typedef struct { snd_pcm_format_t sformat; unsigned int srate; snd_pcm_rate_state_t *states; + snd_pcm_channel_area_t *pareas; /* areas for splitted period (rate pcm) */ + snd_pcm_channel_area_t *sareas; /* areas for splitted period (slave pcm) */ } snd_pcm_rate_t; static int16_t initial_sample(const char *src, unsigned int getidx) @@ -97,14 +103,14 @@ static int16_t initial_sample(const char *src, unsigned int getidx) return sample; } -static snd_pcm_uframes_t snd_pcm_rate_expand(const snd_pcm_channel_area_t *dst_areas, - snd_pcm_uframes_t dst_offset, snd_pcm_uframes_t *dst_framesp, - const snd_pcm_channel_area_t *src_areas, - snd_pcm_uframes_t src_offset, snd_pcm_uframes_t src_frames, - unsigned int channels, - unsigned int getidx, unsigned int putidx, - unsigned int get_threshold, - snd_pcm_rate_state_t *states) +static void snd_pcm_rate_expand(const snd_pcm_channel_area_t *dst_areas, + snd_pcm_uframes_t dst_offset, snd_pcm_uframes_t dst_frames, + const snd_pcm_channel_area_t *src_areas, + snd_pcm_uframes_t src_offset, snd_pcm_uframes_t src_frames, + unsigned int channels, + unsigned int getidx, unsigned int putidx, + unsigned int get_threshold, + snd_pcm_rate_state_t *states) { #define GET16_LABELS #define PUT16_LABELS @@ -116,12 +122,8 @@ static snd_pcm_uframes_t snd_pcm_rate_expand(const snd_pcm_channel_area_t *dst_a unsigned int channel; snd_pcm_uframes_t src_frames1 = 0; snd_pcm_uframes_t dst_frames1 = 0; - snd_pcm_uframes_t dst_frames = *dst_framesp; int16_t sample = 0; - if (src_frames == 0 || - dst_frames == 0) - return 0; for (channel = 0; channel < channels; ++channel) { const snd_pcm_channel_area_t *src_area = &src_areas[channel]; const snd_pcm_channel_area_t *dst_area = &dst_areas[channel]; @@ -130,7 +132,7 @@ static snd_pcm_uframes_t snd_pcm_rate_expand(const snd_pcm_channel_area_t *dst_a int src_step, dst_step; int16_t old_sample = states->u.linear.old_sample; int16_t new_sample = states->u.linear.new_sample; - unsigned int pos = states->u.linear.pos; + unsigned int pos = 0; src = snd_pcm_channel_area_addr(src_area, src_offset); dst = snd_pcm_channel_area_addr(dst_area, dst_offset); src_step = snd_pcm_channel_area_step(src_area); @@ -140,9 +142,7 @@ static snd_pcm_uframes_t snd_pcm_rate_expand(const snd_pcm_channel_area_t *dst_a if (! states->u.linear.init) { sample = initial_sample(src, getidx); old_sample = new_sample = sample; - src += src_step; - src_frames1++; - states->u.linear.init = 2; /* get a new sample */ + states->u.linear.init = 1; } while (dst_frames1 < dst_frames) { if (states->u.linear.init == 2) { @@ -163,33 +163,29 @@ static snd_pcm_uframes_t snd_pcm_rate_expand(const snd_pcm_channel_area_t *dst_a after_put: dst += dst_step; dst_frames1++; - pos += DIV; + pos += LINEAR_DIV; if (pos >= get_threshold) { pos -= get_threshold; src += src_step; src_frames1++; states->u.linear.init = 2; /* get a new sample */ - if (src_frames1 >= src_frames) - break; + assert(src_frames1 <= src_frames); } } states->u.linear.old_sample = old_sample; states->u.linear.new_sample = new_sample; - states->u.linear.pos = pos; states++; } - *dst_framesp = dst_frames1; - return src_frames1; } -static snd_pcm_uframes_t snd_pcm_rate_shrink(const snd_pcm_channel_area_t *dst_areas, - snd_pcm_uframes_t dst_offset, snd_pcm_uframes_t *dst_framesp, - const snd_pcm_channel_area_t *src_areas, - snd_pcm_uframes_t src_offset, snd_pcm_uframes_t src_frames, - unsigned int channels, - unsigned int getidx, unsigned int putidx, - unsigned int get_increment, - snd_pcm_rate_state_t *states) +static void snd_pcm_rate_shrink(const snd_pcm_channel_area_t *dst_areas, + snd_pcm_uframes_t dst_offset, snd_pcm_uframes_t dst_frames, + const snd_pcm_channel_area_t *src_areas, + snd_pcm_uframes_t src_offset, snd_pcm_uframes_t src_frames, + unsigned int channels, + unsigned int getidx, unsigned int putidx, + unsigned int get_increment, + snd_pcm_rate_state_t *states) { #define GET16_LABELS #define PUT16_LABELS @@ -201,12 +197,8 @@ static snd_pcm_uframes_t snd_pcm_rate_shrink(const snd_pcm_channel_area_t *dst_a unsigned int channel; snd_pcm_uframes_t src_frames1 = 0; snd_pcm_uframes_t dst_frames1 = 0; - snd_pcm_uframes_t dst_frames = *dst_framesp; int16_t sample = 0; - if (src_frames == 0 || - dst_frames == 0) - return 0; for (channel = 0; channel < channels; ++channel) { const snd_pcm_channel_area_t *src_area = &src_areas[channel]; const snd_pcm_channel_area_t *dst_area = &dst_areas[channel]; @@ -216,7 +208,7 @@ static snd_pcm_uframes_t snd_pcm_rate_shrink(const snd_pcm_channel_area_t *dst_a char *dst; int src_step, dst_step; sum = states->u.linear.sum; - pos = states->u.linear.pos; + pos = 0; states->u.linear.init = 0; src = snd_pcm_channel_area_addr(src_area, src_offset); dst = snd_pcm_channel_area_addr(dst_area, dst_offset); @@ -234,11 +226,11 @@ static snd_pcm_uframes_t snd_pcm_rate_shrink(const snd_pcm_channel_area_t *dst_a src += src_step; src_frames1++; pos += get_increment; - if (pos >= DIV) { + if (pos >= LINEAR_DIV) { int s = sample; - pos -= DIV; + pos -= LINEAR_DIV; sum += s * (get_increment - pos); - sum /= DIV; + sum /= LINEAR_DIV; sample = sum; goto *put; #define PUT16_END after_put @@ -248,21 +240,39 @@ static snd_pcm_uframes_t snd_pcm_rate_shrink(const snd_pcm_channel_area_t *dst_a dst += dst_step; sum = s * pos; dst_frames1++; - if (dst_frames1 == dst_frames) - break; + assert(dst_frames1 <= dst_frames); } else sum += sample * get_increment; } states->u.linear.sum = sum; - states->u.linear.pos = pos; states++; } - *dst_framesp = dst_frames1; - return src_frames1; } #endif /* DOC_HIDDEN */ +static snd_pcm_sframes_t snd_pcm_rate_client_frames(snd_pcm_t *pcm, snd_pcm_sframes_t frames) +{ + snd_pcm_rate_t *rate = pcm->private_data; + if (frames == 0) + return 0; + /* Round toward zero */ + if (pcm->stream == SND_PCM_STREAM_PLAYBACK) + return muldiv_down(frames, LINEAR_DIV, rate->pitch); + else + return muldiv_down(frames, rate->pitch, LINEAR_DIV); +} + +static snd_pcm_sframes_t snd_pcm_rate_slave_frames(snd_pcm_t *pcm, snd_pcm_sframes_t frames) +{ + snd_pcm_rate_t *rate = pcm->private_data; + /* Round toward zero */ + if (pcm->stream == SND_PCM_STREAM_PLAYBACK) + return muldiv_down(frames, rate->pitch, LINEAR_DIV); + else + return muldiv_down(frames, LINEAR_DIV, rate->pitch); +} + static int snd_pcm_rate_hw_refine_cprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params) { int err; @@ -366,6 +376,18 @@ static int snd_pcm_rate_hw_refine_cchange(snd_pcm_t *pcm, snd_pcm_hw_params_t *p return 0; } +static int snd_pcm_rate_hw_refine_slave(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) +{ + snd_pcm_rate_t *rate = pcm->private_data; + return snd_pcm_hw_refine(rate->slave, params); +} + +static int snd_pcm_rate_hw_params_slave(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) +{ + snd_pcm_rate_t *rate = pcm->private_data; + return _snd_pcm_hw_params(rate->slave, params); +} + static int snd_pcm_rate_hw_refine(snd_pcm_t *pcm, snd_pcm_hw_params_t *params) { @@ -374,38 +396,53 @@ static int snd_pcm_rate_hw_refine(snd_pcm_t *pcm, snd_pcm_rate_hw_refine_cchange, snd_pcm_rate_hw_refine_sprepare, snd_pcm_rate_hw_refine_schange, - snd_pcm_plugin_hw_refine_slave); + snd_pcm_rate_hw_refine_slave); } static int snd_pcm_rate_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params) { snd_pcm_rate_t *rate = pcm->private_data; - snd_pcm_t *slave = rate->plug.slave; - snd_pcm_format_t src_format, dst_format; - unsigned int src_rate, dst_rate; + snd_pcm_t *slave = rate->slave; + snd_pcm_format_t src_format, dst_format, pformat, sformat; + unsigned int src_rate, dst_rate, channels, pwidth, swidth, chn; + snd_pcm_uframes_t period_size, buffer_size; int err = snd_pcm_hw_params_slave(pcm, params, snd_pcm_rate_hw_refine_cchange, snd_pcm_rate_hw_refine_sprepare, snd_pcm_rate_hw_refine_schange, - snd_pcm_plugin_hw_params_slave); + snd_pcm_rate_hw_params_slave); if (err < 0) return err; if (pcm->stream == SND_PCM_STREAM_PLAYBACK) { + period_size = slave->period_size; + buffer_size = slave->buffer_size; err = INTERNAL(snd_pcm_hw_params_get_format)(params, &src_format); if (err < 0) return err; + pformat = src_format; dst_format = slave->format; - err = INTERNAL(snd_pcm_hw_params_get_rate)(params, &src_rate, 0); + sformat = dst_format; dst_rate = slave->rate; + err = INTERNAL(snd_pcm_hw_params_get_rate)(params, &src_rate, 0); } else { - src_format = slave->format; + err = INTERNAL(snd_pcm_hw_params_get_period_size)(params, &period_size, 0); + if (err < 0) + return err; + err = INTERNAL(snd_pcm_hw_params_get_buffer_size)(params, &buffer_size); + if (err < 0) + return err; + sformat = src_format = slave->format; err = INTERNAL(snd_pcm_hw_params_get_format)(params, &dst_format); if (err < 0) return err; + pformat = dst_format; src_rate = slave->rate; err = INTERNAL(snd_pcm_hw_params_get_rate)(params, &dst_rate, 0); } + if (err < 0) + return err; + err = INTERNAL(snd_pcm_hw_params_get_channels)(params, &channels); if (err < 0) return err; rate->get_idx = snd_pcm_linear_get_index(src_format, SND_PCM_FORMAT_S16); @@ -417,26 +454,58 @@ static int snd_pcm_rate_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params) rate->func = snd_pcm_rate_shrink; /* pitch is get_increment */ } - rate->pitch = (((u_int64_t)dst_rate * DIV) + src_rate / 2) / src_rate; + rate->pitch = (((u_int64_t)dst_rate * LINEAR_DIV) + src_rate - 1) / src_rate; assert(!rate->states); - rate->states = malloc(rate->plug.slave->channels * sizeof(*rate->states)); + assert(!rate->pareas); + rate->states = malloc(channels * sizeof(*rate->states)); + if (rate->states == NULL) + return -ENOMEM; + if ((buffer_size / period_size) * period_size == buffer_size) + return 0; + rate->pareas = malloc(2 * channels * sizeof(*rate->pareas)); + if (rate->pareas == NULL) + return -ENOMEM; + pwidth = snd_pcm_format_physical_width(pformat); + swidth = snd_pcm_format_physical_width(sformat); + rate->pareas[0].addr = malloc(((pwidth * channels * period_size) / 8) + + ((swidth * channels * slave->period_size) / 8)); + if (rate->pareas[0].addr == NULL) { + free(rate->pareas); + return -ENOMEM; + } + rate->sareas = rate->pareas + channels; + rate->sareas[0].addr = (char *)rate->pareas[0].addr + ((pwidth * channels * period_size) / 8); + for (chn = 0; chn < channels; chn++) { + rate->pareas[chn].addr = rate->pareas[0].addr + (pwidth * chn * period_size) / 8; + rate->pareas[chn].first = 0; + rate->pareas[chn].step = pwidth; + rate->sareas[chn].addr = rate->sareas[0].addr + (swidth * chn * slave->period_size) / 8; + rate->sareas[chn].first = 0; + rate->sareas[chn].step = swidth; + } return 0; } static int snd_pcm_rate_hw_free(snd_pcm_t *pcm) { snd_pcm_rate_t *rate = pcm->private_data; + if (rate->pareas) { + free(rate->pareas[0].addr); + free(rate->pareas); + rate->pareas = NULL; + rate->sareas = NULL; + } if (rate->states) { free(rate->states); rate->states = NULL; } - return snd_pcm_hw_free(rate->plug.slave); + return snd_pcm_hw_free(rate->slave); } static void recalc(snd_pcm_t *pcm, snd_pcm_uframes_t *val) { snd_pcm_rate_t *rate = pcm->private_data; - snd_pcm_t *slave = rate->plug.slave; + snd_pcm_t *slave = rate->slave; unsigned long div; if (*val == pcm->buffer_size) { @@ -446,19 +515,19 @@ static void recalc(snd_pcm_t *pcm, snd_pcm_uframes_t *val) if (div * pcm->period_size == *val) *val = div * slave->period_size; else - *val = muldiv_near(*val, slave->rate, pcm->rate); + *val = muldiv_near(*val, slave->period_size, pcm->period_size); } } static int snd_pcm_rate_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t * params) { snd_pcm_rate_t *rate = pcm->private_data; - snd_pcm_t *slave = rate->plug.slave; + snd_pcm_t *slave = rate->slave; snd_pcm_sw_params_t sparams; snd_pcm_uframes_t boundary1, boundary2; sparams = *params; - if ((rate->pitch >= DIV ? 1 : 0) ^ (pcm->stream == SND_PCM_STREAM_CAPTURE ? 1 : 0)) { + if ((rate->pitch >= LINEAR_DIV ? 1 : 0) ^ (pcm->stream == SND_PCM_STREAM_CAPTURE ? 1 : 0)) { boundary1 = pcm->buffer_size; boundary2 = slave->buffer_size; while (boundary2 * 2 <= LONG_MAX - slave->buffer_size) { @@ -475,13 +544,53 @@ static int snd_pcm_rate_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t * params) } params->boundary = boundary1; sparams.boundary = boundary2; -#if 0 if (pcm->stream == SND_PCM_STREAM_PLAYBACK) { - rate->pitch = (((u_int64_t)boundary2 * DIV) + boundary1 / 2) / boundary1; + rate->pitch = (((u_int64_t)slave->period_size * LINEAR_DIV) + pcm->period_size - 1) / pcm->period_size; + do { + snd_pcm_uframes_t cframes; + + cframes = snd_pcm_rate_client_frames(pcm, slave->period_size - 1); + if (cframes == pcm->period_size - 1) + break; + if (cframes > pcm->period_size - 1) { + rate->pitch++; + if ((snd_pcm_uframes_t)snd_pcm_rate_client_frames(pcm, slave->period_size - 1) < pcm->period_size - 1) { + SNDERR("Unable to satisfy pitch condition (%i/%i - %li/%li)\n", slave->rate, pcm->rate, slave->period_size - 1, pcm->period_size - 1); + return -EIO; + } + } else { + rate->pitch--; + if ((snd_pcm_uframes_t)snd_pcm_rate_client_frames(pcm, slave->period_size - 1) > pcm->period_size - 1) { + SNDERR("Unable to satisfy pitch condition (%i/%i - %li/%li)\n", slave->rate, pcm->rate, slave->period_size - 1, pcm->period_size - 1); + return -EIO; + } + } + } while (1); + assert((snd_pcm_uframes_t)snd_pcm_rate_client_frames(pcm, slave->period_size - 1) == pcm->period_size - 1); } else { - rate->pitch = (((u_int64_t)boundary1 * DIV) + boundary2 / 2) / boundary2; + rate->pitch = (((u_int64_t)pcm->period_size * LINEAR_DIV) + slave->period_size - 1) / slave->period_size; + do { + snd_pcm_uframes_t cframes; + + cframes = snd_pcm_rate_slave_frames(pcm, pcm->period_size - 1); + if (cframes == slave->period_size - 1) + break; + if (cframes > slave->period_size - 1) { + rate->pitch++; + if ((snd_pcm_uframes_t)snd_pcm_rate_slave_frames(pcm, pcm->period_size - 1) < slave->period_size - 1) { + SNDERR("Unable to satisfy pitch condition (%i/%i - %li/%li)\n", slave->rate, pcm->rate, slave->period_size - 1, pcm->period_size - 1); + return -EIO; + } + } else { + rate->pitch--; + if ((snd_pcm_uframes_t)snd_pcm_rate_slave_frames(pcm, pcm->period_size - 1) > slave->period_size - 1) { + SNDERR("Unable to satisfy pitch condition (%i/%i - %li/%li)\n", slave->rate, pcm->rate, slave->period_size - 1, pcm->period_size - 1); + return -EIO; + } + } + } while (1); + assert((snd_pcm_uframes_t)snd_pcm_rate_slave_frames(pcm, pcm->period_size - 1) == slave->period_size - 1); } -#endif recalc(pcm, &sparams.avail_min); recalc(pcm, &sparams.xfer_align); recalc(pcm, &sparams.start_threshold); @@ -505,7 +614,6 @@ static int snd_pcm_rate_init(snd_pcm_t *pcm) rate->states[k].u.linear.sum = 0; rate->states[k].u.linear.old_sample = 0; rate->states[k].u.linear.new_sample = 0; - rate->states[k].u.linear.pos = 0; rate->states[k].u.linear.init = 0; } break; @@ -515,61 +623,596 @@ static int snd_pcm_rate_init(snd_pcm_t *pcm) return 0; } -static snd_pcm_uframes_t -snd_pcm_rate_write_areas(snd_pcm_t *pcm, +static inline int +snd_pcm_rate_write_areas1(snd_pcm_t *pcm, const snd_pcm_channel_area_t *areas, snd_pcm_uframes_t offset, - snd_pcm_uframes_t size, const snd_pcm_channel_area_t *slave_areas, - snd_pcm_uframes_t slave_offset, - snd_pcm_uframes_t *slave_sizep) + snd_pcm_uframes_t slave_offset) { snd_pcm_rate_t *rate = pcm->private_data; - return rate->func(slave_areas, slave_offset, slave_sizep, - areas, offset, size, - pcm->channels, - rate->get_idx, rate->put_idx, - rate->pitch, rate->states); + rate->func(slave_areas, slave_offset, rate->slave->period_size, + areas, offset, pcm->period_size, + pcm->channels, + rate->get_idx, rate->put_idx, + rate->pitch, rate->states); + return 0; } -static snd_pcm_uframes_t -snd_pcm_rate_read_areas(snd_pcm_t *pcm, +static inline int +snd_pcm_rate_read_areas1(snd_pcm_t *pcm, const snd_pcm_channel_area_t *areas, snd_pcm_uframes_t offset, - snd_pcm_uframes_t size, const snd_pcm_channel_area_t *slave_areas, - snd_pcm_uframes_t slave_offset, - snd_pcm_uframes_t *slave_sizep) + snd_pcm_uframes_t slave_offset) { snd_pcm_rate_t *rate = pcm->private_data; - *slave_sizep = rate->func(areas, offset, &size, - slave_areas, slave_offset, *slave_sizep, - pcm->channels, - rate->get_idx, rate->put_idx, - rate->pitch, rate->states); - return size; + rate->func(areas, offset, pcm->period_size, + slave_areas, slave_offset, rate->slave->period_size, + pcm->channels, + rate->get_idx, rate->put_idx, + rate->pitch, rate->states); + return 0; } -static snd_pcm_sframes_t snd_pcm_rate_client_frames(snd_pcm_t *pcm, snd_pcm_sframes_t frames) +static inline snd_pcm_sframes_t snd_pcm_rate_move_applptr(snd_pcm_t *pcm, snd_pcm_sframes_t frames) { snd_pcm_rate_t *rate = pcm->private_data; - if (frames == 0) - return 0; - /* Round toward zero */ - if (pcm->stream == SND_PCM_STREAM_PLAYBACK) - return muldiv_down(frames, DIV, rate->pitch); + snd_pcm_uframes_t orig_appl_ptr, appl_ptr = rate->appl_ptr, slave_appl_ptr; + snd_pcm_sframes_t diff, ndiff; + snd_pcm_t *slave = rate->slave; + + orig_appl_ptr = rate->appl_ptr; + if (frames > 0) + snd_pcm_mmap_appl_forward(pcm, frames); else - return muldiv_down(frames, rate->pitch, DIV); + snd_pcm_mmap_appl_backward(pcm, -frames); + slave_appl_ptr = + (appl_ptr / pcm->period_size) * rate->slave->period_size; + diff = slave_appl_ptr - *slave->appl.ptr; + if (diff < -(snd_pcm_sframes_t)(slave->boundary / 2)) { + diff = (slave->boundary - *slave->appl.ptr) + slave_appl_ptr; + } else if (diff > (snd_pcm_sframes_t)(slave->boundary / 2)) { + diff = -((slave->boundary - slave_appl_ptr) + *slave->appl.ptr); + } + if (diff == 0) + return frames; + if (diff > 0) { + ndiff = snd_pcm_forward(rate->slave, diff); + } else { + ndiff = snd_pcm_rewind(rate->slave, diff); + } + if (ndiff < 0) + return diff; + slave_appl_ptr = *slave->appl.ptr; + rate->appl_ptr = + (slave_appl_ptr / rate->slave->period_size) * pcm->period_size + + snd_pcm_rate_client_frames(pcm, slave_appl_ptr % rate->slave->period_size) + + orig_appl_ptr % pcm->period_size; + diff = orig_appl_ptr - rate->appl_ptr; + if (diff < -(snd_pcm_sframes_t)(slave->boundary / 2)) { + diff = (slave->boundary - rate->appl_ptr) + orig_appl_ptr; + } else if (diff > (snd_pcm_sframes_t)(slave->boundary / 2)) { + diff = -((slave->boundary - orig_appl_ptr) + rate->appl_ptr); + } + if (frames < 0) + return -diff; + return diff; } -static snd_pcm_sframes_t snd_pcm_rate_slave_frames(snd_pcm_t *pcm, snd_pcm_sframes_t frames) +static inline void snd_pcm_rate_sync_hwptr(snd_pcm_t *pcm) { snd_pcm_rate_t *rate = pcm->private_data; - /* Round toward zero */ + snd_pcm_uframes_t slave_hw_ptr = *rate->slave->hw.ptr; + + if (pcm->stream != SND_PCM_STREAM_PLAYBACK) + return; + rate->hw_ptr = + (slave_hw_ptr / rate->slave->period_size) * pcm->period_size + + snd_pcm_rate_client_frames(pcm, slave_hw_ptr % rate->slave->period_size); +} + +static int snd_pcm_rate_close(snd_pcm_t *pcm) +{ + snd_pcm_rate_t *rate = pcm->private_data; + int err = 0; + if (rate->close_slave) + err = snd_pcm_close(rate->slave); + free(rate); + return 0; +} + +static int snd_pcm_rate_nonblock(snd_pcm_t *pcm, int nonblock) +{ + snd_pcm_rate_t *rate = pcm->private_data; + return snd_pcm_nonblock(rate->slave, nonblock); +} + +static int snd_pcm_rate_async(snd_pcm_t *pcm, int sig, pid_t pid) +{ + snd_pcm_rate_t *rate = pcm->private_data; + return snd_pcm_async(rate->slave, sig, pid); +} + +static int snd_pcm_rate_poll_revents(snd_pcm_t *pcm, struct pollfd *pfds, unsigned int nfds, unsigned short *revents) +{ + snd_pcm_rate_t *rate = pcm->private_data; + return snd_pcm_poll_descriptors_revents(rate->slave, pfds, nfds, revents); +} + +static int snd_pcm_rate_info(snd_pcm_t *pcm, snd_pcm_info_t * info) +{ + snd_pcm_rate_t *rate = pcm->private_data; + return snd_pcm_info(rate->slave, info); +} + +static int snd_pcm_rate_channel_info(snd_pcm_t *pcm, snd_pcm_channel_info_t *info) +{ + return snd_pcm_channel_info_shm(pcm, info, -1); +} + +static snd_pcm_state_t snd_pcm_rate_state(snd_pcm_t *pcm) +{ + snd_pcm_rate_t *rate = pcm->private_data; + return snd_pcm_state(rate->slave); +} + +static int snd_pcm_rate_hwsync(snd_pcm_t *pcm) +{ + snd_pcm_rate_t *rate = pcm->private_data; + int err = snd_pcm_hwsync(rate->slave); + if (err < 0) + return err; + snd_atomic_write_begin(&rate->watom); + snd_pcm_rate_sync_hwptr(pcm); + snd_atomic_write_end(&rate->watom); + return 0; +} + +static int snd_pcm_rate_delay(snd_pcm_t *pcm, snd_pcm_sframes_t *delayp) +{ + snd_pcm_rate_hwsync(pcm); if (pcm->stream == SND_PCM_STREAM_PLAYBACK) - return muldiv_down(frames, rate->pitch, DIV); + *delayp = snd_pcm_mmap_playback_hw_avail(pcm); else - return muldiv_down(frames, DIV, rate->pitch); + *delayp = snd_pcm_mmap_capture_hw_avail(pcm); + return 0; +} + +static int snd_pcm_rate_prepare(snd_pcm_t *pcm) +{ + snd_pcm_rate_t *rate = pcm->private_data; + int err; + + snd_atomic_write_begin(&rate->watom); + err = snd_pcm_prepare(rate->slave); + if (err < 0) { + snd_atomic_write_end(&rate->watom); + return err; + } + *pcm->hw.ptr = 0; + *pcm->appl.ptr = 0; + snd_atomic_write_end(&rate->watom); + err = snd_pcm_rate_init(pcm); + if (err < 0) + return err; + return 0; +} + +static int snd_pcm_rate_reset(snd_pcm_t *pcm) +{ + snd_pcm_rate_t *rate = pcm->private_data; + int err; + snd_atomic_write_begin(&rate->watom); + err = snd_pcm_reset(rate->slave); + if (err < 0) { + snd_atomic_write_end(&rate->watom); + return err; + } + *pcm->hw.ptr = 0; + *pcm->appl.ptr = 0; + snd_atomic_write_end(&rate->watom); + err = snd_pcm_rate_init(pcm); + if (err < 0) + return err; + return 0; +} + +static int snd_pcm_rate_start(snd_pcm_t *pcm) +{ + snd_pcm_rate_t *rate = pcm->private_data; + return snd_pcm_start(rate->slave); +} + +static int snd_pcm_rate_drop(snd_pcm_t *pcm) +{ + snd_pcm_rate_t *rate = pcm->private_data; + return snd_pcm_drop(rate->slave); +} + +static int snd_pcm_rate_drain(snd_pcm_t *pcm) +{ + snd_pcm_rate_t *rate = pcm->private_data; + return snd_pcm_drain(rate->slave); +} + +static int snd_pcm_rate_pause(snd_pcm_t *pcm, int enable) +{ + snd_pcm_rate_t *rate = pcm->private_data; + return snd_pcm_pause(rate->slave, enable); +} + +static snd_pcm_sframes_t snd_pcm_rate_rewind(snd_pcm_t *pcm, snd_pcm_uframes_t frames) +{ + snd_pcm_rate_t *rate = pcm->private_data; + snd_pcm_sframes_t n = snd_pcm_mmap_hw_avail(pcm); + + if ((snd_pcm_uframes_t)n > frames) + frames = n; + if (frames == 0) + return 0; + + snd_atomic_write_begin(&rate->watom); + n = snd_pcm_rate_move_applptr(pcm, -frames); + snd_atomic_write_end(&rate->watom); + return n; +} + +static snd_pcm_sframes_t snd_pcm_rate_forward(snd_pcm_t *pcm, snd_pcm_uframes_t frames) +{ + snd_pcm_rate_t *rate = pcm->private_data; + snd_pcm_sframes_t n = snd_pcm_mmap_avail(pcm); + + if ((snd_pcm_uframes_t)n > frames) + frames = n; + if (frames == 0) + return 0; + + snd_atomic_write_begin(&rate->watom); + n = snd_pcm_rate_move_applptr(pcm, frames); + snd_atomic_write_end(&rate->watom); + return n; +} + +static int snd_pcm_rate_resume(snd_pcm_t *pcm) +{ + snd_pcm_rate_t *rate = pcm->private_data; + return snd_pcm_resume(rate->slave); +} + +static int snd_pcm_rate_commit_next_period(snd_pcm_t *pcm, snd_pcm_uframes_t appl_offset) +{ + snd_pcm_rate_t *rate = pcm->private_data; + snd_pcm_uframes_t cont = pcm->buffer_size - appl_offset; + const snd_pcm_channel_area_t *areas; + const snd_pcm_channel_area_t *slave_areas; + snd_pcm_uframes_t slave_offset, xfer; + snd_pcm_uframes_t slave_frames = ULONG_MAX; + snd_pcm_sframes_t result; + + areas = snd_pcm_mmap_areas(pcm); + if (cont >= pcm->period_size) { + result = snd_pcm_mmap_begin(rate->slave, &slave_areas, &slave_offset, &slave_frames); + if (result < 0) + return result; + if (slave_frames < rate->slave->period_size) { + snd_pcm_rate_write_areas1(pcm, areas, appl_offset, rate->sareas, 0); + goto __partial; + } + snd_pcm_rate_write_areas1(pcm, areas, appl_offset, + slave_areas, slave_offset); + result = snd_pcm_mmap_commit(rate->slave, slave_offset, rate->slave->period_size); + if (result < (snd_pcm_sframes_t)rate->slave->period_size) { + if (result < 0) + return result; + result = snd_pcm_rewind(rate->slave, result); + if (result < 0) + return result; + return 0; + } + } else { + snd_pcm_areas_copy(rate->pareas, cont, + areas, 0, + pcm->channels, pcm->period_size - cont, + pcm->format); + snd_pcm_areas_copy(rate->pareas, 0, + areas, appl_offset, + pcm->channels, cont, + pcm->format); + + snd_pcm_rate_write_areas1(pcm, rate->pareas, 0, rate->sareas, 0); + + /* ok, commit first fragment */ + result = snd_pcm_mmap_begin(rate->slave, &slave_areas, &slave_offset, &slave_frames); + if (result < 0) + return result; + __partial: + xfer = 0; + cont = rate->slave->buffer_size - slave_offset; + if (cont > rate->slave->period_size) + cont = rate->slave->period_size; + snd_pcm_areas_copy(slave_areas, slave_offset, + rate->sareas, 0, + pcm->channels, cont, + rate->slave->format); + result = snd_pcm_mmap_commit(rate->slave, slave_offset, cont); + if (result < (snd_pcm_sframes_t)cont) { + if (result < 0) + return result; + result = snd_pcm_rewind(rate->slave, result); + if (result < 0) + return result; + return 0; + } + xfer = cont; + + if (xfer == rate->slave->period_size) + return 1; + + /* commit second fragment */ + cont = rate->slave->period_size - cont; + slave_frames = cont; + result = snd_pcm_mmap_begin(rate->slave, &slave_areas, &slave_offset, &slave_frames); + if (result < 0) + return result; + assert(slave_offset == 0); + snd_pcm_areas_copy(slave_areas, slave_offset, + rate->sareas, xfer, + pcm->channels, cont, + rate->slave->format); + result = snd_pcm_mmap_commit(rate->slave, slave_offset, cont); + if (result < (snd_pcm_sframes_t)cont) { + if (result < 0) + return result; + result = snd_pcm_rewind(rate->slave, result + xfer); + if (result < 0) + return result; + return 0; + } + } + return 1; +} + +static int snd_pcm_rate_grab_next_period(snd_pcm_t *pcm, snd_pcm_uframes_t hw_offset) +{ + snd_pcm_rate_t *rate = pcm->private_data; + snd_pcm_uframes_t cont = pcm->buffer_size - hw_offset; + const snd_pcm_channel_area_t *areas; + const snd_pcm_channel_area_t *slave_areas; + snd_pcm_uframes_t slave_offset; + snd_pcm_uframes_t slave_frames = ULONG_MAX; + snd_pcm_sframes_t result; + + areas = snd_pcm_mmap_areas(pcm); + if (cont >= pcm->period_size) { + result = snd_pcm_mmap_begin(rate->slave, &slave_areas, &slave_offset, &slave_frames); + if (result < 0) + return result; + if (slave_frames < rate->slave->period_size) + goto __partial; + snd_pcm_rate_read_areas1(pcm, areas, hw_offset, + slave_areas, slave_offset); + result = snd_pcm_mmap_commit(rate->slave, slave_offset, rate->slave->period_size); + if (result < (snd_pcm_sframes_t)rate->slave->period_size) { + if (result < 0) + return result; + result = snd_pcm_rewind(rate->slave, result); + if (result < 0) + return result; + return 0; + } + } else { + snd_pcm_rate_t *rate = pcm->private_data; + snd_pcm_uframes_t xfer; + + /* ok, grab first fragment */ + result = snd_pcm_mmap_begin(rate->slave, &slave_areas, &slave_offset, &slave_frames); + if (result < 0) + return result; + __partial: + xfer = 0; + cont = rate->slave->buffer_size - slave_offset; + snd_pcm_areas_copy(rate->sareas, 0, + slave_areas, slave_offset, + pcm->channels, cont, + rate->slave->format); + result = snd_pcm_mmap_commit(rate->slave, slave_offset, cont); + if (result < (snd_pcm_sframes_t)cont) { + if (result < 0) + return result; + result = snd_pcm_rewind(rate->slave, result); + if (result < 0) + return result; + return 0; + } + xfer = cont; + + /* grab second fragment */ + cont = rate->slave->period_size - cont; + slave_frames = cont; + result = snd_pcm_mmap_begin(rate->slave, &slave_areas, &slave_offset, &slave_frames); + if (result < 0) + return result; + assert(slave_offset == 0); + snd_pcm_areas_copy(rate->sareas, xfer, + slave_areas, slave_offset, + pcm->channels, cont, + rate->slave->format); + result = snd_pcm_mmap_commit(rate->slave, slave_offset, cont); + if (result < (snd_pcm_sframes_t)cont) { + if (result < 0) + return result; + result = snd_pcm_rewind(rate->slave, result + xfer); + if (result < 0) + return result; + return 0; + } + + cont = pcm->buffer_size - hw_offset; + if (cont >= pcm->period_size) { + snd_pcm_rate_read_areas1(pcm, areas, hw_offset, + rate->sareas, 0); + } else { + snd_pcm_rate_read_areas1(pcm, + rate->pareas, 0, + rate->sareas, 0); + snd_pcm_areas_copy(areas, hw_offset, + rate->pareas, 0, + pcm->channels, cont, + pcm->format); + snd_pcm_areas_copy(areas, 0, + rate->pareas, cont, + pcm->channels, pcm->period_size - cont, + pcm->format); + } + } + return 1; +} + +static snd_pcm_sframes_t snd_pcm_rate_mmap_commit(snd_pcm_t *pcm, + snd_pcm_uframes_t offset ATTRIBUTE_UNUSED, + snd_pcm_uframes_t size) +{ + snd_pcm_rate_t *rate = pcm->private_data; + snd_pcm_t *slave = rate->slave; + snd_pcm_uframes_t appl_offset, xfer; + snd_pcm_sframes_t slave_size; + int err; + + if (size == 0) + return 0; + if (pcm->stream == SND_PCM_STREAM_CAPTURE) { + snd_atomic_write_begin(&rate->watom); + snd_pcm_mmap_appl_forward(pcm, size); + snd_atomic_write_end(&rate->watom); + return size; + } + slave_size = snd_pcm_avail_update(slave); + if (slave_size < 0) + return slave_size; + xfer = rate->appl_ptr % pcm->period_size; + appl_offset = (rate->appl_ptr - xfer) % pcm->buffer_size; + xfer = pcm->period_size - xfer; + if (xfer >= size) { + if (xfer == size) { + err = snd_pcm_rate_commit_next_period(pcm, appl_offset); + if (err < 0) + return err; + if (err == 0) + return 0; + } + snd_atomic_write_begin(&rate->watom); + snd_pcm_mmap_appl_forward(pcm, size); + snd_atomic_write_end(&rate->watom); + return size; + } else { + size -= xfer; + err = snd_pcm_rate_commit_next_period(pcm, appl_offset); + if (err < 0) + return err; + if (err == 0) + return 0; + snd_atomic_write_begin(&rate->watom); + snd_pcm_mmap_appl_forward(pcm, xfer); + snd_atomic_write_end(&rate->watom); + } + while ((snd_pcm_uframes_t)size >= pcm->period_size && + (snd_pcm_uframes_t)slave_size >= rate->slave->period_size) { + err = snd_pcm_rate_commit_next_period(pcm, appl_offset); + if (err == 0) + return xfer; + if (err < 0) + return xfer > 0 ? (snd_pcm_sframes_t)xfer : err; + xfer += pcm->period_size; + size -= pcm->period_size; + slave_size -= rate->slave->period_size; + snd_atomic_write_begin(&rate->watom); + snd_pcm_mmap_appl_forward(pcm, pcm->period_size); + snd_atomic_write_end(&rate->watom); + } + if (size > 0) { + snd_atomic_write_begin(&rate->watom); + snd_pcm_mmap_appl_forward(pcm, size); + snd_atomic_write_end(&rate->watom); + xfer += size; + } + return xfer; +} + +static snd_pcm_sframes_t snd_pcm_rate_avail_update(snd_pcm_t *pcm) +{ + snd_pcm_rate_t *rate = pcm->private_data; + snd_pcm_t *slave = rate->slave; + snd_pcm_uframes_t slave_size; + + slave_size = snd_pcm_avail_update(slave); + if (pcm->stream == SND_PCM_STREAM_CAPTURE) + goto _capture; + snd_atomic_write_begin(&rate->watom); + snd_pcm_rate_sync_hwptr(pcm); + snd_atomic_write_end(&rate->watom); + return snd_pcm_mmap_avail(pcm); + _capture: { + snd_pcm_uframes_t xfer, hw_offset, size; + + xfer = snd_pcm_mmap_capture_avail(pcm); + size = pcm->buffer_size - xfer; + hw_offset = snd_pcm_mmap_hw_offset(pcm); + while (size >= pcm->period_size && + slave_size >= rate->slave->period_size) { + int err = snd_pcm_rate_grab_next_period(pcm, hw_offset); + if (err < 0) + return err; + if (err == 0) + return (snd_pcm_sframes_t)xfer; + xfer += pcm->period_size; + size -= pcm->period_size; + slave_size -= rate->slave->period_size; + snd_pcm_mmap_hw_forward(pcm, pcm->period_size); + } + return (snd_pcm_sframes_t)xfer; + } +} + +static int snd_pcm_rate_mmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED) +{ + return 0; +} + +static int snd_pcm_rate_munmap(snd_pcm_t *pcm ATTRIBUTE_UNUSED) +{ + return 0; +} + +static int snd_pcm_rate_status(snd_pcm_t *pcm, snd_pcm_status_t * status) +{ + snd_pcm_rate_t *rate = pcm->private_data; + snd_pcm_sframes_t err; + snd_atomic_read_t ratom; + snd_atomic_read_init(&ratom, &rate->watom); + _again: + snd_atomic_read_begin(&ratom); + err = snd_pcm_status(rate->slave, status); + if (err < 0) { + snd_atomic_read_ok(&ratom); + return err; + } + snd_pcm_rate_sync_hwptr(pcm); + status->appl_ptr = *pcm->appl.ptr; + status->hw_ptr = *pcm->hw.ptr; + if (pcm->stream == SND_PCM_STREAM_PLAYBACK) { + status->delay = snd_pcm_mmap_playback_hw_avail(pcm); + status->avail = snd_pcm_mmap_playback_avail(pcm); + } else { + status->delay = snd_pcm_mmap_capture_hw_avail(pcm); + status->avail = snd_pcm_mmap_capture_avail(pcm); + } + if (!snd_atomic_read_ok(&ratom)) { + snd_atomic_read_wait(&ratom); + goto _again; + } + status->avail_max = snd_pcm_rate_client_frames(pcm, (snd_pcm_sframes_t) status->avail_max); + return 0; } static void snd_pcm_rate_dump(snd_pcm_t *pcm, snd_output_t *out) @@ -587,23 +1230,45 @@ static void snd_pcm_rate_dump(snd_pcm_t *pcm, snd_output_t *out) snd_pcm_dump_setup(pcm, out); } snd_output_printf(out, "Slave: "); - snd_pcm_dump(rate->plug.slave, out); + snd_pcm_dump(rate->slave, out); } +static snd_pcm_fast_ops_t snd_pcm_rate_fast_ops = { + .status = snd_pcm_rate_status, + .state = snd_pcm_rate_state, + .hwsync = snd_pcm_rate_hwsync, + .delay = snd_pcm_rate_delay, + .prepare = snd_pcm_rate_prepare, + .reset = snd_pcm_rate_reset, + .start = snd_pcm_rate_start, + .drop = snd_pcm_rate_drop, + .drain = snd_pcm_rate_drain, + .pause = snd_pcm_rate_pause, + .rewind = snd_pcm_rate_rewind, + .forward = snd_pcm_rate_forward, + .resume = snd_pcm_rate_resume, + .writei = snd_pcm_mmap_writei, + .writen = snd_pcm_mmap_writen, + .readi = snd_pcm_mmap_readi, + .readn = snd_pcm_mmap_readn, + .avail_update = snd_pcm_rate_avail_update, + .mmap_commit = snd_pcm_rate_mmap_commit, +}; + static snd_pcm_ops_t snd_pcm_rate_ops = { - .close = snd_pcm_plugin_close, - .info = snd_pcm_plugin_info, + .close = snd_pcm_rate_close, + .info = snd_pcm_rate_info, .hw_refine = snd_pcm_rate_hw_refine, .hw_params = snd_pcm_rate_hw_params, .hw_free = snd_pcm_rate_hw_free, .sw_params = snd_pcm_rate_sw_params, - .channel_info = snd_pcm_plugin_channel_info, + .channel_info = snd_pcm_rate_channel_info, .dump = snd_pcm_rate_dump, - .nonblock = snd_pcm_plugin_nonblock, - .async = snd_pcm_plugin_async, - .poll_revents = snd_pcm_plugin_poll_revents, - .mmap = snd_pcm_plugin_mmap, - .munmap = snd_pcm_plugin_munmap, + .nonblock = snd_pcm_rate_nonblock, + .async = snd_pcm_rate_async, + .poll_revents = snd_pcm_rate_poll_revents, + .mmap = snd_pcm_rate_mmap, + .munmap = snd_pcm_rate_munmap, }; @@ -633,17 +1298,12 @@ int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sform if (!rate) { return -ENOMEM; } - snd_pcm_plugin_init(&rate->plug); + rate->slave = slave; + rate->close_slave = close_slave; rate->type = RATE_TYPE_LINEAR; rate->srate = srate; rate->sformat = sformat; - rate->plug.read = snd_pcm_rate_read_areas; - rate->plug.write = snd_pcm_rate_write_areas; - rate->plug.client_frames = snd_pcm_rate_client_frames; - rate->plug.slave_frames = snd_pcm_rate_slave_frames; - rate->plug.init = snd_pcm_rate_init; - rate->plug.slave = slave; - rate->plug.close_slave = close_slave; + snd_atomic_write_init(&rate->watom); err = snd_pcm_new(&pcm, SND_PCM_TYPE_RATE, name, slave->stream, slave->mode); if (err < 0) { @@ -651,12 +1311,13 @@ int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sform return err; } pcm->ops = &snd_pcm_rate_ops; - pcm->fast_ops = &snd_pcm_plugin_fast_ops; + pcm->fast_ops = &snd_pcm_rate_fast_ops; pcm->private_data = rate; pcm->poll_fd = slave->poll_fd; pcm->poll_events = slave->poll_events; - snd_pcm_set_hw_ptr(pcm, &rate->plug.hw_ptr, -1, 0); - snd_pcm_set_appl_ptr(pcm, &rate->plug.appl_ptr, -1, 0); + pcm->mmap_rw = 1; + snd_pcm_set_hw_ptr(pcm, &rate->hw_ptr, -1, 0); + snd_pcm_set_appl_ptr(pcm, &rate->appl_ptr, -1, 0); *pcmp = pcm; return 0; -- 2.47.1