From 4a01380a7bed61552c3cf3300ad227122c3849cf Mon Sep 17 00:00:00 2001 From: Jaroslav Kysela Date: Wed, 24 Nov 1999 17:00:16 +0000 Subject: [PATCH] Added new PCM functions (pcm_misc.c). New plugins: rate, voices. --- src/pcm/Makefile.am | 2 +- src/pcm/pcm_misc.c | 185 ++++++++++++++++++++++++++++++++++++++++ src/pcm/pcm_plugin.c | 142 +++++++++++++++++++++++++------ src/pcm/plugin/Makefile.am | 2 +- src/pcm/plugin/linear.c | 6 +- src/pcm/plugin/rate.c | 144 +++++++++++++++++++++++++++++++ src/pcm/plugin/voices.c | 207 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 655 insertions(+), 33 deletions(-) create mode 100644 src/pcm/pcm_misc.c create mode 100644 src/pcm/plugin/rate.c create mode 100644 src/pcm/plugin/voices.c diff --git a/src/pcm/Makefile.am b/src/pcm/Makefile.am index 6feb8baa..0e480262 100644 --- a/src/pcm/Makefile.am +++ b/src/pcm/Makefile.am @@ -2,7 +2,7 @@ SUBDIRS = plugin EXTRA_LTLIBRARIES = libpcm.la -libpcm_la_SOURCES = pcm.c pcm_plugin.c pcm_loopback.c +libpcm_la_SOURCES = pcm.c pcm_plugin.c pcm_misc.c pcm_loopback.c libpcm_la_LIBADD = plugin/libpcmplugin.la all: libpcm.la diff --git a/src/pcm/pcm_misc.c b/src/pcm/pcm_misc.c new file mode 100644 index 00000000..e0f3ae07 --- /dev/null +++ b/src/pcm/pcm_misc.c @@ -0,0 +1,185 @@ +/* + * PCM Interface - misc routines + * Copyright (c) 1998 by Jaroslav Kysela + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "pcm_local.h" + +int snd_pcm_format_signed(int format) +{ + switch (format) { + case SND_PCM_SFMT_S8: + case SND_PCM_SFMT_S16_LE: + case SND_PCM_SFMT_S16_BE: + case SND_PCM_SFMT_S24_LE: + case SND_PCM_SFMT_S24_BE: + case SND_PCM_SFMT_S32_LE: + case SND_PCM_SFMT_S32_BE: + return 1; + case SND_PCM_SFMT_U8: + case SND_PCM_SFMT_U16_LE: + case SND_PCM_SFMT_U16_BE: + case SND_PCM_SFMT_U24_LE: + case SND_PCM_SFMT_U24_BE: + case SND_PCM_SFMT_U32_LE: + case SND_PCM_SFMT_U32_BE: + return 0; + default: + return -EINVAL; + } +} + +int snd_pcm_format_unsigned(int format) +{ + int val; + + val = snd_pcm_format_signed(format); + if (val >= 0) + val ^= 1; + return val; +} + +int snd_pcm_format_little_endian(int format) +{ + switch (format) { + case SND_PCM_SFMT_S16_LE: + case SND_PCM_SFMT_U16_LE: + case SND_PCM_SFMT_S24_LE: + case SND_PCM_SFMT_U24_LE: + case SND_PCM_SFMT_S32_LE: + case SND_PCM_SFMT_U32_LE: + case SND_PCM_SFMT_FLOAT_LE: + case SND_PCM_SFMT_FLOAT64_LE: + case SND_PCM_SFMT_IEC958_SUBFRAME_LE: + return 1; + case SND_PCM_SFMT_S16_BE: + case SND_PCM_SFMT_U16_BE: + case SND_PCM_SFMT_S24_BE: + case SND_PCM_SFMT_U24_BE: + case SND_PCM_SFMT_S32_BE: + case SND_PCM_SFMT_U32_BE: + case SND_PCM_SFMT_FLOAT_BE: + case SND_PCM_SFMT_FLOAT64_BE: + case SND_PCM_SFMT_IEC958_SUBFRAME_BE: + return 0; + default: + return -EINVAL; + } +} + +int snd_pcm_format_big_endian(int format) +{ + int val; + + val = snd_pcm_format_little_endian(format); + if (val >= 0) + val ^= 1; + return val; +} + +int snd_pcm_format_width(int format) +{ + switch (format) { + case SND_PCM_SFMT_S8: + case SND_PCM_SFMT_U8: + return 8; + case SND_PCM_SFMT_S16_LE: + case SND_PCM_SFMT_S16_BE: + case SND_PCM_SFMT_U16_LE: + case SND_PCM_SFMT_U16_BE: + return 16; + case SND_PCM_SFMT_S24_LE: + case SND_PCM_SFMT_S24_BE: + case SND_PCM_SFMT_U24_LE: + case SND_PCM_SFMT_U24_BE: + return 24; + case SND_PCM_SFMT_S32_LE: + case SND_PCM_SFMT_S32_BE: + case SND_PCM_SFMT_U32_LE: + case SND_PCM_SFMT_U32_BE: + case SND_PCM_SFMT_FLOAT_LE: + case SND_PCM_SFMT_FLOAT_BE: + return 32; + case SND_PCM_SFMT_FLOAT64_LE: + case SND_PCM_SFMT_FLOAT64_BE: + return 64; + case SND_PCM_SFMT_IEC958_SUBFRAME_LE: + case SND_PCM_SFMT_IEC958_SUBFRAME_BE: + return 24; + case SND_PCM_SFMT_MU_LAW: + case SND_PCM_SFMT_A_LAW: + return 8; + case SND_PCM_SFMT_IMA_ADPCM: + return 4; + default: + return -EINVAL; + } +} + +ssize_t snd_pcm_format_size(int format, size_t samples) +{ + if (samples < 0) + return -EINVAL; + if (samples == 0) + return samples; + switch (format) { + case SND_PCM_SFMT_S8: + case SND_PCM_SFMT_U8: + return samples; + case SND_PCM_SFMT_S16_LE: + case SND_PCM_SFMT_S16_BE: + case SND_PCM_SFMT_U16_LE: + case SND_PCM_SFMT_U16_BE: + return samples * 2; + case SND_PCM_SFMT_S24_LE: + case SND_PCM_SFMT_S24_BE: + case SND_PCM_SFMT_U24_LE: + case SND_PCM_SFMT_U24_BE: + case SND_PCM_SFMT_S32_LE: + case SND_PCM_SFMT_S32_BE: + case SND_PCM_SFMT_U32_LE: + case SND_PCM_SFMT_U32_BE: + case SND_PCM_SFMT_FLOAT_LE: + case SND_PCM_SFMT_FLOAT_BE: + return samples * 4; + case SND_PCM_SFMT_FLOAT64_LE: + case SND_PCM_SFMT_FLOAT64_BE: + return samples * 8; + case SND_PCM_SFMT_IEC958_SUBFRAME_LE: + case SND_PCM_SFMT_IEC958_SUBFRAME_BE: + return samples * 4; + case SND_PCM_SFMT_MU_LAW: + case SND_PCM_SFMT_A_LAW: + return samples; + case SND_PCM_SFMT_IMA_ADPCM: + if (samples & 1) + return -EINVAL; + return samples / 2; + default: + return -EINVAL; + } +} diff --git a/src/pcm/pcm_plugin.c b/src/pcm/pcm_plugin.c index 5639f710..a460e5ab 100644 --- a/src/pcm/pcm_plugin.c +++ b/src/pcm/pcm_plugin.c @@ -97,6 +97,22 @@ int snd_pcm_plugin_insert(snd_pcm_t *pcm, int channel, snd_pcm_plugin_t *plugin) return 0; } +int snd_pcm_plugin_append(snd_pcm_t *pcm, int channel, snd_pcm_plugin_t *plugin) +{ + if (!pcm || channel < 0 || channel > 1 || !plugin) + return -EINVAL; + plugin->next = NULL; + plugin->prev = pcm->plugin_last[channel]; + if (pcm->plugin_last[channel]) { + pcm->plugin_last[channel]->next = plugin; + pcm->plugin_last[channel] = plugin; + } else { + pcm->plugin_last[channel] = + pcm->plugin_first[channel] = plugin; + } + return 0; +} + int snd_pcm_plugin_remove_to(snd_pcm_t *pcm, int channel, snd_pcm_plugin_t *plugin) { snd_pcm_plugin_t *plugin1, *plugin1_prev; @@ -247,6 +263,9 @@ int snd_pcm_plugin_info(snd_pcm_t *pcm, snd_pcm_channel_info_t *info) if ((err = snd_pcm_channel_info(pcm, info)) < 0) return err; info->formats = snd_pcm_plugin_formats(pcm, info->formats); + info->min_rate = 4000; + info->max_rate = 192000; + info->rates = SND_PCM_RATE_8000_48000; return 0; } @@ -372,25 +391,90 @@ int snd_pcm_plugin_params(snd_pcm_t *pcm, snd_pcm_channel_params_t *params) */ snd_pcm_plugin_clear(pcm, params->channel); - if (sinfo.mode == SND_PCM_MODE_STREAM) { - err = snd_pcm_plugin_build_stream(pcm, params->channel, &plugin); - } else if (sinfo.mode == SND_PCM_MODE_BLOCK) { - if (sinfo.flags & SND_PCM_CHNINFO_MMAP) { - err = snd_pcm_plugin_build_mmap(pcm, params->channel, &plugin); - } else { - err = snd_pcm_plugin_build_block(pcm, params->channel, &plugin); + + if (sparams.format.voices < sinfo.min_voices || + sparams.format.voices > sinfo.max_voices) { + int src_voices = sparams.format.voices; + int dst_voices = sparams.format.voices < sinfo.min_voices ? + sinfo.min_voices : sinfo.max_voices; + if (sparams.format.rate < sinfo.min_rate || + sparams.format.rate > sinfo.max_rate) + dst_voices = 2; + sparams.format.voices = dst_voices; + swap_formats(params->channel, &src_voices, &dst_voices); + err = snd_pcm_plugin_build_voices(params->format.format, src_voices, + params->format.format, dst_voices, + &plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + err = snd_pcm_plugin_append(pcm, params->channel, plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; } - } else { - return -EINVAL; + } + + /* + * formats + */ + + if (params->format.format == sparams.format.format) + goto __format_skip; + /* build additional plugins for conversion */ + switch (params->format.format) { + case SND_PCM_SFMT_MU_LAW: + srcfmt = SND_PCM_SFMT_MU_LAW; + dstfmt = sparams.format.format; + swap_formats(params->channel, &srcfmt, &dstfmt); + err = snd_pcm_plugin_build_mulaw(srcfmt, dstfmt, &plugin); + break; + default: + srcfmt = params->format.format; + dstfmt = sparams.format.format; + swap_formats(params->channel, &srcfmt, &dstfmt); + err = snd_pcm_plugin_build_linear(srcfmt, dstfmt, &plugin); } if (err < 0) return err; - err = snd_pcm_plugin_insert(pcm, params->channel, plugin); + err = snd_pcm_plugin_append(pcm, params->channel, plugin); if (err < 0) { snd_pcm_plugin_free(plugin); return err; } + __format_skip: + + /* + * rate + */ + + if (sparams.format.rate < sinfo.min_rate || + sparams.format.rate > sinfo.max_rate) { + int src_rate = sparams.format.rate; + int dst_rate = sparams.format.rate < sinfo.min_rate ? + sinfo.min_rate : sinfo.max_rate; + sparams.format.rate = dst_rate; + swap_formats(params->channel, &src_rate, &dst_rate); + err = snd_pcm_plugin_build_rate(sparams.format.format, src_rate, sparams.format.voices, + sparams.format.format, dst_rate, sparams.format.voices, + &plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + err = snd_pcm_plugin_append(pcm, params->channel, plugin); + if (err < 0) { + snd_pcm_plugin_free(plugin); + return err; + } + } + + /* + * interleave + */ + if (params->format.voices > 1 && sinfo.mode == SND_PCM_MODE_BLOCK) { int src_interleave, dst_interleave; @@ -409,7 +493,7 @@ int snd_pcm_plugin_params(snd_pcm_t *pcm, snd_pcm_channel_params_t *params) err = snd_pcm_plugin_build_interleave(src_interleave, dst_interleave, sparams.format.format, &plugin); if (err < 0) return err; - err = snd_pcm_plugin_insert(pcm, params->channel, plugin); + err = snd_pcm_plugin_append(pcm, params->channel, plugin); if (err < 0) { snd_pcm_plugin_free(plugin); return err; @@ -417,31 +501,33 @@ int snd_pcm_plugin_params(snd_pcm_t *pcm, snd_pcm_channel_params_t *params) } } - if (params->format.format == sparams.format.format) - goto __skip; - /* build additional plugins for conversion */ - switch (params->format.format) { - case SND_PCM_SFMT_MU_LAW: - srcfmt = SND_PCM_SFMT_MU_LAW; - dstfmt = sparams.format.format; - swap_formats(params->channel, &srcfmt, &dstfmt); - err = snd_pcm_plugin_build_mulaw(srcfmt, dstfmt, &plugin); - break; - default: - srcfmt = params->format.format; - dstfmt = sparams.format.format; - swap_formats(params->channel, &srcfmt, &dstfmt); - err = snd_pcm_plugin_build_linear(srcfmt, dstfmt, &plugin); + /* + * I/O plugins + */ + + if (sinfo.mode == SND_PCM_MODE_STREAM) { + err = snd_pcm_plugin_build_stream(pcm, params->channel, &plugin); + } else if (sinfo.mode == SND_PCM_MODE_BLOCK) { + if (sinfo.flags & SND_PCM_CHNINFO_MMAP) { + err = snd_pcm_plugin_build_mmap(pcm, params->channel, &plugin); + } else { + err = snd_pcm_plugin_build_block(pcm, params->channel, &plugin); + } + } else { + return -EINVAL; } if (err < 0) return err; - err = snd_pcm_plugin_insert(pcm, params->channel, plugin); + err = snd_pcm_plugin_append(pcm, params->channel, plugin); if (err < 0) { snd_pcm_plugin_free(plugin); return err; } - __skip: + /* + * ratio + */ + ratio = snd_pcm_plugin_hardware_ratio(pcm, params->channel); if (ratio <= 0) return -EINVAL; diff --git a/src/pcm/plugin/Makefile.am b/src/pcm/plugin/Makefile.am index f1d26b15..2c651b8a 100644 --- a/src/pcm/plugin/Makefile.am +++ b/src/pcm/plugin/Makefile.am @@ -1,7 +1,7 @@ EXTRA_LTLIBRARIES = libpcmplugin.la libpcmplugin_la_SOURCES = block.c mmap.c stream.c linear.c interleave.c \ - mulaw.c + mulaw.c rate.c voices.c all: libpcmplugin.la diff --git a/src/pcm/plugin/linear.c b/src/pcm/plugin/linear.c index 0852fb8f..1a0958f6 100644 --- a/src/pcm/plugin/linear.c +++ b/src/pcm/plugin/linear.c @@ -330,10 +330,10 @@ int snd_pcm_plugin_build_linear(int src_format, int dst_format, snd_pcm_plugin_t return -EINVAL; #if __BYTE_ORDER == __LITTLE_ENDIAN endian1 = endian1 == __BIG_ENDIAN ? 1 : 0; - endian1 = endian2 == __BIG_ENDIAN ? 1 : 0; + endian2 = endian2 == __BIG_ENDIAN ? 1 : 0; #elif __BYTE_ORDER == __BIG_ENDIAN endian1 = endian1 == __LITTLE_ENDIAN ? 1 : 0; - endian1 = endian2 == __LITTLE_ENDIAN ? 1 : 0; + endian2 = endian2 == __LITTLE_ENDIAN ? 1 : 0; #else #error "Unsupported endian..." #endif @@ -380,7 +380,7 @@ int snd_pcm_plugin_build_linear(int src_format, int dst_format, snd_pcm_plugin_t return -ENOMEM; data = (struct linear_private_data *)snd_pcm_plugin_extra_data(plugin); data->cmd = cmd; - if (!endian1 && !endian2) { + if (endian1 == endian2) { data->endian = NONE; } else if (endian1 && !endian2) { data->endian = SOURCE; diff --git a/src/pcm/plugin/rate.c b/src/pcm/plugin/rate.c new file mode 100644 index 00000000..5e8a43d2 --- /dev/null +++ b/src/pcm/plugin/rate.c @@ -0,0 +1,144 @@ +/* + * Rate conversion Plug-In + * Copyright (c) 1999 by Jaroslav Kysela + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../pcm_local.h" + +/* + * Basic rate conversion plugin + */ + +struct rate_private_data { + unsigned int src_rate; + unsigned int dst_rate; + unsigned int pitch; + unsigned int pos; + signed short last_L_S1, last_R_S1; +}; + +static void mix(struct rate_private_data *data, + signed short *src_ptr, int src_size, + signed short *dst_ptr, int dst_size) +{ + unsigned int pos; + signed int val; + signed short L_S1, R_S1, L_S2, R_S2; + + pos = data->pos; + L_S1 = L_S2 = data->last_L_S1; + R_S1 = R_S2 = data->last_R_S1; + while (dst_size-- > 0) { + pos += data->pitch; + src_ptr += (pos >> 10) * 2; pos &= 0x3ff; + L_S2 = *src_ptr; + val = L_S1 + ((L_S2 + L_S1) * (signed int)pos) / 1024; + if (val < -32768) + val = -32768; + else if (val > 32767) + val = 32767; + *dst_ptr++ = val; + R_S2 = *(src_ptr + 1); + val = R_S1 + ((R_S2 + R_S1) * (signed int)pos) / 1024; + if (val < -32768) + val = -32768; + else if (val > 32767) + val = 32767; + *dst_ptr++ = val; + } + data->last_L_S1 = L_S2; + data->last_R_S1 = R_S2; + data->pos = pos & 0x3ff; +} + +static ssize_t rate_transfer(snd_pcm_plugin_t *plugin, + char *src_ptr, size_t src_size, + char *dst_ptr, size_t dst_size) +{ + struct rate_private_data *data; + + if (plugin == NULL || src_ptr == NULL || src_size < 0 || + dst_ptr == NULL || dst_size < 0) + return -EINVAL; + if (src_size == 0) + return 0; + data = (struct rate_private_data *)snd_pcm_plugin_extra_data(plugin); + if (data == NULL) + return -EINVAL; + mix(data, (signed short *)src_ptr, src_size / 4, + (signed short *)dst_ptr, dst_size / 4); + return (dst_size / 4) * 4; +} + +static ssize_t rate_src_size(snd_pcm_plugin_t *plugin, size_t size) +{ + struct rate_private_data *data; + + if (!plugin || size <= 0) + return -EINVAL; + data = (struct rate_private_data *)snd_pcm_plugin_extra_data(plugin); + return (((size * data->pitch) + 0x1ff) >> 10) & ~3; +} + +static ssize_t rate_dst_size(snd_pcm_plugin_t *plugin, size_t size) +{ + struct rate_private_data *data; + + if (!plugin || size <= 0) + return -EINVAL; + data = (struct rate_private_data *)snd_pcm_plugin_extra_data(plugin); + return (((size << 10) + (data->pitch / 2)) / data->pitch) & ~3; +} + +int snd_pcm_plugin_build_rate(int src_format, int src_rate, int src_voices, + int dst_format, int dst_rate, int dst_voices, + snd_pcm_plugin_t **r_plugin) +{ + struct rate_private_data *data; + snd_pcm_plugin_t *plugin; + + if (!r_plugin) + return -EINVAL; + *r_plugin = NULL; + if (src_voices != 2 || dst_voices != 2) + return -EINVAL; + if (src_format != SND_PCM_SFMT_S16_LE || + dst_format != SND_PCM_SFMT_S16_LE) + return -EINVAL; + if (src_rate == dst_rate) + return -EINVAL; + plugin = snd_pcm_plugin_build("rate format conversion", + sizeof(struct rate_private_data)); + if (plugin == NULL) + return -ENOMEM; + data = (struct rate_private_data *)snd_pcm_plugin_extra_data(plugin); + data->pitch = ((src_rate << 10) + (dst_rate >> 1)) / dst_rate; + plugin->transfer = rate_transfer; + plugin->src_size = rate_src_size; + plugin->dst_size = rate_dst_size; + *r_plugin = plugin; + return 0; +} diff --git a/src/pcm/plugin/voices.c b/src/pcm/plugin/voices.c new file mode 100644 index 00000000..b5f3aedd --- /dev/null +++ b/src/pcm/plugin/voices.c @@ -0,0 +1,207 @@ +/* + * Voices conversion Plug-In + * Copyright (c) 1999 by Jaroslav Kysela + * + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Library General Public License as + * published by the Free Software Foundation; either version 2 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 Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include "../pcm_local.h" + +/* + * Basic voices conversion plugin + */ + +struct voices_private_data { + int src_voices; + int dst_voices; + int width; /* in bites */ + int flg_merge: 1, + flg_signed: 1; +}; + +static void divide_8bit(char *src_ptr, char *dst_ptr, int size) +{ + while (size-- > 0) { + *dst_ptr++ = *src_ptr; + *dst_ptr++ = *src_ptr++; + } +} + +static void divide_16bit(short *src_ptr, short *dst_ptr, int size) +{ + while (size-- > 0) { + *dst_ptr++ = *src_ptr; + *dst_ptr++ = *src_ptr++; + } +} + +static void merge_8bit_unsigned(unsigned char *src_ptr, + unsigned char *dst_ptr, + int size) +{ + while (size-- > 0) { + *dst_ptr++ = (*src_ptr + *(src_ptr + 1)) / 2; + src_ptr += 2; + } +} + +static void merge_8bit_signed(signed char *src_ptr, + signed char *dst_ptr, + int size) +{ + while (size-- > 0) { + *dst_ptr++ = (*src_ptr + *(src_ptr + 1)) / 2; + src_ptr += 2; + } +} + +static void merge_16bit_unsigned(unsigned short *src_ptr, + unsigned short *dst_ptr, + int size) +{ + while (size-- > 0) { + *dst_ptr++ = (*src_ptr + *(src_ptr + 1)) / 2; + src_ptr += 2; + } +} + +static void merge_16bit_signed(signed short *src_ptr, + signed short *dst_ptr, + int size) +{ + while (size-- > 0) { + *dst_ptr++ = (*src_ptr + *(src_ptr + 1)) / 2; + src_ptr += 2; + } +} + +static ssize_t voices_transfer(snd_pcm_plugin_t *plugin, + char *src_ptr, size_t src_size, + char *dst_ptr, size_t dst_size) +{ + struct voices_private_data *data; + + if (plugin == NULL || src_ptr == NULL || src_size < 0 || + dst_ptr == NULL || dst_size < 0) + return -EINVAL; + if (src_size == 0) + return 0; + data = (struct voices_private_data *)snd_pcm_plugin_extra_data(plugin); + if (data == NULL) + return -EINVAL; + switch (data->width) { + case 8: + if (data->src_voices > data->dst_voices) { + if (data->flg_signed) { + merge_8bit_signed(src_ptr, dst_ptr, src_size / 2); + } else { + merge_8bit_unsigned(src_ptr, dst_ptr, src_size / 2); + } + return (src_size * data->src_voices) / data->dst_voices; + } else { + divide_8bit(src_ptr, dst_ptr, src_size); + return (src_size * data->dst_voices) / data->src_voices; + } + break; + case 16: + if (data->src_voices > data->dst_voices) { + if (data->flg_signed) { + merge_16bit_signed((short *)src_ptr, (short *)dst_ptr, src_size / 4); + } else { + merge_16bit_unsigned((short *)src_ptr, (short *)dst_ptr, src_size / 4); + } + return (src_size * data->src_voices) / data->dst_voices; + } else { + divide_16bit((short *)src_ptr, (short *)dst_ptr, src_size / 2); + return (src_size * data->dst_voices) / data->src_voices; + } + break; + default: + return -EINVAL; + } +} + +static ssize_t voices_src_size(snd_pcm_plugin_t *plugin, size_t size) +{ + struct voices_private_data *data; + + if (!plugin || size <= 0) + return -EINVAL; + data = (struct voices_private_data *)snd_pcm_plugin_extra_data(plugin); + if (data->src_voices < data->dst_voices) + return (size * data->src_voices) / data->dst_voices; + else + return (size * data->dst_voices) / data->src_voices; +} + +static ssize_t voices_dst_size(snd_pcm_plugin_t *plugin, size_t size) +{ + struct voices_private_data *data; + + if (!plugin || size <= 0) + return -EINVAL; + data = (struct voices_private_data *)snd_pcm_plugin_extra_data(plugin); + if (data->src_voices > data->dst_voices) + return (size * data->src_voices) / data->dst_voices; + else + return (size * data->dst_voices) / data->src_voices; +} + +int snd_pcm_plugin_build_voices(int src_format, int src_voices, + int dst_format, int dst_voices, + snd_pcm_plugin_t **r_plugin) +{ + struct voices_private_data *data; + snd_pcm_plugin_t *plugin; + + if (!r_plugin) + return -EINVAL; + *r_plugin = NULL; + if (src_voices == dst_voices) + return -EINVAL; + if (src_voices < 1 || src_voices > 2 || + dst_voices < 1 || dst_voices > 2) + return -EINVAL; + if (src_format != dst_format) + return -EINVAL; + if (src_format < SND_PCM_SFMT_S8 || src_format > SND_PCM_SFMT_U16_BE) { + if (src_format != SND_PCM_SFMT_MU_LAW && src_format != SND_PCM_SFMT_A_LAW) + return -EINVAL; + } + plugin = snd_pcm_plugin_build("voices conversion", + sizeof(struct voices_private_data)); + if (plugin == NULL) + return -ENOMEM; + data = (struct voices_private_data *)snd_pcm_plugin_extra_data(plugin); + data->src_voices = src_voices; + data->dst_voices = dst_voices; + data->width = snd_pcm_format_width(src_format); + data->flg_merge = src_voices > dst_voices; + data->flg_signed = snd_pcm_format_signed(src_format); + plugin->transfer = voices_transfer; + plugin->src_size = voices_src_size; + plugin->dst_size = voices_dst_size; + *r_plugin = plugin; + return 0; +} -- 2.11.0