OSDN Git Service

Introduce bswap.h for portable definitions of byte swap macros.
[android-x86/external-alsa-lib.git] / src / pcm / pcm_misc.c
index d2a51dc..7d2b05d 100644 (file)
@@ -1,47 +1,40 @@
 /*
  *  PCM Interface - misc routines
- *  Copyright (c) 1998 by Jaroslav Kysela <perex@suse.cz>
+ *  Copyright (c) 1998 by Jaroslav Kysela <perex@perex.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
+ *   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 Library General Public License for more details.
+ *   GNU Lesser General Public License for more details.
  *
- *   You should have received a copy of the GNU Library General Public
+ *   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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
  *
  */
   
-#ifdef __KERNEL__
-#include "../include/driver.h"
-#include "../include/pcm.h"
-#include "../include/pcm_plugin.h"
-#define bswap_16 swab16
-#define bswap_32 swab32
-#define bswap_64 swab64
-#define SND_PCM_FORMAT_UNKNOWN (-1)
-#define snd_enum_to_int(v) (v)
-#define snd_int_to_enum(v) (v)
-#else
 #include <stdio.h>
 #include <stdlib.h>
 #include <unistd.h>
 #include <string.h>
-#include <errno.h>
-#include <byteswap.h>
+#include "bswap.h"
 #include "pcm_local.h"
-#endif
 
