diff --git a/include/pcm_rate.h b/include/pcm_rate.h index 4171fb9d..48473ed4 100644 --- a/include/pcm_rate.h +++ b/include/pcm_rate.h @@ -38,7 +38,7 @@ extern "C" { /** * Protocol version */ -#define SND_PCM_RATE_PLUGIN_VERSION 0x010002 +#define SND_PCM_RATE_PLUGIN_VERSION 0x010003 /** hw_params information for a single side */ typedef struct snd_pcm_rate_side_info { @@ -55,6 +55,11 @@ typedef struct snd_pcm_rate_info { unsigned int channels; } snd_pcm_rate_info_t; +enum { + SND_PCM_RATE_FLAG_INTERLEAVED = (1U << 0), /** only interleaved format */ + SND_PCM_RATE_FLAG_SYNC_FORMATS = (1U << 1), /** both input and output formats have to be identical */ +}; + /** Callback table of rate-converter */ typedef struct snd_pcm_rate_ops { /** @@ -114,6 +119,13 @@ typedef struct snd_pcm_rate_ops { * new ops since version 0x010002 */ void (*dump)(void *obj, snd_output_t *out); + /** + * get the supported input and output formats (optional); + * new ops since version 0x010003 + */ + int (*get_supported_formats)(void *obj, uint64_t *in_formats, + uint64_t *out_formats, + unsigned int *flags); } snd_pcm_rate_ops_t; /** open function type */ @@ -147,6 +159,28 @@ typedef struct snd_pcm_rate_old_ops { snd_pcm_uframes_t (*input_frames)(void *obj, snd_pcm_uframes_t frames); snd_pcm_uframes_t (*output_frames)(void *obj, snd_pcm_uframes_t frames); } snd_pcm_rate_old_ops_t; + +/* old rate_ops for protocol version 0x010002 */ +typedef struct snd_pcm_rate_v2_ops { + void (*close)(void *obj); + int (*init)(void *obj, snd_pcm_rate_info_t *info); + void (*free)(void *obj); + void (*reset)(void *obj); + int (*adjust_pitch)(void *obj, snd_pcm_rate_info_t *info); + 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); + void (*convert_s16)(void *obj, int16_t *dst, unsigned int dst_frames, + const int16_t *src, unsigned int src_frames); + snd_pcm_uframes_t (*input_frames)(void *obj, snd_pcm_uframes_t frames); + snd_pcm_uframes_t (*output_frames)(void *obj, snd_pcm_uframes_t frames); + unsigned int version; + int (*get_supported_rates)(void *obj, unsigned int *rate_min, + unsigned int *rate_max); + void (*dump)(void *obj, snd_output_t *out); +} snd_pcm_rate_v2_ops_t; #endif #ifdef __cplusplus diff --git a/src/pcm/pcm_rate.c b/src/pcm/pcm_rate.c index 1e996134..13111d29 100644 --- a/src/pcm/pcm_rate.c +++ b/src/pcm/pcm_rate.c @@ -62,18 +62,22 @@ struct _snd_pcm_rate { void *open_func; void *obj; snd_pcm_rate_ops_t ops; - unsigned int get_idx; - unsigned int put_idx; - int16_t *src_buf; - int16_t *dst_buf; + unsigned int src_conv_idx; + unsigned int dst_conv_idx; + snd_pcm_channel_area_t *src_buf; + snd_pcm_channel_area_t *dst_buf; int start_pending; /* start is triggered but not commited to slave */ snd_htimestamp_t trigger_tstamp; unsigned int plugin_version; unsigned int rate_min, rate_max; + snd_pcm_format_t orig_in_format; + snd_pcm_format_t orig_out_format; + uint64_t in_formats; + uint64_t out_formats; + unsigned int format_flags; }; #define SND_PCM_RATE_PLUGIN_VERSION_OLD 0x010001 /* old rate plugin */ - #endif /* DOC_HIDDEN */ /* allocate a channel area and a temporary buffer for the given size */ @@ -274,12 +278,84 @@ static int snd_pcm_rate_hw_refine(snd_pcm_t *pcm, snd_pcm_generic_hw_refine); } +/* evaluate the best matching available format to the given format */ +static int get_best_format(uint64_t mask, snd_pcm_format_t orig) +{ + int pwidth = snd_pcm_format_physical_width(orig); + int width = snd_pcm_format_width(orig); + int signd = snd_pcm_format_signed(orig); + int best_score = -1; + int match = -1; + int f, score; + + for (f = 0; f <= SND_PCM_FORMAT_LAST; f++) { + if (!(mask & (1ULL << f))) + continue; + score = 0; + if (snd_pcm_format_linear(f)) { + if (snd_pcm_format_physical_width(f) == pwidth) + score++; + if (snd_pcm_format_physical_width(f) >= pwidth) + score++; + if (snd_pcm_format_width(f) == width) + score++; + if (snd_pcm_format_signed(f) == signd) + score++; + } + if (score > best_score) { + match = f; + best_score = score; + } + } + + return match; +} + +/* set up the input and output formats from the available lists */ +static int choose_preferred_format(snd_pcm_rate_t *rate) +{ + uint64_t in_mask = rate->in_formats; + uint64_t out_mask = rate->out_formats; + int in, out; + + if (!in_mask || !out_mask) + return 0; + + if (rate->orig_in_format == rate->orig_out_format) + if (in_mask & out_mask & (1ULL << rate->orig_in_format)) + return 0; /* nothing changed */ + + repeat: + in = get_best_format(in_mask, rate->orig_in_format); + out = get_best_format(out_mask, rate->orig_out_format); + if (in < 0 || out < 0) + return -ENOENT; + + if ((rate->format_flags & SND_PCM_RATE_FLAG_SYNC_FORMATS) && + in != out) { + if (out_mask & (1ULL << in)) + out = in; + else if (in_mask & (1ULL << out)) + in = out; + else { + in_mask &= ~(1ULL << in); + out_mask &= ~(1ULL << out); + goto repeat; + } + } + + rate->info.in.format = in; + rate->info.out.format = out; + return 0; +} + static int snd_pcm_rate_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params) { snd_pcm_rate_t *rate = pcm->private_data; snd_pcm_t *slave = rate->gen.slave; snd_pcm_rate_side_info_t *sinfo, *cinfo; - unsigned int channels, cwidth, swidth, chn; + unsigned int channels, cwidth, swidth, chn, acc; + int need_src_buf, need_dst_buf; int err = snd_pcm_hw_params_slave(pcm, params, snd_pcm_rate_hw_refine_cchange, snd_pcm_rate_hw_refine_sprepare, @@ -308,6 +384,9 @@ static int snd_pcm_rate_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params) if (err < 0) return err; err = INTERNAL(snd_pcm_hw_params_get_channels)(params, &channels); + if (err < 0) + return err; + err = INTERNAL(snd_pcm_hw_params_get_access)(params, &acc); if (err < 0) return err; @@ -321,36 +400,80 @@ static int snd_pcm_rate_hw_params(snd_pcm_t *pcm, snd_pcm_hw_params_t * params) SNDMSG("rate plugin already in use"); return -EBUSY; } - err = rate->ops.init(rate->obj, &rate->info); - if (err < 0) - return err; rate->pareas = rate_alloc_tmp_buf(rate, cinfo->format, channels, cinfo->period_size); rate->sareas = rate_alloc_tmp_buf(rate, sinfo->format, channels, sinfo->period_size); - if (!rate->pareas || !rate->sareas) - goto error; + if (!rate->pareas || !rate->sareas) { + err = -ENOMEM; + goto error_pareas; + } - 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) + rate->orig_in_format = rate->info.in.format; + rate->orig_out_format = rate->info.out.format; + if (choose_preferred_format(rate) < 0) { + SNDERR("No matching format in rate plugin"); + err = -EINVAL; + goto error_pareas; + } + + err = rate->ops.init(rate->obj, &rate->info); + if (err < 0) + goto error_init; + + rate_free_tmp_buf(&rate->src_buf); + rate_free_tmp_buf(&rate->dst_buf); + + need_src_buf = need_dst_buf = 0; + + if ((rate->format_flags & SND_PCM_RATE_FLAG_INTERLEAVED) && + !(acc == SND_PCM_ACCESS_MMAP_INTERLEAVED || + acc == SND_PCM_ACCESS_RW_INTERLEAVED)) { + need_src_buf = need_dst_buf = 1; + } else { + if (rate->orig_in_format != rate->info.in.format) + need_src_buf = 1; + if (rate->orig_out_format != rate->info.out.format) + need_dst_buf = 1; + } + + if (need_src_buf) { + rate->src_conv_idx = + snd_pcm_linear_convert_index(rate->orig_in_format, + rate->info.in.format); + rate->src_buf = rate_alloc_tmp_buf(rate, rate->info.in.format, + channels, rate->info.in.period_size); + if (!rate->src_buf) { + err = -ENOMEM; goto error; + } + } + + if (need_dst_buf) { + rate->dst_conv_idx = + snd_pcm_linear_convert_index(rate->info.out.format, + rate->orig_out_format); + rate->dst_buf = rate_alloc_tmp_buf(rate, rate->info.out.format, + channels, rate->info.out.period_size); + if (!rate->dst_buf) { + err = -ENOMEM; + goto error; + } } return 0; error: - rate_free_tmp_buf(&rate->pareas); - rate_free_tmp_buf(&rate->sareas); + rate_free_tmp_buf(&rate->src_buf); + rate_free_tmp_buf(&rate->dst_buf); + error_init: if (rate->ops.free) rate->ops.free(rate->obj); - return -ENOMEM; + error_pareas: + rate_free_tmp_buf(&rate->pareas); + rate_free_tmp_buf(&rate->sareas); + return err; } static int snd_pcm_rate_hw_free(snd_pcm_t *pcm) @@ -361,9 +484,8 @@ static int snd_pcm_rate_hw_free(snd_pcm_t *pcm) rate_free_tmp_buf(&rate->sareas); if (rate->ops.free) rate->ops.free(rate->obj); - free(rate->src_buf); - free(rate->dst_buf); - rate->src_buf = rate->dst_buf = NULL; + rate_free_tmp_buf(&rate->src_buf); + rate_free_tmp_buf(&rate->dst_buf); return snd_pcm_hw_free(rate->gen.slave); } @@ -444,82 +566,6 @@ static int snd_pcm_rate_init(snd_pcm_t *pcm) return 0; } -static void convert_to_s16(snd_pcm_rate_t *rate, int16_t *buf, - const snd_pcm_channel_area_t *areas, - snd_pcm_uframes_t offset, unsigned int frames, - unsigned int channels) -{ -#ifndef DOC_HIDDEN -#define GET16_LABELS -#include "plugin_ops.h" -#undef GET16_LABELS -#endif /* DOC_HIDDEN */ - void *get = get16_labels[rate->get_idx]; - const char *src; - int16_t sample; - const char *srcs[channels]; - int src_step[channels]; - unsigned int c; - - for (c = 0; c < channels; c++) { - srcs[c] = snd_pcm_channel_area_addr(areas + c, offset); - src_step[c] = snd_pcm_channel_area_step(areas + c); - } - - while (frames--) { - for (c = 0; c < channels; c++) { - src = srcs[c]; - goto *get; -#ifndef DOC_HIDDEN -#define GET16_END after_get -#include "plugin_ops.h" -#undef GET16_END -#endif /* DOC_HIDDEN */ - after_get: - *buf++ = sample; - srcs[c] += src_step[c]; - } - } -} - -static void convert_from_s16(snd_pcm_rate_t *rate, const int16_t *buf, - const snd_pcm_channel_area_t *areas, - snd_pcm_uframes_t offset, unsigned int frames, - unsigned int channels) -{ -#ifndef DOC_HIDDEN -#define PUT16_LABELS -#include "plugin_ops.h" -#undef PUT16_LABELS -#endif /* DOC_HIDDEN */ - void *put = put16_labels[rate->put_idx]; - char *dst; - int16_t sample; - char *dsts[channels]; - int dst_step[channels]; - unsigned int c; - - for (c = 0; c < channels; c++) { - dsts[c] = snd_pcm_channel_area_addr(areas + c, offset); - dst_step[c] = snd_pcm_channel_area_step(areas + c); - } - - while (frames--) { - for (c = 0; c < channels; c++) { - dst = dsts[c]; - sample = *buf++; - goto *put; -#ifndef DOC_HIDDEN -#define PUT16_END after_put -#include "plugin_ops.h" -#undef PUT16_END -#endif /* DOC_HIDDEN */ - after_put: - dsts[c] += dst_step[c]; - } - } -} - static void do_convert(const snd_pcm_channel_area_t *dst_areas, snd_pcm_uframes_t dst_offset, unsigned int dst_frames, const snd_pcm_channel_area_t *src_areas, @@ -527,28 +573,40 @@ static void do_convert(const snd_pcm_channel_area_t *dst_areas, unsigned int channels, snd_pcm_rate_t *rate) { - if (rate->ops.convert_s16) { - const int16_t *src; - int16_t *dst; - if (! rate->src_buf) - src = (int16_t *)src_areas->addr + src_offset * channels; - else { - convert_to_s16(rate, rate->src_buf, src_areas, src_offset, - src_frames, channels); - src = rate->src_buf; - } - if (! rate->dst_buf) - dst = (int16_t *)dst_areas->addr + dst_offset * channels; - else - dst = rate->dst_buf; - rate->ops.convert_s16(rate->obj, dst, dst_frames, src, src_frames); - if (dst == rate->dst_buf) - convert_from_s16(rate, rate->dst_buf, dst_areas, dst_offset, - dst_frames, channels); + const snd_pcm_channel_area_t *out_areas; + snd_pcm_uframes_t out_offset; + + if (rate->dst_buf) { + out_areas = rate->dst_buf; + out_offset = 0; } else { - rate->ops.convert(rate->obj, dst_areas, dst_offset, dst_frames, - src_areas, src_offset, src_frames); + out_areas = dst_areas; + out_offset = dst_offset; } + + if (rate->src_buf) { + snd_pcm_linear_convert(rate->src_buf, 0, + src_areas, src_offset, + channels, src_frames, + rate->src_conv_idx); + src_areas = rate->src_buf; + src_offset = 0; + } + + if (rate->ops.convert) + rate->ops.convert(rate->obj, out_areas, out_offset, dst_frames, + src_areas, src_offset, src_frames); + else + rate->ops.convert_s16(rate->obj, + snd_pcm_channel_area_addr(out_areas, out_offset), + dst_frames, + snd_pcm_channel_area_addr(src_areas, src_offset), + src_frames); + if (rate->dst_buf) + snd_pcm_linear_convert(dst_areas, dst_offset, + rate->dst_buf, 0, + channels, dst_frames, + rate->dst_conv_idx); } static inline void @@ -1276,6 +1334,30 @@ const snd_config_t *snd_pcm_rate_get_default_converter(snd_config_t *root) return NULL; } +static void rate_initial_setup(snd_pcm_rate_t *rate) +{ + if (rate->plugin_version == SND_PCM_RATE_PLUGIN_VERSION) + rate->plugin_version = rate->ops.version; + + if (rate->plugin_version >= 0x010002 && + rate->ops.get_supported_rates) + rate->ops.get_supported_rates(rate->obj, + &rate->rate_min, + &rate->rate_max); + + if (rate->plugin_version >= 0x010003 && + rate->ops.get_supported_formats) { + rate->ops.get_supported_formats(rate->obj, + &rate->in_formats, + &rate->out_formats, + &rate->format_flags); + } else if (!rate->ops.convert && rate->ops.convert_s16) { + rate->in_formats = rate->out_formats = + 1ULL << SND_PCM_FORMAT_S16; + rate->format_flags = SND_PCM_RATE_FLAG_INTERLEAVED; + } +} + #ifdef PIC static int is_builtin_plugin(const char *type) { @@ -1301,20 +1383,11 @@ static int rate_open_func(snd_pcm_rate_t *rate, const char *type, const snd_conf lib = lib_name; } - rate->rate_min = SND_PCM_PLUGIN_RATE_MIN; - rate->rate_max = SND_PCM_PLUGIN_RATE_MAX; - rate->plugin_version = SND_PCM_RATE_PLUGIN_VERSION; - open_conf_func = snd_dlobj_cache_get(lib, open_conf_name, NULL, verbose && converter_conf != NULL); if (open_conf_func) { err = open_conf_func(SND_PCM_RATE_PLUGIN_VERSION, &rate->obj, &rate->ops, converter_conf); if (!err) { - rate->plugin_version = rate->ops.version; - if (rate->ops.get_supported_rates) - rate->ops.get_supported_rates(rate->obj, - &rate->rate_min, - &rate->rate_max); rate->open_func = open_conf_func; return 0; } else { @@ -1330,23 +1403,18 @@ static int rate_open_func(snd_pcm_rate_t *rate, const char *type, const snd_conf rate->open_func = open_func; err = open_func(SND_PCM_RATE_PLUGIN_VERSION, &rate->obj, &rate->ops); - if (!err) { - rate->plugin_version = rate->ops.version; - if (rate->ops.get_supported_rates) - rate->ops.get_supported_rates(rate->obj, - &rate->rate_min, - &rate->rate_max); + if (!err) return 0; - } /* try to open with the old protocol version */ rate->plugin_version = SND_PCM_RATE_PLUGIN_VERSION_OLD; err = open_func(SND_PCM_RATE_PLUGIN_VERSION_OLD, &rate->obj, &rate->ops); - if (err) { - snd_dlobj_cache_put(open_func); - rate->open_func = NULL; - } + if (!err) + return 0; + + snd_dlobj_cache_put(open_func); + rate->open_func = NULL; return err; } #endif @@ -1417,6 +1485,10 @@ int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name, rate->srate = srate; rate->sformat = sformat; + rate->rate_min = SND_PCM_PLUGIN_RATE_MIN; + rate->rate_max = SND_PCM_PLUGIN_RATE_MAX; + rate->plugin_version = SND_PCM_RATE_PLUGIN_VERSION; + err = snd_pcm_new(&pcm, SND_PCM_TYPE_RATE, name, slave->stream, slave->mode); if (err < 0) { free(rate); @@ -1496,6 +1568,8 @@ int snd_pcm_rate_open(snd_pcm_t **pcmp, const char *name, return err; } + rate_initial_setup(rate); + pcm->ops = &snd_pcm_rate_ops; pcm->fast_ops = &snd_pcm_rate_fast_ops; pcm->private_data = rate;