diff options
author | Takashi Iwai <tiwai@suse.de> | 2006-04-06 18:37:55 +0200 |
---|---|---|
committer | Takashi Iwai <tiwai@suse.de> | 2006-04-06 18:37:55 +0200 |
commit | 33d69ef33be28a4d205a852462c25c67d2f51707 (patch) | |
tree | 18b8d96738df6476cfb24bdbafcbd57dda96b6d5 | |
parent | 8f605df24aa972d606ab5fbd895c76b525f262bb (diff) | |
download | alsa-lib-33d69ef33be28a4d205a852462c25c67d2f51707.tar.gz |
Create rate converter plugin SDK
Created a new rate converter plugin SDK.
A rate converter can be replaced as an extra plugin now.
The default rate converter is a built-in linear converter.
You can find a sample external converter in alsa-plugins package.
-rw-r--r-- | include/Makefile.am | 2 | ||||
-rw-r--r-- | include/pcm_plugin.h | 2 | ||||
-rw-r--r-- | include/pcm_rate.h | 117 | ||||
-rw-r--r-- | src/pcm/Makefile.am | 2 | ||||
-rw-r--r-- | src/pcm/pcm_local.h | 2 | ||||
-rw-r--r-- | src/pcm/pcm_plug.c | 28 | ||||
-rw-r--r-- | src/pcm/pcm_rate.c | 752 | ||||
-rw-r--r-- | src/pcm/pcm_rate_linear.c | 435 |
8 files changed, 879 insertions, 461 deletions
diff --git a/include/Makefile.am b/include/Makefile.am index 307e7c54..7782f79c 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -10,7 +10,7 @@ alsainclude_HEADERS = asoundlib.h asoundef.h \ seq_event.h seq.h seqmid.h seq_midi_event.h \ conv.h instr.h iatomic.h \ alisp.h pcm_external.h pcm_ioplug.h pcm_extplug.h \ - control_external.h + pcm_rate.h control_external.h noinst_HEADERS = alsa sys.h search.h list.h aserver.h local.h alsa-symbols.h diff --git a/include/pcm_plugin.h b/include/pcm_plugin.h index 374ba710..7d48527e 100644 --- a/include/pcm_plugin.h +++ b/include/pcm_plugin.h @@ -156,7 +156,7 @@ int _snd_pcm_route_open(snd_pcm_t **pcmp, const char *name, * Rate plugin for linear formats */ int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name, - snd_pcm_format_t sformat, unsigned int srate, + snd_pcm_format_t sformat, unsigned int srate, const char *converter, snd_pcm_t *slave, int close_slave); int _snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name, snd_config_t *root, snd_config_t *conf, diff --git a/include/pcm_rate.h b/include/pcm_rate.h new file mode 100644 index 00000000..d211e097 --- /dev/null +++ b/include/pcm_rate.h @@ -0,0 +1,117 @@ +/** + * \file include/pcm_rate.h + * \brief External Rate-Converter-Plugin SDK + * \author Takashi Iwai <tiwai@suse.de> + * \date 2006 + * + * External Rate-Converter-Plugin SDK + */ + +/* + * ALSA external PCM rate-converter plugin SDK (draft version) + * + * Copyright (c) 2006 Takashi Iwai <tiwai@suse.de> + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef __ALSA_PCM_RATE_H +#define __ALSA_PCM_RATE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Protocol version + */ +#define SND_PCM_RATE_PLUGIN_VERSION 0x010001 + +/** hw_params information for a single side */ +typedef struct snd_pcm_rate_side_info { + snd_pcm_format_t format; + unsigned int rate; + snd_pcm_uframes_t buffer_size; + snd_pcm_uframes_t period_size; +} snd_pcm_rate_side_info_t; + +/** hw_params information */ +typedef struct snd_pcm_rate_info { + struct snd_pcm_rate_side_info in; + struct snd_pcm_rate_side_info out; + unsigned int channels; +} snd_pcm_rate_info_t; + +/** Callback table of rate-converter */ +typedef struct snd_pcm_rate_ops { + /** + * close the converter; optional + */ + void (*close)(void *obj); + /** + * initialize the converter, called at hw_params + */ + int (*init)(void *obj, snd_pcm_rate_info_t *info); + /** + * free the converter; optional + */ + void (*free)(void *obj); + /** + * reset the converter, called at prepare; optional + */ + void (*reset)(void *obj); + /** + * adjust the pitch, called at sw_params; optional + */ + int (*adjust_pitch)(void *obj, snd_pcm_rate_info_t *info); + /** + * convert the data + */ + void (*convert)(void *obj, + 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); + /** + * convert an s16 interleaved-data array; exclusive with convert + */ + void (*convert_s16)(void *obj, int16_t *dst, unsigned int dst_frames, + const int16_t *src, unsigned int src_frames); + /** + * compute the frame size for input + */ + snd_pcm_uframes_t (*input_frames)(void *obj, snd_pcm_uframes_t frames); + /** + * compute the frame size for output + */ + snd_pcm_uframes_t (*output_frames)(void *obj, snd_pcm_uframes_t frames); +} snd_pcm_rate_ops_t; + +/** open function type */ +typedef int (*snd_pcm_rate_open_func_t)(unsigned int version, void **objp, + snd_pcm_rate_ops_t *opsp); + +/** + * Define the object entry for external PCM rate-converter plugins + */ +#define SND_PCM_RATE_PLUGIN_ENTRY(name) _snd_pcm_rate_##name##_open + + +#ifdef __cplusplus +} +#endif + +#endif /* __ALSA_PCM_RATE_H */ diff --git a/src/pcm/Makefile.am b/src/pcm/Makefile.am index 0e032b78..990ad330 100644 --- a/src/pcm/Makefile.am +++ b/src/pcm/Makefile.am @@ -29,7 +29,7 @@ if BUILD_PCM_PLUGIN_ADPCM libpcm_la_SOURCES += pcm_adpcm.c endif if BUILD_PCM_PLUGIN_RATE -libpcm_la_SOURCES += pcm_rate.c +libpcm_la_SOURCES += pcm_rate.c pcm_rate_linear.c endif if BUILD_PCM_PLUGIN_PLUG libpcm_la_SOURCES += pcm_plug.c diff --git a/src/pcm/pcm_local.h b/src/pcm/pcm_local.h index e94125ad..be17e7a2 100644 --- a/src/pcm/pcm_local.h +++ b/src/pcm/pcm_local.h @@ -755,6 +755,8 @@ int snd_pcm_hw_open_fd(snd_pcm_t **pcmp, const char *name, int fd, int mmap_emul int snd_pcm_wait_nocheck(snd_pcm_t *pcm, int timeout); +const char *snd_pcm_rate_get_default_converter(snd_config_t *root); + #define SND_PCM_HW_PARBIT_ACCESS (1U << SND_PCM_HW_PARAM_ACCESS) #define SND_PCM_HW_PARBIT_FORMAT (1U << SND_PCM_HW_PARAM_FORMAT) #define SND_PCM_HW_PARBIT_SUBFORMAT (1U << SND_PCM_HW_PARAM_SUBFORMAT) diff --git a/src/pcm/pcm_plug.c b/src/pcm/pcm_plug.c index 98ee6835..58a165a9 100644 --- a/src/pcm/pcm_plug.c +++ b/src/pcm/pcm_plug.c @@ -50,6 +50,7 @@ typedef struct { snd_pcm_format_t sformat; int schannels; int srate; + const char *rate_converter; enum snd_pcm_plug_route_policy route_policy; snd_pcm_route_ttable_entry_t *ttable; int ttable_ok, ttable_last; @@ -359,7 +360,8 @@ static int snd_pcm_plug_change_rate(snd_pcm_t *pcm, snd_pcm_t **new, snd_pcm_plu if (clt->rate == slv->rate) return 0; assert(snd_pcm_format_linear(slv->format)); - err = snd_pcm_rate_open(new, NULL, slv->format, slv->rate, plug->gen.slave, plug->gen.slave != plug->req_slave); + err = snd_pcm_rate_open(new, NULL, slv->format, slv->rate, plug->rate_converter, + plug->gen.slave, plug->gen.slave != plug->req_slave); if (err < 0) return err; slv->access = clt->access; @@ -1013,6 +1015,7 @@ static snd_pcm_ops_t snd_pcm_plug_ops = { int snd_pcm_plug_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sformat, int schannels, int srate, + const char *rate_converter, enum snd_pcm_plug_route_policy route_policy, snd_pcm_route_ttable_entry_t *ttable, unsigned int tt_ssize, @@ -1030,6 +1033,7 @@ int snd_pcm_plug_open(snd_pcm_t **pcmp, plug->sformat = sformat; plug->schannels = schannels; plug->srate = srate; + plug->rate_converter = rate_converter; plug->gen.slave = plug->req_slave = slave; plug->gen.close_slave = close_slave; plug->route_policy = route_policy; @@ -1087,6 +1091,8 @@ pcm.name { SCHANNEL REAL # route value (0.0 - 1.0) } } + rate_converter STR # type of rate converter + # default value is taken from defaults.pcm.rate_converter } \endcode @@ -1127,6 +1133,8 @@ int _snd_pcm_plug_open(snd_pcm_t **pcmp, const char *name, unsigned int cused, sused; snd_pcm_format_t sformat = SND_PCM_FORMAT_UNKNOWN; int schannels = -1, srate = -1; + const char *rate_converter = NULL; + snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id; @@ -1167,6 +1175,17 @@ int _snd_pcm_plug_open(snd_pcm_t **pcmp, const char *name, continue; } #endif +#ifdef BUILD_PCM_PLUGIN_RATE + if (strcmp(id, "rate_converter") == 0) { + const char *str; + if ((err = snd_config_get_string(n, &str)) < 0) { + SNDERR("Invalid type for %s", id); + return -EINVAL; + } + rate_converter = str; + continue; + } +#endif SNDERR("Unknown field %s", id); return -EINVAL; } @@ -1200,11 +1219,16 @@ int _snd_pcm_plug_open(snd_pcm_t **pcmp, const char *name, } #endif +#ifdef BUILD_PCM_PLUGIN_RATE + if (! rate_converter) + rate_converter = snd_pcm_rate_get_default_converter(root); +#endif + err = snd_pcm_open_slave(&spcm, root, sconf, stream, mode, conf); snd_config_delete(sconf); if (err < 0) return err; - err = snd_pcm_plug_open(pcmp, name, sformat, schannels, srate, + err = snd_pcm_plug_open(pcmp, name, sformat, schannels, srate, rate_converter, route_policy, ttable, ssize, cused, sused, spcm, 1); if (err < 0) snd_pcm_close(spcm); diff --git a/src/pcm/pcm_rate.c b/src/pcm/pcm_rate.c index 423ef24c..3c65e2ca 100644 --- a/src/pcm/pcm_rate.c +++ b/src/pcm/pcm_rate.c @@ -29,8 +29,10 @@ */ #include <inttypes.h> #include <byteswap.h> +#include <dlfcn.h> #include "pcm_local.h" #include "pcm_plugin.h" +#include "pcm_rate.h" #include "iatomic.h" #include "plugin_ops.h" @@ -46,27 +48,8 @@ const char *_snd_module_pcm_rate = ""; #ifndef DOC_HIDDEN -/* LINEAR_DIV needs to be large enough to handle resampling from 192000 -> 8000 */ -#define LINEAR_DIV_SHIFT 19 -#define LINEAR_DIV (1<<LINEAR_DIV_SHIFT) - -enum rate_type { - RATE_TYPE_LINEAR, /* linear interpolation */ - RATE_TYPE_BANDLIMIT, /* bandlimited interpolation */ - RATE_TYPE_POLYPHASE, /* polyphase resampling */ -}; - typedef struct _snd_pcm_rate snd_pcm_rate_t; -typedef void (*rate_f)(const snd_pcm_channel_area_t *dst_areas, - snd_pcm_uframes_t dst_offset, - snd_pcm_uframes_t dst_frames, - const snd_pcm_channel_area_t *src_areas, - snd_pcm_uframes_t src_offset, - snd_pcm_uframes_t src_frames, - unsigned int channels, - snd_pcm_rate_t *rate); - struct _snd_pcm_rate { snd_pcm_generic_t gen; snd_atomic_write_t watom; @@ -74,290 +57,21 @@ struct _snd_pcm_rate { snd_pcm_uframes_t last_commit_ptr; snd_pcm_uframes_t orig_avail_min; snd_pcm_sw_params_t sw_params; - enum rate_type type; - unsigned int get_idx; - unsigned int put_idx; - unsigned int pitch; - unsigned int pitch_shift; /* for expand interpolation */ - rate_f func; snd_pcm_format_t sformat; unsigned int srate; snd_pcm_channel_area_t *pareas; /* areas for splitted period (rate pcm) */ snd_pcm_channel_area_t *sareas; /* areas for splitted period (slave pcm) */ - int16_t *old_sample; + snd_pcm_rate_info_t info; + void *obj; + snd_pcm_rate_ops_t ops; + unsigned int get_idx; + unsigned int put_idx; + int16_t *src_buf; + int16_t *dst_buf; }; -static void snd_pcm_rate_expand(const snd_pcm_channel_area_t *dst_areas, - snd_pcm_uframes_t dst_offset, snd_pcm_uframes_t dst_frames, - const snd_pcm_channel_area_t *src_areas, - snd_pcm_uframes_t src_offset, snd_pcm_uframes_t src_frames, - unsigned int channels, - snd_pcm_rate_t *rate) -{ -#define GET16_LABELS -#define PUT16_LABELS -#include "plugin_ops.h" -#undef GET16_LABELS -#undef PUT16_LABELS - void *get = get16_labels[rate->get_idx]; - void *put = put16_labels[rate->put_idx]; - unsigned int get_threshold = rate->pitch; - unsigned int channel; - snd_pcm_uframes_t src_frames1; - snd_pcm_uframes_t dst_frames1; - int16_t sample = 0; - unsigned int pos; - - for (channel = 0; channel < channels; ++channel) { - const snd_pcm_channel_area_t *src_area = &src_areas[channel]; - const snd_pcm_channel_area_t *dst_area = &dst_areas[channel]; - const char *src; - char *dst; - int src_step, dst_step; - int16_t old_sample = 0; - int16_t new_sample; - int old_weight, new_weight; - src = snd_pcm_channel_area_addr(src_area, src_offset); - dst = snd_pcm_channel_area_addr(dst_area, dst_offset); - src_step = snd_pcm_channel_area_step(src_area); - dst_step = snd_pcm_channel_area_step(dst_area); - src_frames1 = 0; - dst_frames1 = 0; - new_sample = rate->old_sample[channel]; - pos = get_threshold; - while (dst_frames1 < dst_frames) { - if (pos >= get_threshold) { - pos -= get_threshold; - old_sample = new_sample; - if (src_frames1 < src_frames) { - goto *get; -#define GET16_END after_get -#include "plugin_ops.h" -#undef GET16_END - after_get: - new_sample = sample; - } - } - new_weight = (pos << (16 - rate->pitch_shift)) / (get_threshold >> rate->pitch_shift); - old_weight = 0x10000 - new_weight; - sample = (old_sample * old_weight + new_sample * new_weight) >> 16; - goto *put; -#define PUT16_END after_put -#include "plugin_ops.h" -#undef PUT16_END - after_put: - dst += dst_step; - dst_frames1++; - pos += LINEAR_DIV; - if (pos >= get_threshold) { - src += src_step; - src_frames1++; - } - } - rate->old_sample[channel] = new_sample; - } -} - -/* optimized version for S16 format */ -static void snd_pcm_rate_expand_s16(const snd_pcm_channel_area_t *dst_areas, - snd_pcm_uframes_t dst_offset, snd_pcm_uframes_t dst_frames, - const snd_pcm_channel_area_t *src_areas, - snd_pcm_uframes_t src_offset, snd_pcm_uframes_t src_frames, - unsigned int channels, - snd_pcm_rate_t *rate) -{ - unsigned int channel; - snd_pcm_uframes_t src_frames1; - snd_pcm_uframes_t dst_frames1; - unsigned int get_threshold = rate->pitch; - unsigned int pos; - - for (channel = 0; channel < channels; ++channel) { - const snd_pcm_channel_area_t *src_area = &src_areas[channel]; - const snd_pcm_channel_area_t *dst_area = &dst_areas[channel]; - const int16_t *src; - int16_t *dst; - int src_step, dst_step; - int16_t old_sample = 0; - int16_t new_sample; - int old_weight, new_weight; - src = snd_pcm_channel_area_addr(src_area, src_offset); - dst = snd_pcm_channel_area_addr(dst_area, dst_offset); - src_step = snd_pcm_channel_area_step(src_area) >> 1; - dst_step = snd_pcm_channel_area_step(dst_area) >> 1; - src_frames1 = 0; - dst_frames1 = 0; - new_sample = rate->old_sample[channel]; - pos = get_threshold; - while (dst_frames1 < dst_frames) { - if (pos >= get_threshold) { - pos -= get_threshold; - old_sample = new_sample; - if (src_frames1 < src_frames) - new_sample = *src; - } - new_weight = (pos << (16 - rate->pitch_shift)) / (get_threshold >> rate->pitch_shift); - old_weight = 0x10000 - new_weight; - *dst = (old_sample * old_weight + new_sample * new_weight) >> 16; - dst += dst_step; - dst_frames1++; - pos += LINEAR_DIV; - if (pos >= get_threshold) { - src += src_step; - src_frames1++; - } - } - rate->old_sample[channel] = new_sample; - } -} - -static void snd_pcm_rate_shrink(const snd_pcm_channel_area_t *dst_areas, - snd_pcm_uframes_t dst_offset, snd_pcm_uframes_t dst_frames, - const snd_pcm_channel_area_t *src_areas, - snd_pcm_uframes_t src_offset, snd_pcm_uframes_t src_frames, - unsigned int channels, - snd_pcm_rate_t *rate) -{ -#define GET16_LABELS -#define PUT16_LABELS -#include "plugin_ops.h" -#undef GET16_LABELS -#undef PUT16_LABELS - void *get = get16_labels[rate->get_idx]; - void *put = put16_labels[rate->put_idx]; - unsigned int get_increment = rate->pitch; - unsigned int channel; - snd_pcm_uframes_t src_frames1; - snd_pcm_uframes_t dst_frames1; - int16_t sample = 0; - unsigned int pos; - - for (channel = 0; channel < channels; ++channel) { - const snd_pcm_channel_area_t *src_area = &src_areas[channel]; - const snd_pcm_channel_area_t *dst_area = &dst_areas[channel]; - const char *src; - char *dst; - int src_step, dst_step; - int16_t old_sample = 0; - int16_t new_sample = 0; - int old_weight, new_weight; - pos = LINEAR_DIV - get_increment; /* Force first sample to be copied */ - src = snd_pcm_channel_area_addr(src_area, src_offset); - dst = snd_pcm_channel_area_addr(dst_area, dst_offset); - src_step = snd_pcm_channel_area_step(src_area); - dst_step = snd_pcm_channel_area_step(dst_area); - src_frames1 = 0; - dst_frames1 = 0; - while (src_frames1 < src_frames) { - - goto *get; -#define GET16_END after_get -#include "plugin_ops.h" -#undef GET16_END - after_get: - new_sample = sample; - src += src_step; - src_frames1++; - pos += get_increment; - if (pos >= LINEAR_DIV) { - pos -= LINEAR_DIV; - old_weight = (pos << (32 - LINEAR_DIV_SHIFT)) / (get_increment >> (LINEAR_DIV_SHIFT - 16)); - new_weight = 0x10000 - old_weight; - sample = (old_sample * old_weight + new_sample * new_weight) >> 16; - goto *put; -#define PUT16_END after_put -#include "plugin_ops.h" -#undef PUT16_END - after_put: - dst += dst_step; - dst_frames1++; - if (CHECK_SANITY(dst_frames1 > dst_frames)) { - SNDERR("dst_frames overflow"); - break; - } - } - old_sample = new_sample; - } - } -} - -/* optimized version for S16 format */ -static void snd_pcm_rate_shrink_s16(const snd_pcm_channel_area_t *dst_areas, - snd_pcm_uframes_t dst_offset, snd_pcm_uframes_t dst_frames, - const snd_pcm_channel_area_t *src_areas, - snd_pcm_uframes_t src_offset, snd_pcm_uframes_t src_frames, - unsigned int channels, - snd_pcm_rate_t *rate) -{ - unsigned int get_increment = rate->pitch; - unsigned int channel; - snd_pcm_uframes_t src_frames1; - snd_pcm_uframes_t dst_frames1; - unsigned int pos = 0; - - for (channel = 0; channel < channels; ++channel) { - const snd_pcm_channel_area_t *src_area = &src_areas[channel]; - const snd_pcm_channel_area_t *dst_area = &dst_areas[channel]; - const int16_t *src; - int16_t *dst; - int src_step, dst_step; - int16_t old_sample = 0; - int16_t new_sample = 0; - int old_weight, new_weight; - pos = LINEAR_DIV - get_increment; /* Force first sample to be copied */ - src = snd_pcm_channel_area_addr(src_area, src_offset); - dst = snd_pcm_channel_area_addr(dst_area, dst_offset); - src_step = snd_pcm_channel_area_step(src_area) >> 1; - dst_step = snd_pcm_channel_area_step(dst_area) >> 1 ; - src_frames1 = 0; - dst_frames1 = 0; - while (src_frames1 < src_frames) { - - new_sample = *src; - src += src_step; - src_frames1++; - pos += get_increment; - if (pos >= LINEAR_DIV) { - pos -= LINEAR_DIV; - old_weight = (pos << (32 - LINEAR_DIV_SHIFT)) / (get_increment >> (LINEAR_DIV_SHIFT - 16)); - new_weight = 0x10000 - old_weight; - *dst = (old_sample * old_weight + new_sample * new_weight) >> 16; - dst += dst_step; - dst_frames1++; - if (CHECK_SANITY(dst_frames1 > dst_frames)) { - SNDERR("dst_frames overflow"); - break; - } - } - old_sample = new_sample; - } - } -} - #endif /* DOC_HIDDEN */ -static snd_pcm_sframes_t snd_pcm_rate_client_frames(snd_pcm_t *pcm, snd_pcm_sframes_t frames) -{ - snd_pcm_rate_t *rate = pcm->private_data; - if (frames == 0) - return 0; - /* Round toward zero */ - if (pcm->stream == SND_PCM_STREAM_PLAYBACK) - return muldiv_near(frames, LINEAR_DIV, rate->pitch); - else - return muldiv_near(frames, rate->pitch, LINEAR_DIV); -} - -static snd_pcm_sframes_t snd_pcm_rate_slave_frames(snd_pcm_t *pcm, snd_pcm_sframes_t frames) -{ - snd_pcm_rate_t *rate = pcm->private_data; - /* Round toward zero */ - if (pcm->stream == SND_PCM_STREAM_PLAYBACK) - return muldiv_near(frames, rate->pitch, LINEAR_DIV); - else - return muldiv_near(frames, LINEAR_DIV, rate->pitch); -} - static int snd_pcm_rate_hw_refine_cprepare(snd_pcm_t *pcm ATTRIBUTE_UNUSED, snd_pcm_hw_params_t *params) { int err; @@ -516,9 +230,8 @@ static int snd_pcm_rate_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params) { snd_pcm_rate_t *rate = pcm->private_data; snd_pcm_t *slave = rate->gen.slave; - snd_pcm_format_t src_format, dst_format, pformat, sformat; - unsigned int src_rate, dst_rate, channels, pwidth, swidth, chn; - snd_pcm_uframes_t period_size, buffer_size; + 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, @@ -528,85 +241,86 @@ static int snd_pcm_rate_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params) return err; if (pcm->stream == SND_PCM_STREAM_PLAYBACK) { - err = INTERNAL(snd_pcm_hw_params_get_format)(params, &src_format); - if (err < 0) - return err; - pformat = src_format; - dst_format = slave->format; - sformat = dst_format; - dst_rate = slave->rate; - err = INTERNAL(snd_pcm_hw_params_get_rate)(params, &src_rate, 0); + cinfo = &rate->info.in; + sinfo = &rate->info.out; } else { - sformat = src_format = slave->format; - err = INTERNAL(snd_pcm_hw_params_get_format)(params, &dst_format); - if (err < 0) - return err; - pformat = dst_format; - src_rate = slave->rate; - err = INTERNAL(snd_pcm_hw_params_get_rate)(params, &dst_rate, 0); + 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, &period_size, 0); + 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, &buffer_size); + 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->get_idx = snd_pcm_linear_get_index(src_format, SND_PCM_FORMAT_S16); - rate->put_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S16, dst_format); - if (src_rate < dst_rate) { - if (src_format == dst_format && src_format == SND_PCM_FORMAT_S16) - rate->func = snd_pcm_rate_expand_s16; - else - rate->func = snd_pcm_rate_expand; - /* pitch is get_threshold */ - } else { - if (src_format == dst_format && src_format == SND_PCM_FORMAT_S16) - rate->func = snd_pcm_rate_shrink_s16; - else - rate->func = snd_pcm_rate_shrink; - /* pitch is get_increment */ - } - rate->pitch = (((u_int64_t)dst_rate * LINEAR_DIV) + (src_rate / 2)) / src_rate; + + 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; } -#if 0 - if ((buffer_size / period_size) * period_size == buffer_size && - (slave->buffer_size / slave->period_size) * slave->period_size == slave->buffer_size) - return 0; -#endif + 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) - return -ENOMEM; - free(rate->old_sample); - rate->old_sample = malloc(sizeof(*rate->old_sample) * channels); - if (rate->old_sample == NULL) - return -ENOMEM; - pwidth = snd_pcm_format_physical_width(pformat); - swidth = snd_pcm_format_physical_width(sformat); - rate->pareas[0].addr = malloc(((pwidth * channels * period_size) / 8) + - ((swidth * channels * slave->period_size) / 8)); - if (rate->pareas[0].addr == NULL) { - free(rate->pareas); - return -ENOMEM; - } + 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 + ((pwidth * channels * period_size) / 8); + 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 + (pwidth * chn * period_size) / 8; + rate->pareas[chn].addr = rate->pareas[0].addr + (cwidth * chn * cinfo->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->pareas[chn].step = cwidth; + rate->sareas[chn].addr = rate->sareas[0].addr + (swidth * chn * sinfo->period_size) / 8; rate->sareas[chn].first = 0; rate->sareas[chn].step = swidth; } + + if (rate->ops.convert_s16) { + rate->get_idx = snd_pcm_linear_get_index(rate->info.in.format, SND_PCM_FORMAT_S16); + rate->put_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S16, rate->info.out.format); + free(rate->src_buf); + rate->src_buf = malloc(channels * rate->info.in.period_size * 2); + free(rate->dst_buf); + rate->dst_buf = malloc(channels * rate->info.out.period_size * 2); + if (! rate->src_buf || ! rate->dst_buf) + goto error; + } + return 0; + + error: + if (rate->pareas) { + free(rate->pareas[0].addr); + free(rate->pareas); + rate->pareas = NULL; + } + if (rate->ops.free) + rate->ops.free(rate->obj); + return -ENOMEM; } static int snd_pcm_rate_hw_free(snd_pcm_t *pcm) @@ -618,8 +332,11 @@ static int snd_pcm_rate_hw_free(snd_pcm_t *pcm) rate->pareas = NULL; rate->sareas = NULL; } - free(rate->old_sample); - rate->old_sample = 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); } @@ -649,86 +366,19 @@ static int snd_pcm_rate_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t * params) rate->sw_params = *params; sparams = &rate->sw_params; - if ((rate->pitch >= LINEAR_DIV ? 1 : 0) ^ (pcm->stream == SND_PCM_STREAM_CAPTURE ? 1 : 0)) { - boundary1 = pcm->buffer_size; - boundary2 = slave->buffer_size; - while (boundary2 * 2 <= LONG_MAX - slave->buffer_size) { - boundary1 *= 2; - boundary2 *= 2; - } - } else { - boundary1 = pcm->buffer_size; - boundary2 = slave->buffer_size; - while (boundary1 * 2 <= LONG_MAX - pcm->buffer_size) { - boundary1 *= 2; - boundary2 *= 2; - } + 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 = boundary2; - if (pcm->stream == SND_PCM_STREAM_PLAYBACK) { - rate->pitch = (((u_int64_t)slave->period_size * LINEAR_DIV) + (pcm->period_size/2) ) / pcm->period_size; - do { - snd_pcm_uframes_t cframes,cframes_test; - - cframes = snd_pcm_rate_client_frames(pcm, slave->period_size ); - if (cframes == pcm->period_size ) - break; - if (cframes > pcm->period_size ) { - rate->pitch++; - cframes_test = snd_pcm_rate_client_frames(pcm, slave->period_size ); - if (cframes_test < pcm->period_size ) { - SNDERR("Unable to satisfy pitch condition (%i/%i - %li/%li)\n", slave->rate, pcm->rate, slave->period_size, pcm->period_size); - return -EIO; - } - } else { - rate->pitch--; - cframes_test = snd_pcm_rate_client_frames(pcm, slave->period_size ); - if (cframes_test > pcm->period_size) { - SNDERR("Unable to satisfy pitch condition (%i/%i - %li/%li)\n", slave->rate, pcm->rate, slave->period_size, pcm->period_size); - return -EIO; - } - } - } while (1); - if ((snd_pcm_uframes_t)snd_pcm_rate_client_frames(pcm, slave->period_size ) != pcm->period_size) { - SNDERR("invalid slave period_size %ld for pcm period_size %ld", - slave->period_size, pcm->period_size); - return -EIO; - } - } else { - rate->pitch = (((u_int64_t)pcm->period_size * LINEAR_DIV) + (slave->period_size/2) ) / slave->period_size; - do { - snd_pcm_uframes_t cframes; - - cframes = snd_pcm_rate_slave_frames(pcm, pcm->period_size ); - if (cframes == slave->period_size ) - break; - if (cframes > slave->period_size ) { - rate->pitch++; - if ((snd_pcm_uframes_t)snd_pcm_rate_slave_frames(pcm, pcm->period_size ) < slave->period_size ) { - SNDERR("Unable to satisfy pitch condition (%i/%i - %li/%li)\n", slave->rate, pcm->rate, slave->period_size, pcm->period_size); - return -EIO; - } - } else { - rate->pitch--; - if ((snd_pcm_uframes_t)snd_pcm_rate_slave_frames(pcm, pcm->period_size) > slave->period_size ) { - SNDERR("Unable to satisfy pitch condition (%i/%i - %li/%li)\n", slave->rate, pcm->rate, slave->period_size , pcm->period_size ); - return -EIO; - } - } - } while (1); - if ((snd_pcm_uframes_t)snd_pcm_rate_slave_frames(pcm, pcm->period_size ) != slave->period_size) { - SNDERR("invalid pcm period_size %ld for slave period_size", - pcm->period_size, slave->period_size); - return -EIO; - } - } - if (rate->pitch >= LINEAR_DIV) { - /* shift for expand linear interpolation */ - rate->pitch_shift = 0; - while ((rate->pitch >> rate->pitch_shift) >= (1 << 16)) - rate->pitch_shift++; - } + + 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->xfer_align); @@ -754,20 +404,113 @@ static int snd_pcm_rate_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t * params) static int snd_pcm_rate_init(snd_pcm_t *pcm) { snd_pcm_rate_t *rate = pcm->private_data; - switch (rate->type) { - case RATE_TYPE_LINEAR: - /* for expand */ - if (rate->old_sample) - memset(rate->old_sample, 0, sizeof(*rate->old_sample) * pcm->channels); - break; - default: - assert(0); - } + + if (rate->ops.reset) + rate->ops.reset(rate->obj); rate->last_commit_ptr = 0; return 0; } -static inline int +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) +{ +#define GET16_LABELS +#include "plugin_ops.h" +#undef GET16_LABELS + 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; +#define GET16_END after_get +#include "plugin_ops.h" +#undef GET16_END + 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) +{ +#define PUT16_LABELS +#include "plugin_ops.h" +#undef PUT16_LABELS + 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; +#define PUT16_END after_put +#include "plugin_ops.h" +#undef PUT16_END + 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, @@ -775,13 +518,12 @@ snd_pcm_rate_write_areas1(snd_pcm_t *pcm, snd_pcm_uframes_t slave_offset) { snd_pcm_rate_t *rate = pcm->private_data; - rate->func(slave_areas, slave_offset, rate->gen.slave->period_size, + do_convert(slave_areas, slave_offset, rate->gen.slave->period_size, areas, offset, pcm->period_size, pcm->channels, rate); - return 0; } -static inline int +static inline void snd_pcm_rate_read_areas1(snd_pcm_t *pcm, const snd_pcm_channel_area_t *areas, snd_pcm_uframes_t offset, @@ -789,10 +531,9 @@ snd_pcm_rate_read_areas1(snd_pcm_t *pcm, snd_pcm_uframes_t slave_offset) { snd_pcm_rate_t *rate = pcm->private_data; - rate->func(areas, offset, pcm->period_size, + do_convert(areas, offset, pcm->period_size, slave_areas, slave_offset, rate->gen.slave->period_size, pcm->channels, rate); - return 0; } static inline snd_pcm_sframes_t snd_pcm_rate_move_applptr(snd_pcm_t *pcm, snd_pcm_sframes_t frames) @@ -827,8 +568,12 @@ static inline snd_pcm_sframes_t snd_pcm_rate_move_applptr(snd_pcm_t *pcm, snd_pc slave_appl_ptr = *slave->appl.ptr; rate->appl_ptr = (slave_appl_ptr / rate->gen.slave->period_size) * pcm->period_size + - snd_pcm_rate_client_frames(pcm, slave_appl_ptr % rate->gen.slave->period_size) + orig_appl_ptr % pcm->period_size; + if (pcm->stream == SND_PCM_STREAM_PLAYBACK) + rate->appl_ptr += rate->ops.input_frames(rate->obj, slave_appl_ptr % rate->gen.slave->period_size); + else + rate->appl_ptr += rate->ops.output_frames(rate->obj, slave_appl_ptr % rate->gen.slave->period_size); + diff = orig_appl_ptr - rate->appl_ptr; if (diff < -(snd_pcm_sframes_t)(slave->boundary / 2)) { diff = (slave->boundary - rate->appl_ptr) + orig_appl_ptr; @@ -855,7 +600,7 @@ static inline void snd_pcm_rate_sync_hwptr(snd_pcm_t *pcm) */ rate->hw_ptr = (slave_hw_ptr / rate->gen.slave->period_size) * pcm->period_size + - snd_pcm_rate_client_frames(pcm, slave_hw_ptr % rate->gen.slave->period_size); + rate->ops.output_frames(rate->obj, slave_hw_ptr % rate->gen.slave->period_size); } static int snd_pcm_rate_hwsync(snd_pcm_t *pcm) @@ -1323,7 +1068,7 @@ static int snd_pcm_rate_drain(snd_pcm_t *pcm) spsize = rate->gen.slave->period_size; } else { psize = size; - spsize = snd_pcm_rate_slave_frames(pcm, size); + spsize = rate->ops.output_frames(rate->obj, size); if (! spsize) break; } @@ -1357,15 +1102,16 @@ static int snd_pcm_rate_status(snd_pcm_t *pcm, snd_pcm_status_t * status) if (pcm->stream == SND_PCM_STREAM_PLAYBACK) { status->delay = snd_pcm_mmap_playback_hw_avail(pcm); status->avail = snd_pcm_mmap_playback_avail(pcm); + status->avail_max = rate->ops.input_frames(rate->obj, status->avail_max); } else { status->delay = snd_pcm_mmap_capture_hw_avail(pcm); status->avail = snd_pcm_mmap_capture_avail(pcm); + status->avail_max = rate->ops.output_frames(rate->obj, status->avail_max); } if (!snd_atomic_read_ok(&ratom)) { snd_atomic_read_wait(&ratom); goto _again; } - status->avail_max = snd_pcm_rate_client_frames(pcm, (snd_pcm_sframes_t) status->avail_max); return 0; } @@ -1387,6 +1133,15 @@ static void snd_pcm_rate_dump(snd_pcm_t *pcm, snd_output_t *out) 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); + return snd_pcm_generic_close(pcm); +} + static snd_pcm_fast_ops_t snd_pcm_rate_fast_ops = { .status = snd_pcm_rate_status, .state = snd_pcm_generic_state, @@ -1413,7 +1168,7 @@ static snd_pcm_fast_ops_t snd_pcm_rate_fast_ops = { }; static snd_pcm_ops_t snd_pcm_rate_ops = { - .close = snd_pcm_generic_close, + .close = snd_pcm_rate_close, .info = snd_pcm_generic_info, .hw_refine = snd_pcm_rate_hw_refine, .hw_params = snd_pcm_rate_hw_params, @@ -1427,6 +1182,22 @@ static snd_pcm_ops_t snd_pcm_rate_ops = { .munmap = snd_pcm_generic_munmap, }; +/** + * \brief Get a default converter string + * \param root Root configuration node + * \retval A const string if found, or NULL + */ +const char *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) { + const char *str; + if (snd_config_get_string(n, &str) >= 0) + return str; + } + return NULL; +} /** * \brief Creates a new rate PCM @@ -1434,6 +1205,7 @@ static snd_pcm_ops_t snd_pcm_rate_ops = { * \param name Name of PCM * \param sformat Slave format * \param srate Slave rate + * \param type SRC type string * \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 @@ -1441,11 +1213,18 @@ static snd_pcm_ops_t snd_pcm_rate_ops = { * of compatibility reasons. The prototype might be freely * changed in future. */ -int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sformat, unsigned int srate, snd_pcm_t *slave, int close_slave) +int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sformat, + unsigned int srate, const char *type, snd_pcm_t *slave, int close_slave) { snd_pcm_t *pcm; snd_pcm_rate_t *rate; + snd_pcm_rate_open_func_t open_func; + char open_name[64]; int err; +#ifndef PIC + 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) @@ -1456,16 +1235,60 @@ int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name, snd_pcm_format_t sform } rate->gen.slave = slave; rate->gen.close_slave = close_slave; - rate->type = RATE_TYPE_LINEAR; rate->srate = srate; rate->sformat = sformat; snd_atomic_write_init(&rate->watom); + if (! type || ! *type) + type = "linear"; + +#ifdef PIC + snprintf(open_name, sizeof(open_name), "_snd_pcm_rate_%s_open", type); + open_func = snd_dlobj_cache_lookup(open_name); + if (! open_func) { + void *h; + char lib_name[128], *lib = NULL; + if (strcmp(type, "linear")) { + snprintf(lib_name, sizeof(lib_name), + "%s/libasound_module_rate_%s.so", PKGLIBDIR, type); + lib = lib_name; + } + h = snd_dlopen(lib, RTLD_NOW); + if (! h) { + SNDERR("Cannot open library for type %s", type); + free(rate); + return -ENOENT; + } + open_func = dlsym(h, open_name); + if (! open_func) { + SNDERR("Cannot find function %s", open_name); + snd_dlclose(h); + free(rate); + return -ENOENT; + } + snd_dlobj_cache_add(open_name, h, open_func); + } +#else + open_func = SND_PCM_RATE_PLUGIN_ENTRY(linear); +#endif + err = snd_pcm_new(&pcm, SND_PCM_TYPE_RATE, name, slave->stream, slave->mode); if (err < 0) { free(rate); return err; } + err = open_func(SND_PCM_RATE_PLUGIN_VERSION, &rate->obj, &rate->ops); + if (err < 0) { + snd_pcm_close(pcm); + return err; + } + 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_close(pcm); + return err; + } + pcm->ops = &snd_pcm_rate_ops; pcm->fast_ops = &snd_pcm_rate_fast_ops; pcm->private_data = rate; @@ -1497,6 +1320,8 @@ pcm.name { rate INT # Slave rate [format STR] # Slave format } + [converter STR] # Converter type, default is taken from + # defaults.pcm.rate_converter } \endcode @@ -1532,6 +1357,8 @@ int _snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name, snd_config_t *slave = NULL, *sconf; snd_pcm_format_t sformat = SND_PCM_FORMAT_UNKNOWN; int srate = -1; + const char *type = NULL; + snd_config_for_each(i, next, conf) { snd_config_t *n = snd_config_iterator_entry(i); const char *id; @@ -1543,6 +1370,15 @@ int _snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name, slave = n; continue; } + if (strcmp(id, "converter") == 0) { + const char *str; + if ((err = snd_config_get_string(n, &str)) < 0) { + SNDERR("invalid converter string"); + return -EINVAL; + } + type = str; + continue; + } SNDERR("Unknown field %s", id); return -EINVAL; } @@ -1550,6 +1386,10 @@ int _snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name, SNDERR("slave is not defined"); return -EINVAL; } + + if (! type) { + } + err = snd_pcm_slave_conf(root, slave, &sconf, 2, SND_PCM_HW_PARAM_FORMAT, 0, &sformat, SND_PCM_HW_PARAM_RATE, SCONF_MANDATORY, &srate); @@ -1565,8 +1405,8 @@ int _snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name, snd_config_delete(sconf); if (err < 0) return err; - err = snd_pcm_rate_open(pcmp, name, - sformat, (unsigned int) srate, spcm, 1); + err = snd_pcm_rate_open(pcmp, name, sformat, (unsigned int) srate, + type, spcm, 1); if (err < 0) snd_pcm_close(spcm); return err; diff --git a/src/pcm/pcm_rate_linear.c b/src/pcm/pcm_rate_linear.c new file mode 100644 index 00000000..dade0c6d --- /dev/null +++ b/src/pcm/pcm_rate_linear.c @@ -0,0 +1,435 @@ +/* + * Linear rate converter plugin + * + * Copyright (c) 2000 by Abramo Bagnara <abramo@alsa-project.org> + * 2004 by Jaroslav Kysela <perex@suse.cz> + * 2006 by Takashi Iwai <tiwai@suse.de> + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <inttypes.h> +#include <byteswap.h> +#include "pcm_local.h" +#include "pcm_plugin.h" +#include "pcm_rate.h" + +#include "plugin_ops.h" + + +/* LINEAR_DIV needs to be large enough to handle resampling from 192000 -> 8000 */ +#define LINEAR_DIV_SHIFT 19 +#define LINEAR_DIV (1<<LINEAR_DIV_SHIFT) + +struct rate_linear { + unsigned int get_idx; + unsigned int put_idx; + unsigned int pitch; + unsigned int pitch_shift; /* for expand interpolation */ + unsigned int channels; + int16_t *old_sample; + void (*func)(struct rate_linear *rate, + 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); +}; + +static snd_pcm_uframes_t input_frames(void *obj, snd_pcm_uframes_t frames) +{ + struct rate_linear *rate = obj; + if (frames == 0) + return 0; + /* Round toward zero */ + return muldiv_near(frames, LINEAR_DIV, rate->pitch); +} + +static snd_pcm_uframes_t output_frames(void *obj, snd_pcm_uframes_t frames) +{ + struct rate_linear *rate = obj; + if (frames == 0) + return 0; + /* Round toward zero */ + return muldiv_near(frames, rate->pitch, LINEAR_DIV); +} + +static void linear_expand(struct rate_linear *rate, + 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) +{ +#define GET16_LABELS +#define PUT16_LABELS +#include "plugin_ops.h" +#undef GET16_LABELS +#undef PUT16_LABELS + void *get = get16_labels[rate->get_idx]; + void *put = put16_labels[rate->put_idx]; + unsigned int get_threshold = rate->pitch; + unsigned int channel; + unsigned int src_frames1; + unsigned int dst_frames1; + int16_t sample = 0; + unsigned int pos; + + for (channel = 0; channel < rate->channels; ++channel) { + const snd_pcm_channel_area_t *src_area = &src_areas[channel]; + const snd_pcm_channel_area_t *dst_area = &dst_areas[channel]; + const char *src; + char *dst; + int src_step, dst_step; + int16_t old_sample = 0; + int16_t new_sample; + int old_weight, new_weight; + src = snd_pcm_channel_area_addr(src_area, src_offset); + dst = snd_pcm_channel_area_addr(dst_area, dst_offset); + src_step = snd_pcm_channel_area_step(src_area); + dst_step = snd_pcm_channel_area_step(dst_area); + src_frames1 = 0; + dst_frames1 = 0; + new_sample = rate->old_sample[channel]; + pos = get_threshold; + while (dst_frames1 < dst_frames) { + if (pos >= get_threshold) { + pos -= get_threshold; + old_sample = new_sample; + if (src_frames1 < src_frames) { + goto *get; +#define GET16_END after_get +#include "plugin_ops.h" +#undef GET16_END + after_get: + new_sample = sample; + } + } + new_weight = (pos << (16 - rate->pitch_shift)) / (get_threshold >> rate->pitch_shift); + old_weight = 0x10000 - new_weight; + sample = (old_sample * old_weight + new_sample * new_weight) >> 16; + goto *put; +#define PUT16_END after_put +#include "plugin_ops.h" +#undef PUT16_END + after_put: + dst += dst_step; + dst_frames1++; + pos += LINEAR_DIV; + if (pos >= get_threshold) { + src += src_step; + src_frames1++; + } + } + rate->old_sample[channel] = new_sample; + } +} + +/* optimized version for S16 format */ +static void linear_expand_s16(struct rate_linear *rate, + 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 channel; + unsigned int src_frames1; + unsigned int dst_frames1; + unsigned int get_threshold = rate->pitch; + unsigned int pos; + + for (channel = 0; channel < rate->channels; ++channel) { + const snd_pcm_channel_area_t *src_area = &src_areas[channel]; + const snd_pcm_channel_area_t *dst_area = &dst_areas[channel]; + const int16_t *src; + int16_t *dst; + int src_step, dst_step; + int16_t old_sample = 0; + int16_t new_sample; + int old_weight, new_weight; + src = snd_pcm_channel_area_addr(src_area, src_offset); + dst = snd_pcm_channel_area_addr(dst_area, dst_offset); + src_step = snd_pcm_channel_area_step(src_area) >> 1; + dst_step = snd_pcm_channel_area_step(dst_area) >> 1; + src_frames1 = 0; + dst_frames1 = 0; + new_sample = rate->old_sample[channel]; + pos = get_threshold; + while (dst_frames1 < dst_frames) { + if (pos >= get_threshold) { + pos -= get_threshold; + old_sample = new_sample; + if (src_frames1 < src_frames) + new_sample = *src; + } + new_weight = (pos << (16 - rate->pitch_shift)) / (get_threshold >> rate->pitch_shift); + old_weight = 0x10000 - new_weight; + *dst = (old_sample * old_weight + new_sample * new_weight) >> 16; + dst += dst_step; + dst_frames1++; + pos += LINEAR_DIV; + if (pos >= get_threshold) { + src += src_step; + src_frames1++; + } + } + rate->old_sample[channel] = new_sample; + } +} + +static void linear_shrink(struct rate_linear *rate, + 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) +{ +#define GET16_LABELS +#define PUT16_LABELS +#include "plugin_ops.h" +#undef GET16_LABELS +#undef PUT16_LABELS + void *get = get16_labels[rate->get_idx]; + void *put = put16_labels[rate->put_idx]; + unsigned int get_increment = rate->pitch; + unsigned int channel; + unsigned int src_frames1; + unsigned int dst_frames1; + int16_t sample = 0; + unsigned int pos; + + for (channel = 0; channel < rate->channels; ++channel) { + const snd_pcm_channel_area_t *src_area = &src_areas[channel]; + const snd_pcm_channel_area_t *dst_area = &dst_areas[channel]; + const char *src; + char *dst; + int src_step, dst_step; + int16_t old_sample = 0; + int16_t new_sample = 0; + int old_weight, new_weight; + pos = LINEAR_DIV - get_increment; /* Force first sample to be copied */ + src = snd_pcm_channel_area_addr(src_area, src_offset); + dst = snd_pcm_channel_area_addr(dst_area, dst_offset); + src_step = snd_pcm_channel_area_step(src_area); + dst_step = snd_pcm_channel_area_step(dst_area); + src_frames1 = 0; + dst_frames1 = 0; + while (src_frames1 < src_frames) { + + goto *get; +#define GET16_END after_get +#include "plugin_ops.h" +#undef GET16_END + after_get: + new_sample = sample; + src += src_step; + src_frames1++; + pos += get_increment; + if (pos >= LINEAR_DIV) { + pos -= LINEAR_DIV; + old_weight = (pos << (32 - LINEAR_DIV_SHIFT)) / (get_increment >> (LINEAR_DIV_SHIFT - 16)); + new_weight = 0x10000 - old_weight; + sample = (old_sample * old_weight + new_sample * new_weight) >> 16; + goto *put; +#define PUT16_END after_put +#include "plugin_ops.h" +#undef PUT16_END + after_put: + dst += dst_step; + dst_frames1++; + if (CHECK_SANITY(dst_frames1 > dst_frames)) { + SNDERR("dst_frames overflow"); + break; + } + } + old_sample = new_sample; + } + } +} + +/* optimized version for S16 format */ +static void linear_shrink_s16(struct rate_linear *rate, + 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 get_increment = rate->pitch; + unsigned int channel; + unsigned int src_frames1; + unsigned int dst_frames1; + unsigned int pos = 0; + + for (channel = 0; channel < rate->channels; ++channel) { + const snd_pcm_channel_area_t *src_area = &src_areas[channel]; + const snd_pcm_channel_area_t *dst_area = &dst_areas[channel]; + const int16_t *src; + int16_t *dst; + int src_step, dst_step; + int16_t old_sample = 0; + int16_t new_sample = 0; + int old_weight, new_weight; + pos = LINEAR_DIV - get_increment; /* Force first sample to be copied */ + src = snd_pcm_channel_area_addr(src_area, src_offset); + dst = snd_pcm_channel_area_addr(dst_area, dst_offset); + src_step = snd_pcm_channel_area_step(src_area) >> 1; + dst_step = snd_pcm_channel_area_step(dst_area) >> 1 ; + src_frames1 = 0; + dst_frames1 = 0; + while (src_frames1 < src_frames) { + + new_sample = *src; + src += src_step; + src_frames1++; + pos += get_increment; + if (pos >= LINEAR_DIV) { + pos -= LINEAR_DIV; + old_weight = (pos << (32 - LINEAR_DIV_SHIFT)) / (get_increment >> (LINEAR_DIV_SHIFT - 16)); + new_weight = 0x10000 - old_weight; + *dst = (old_sample * old_weight + new_sample * new_weight) >> 16; + dst += dst_step; + dst_frames1++; + if (CHECK_SANITY(dst_frames1 > dst_frames)) { + SNDERR("dst_frames overflow"); + break; + } + } + old_sample = new_sample; + } + } +} + +static void linear_convert(void *obj, + 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) +{ + struct rate_linear *rate = obj; + rate->func(rate, dst_areas, dst_offset, dst_frames, + src_areas, src_offset, src_frames); +} + +static void linear_free(void *obj) +{ + struct rate_linear *rate = obj; + + free(rate->old_sample); + rate->old_sample = NULL; +} + +static int linear_init(void *obj, snd_pcm_rate_info_t *info) +{ + struct rate_linear *rate = obj; + + rate->get_idx = snd_pcm_linear_get_index(info->in.format, SND_PCM_FORMAT_S16); + rate->put_idx = snd_pcm_linear_put_index(SND_PCM_FORMAT_S16, info->out.format); + if (info->in.rate < info->out.rate) { + if (info->in.format == info->out.format && info->in.format == SND_PCM_FORMAT_S16) + rate->func = linear_expand_s16; + else + rate->func = linear_expand; + /* pitch is get_threshold */ + } else { + if (info->in.format == info->out.format && info->in.format == SND_PCM_FORMAT_S16) + rate->func = linear_shrink_s16; + else + rate->func = linear_shrink; + /* pitch is get_increment */ + } + rate->pitch = (((u_int64_t)info->out.rate * LINEAR_DIV) + + (info->in.rate / 2)) / info->in.rate; + rate->channels = info->channels; + + free(rate->old_sample); + rate->old_sample = malloc(sizeof(*rate->old_sample) * rate->channels); + if (! rate->old_sample) + return -ENOMEM; + + return 0; +} + +static int linear_adjust_pitch(void *obj, snd_pcm_rate_info_t *info) +{ + struct rate_linear *rate = obj; + snd_pcm_uframes_t cframes; + + rate->pitch = (((u_int64_t)info->out.period_size * LINEAR_DIV) + + (info->in.period_size/2) ) / info->in.period_size; + + cframes = input_frames(rate, info->out.period_size); + while (cframes != info->in.period_size) { + snd_pcm_uframes_t cframes_new; + if (cframes > info->in.period_size) + rate->pitch++; + else + rate->pitch--; + cframes_new = input_frames(rate, info->out.period_size); + if ((cframes > info->in.period_size && cframes_new < info->in.period_size) || + (cframes < info->in.period_size && cframes_new > info->in.period_size)) { + SNDERR("invalid pcm period_size %ld -> %ld", + info->in.period_size, info->out.period_size); + return -EIO; + } + cframes = cframes_new; + } + if (rate->pitch >= LINEAR_DIV) { + /* shift for expand linear interpolation */ + rate->pitch_shift = 0; + while ((rate->pitch >> rate->pitch_shift) >= (1 << 16)) + rate->pitch_shift++; + } + return 0; +} + +static void linear_reset(void *obj) +{ + struct rate_linear *rate = obj; + + /* for expand */ + if (rate->old_sample) + memset(rate->old_sample, 0, sizeof(*rate->old_sample) * rate->channels); +} + +static void linear_close(void *obj) +{ + free(obj); +} + +static snd_pcm_rate_ops_t linear_ops = { + .close = linear_close, + .init = linear_init, + .free = linear_free, + .reset = linear_reset, + .adjust_pitch = linear_adjust_pitch, + .convert = linear_convert, + .input_frames = input_frames, + .output_frames = output_frames, +}; + +int SND_PCM_RATE_PLUGIN_ENTRY(linear) (unsigned int version, void **objp, snd_pcm_rate_ops_t *ops) +{ + struct rate_linear *rate; + + if (version != SND_PCM_RATE_PLUGIN_VERSION) { + SNDERR("Invalid plugin version %x\n", version); + return -EINVAL; + } + + rate = calloc(1, sizeof(*rate)); + if (! rate) + return -ENOMEM; + + *objp = rate; + *ops = linear_ops; + return 0; +} |