+
+/**
+ * \brief Return sign info for a PCM sample linear format
+ * \param format Format
+ * \return 0 unsigned, 1 signed, a negative error code if format is not linear
+ */
 int snd_pcm_format_signed(snd_pcm_format_t format)
 {
-       switch (snd_enum_to_int(format)) {
+       switch (format) {
        case SNDRV_PCM_FORMAT_S8:
        case SNDRV_PCM_FORMAT_S16_LE:
        case SNDRV_PCM_FORMAT_S16_BE:
@@ -49,6 +42,12 @@ int snd_pcm_format_signed(snd_pcm_format_t format)
        case SNDRV_PCM_FORMAT_S24_BE:
        case SNDRV_PCM_FORMAT_S32_LE:
        case SNDRV_PCM_FORMAT_S32_BE:
+       case SNDRV_PCM_FORMAT_S24_3LE:
+       case SNDRV_PCM_FORMAT_S24_3BE:
+       case SNDRV_PCM_FORMAT_S20_3LE:
+       case SNDRV_PCM_FORMAT_S20_3BE:
+       case SNDRV_PCM_FORMAT_S18_3LE:
+       case SNDRV_PCM_FORMAT_S18_3BE:
                return 1;
        case SNDRV_PCM_FORMAT_U8:
        case SNDRV_PCM_FORMAT_U16_LE:
@@ -57,12 +56,28 @@ int snd_pcm_format_signed(snd_pcm_format_t format)
        case SNDRV_PCM_FORMAT_U24_BE:
        case SNDRV_PCM_FORMAT_U32_LE:
        case SNDRV_PCM_FORMAT_U32_BE:
+       case SNDRV_PCM_FORMAT_U24_3LE:
+       case SNDRV_PCM_FORMAT_U24_3BE:
+       case SNDRV_PCM_FORMAT_U20_3LE:
+       case SNDRV_PCM_FORMAT_U20_3BE:
+       case SNDRV_PCM_FORMAT_U18_3LE:
+       case SNDRV_PCM_FORMAT_U18_3BE:
+       case SNDRV_PCM_FORMAT_DSD_U8:
+       case SNDRV_PCM_FORMAT_DSD_U16_LE:
+       case SNDRV_PCM_FORMAT_DSD_U32_LE:
+       case SNDRV_PCM_FORMAT_DSD_U16_BE:
+       case SNDRV_PCM_FORMAT_DSD_U32_BE:
                return 0;
        default:
                return -EINVAL;
        }
 }
 
+/**
+ * \brief Return sign info for a PCM sample linear format
+ * \param format Format
+ * \return 0 signed, 1 unsigned, a negative error code if format is not linear
+ */
 int snd_pcm_format_unsigned(snd_pcm_format_t format)
 {
        int val;
@@ -73,14 +88,42 @@ int snd_pcm_format_unsigned(snd_pcm_format_t format)
        return !val;
 }
 
+/**
+ * \brief Return linear info for a PCM sample format
+ * \param format Format
+ * \return 0 non linear, 1 linear
+ */
 int snd_pcm_format_linear(snd_pcm_format_t format)
 {
        return snd_pcm_format_signed(format) >= 0;
 }
 
+/**
+ * \brief Return float info for a PCM sample format
+ * \param format Format
+ * \return 0 non float, 1 float
+ */
+int snd_pcm_format_float(snd_pcm_format_t format)
+{
+       switch (format) {
+       case SNDRV_PCM_FORMAT_FLOAT_LE:
+       case SNDRV_PCM_FORMAT_FLOAT_BE:
+       case SNDRV_PCM_FORMAT_FLOAT64_LE:
+       case SNDRV_PCM_FORMAT_FLOAT64_BE:
+               return 1;
+       default:
+               return 0;
+       }
+}
+
+/**
+ * \brief Return endian info for a PCM sample format
+ * \param format Format
+ * \return 0 big endian, 1 little endian, a negative error code if endian independent
+ */
 int snd_pcm_format_little_endian(snd_pcm_format_t format)
 {
-       switch (snd_enum_to_int(format)) {
+       switch (format) {
        case SNDRV_PCM_FORMAT_S16_LE:
        case SNDRV_PCM_FORMAT_U16_LE:
        case SNDRV_PCM_FORMAT_S24_LE:
@@ -90,6 +133,14 @@ int snd_pcm_format_little_endian(snd_pcm_format_t format)
        case SNDRV_PCM_FORMAT_FLOAT_LE:
        case SNDRV_PCM_FORMAT_FLOAT64_LE:
        case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE:
+       case SNDRV_PCM_FORMAT_S24_3LE:
+       case SNDRV_PCM_FORMAT_S20_3LE:
+       case SNDRV_PCM_FORMAT_S18_3LE:
+       case SNDRV_PCM_FORMAT_U24_3LE:
+       case SNDRV_PCM_FORMAT_U20_3LE:
+       case SNDRV_PCM_FORMAT_U18_3LE:
+       case SNDRV_PCM_FORMAT_DSD_U16_LE:
+       case SNDRV_PCM_FORMAT_DSD_U32_LE:
                return 1;
        case SNDRV_PCM_FORMAT_S16_BE:
        case SNDRV_PCM_FORMAT_U16_BE:
@@ -100,12 +151,25 @@ int snd_pcm_format_little_endian(snd_pcm_format_t format)
        case SNDRV_PCM_FORMAT_FLOAT_BE:
        case SNDRV_PCM_FORMAT_FLOAT64_BE:
        case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE:
+       case SNDRV_PCM_FORMAT_S24_3BE:
+       case SNDRV_PCM_FORMAT_S20_3BE:
+       case SNDRV_PCM_FORMAT_S18_3BE:
+       case SNDRV_PCM_FORMAT_U24_3BE:
+       case SNDRV_PCM_FORMAT_U20_3BE:
+       case SNDRV_PCM_FORMAT_U18_3BE:
+       case SNDRV_PCM_FORMAT_DSD_U16_BE:
+       case SNDRV_PCM_FORMAT_DSD_U32_BE:
                return 0;
        default:
                return -EINVAL;
        }
 }
 
+/**
+ * \brief Return endian info for a PCM sample format
+ * \param format Format
+ * \return 0 little endian, 1 big endian, a negative error code if endian independent
+ */
 int snd_pcm_format_big_endian(snd_pcm_format_t format)
 {
        int val;
@@ -116,6 +180,11 @@ int snd_pcm_format_big_endian(snd_pcm_format_t format)
        return !val;
 }
 
+/**
+ * \brief Return endian info for a PCM sample format
+ * \param format Format
+ * \return 0 swapped, 1 CPU endian, a negative error code if endian independent
+ */
 int snd_pcm_format_cpu_endian(snd_pcm_format_t format)
 {
 #ifdef SNDRV_LITTLE_ENDIAN
@@ -125,21 +194,43 @@ int snd_pcm_format_cpu_endian(snd_pcm_format_t format)
 #endif
 }
 
+/**
+ * \brief Return nominal bits per a PCM sample
+ * \param format Sample format
+ * \return bits per sample, a negative error code if not applicable
+ */
 int snd_pcm_format_width(snd_pcm_format_t format)
 {
-       switch (snd_enum_to_int(format)) {
+       switch (format) {
        case SNDRV_PCM_FORMAT_S8:
        case SNDRV_PCM_FORMAT_U8:
+       case SNDRV_PCM_FORMAT_DSD_U8:
                return 8;
        case SNDRV_PCM_FORMAT_S16_LE:
        case SNDRV_PCM_FORMAT_S16_BE:
        case SNDRV_PCM_FORMAT_U16_LE:
        case SNDRV_PCM_FORMAT_U16_BE:
+       case SNDRV_PCM_FORMAT_DSD_U16_LE:
+       case SNDRV_PCM_FORMAT_DSD_U16_BE:
                return 16;
+       case SNDRV_PCM_FORMAT_S18_3LE:
+       case SNDRV_PCM_FORMAT_S18_3BE:
+       case SNDRV_PCM_FORMAT_U18_3LE:
+       case SNDRV_PCM_FORMAT_U18_3BE:
+               return 18;
+       case SNDRV_PCM_FORMAT_S20_3LE:
+       case SNDRV_PCM_FORMAT_S20_3BE:
+       case SNDRV_PCM_FORMAT_U20_3LE:
+       case SNDRV_PCM_FORMAT_U20_3BE:
+               return 20;
        case SNDRV_PCM_FORMAT_S24_LE:
        case SNDRV_PCM_FORMAT_S24_BE:
        case SNDRV_PCM_FORMAT_U24_LE:
        case SNDRV_PCM_FORMAT_U24_BE:
+       case SNDRV_PCM_FORMAT_S24_3LE:
+       case SNDRV_PCM_FORMAT_S24_3BE:
+       case SNDRV_PCM_FORMAT_U24_3LE:
+       case SNDRV_PCM_FORMAT_U24_3BE:
                return 24;
        case SNDRV_PCM_FORMAT_S32_LE:
        case SNDRV_PCM_FORMAT_S32_BE:
@@ -147,13 +238,15 @@ int snd_pcm_format_width(snd_pcm_format_t format)
        case SNDRV_PCM_FORMAT_U32_BE:
        case SNDRV_PCM_FORMAT_FLOAT_LE:
        case SNDRV_PCM_FORMAT_FLOAT_BE:
+       case SNDRV_PCM_FORMAT_DSD_U32_LE:
+       case SNDRV_PCM_FORMAT_DSD_U32_BE:
                return 32;
        case SNDRV_PCM_FORMAT_FLOAT64_LE:
        case SNDRV_PCM_FORMAT_FLOAT64_BE:
                return 64;
        case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE:
        case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE:
-               return 24;
+               return 32;
        case SNDRV_PCM_FORMAT_MU_LAW:
        case SNDRV_PCM_FORMAT_A_LAW:
                return 8;
@@ -164,17 +257,38 @@ int snd_pcm_format_width(snd_pcm_format_t format)
        }
 }
 
+/**
+ * \brief Return bits needed to store a PCM sample
+ * \param format Sample format
+ * \return bits per sample, a negative error code if not applicable
+ */
 int snd_pcm_format_physical_width(snd_pcm_format_t format)
 {
-       switch (snd_enum_to_int(format)) {
+       switch (format) {
        case SNDRV_PCM_FORMAT_S8:
        case SNDRV_PCM_FORMAT_U8:
+       case SNDRV_PCM_FORMAT_DSD_U8:
                return 8;
        case SNDRV_PCM_FORMAT_S16_LE:
        case SNDRV_PCM_FORMAT_S16_BE:
        case SNDRV_PCM_FORMAT_U16_LE:
        case SNDRV_PCM_FORMAT_U16_BE:
+       case SNDRV_PCM_FORMAT_DSD_U16_LE:
+       case SNDRV_PCM_FORMAT_DSD_U16_BE:
                return 16;
+       case SNDRV_PCM_FORMAT_S18_3LE:
+       case SNDRV_PCM_FORMAT_S18_3BE:
+       case SNDRV_PCM_FORMAT_U18_3LE:
+       case SNDRV_PCM_FORMAT_U18_3BE:
+       case SNDRV_PCM_FORMAT_S20_3LE:
+       case SNDRV_PCM_FORMAT_S20_3BE:
+       case SNDRV_PCM_FORMAT_U20_3LE:
+       case SNDRV_PCM_FORMAT_U20_3BE:
+       case SNDRV_PCM_FORMAT_S24_3LE:
+       case SNDRV_PCM_FORMAT_S24_3BE:
+       case SNDRV_PCM_FORMAT_U24_3LE:
+       case SNDRV_PCM_FORMAT_U24_3BE:
+               return 24;
        case SNDRV_PCM_FORMAT_S24_LE:
        case SNDRV_PCM_FORMAT_S24_BE:
        case SNDRV_PCM_FORMAT_U24_LE:
@@ -187,6 +301,8 @@ int snd_pcm_format_physical_width(snd_pcm_format_t format)
        case SNDRV_PCM_FORMAT_FLOAT_BE:
        case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_LE:
        case SNDRV_PCM_FORMAT_IEC958_SUBFRAME_BE:
+       case SNDRV_PCM_FORMAT_DSD_U32_LE:
+       case SNDRV_PCM_FORMAT_DSD_U32_BE:
                return 32;
        case SNDRV_PCM_FORMAT_FLOAT64_LE:
        case SNDRV_PCM_FORMAT_FLOAT64_BE:
@@ -201,17 +317,39 @@ int snd_pcm_format_physical_width(snd_pcm_format_t format)
        }
 }
 
+/**
+ * \brief Return bytes needed to store a quantity of PCM sample
+ * \param format Sample format
+ * \param samples Samples count
+ * \return bytes needed, a negative error code if not integer or unknown
+ */
 ssize_t snd_pcm_format_size(snd_pcm_format_t format, size_t samples)
 {
-       switch (snd_enum_to_int(format)) {
+       switch (format) {
        case SNDRV_PCM_FORMAT_S8:
        case SNDRV_PCM_FORMAT_U8:
+       case SNDRV_PCM_FORMAT_DSD_U8:
                return samples;
        case SNDRV_PCM_FORMAT_S16_LE:
        case SNDRV_PCM_FORMAT_S16_BE:
        case SNDRV_PCM_FORMAT_U16_LE:
        case SNDRV_PCM_FORMAT_U16_BE:
+       case SNDRV_PCM_FORMAT_DSD_U16_LE:
+       case SNDRV_PCM_FORMAT_DSD_U16_BE:
                return samples * 2;
+       case SNDRV_PCM_FORMAT_S18_3LE:
+       case SNDRV_PCM_FORMAT_S18_3BE:
+       case SNDRV_PCM_FORMAT_U18_3LE:
+       case SNDRV_PCM_FORMAT_U18_3BE:
+       case SNDRV_PCM_FORMAT_S20_3LE:
+       case SNDRV_PCM_FORMAT_S20_3BE:
+       case SNDRV_PCM_FORMAT_U20_3LE:
+       case SNDRV_PCM_FORMAT_U20_3BE:
+       case SNDRV_PCM_FORMAT_S24_3LE:
+       case SNDRV_PCM_FORMAT_S24_3BE:
+       case SNDRV_PCM_FORMAT_U24_3LE:
+       case SNDRV_PCM_FORMAT_U24_3BE:
+               return samples * 3;
        case SNDRV_PCM_FORMAT_S24_LE:
        case SNDRV_PCM_FORMAT_S24_BE:
        case SNDRV_PCM_FORMAT_U24_LE:
@@ -222,6 +360,8 @@ ssize_t snd_pcm_format_size(snd_pcm_format_t format, size_t samples)
        case SNDRV_PCM_FORMAT_U32_BE:
        case SNDRV_PCM_FORMAT_FLOAT_LE:
        case SNDRV_PCM_FORMAT_FLOAT_BE:
+       case SNDRV_PCM_FORMAT_DSD_U32_LE:
+       case SNDRV_PCM_FORMAT_DSD_U32_BE:
                return samples * 4;
        case SNDRV_PCM_FORMAT_FLOAT64_LE:
        case SNDRV_PCM_FORMAT_FLOAT64_BE:
@@ -237,13 +377,19 @@ ssize_t snd_pcm_format_size(snd_pcm_format_t format, size_t samples)
                        return -EINVAL;
                return samples / 2;
        default:
+               assert(0);
                return -EINVAL;
        }
 }
 
+/**
+ * \brief Return 64 bit expressing silence for a PCM sample format
+ * \param format Sample format
+ * \return silence 64 bit word
+ */
 u_int64_t snd_pcm_format_silence_64(snd_pcm_format_t format)
 {
-       switch (snd_enum_to_int(format)) {
+       switch (format) {
        case SNDRV_PCM_FORMAT_S8:
        case SNDRV_PCM_FORMAT_S16_LE:
        case SNDRV_PCM_FORMAT_S16_BE:
@@ -251,9 +397,21 @@ u_int64_t snd_pcm_format_silence_64(snd_pcm_format_t format)
        case SNDRV_PCM_FORMAT_S24_BE:
        case SNDRV_PCM_FORMAT_S32_LE:
        case SNDRV_PCM_FORMAT_S32_BE:
+       case SNDRV_PCM_FORMAT_S24_3LE:
+       case SNDRV_PCM_FORMAT_S24_3BE:
+       case SNDRV_PCM_FORMAT_S20_3LE:
+       case SNDRV_PCM_FORMAT_S20_3BE:
+       case SNDRV_PCM_FORMAT_S18_3LE:
+       case SNDRV_PCM_FORMAT_S18_3BE:
                return 0;
        case SNDRV_PCM_FORMAT_U8:
                return 0x8080808080808080ULL;
+       case SNDRV_PCM_FORMAT_DSD_U8:
+       case SNDRV_PCM_FORMAT_DSD_U16_LE:
+       case SNDRV_PCM_FORMAT_DSD_U32_LE:
+       case SNDRV_PCM_FORMAT_DSD_U16_BE:
+       case SNDRV_PCM_FORMAT_DSD_U32_BE:
+               return 0x6969696969696969ULL;
 #ifdef SNDRV_LITTLE_ENDIAN
        case SNDRV_PCM_FORMAT_U16_LE:
                return 0x8000800080008000ULL;
@@ -267,6 +425,18 @@ u_int64_t snd_pcm_format_silence_64(snd_pcm_format_t format)
                return 0x0000800000008000ULL;
        case SNDRV_PCM_FORMAT_U32_BE:
                return 0x0000008000000080ULL;
+       case SNDRV_PCM_FORMAT_U24_3LE:
+               return 0x0000800000800000ULL;
+       case SNDRV_PCM_FORMAT_U24_3BE:
+               return 0x0080000080000080ULL;
+       case SNDRV_PCM_FORMAT_U20_3LE:
+               return 0x0000080000080000ULL;
+       case SNDRV_PCM_FORMAT_U20_3BE:
+               return 0x0008000008000008ULL;
+       case SNDRV_PCM_FORMAT_U18_3LE:
+               return 0x0000020000020000ULL;
+       case SNDRV_PCM_FORMAT_U18_3BE:
+               return 0x0002000002000002ULL;
 #else
        case SNDRV_PCM_FORMAT_U16_LE:
                return 0x0080008000800080ULL;
@@ -280,18 +450,30 @@ u_int64_t snd_pcm_format_silence_64(snd_pcm_format_t format)
                return 0x0080000000800000ULL;
        case SNDRV_PCM_FORMAT_U32_BE:
                return 0x8000000080000000ULL;
+       case SNDRV_PCM_FORMAT_U24_3LE:
+               return 0x0080000080000080ULL;
+       case SNDRV_PCM_FORMAT_U24_3BE:
+               return 0x0000800000800000ULL;
+       case SNDRV_PCM_FORMAT_U20_3LE:
+               return 0x0008000008000008ULL;
+       case SNDRV_PCM_FORMAT_U20_3BE:
+               return 0x0000080000080000ULL;
+       case SNDRV_PCM_FORMAT_U18_3LE:
+               return 0x0002000002000002ULL;
+       case SNDRV_PCM_FORMAT_U18_3BE:
+               return 0x0000020000020000ULL;
 #endif
        case SNDRV_PCM_FORMAT_FLOAT_LE:
        {
                union {
-                       float f;
-                       u_int32_t i;
+                       float f[2];
+                       u_int64_t i;
                } u;
-               u.f = 0.0;
+               u.f[0] = u.f[1] = 0.0;
 #ifdef SNDRV_LITTLE_ENDIAN
                return u.i;
 #else
-               return bswap_32(u.i);
+               return bswap_64(u.i);
 #endif
        }
        case SNDRV_PCM_FORMAT_FLOAT64_LE:
@@ -310,12 +492,12 @@ u_int64_t snd_pcm_format_silence_64(snd_pcm_format_t format)
        case SNDRV_PCM_FORMAT_FLOAT_BE:         
        {
                union {
-                       float f;
-                       u_int32_t i;
+                       float f[2];
+                       u_int64_t i;
                } u;
-               u.f = 0.0;
+               u.f[0] = u.f[1] = 0.0;
 #ifdef SNDRV_LITTLE_ENDIAN
-               return bswap_32(u.i);
+               return bswap_64(u.i);
 #else
                return u.i;
 #endif
@@ -345,30 +527,58 @@ u_int64_t snd_pcm_format_silence_64(snd_pcm_format_t format)
        case SNDRV_PCM_FORMAT_GSM:
        case SNDRV_PCM_FORMAT_SPECIAL:
                return 0;
+       default:
+               assert(0);
+               return 0;
        }
        return 0;
 }
 
+/**
+ * \brief Return 32 bit expressing silence for a PCM sample format
+ * \param format Sample format
+ * \return silence 32 bit word
+ */
 u_int32_t snd_pcm_format_silence_32(snd_pcm_format_t format)
 {
+       assert(snd_pcm_format_physical_width(format) <= 32);
        return (u_int32_t)snd_pcm_format_silence_64(format);
 }
 
+/**
+ * \brief Return 16 bit expressing silence for a PCM sample format
+ * \param format Sample format
+ * \return silence 16 bit word
+ */
 u_int16_t snd_pcm_format_silence_16(snd_pcm_format_t format)
 {
+       assert(snd_pcm_format_physical_width(format) <= 16);
        return (u_int16_t)snd_pcm_format_silence_64(format);
 }
 
+/**
+ * \brief Return 8 bit expressing silence for a PCM sample format
+ * \param format Sample format
+ * \return silence 8 bit word
+ */
 u_int8_t snd_pcm_format_silence(snd_pcm_format_t format)
 {
+       assert(snd_pcm_format_physical_width(format) <= 8);
        return (u_int8_t)snd_pcm_format_silence_64(format);
 }
 
+/**
+ * \brief Silence a PCM samples buffer
+ * \param format Sample format
+ * \param data Buffer
+ * \param samples Samples count
+ * \return 0 if successful or a negative error code
+ */
 int snd_pcm_format_set_silence(snd_pcm_format_t format, void *data, unsigned int samples)
 {
        if (samples == 0)
                return 0;
-       switch (snd_pcm_format_width(format)) {
+       switch (snd_pcm_format_physical_width(format)) {
        case 4: {
                u_int8_t silence = snd_pcm_format_silence_64(format);
                unsigned int samples1;
@@ -385,64 +595,269 @@ int snd_pcm_format_set_silence(snd_pcm_format_t format, void *data, unsigned int
        }
        case 16: {
                u_int16_t silence = snd_pcm_format_silence_64(format);
-               while (samples-- > 0)
-                       *((u_int16_t *)data)++ = silence;
+               u_int16_t *pdata = (u_int16_t *)data;
+               if (! silence)
+                       memset(data, 0, samples * 2);
+               else {
+                       while (samples-- > 0)
+                               *pdata++ = silence;
+               }
+               break;
+       }
+       case 24: {
+               u_int32_t silence = snd_pcm_format_silence_64(format);
+               u_int8_t *pdata = (u_int8_t *)data;
+               if (! silence)
+                       memset(data, 0, samples * 3);
+               else {
+                       while (samples-- > 0) {
+#ifdef SNDRV_LITTLE_ENDIAN
+                               *pdata++ = silence >> 0;
+                               *pdata++ = silence >> 8;
+                               *pdata++ = silence >> 16;
+#else
+                               *pdata++ = silence >> 16;
+                               *pdata++ = silence >> 8;
+                               *pdata++ = silence >> 0;
+#endif
+                       }
+               }
                break;
        }
        case 32: {
                u_int32_t silence = snd_pcm_format_silence_64(format);
-               while (samples-- > 0)
-                       *((u_int32_t *)data)++ = silence;
+               u_int32_t *pdata = (u_int32_t *)data;
+               if (! silence)
+                       memset(data, 0, samples * 4);
+               else {
+                       while (samples-- > 0)
+                               *pdata++ = silence;
+               }
                break;
        }
        case 64: {
                u_int64_t silence = snd_pcm_format_silence_64(format);
-               while (samples-- > 0)
-                       *((u_int64_t *)data)++ = silence;
+               u_int64_t *pdata = (u_int64_t *)data;
+               if (! silence)
+                       memset(data, 0, samples * 8);
+               else {
+                       while (samples-- > 0)
+                               *pdata++ = silence;
+               }
                break;
        }
        default:
+               assert(0);
                return -EINVAL;
        }
        return 0;
 }
 
-static int linear_formats[4*2*2] = {
-       SNDRV_PCM_FORMAT_S8,
-       SNDRV_PCM_FORMAT_S8,
-       SNDRV_PCM_FORMAT_U8,
-       SNDRV_PCM_FORMAT_U8,
-       SNDRV_PCM_FORMAT_S16_LE,
-       SNDRV_PCM_FORMAT_S16_BE,
-       SNDRV_PCM_FORMAT_U16_LE,
-       SNDRV_PCM_FORMAT_U16_BE,
-       SNDRV_PCM_FORMAT_S24_LE,
-       SNDRV_PCM_FORMAT_S24_BE,
-       SNDRV_PCM_FORMAT_U24_LE,
-       SNDRV_PCM_FORMAT_U24_BE,
-       SNDRV_PCM_FORMAT_S32_LE,
-       SNDRV_PCM_FORMAT_S32_BE,
-       SNDRV_PCM_FORMAT_U32_LE,
-       SNDRV_PCM_FORMAT_U32_BE
+static const int linear_formats[4][2][2] = {
+       { { SNDRV_PCM_FORMAT_S8, SNDRV_PCM_FORMAT_S8 },
+         { SNDRV_PCM_FORMAT_U8, SNDRV_PCM_FORMAT_U8 } },
+       { { SNDRV_PCM_FORMAT_S16_LE, SNDRV_PCM_FORMAT_S16_BE },
+         { SNDRV_PCM_FORMAT_U16_LE, SNDRV_PCM_FORMAT_U16_BE } },
+       { { SNDRV_PCM_FORMAT_S24_LE, SNDRV_PCM_FORMAT_S24_BE },
+         { SNDRV_PCM_FORMAT_U24_LE, SNDRV_PCM_FORMAT_U24_BE } },
+       { { SNDRV_PCM_FORMAT_S32_LE, SNDRV_PCM_FORMAT_S32_BE },
+         { SNDRV_PCM_FORMAT_U32_LE, SNDRV_PCM_FORMAT_U32_BE } }
+};
+
+static const int linear24_formats[3][2][2] = {
+       { { SNDRV_PCM_FORMAT_S24_3LE, SNDRV_PCM_FORMAT_S24_3BE },
+         { SNDRV_PCM_FORMAT_U24_3LE, SNDRV_PCM_FORMAT_U24_3BE } },
+       { { SNDRV_PCM_FORMAT_S20_3LE, SNDRV_PCM_FORMAT_S20_3BE },
+         { SNDRV_PCM_FORMAT_U20_3LE, SNDRV_PCM_FORMAT_U20_3BE } },
+       { { SNDRV_PCM_FORMAT_S18_3LE, SNDRV_PCM_FORMAT_S18_3BE },
+         { SNDRV_PCM_FORMAT_U18_3LE, SNDRV_PCM_FORMAT_U18_3BE } },
 };
 
-snd_pcm_format_t snd_pcm_build_linear_format(int width, int unsignd, int big_endian)
+/**
+ * \brief Compose a PCM sample linear format
+ * \param width Nominal bits per sample
+ * \param pwidth Physical bit width of the format
+ * \param unsignd Sign: 0 signed, 1 unsigned
+ * \param big_endian Endian: 0 little endian, 1 big endian
+ * \return The matching format type, or #SND_PCM_FORMAT_UNKNOWN if no match
+ */
+snd_pcm_format_t snd_pcm_build_linear_format(int width, int pwidth, int unsignd, int big_endian)
 {
-       switch (width) {
-       case 8:
-               width = 0;
-               break;
-       case 16:
-               width = 1;
-               break;
-       case 24:
-               width = 2;
-               break;
-       case 32:
-               width = 3;
-               break;
-       default:
-               return SND_PCM_FORMAT_UNKNOWN;
+       if (pwidth == 24) {
+               switch (width) {
+               case 24:
+                       width = 0;
+                       break;
+               case 20:
+                       width = 1;
+                       break;
+               case 18:
+                       width = 2;
+                       break;
+               default:
+                       return SND_PCM_FORMAT_UNKNOWN;
+               }
+               return linear24_formats[width][!!unsignd][!!big_endian];
+       } else {
+               switch (width) {
+               case 8:
+                       width = 0;
+                       break;
+               case 16:
+                       width = 1;
+                       break;
+               case 24:
+                       width = 2;
+                       break;
+               case 32:
+                       width = 3;
+                       break;
+               default:
+                       return SND_PCM_FORMAT_UNKNOWN;
+               }
+               return linear_formats[width][!!unsignd][!!big_endian];
        }
-       return snd_int_to_enum(((int(*)[2][2])linear_formats)[width][!!unsignd][!!big_endian]);
+}
+
+/**
+ * \brief Parse control element id from the config
+ * \param conf the config tree to parse
+ * \param ctl_id the pointer to store the resultant control element id
+ * \param cardp the pointer to store the card index
+ * \param cchannelsp the pointer to store the number of channels (optional)
+ * \param hwctlp the pointer to store the h/w control flag (optional)
+ * \return 0 if successful, or a negative error code
+ *
+ * This function parses the given config tree to retrieve the control element id
+ * and the card index.  It's used by softvol.  External PCM plugins can use this
+ * function for creating or assigining their controls.
+ *
+ * cchannelsp and hwctlp arguments are optional.  Set NULL if not necessary.
+ */
+int snd_pcm_parse_control_id(snd_config_t *conf, snd_ctl_elem_id_t *ctl_id, int *cardp,
+                            int *cchannelsp, int *hwctlp)
+{
+       snd_config_iterator_t i, next;
+       int iface = SND_CTL_ELEM_IFACE_MIXER;
+       const char *name = NULL;
+       long index = 0;
+       long device = -1;
+       long subdevice = -1;
+       int err;
+
+       assert(ctl_id && cardp);
+
+       *cardp = -1;
+       if (cchannelsp)
+               *cchannelsp = 2;
+       snd_config_for_each(i, next, conf) {
+               snd_config_t *n = snd_config_iterator_entry(i);
+               const char *id;
+               if (snd_config_get_id(n, &id) < 0)
+                       continue;
+               if (strcmp(id, "comment") == 0)
+                       continue;
+               if (strcmp(id, "card") == 0) {
+                       const char *str;
+                       long v;
+                       if ((err = snd_config_get_integer(n, &v)) < 0) {
+                               if ((err = snd_config_get_string(n, &str)) < 0) {
+                                       SNDERR("Invalid field %s", id);
+                                       goto _err;
+                               }
+                               *cardp = snd_card_get_index(str);
+                               if (*cardp < 0) {
+                                       SNDERR("Cannot get index for %s", str);
+                                       err = *cardp;
+                                       goto _err;
+                               }
+                       } else
+                               *cardp = v;
+                       continue;
+               }
+               if (strcmp(id, "iface") == 0 || strcmp(id, "interface") == 0) {
+                       const char *ptr;
+                       if ((err = snd_config_get_string(n, &ptr)) < 0) {
+                               SNDERR("field %s is not a string", id);
+                               goto _err;
+                       }
+                       if ((err = snd_config_get_ctl_iface_ascii(ptr)) < 0) {
+                               SNDERR("Invalid value for '%s'", id);
+                               goto _err;
+                       }
+                       iface = err;
+                       continue;
+               }
+               if (strcmp(id, "name") == 0) {
+                       if ((err = snd_config_get_string(n, &name)) < 0) {
+                               SNDERR("field %s is not a string", id);
+                               goto _err;
+                       }
+                       continue;
+               }
+               if (strcmp(id, "index") == 0) {
+                       if ((err = snd_config_get_integer(n, &index)) < 0) {
+                               SNDERR("field %s is not an integer", id);
+                               goto _err;
+                       }
+                       continue;
+               }
+               if (strcmp(id, "device") == 0) {
+                       if ((err = snd_config_get_integer(n, &device)) < 0) {
+                               SNDERR("field %s is not an integer", id);
+                               goto _err;
+                       }
+                       continue;
+               }
+               if (strcmp(id, "subdevice") == 0) {
+                       if ((err = snd_config_get_integer(n, &subdevice)) < 0) {
+                               SNDERR("field %s is not an integer", id);
+                               goto _err;
+                       }
+                       continue;
+               }
+               if (cchannelsp && strcmp(id, "count") == 0) {
+                       long v;
+                       if ((err = snd_config_get_integer(n, &v)) < 0) {
+                               SNDERR("field %s is not an integer", id);
+                               goto _err;
+                       }
+                       if (v < 1 || v > 2) {
+                               SNDERR("Invalid count %ld", v);
+                               goto _err;
+                       }
+                       *cchannelsp = v;
+                       continue;
+               }
+               if (hwctlp && strcmp(id, "hwctl") == 0) {
+                       if ((err = snd_config_get_bool(n)) < 0) {
+                               SNDERR("The field %s must be a boolean type", id);
+                               return err;
+                       }
+                       *hwctlp = err;
+                       continue;
+               }
+               SNDERR("Unknown field %s", id);
+               return -EINVAL;
+       }
+       if (name == NULL) {
+               SNDERR("Missing control name");
+               err = -EINVAL;
+               goto _err;
+       }
+       if (device < 0)
+               device = 0;
+       if (subdevice < 0)
+               subdevice = 0;
+
+       snd_ctl_elem_id_set_interface(ctl_id, iface);
+       snd_ctl_elem_id_set_name(ctl_id, name);
+       snd_ctl_elem_id_set_index(ctl_id, index);
+       snd_ctl_elem_id_set_device(ctl_id, device);
+       snd_ctl_elem_id_set_subdevice(ctl_id, subdevice);
+
+       return 0;
+
+ _err:
+       return err;
 }