OSDN Git Service

Added new PCM functions (pcm_misc.c).
authorJaroslav Kysela <perex@perex.cz>
Wed, 24 Nov 1999 17:00:16 +0000 (17:00 +0000)
committerJaroslav Kysela <perex@perex.cz>
Wed, 24 Nov 1999 17:00:16 +0000 (17:00 +0000)
New plugins: rate, voices.

src/pcm/Makefile.am
src/pcm/pcm_misc.c [new file with mode: 0644]
src/pcm/pcm_plugin.c
src/pcm/plugin/Makefile.am
src/pcm/plugin/linear.c
src/pcm/plugin/rate.c [new file with mode: 0644]
src/pcm/plugin/voices.c [new file with mode: 0644]

index 6feb8ba..0e48026 100644 (file)
@@ -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 (file)
index 0000000..e0f3ae0
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+ *  PCM Interface - misc routines
+ *  Copyright (c) 1998 by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#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;
+       }
+}
index 5639f71..a460e5a 100644 (file)
@@ -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;
index f1d26b1..2c651b8 100644 (file)
@@ -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
 
 
index 0852fb8..1a0958f 100644 (file)
@@ -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 (file)
index 0000000..5e8a43d
--- /dev/null
@@ -0,0 +1,144 @@
+/*
+ *  Rate conversion Plug-In
+ *  Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <endian.h>
+#include <byteswap.h>
+#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 (file)
index 0000000..b5f3aed
--- /dev/null
@@ -0,0 +1,207 @@
+/*
+ *  Voices conversion Plug-In
+ *  Copyright (c) 1999 by Jaroslav Kysela <perex@suse.cz>
+ *
+ *
+ *   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 <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <endian.h>
+#include <byteswap.h>
+#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;
+}