ALSA: PCM: channel mapping API implementation
authorTakashi Iwai <tiwai@suse.de>
Fri, 27 Jul 2012 16:27:00 +0000 (18:27 +0200)
committerTakashi Iwai <tiwai@suse.de>
Thu, 6 Sep 2012 16:01:16 +0000 (18:01 +0200)
This patch implements the basic data types for the standard channel
mapping API handling.

- The definitions of the channel positions and the new TLV types are
  added in sound/asound.h and sound/tlv.h, so that they can be
  referred from user-space.

- Introduced a new helper function snd_pcm_add_chmap_ctls() to create
  control elements representing the channel maps for each PCM
  (sub)stream.

- Some standard pre-defined channel maps are provided for
  convenience.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
include/sound/asound.h
include/sound/pcm.h
include/sound/tlv.h
sound/core/pcm.c
sound/core/pcm_lib.c

index 0876a1e..376e756 100644 (file)
@@ -472,6 +472,36 @@ enum {
        SNDRV_PCM_TSTAMP_TYPE_LAST = SNDRV_PCM_TSTAMP_TYPE_MONOTONIC,
 };
 
+/* channel positions */
+enum {
+       SNDRV_CHMAP_UNKNOWN = 0,
+       SNDRV_CHMAP_FL,         /* front left */
+       SNDRV_CHMAP_FC,         /* front center */
+       SNDRV_CHMAP_FR,         /* front right */
+       SNDRV_CHMAP_FLC,        /* front left center */
+       SNDRV_CHMAP_FRC,        /* front right center */
+       SNDRV_CHMAP_RL,         /* rear left */
+       SNDRV_CHMAP_RC,         /* rear center */
+       SNDRV_CHMAP_RR,         /* rear right */
+       SNDRV_CHMAP_RLC,        /* rear left center */
+       SNDRV_CHMAP_RRC,        /* rear right center */
+       SNDRV_CHMAP_SL,         /* side left */
+       SNDRV_CHMAP_SR,         /* side right */
+       SNDRV_CHMAP_LFE,        /* LFE */
+       SNDRV_CHMAP_FLW,        /* front left wide */
+       SNDRV_CHMAP_FRW,        /* front right wide */
+       SNDRV_CHMAP_FLH,        /* front left high */
+       SNDRV_CHMAP_FCH,        /* front center high */
+       SNDRV_CHMAP_FRH,        /* front right high */
+       SNDRV_CHMAP_TC,         /* top center */
+       SNDRV_CHMAP_NA,         /* N/A, silent */
+       SNDRV_CHMAP_LAST = SNDRV_CHMAP_NA,
+};
+
+#define SNDRV_CHMAP_POSITION_MASK      0xffff
+#define SNDRV_CHMAP_PHASE_INVERSE      (0x01 << 16)
+#define SNDRV_CHMAP_DRIVER_SPEC                (0x02 << 16)
+
 #define SNDRV_PCM_IOCTL_PVERSION       _IOR('A', 0x00, int)
 #define SNDRV_PCM_IOCTL_INFO           _IOR('A', 0x01, struct snd_pcm_info)
 #define SNDRV_PCM_IOCTL_TSTAMP         _IOW('A', 0x02, int)
index cdca2ab..669c85a 100644 (file)
@@ -437,6 +437,7 @@ struct snd_pcm_str {
        struct snd_info_entry *proc_xrun_debug_entry;
 #endif
 #endif
+       struct snd_kcontrol *chmap_kctl; /* channel-mapping controls */
 };
 
 struct snd_pcm {
@@ -1086,4 +1087,51 @@ static inline const char *snd_pcm_stream_str(struct snd_pcm_substream *substream
                return "Capture";
 }
 
+/*
+ * PCM channel-mapping control API
+ */
+/* array element of channel maps */
+struct snd_pcm_chmap_elem {
+       unsigned char channels;
+       unsigned char map[15];
+};
+
+/* channel map information; retrieved via snd_kcontrol_chip() */
+struct snd_pcm_chmap {
+       struct snd_pcm *pcm;    /* assigned PCM instance */
+       int stream;             /* PLAYBACK or CAPTURE */
+       struct snd_kcontrol *kctl;
+       const struct snd_pcm_chmap_elem *chmap;
+       unsigned int max_channels;
+       unsigned int channel_mask;      /* optional: active channels bitmask */
+       void *private_data;     /* optional: private data pointer */
+};
+
+/* get the PCM substream assigned to the given chmap info */
+static inline struct snd_pcm_substream *
+snd_pcm_chmap_substream(struct snd_pcm_chmap *info, unsigned int idx)
+{
+       struct snd_pcm_substream *s;
+       for (s = info->pcm->streams[info->stream].substream; s; s = s->next)
+               if (s->number == idx)
+                       return s;
+       return NULL;
+}
+
+/* ALSA-standard channel maps (RL/RR prior to C/LFE) */
+extern const struct snd_pcm_chmap_elem snd_pcm_std_chmaps[];
+/* Other world's standard channel maps (C/LFE prior to RL/RR) */
+extern const struct snd_pcm_chmap_elem snd_pcm_alt_chmaps[];
+
+/* bit masks to be passed to snd_pcm_chmap.channel_mask field */
+#define SND_PCM_CHMAP_MASK_24  ((1U << 2) | (1U << 4))
+#define SND_PCM_CHMAP_MASK_246 (SND_PCM_CHMAP_MASK_24 | (1U << 6))
+#define SND_PCM_CHMAP_MASK_2468        (SND_PCM_CHMAP_MASK_246 | (1U << 8))
+
+int snd_pcm_add_chmap_ctls(struct snd_pcm *pcm, int stream,
+                          const struct snd_pcm_chmap_elem *chmap,
+                          int max_channels,
+                          unsigned long private_value,
+                          struct snd_pcm_chmap **info_ret);
+
 #endif /* __SOUND_PCM_H */
index a64d8fe..28c65e1 100644 (file)
 
 #define TLV_DB_GAIN_MUTE       -9999999
 
+/*
+ * channel-mapping TLV items
+ *  TLV length must match with num_channels
+ */
+#define SNDRV_CTL_TLVT_CHMAP_FIXED     0x101   /* fixed channel position */
+#define SNDRV_CTL_TLVT_CHMAP_VAR       0x102   /* channels freely swappable */
+#define SNDRV_CTL_TLVT_CHMAP_PAIRED    0x103   /* pair-wise swappable */
+
 #endif /* __SOUND_TLV_H */
index 1a3070b..f299194 100644 (file)
@@ -1105,6 +1105,10 @@ static int snd_pcm_dev_disconnect(struct snd_device *device)
                        break;
                }
                snd_unregister_device(devtype, pcm->card, pcm->device);
+               if (pcm->streams[cidx].chmap_kctl) {
+                       snd_ctl_remove(pcm->card, pcm->streams[cidx].chmap_kctl);
+                       pcm->streams[cidx].chmap_kctl = NULL;
+               }
        }
  unlock:
        mutex_unlock(&register_mutex);
index 7ae6719..5651027 100644 (file)
@@ -26,6 +26,7 @@
 #include <linux/export.h>
 #include <sound/core.h>
 #include <sound/control.h>
+#include <sound/tlv.h>
 #include <sound/info.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
@@ -2302,3 +2303,217 @@ snd_pcm_sframes_t snd_pcm_lib_readv(struct snd_pcm_substream *substream,
 }
 
 EXPORT_SYMBOL(snd_pcm_lib_readv);
+
+/*
+ * standard channel mapping helpers
+ */
+
+/* default channel maps for multi-channel playbacks, up to 8 channels */
+const struct snd_pcm_chmap_elem snd_pcm_std_chmaps[] = {
+       { .channels = 1,
+         .map = { SNDRV_CHMAP_FC } },
+       { .channels = 2,
+         .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } },
+       { .channels = 4,
+         .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+                  SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
+       { .channels = 6,
+         .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+                  SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
+                  SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE } },
+       { .channels = 8,
+         .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+                  SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
+                  SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE,
+                  SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } },
+       { }
+};
+EXPORT_SYMBOL_GPL(snd_pcm_std_chmaps);
+
+/* alternative channel maps with CLFE <-> surround swapped for 6/8 channels */
+const struct snd_pcm_chmap_elem snd_pcm_alt_chmaps[] = {
+       { .channels = 1,
+         .map = { SNDRV_CHMAP_FC } },
+       { .channels = 2,
+         .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } },
+       { .channels = 4,
+         .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+                  SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
+       { .channels = 6,
+         .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+                  SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE,
+                  SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
+       { .channels = 8,
+         .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR,
+                  SNDRV_CHMAP_FC, SNDRV_CHMAP_LFE,
+                  SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
+                  SNDRV_CHMAP_SL, SNDRV_CHMAP_SR } },
+       { }
+};
+EXPORT_SYMBOL_GPL(snd_pcm_alt_chmaps);
+
+static bool valid_chmap_channels(const struct snd_pcm_chmap *info, int ch)
+{
+       if (ch > info->max_channels)
+               return false;
+       return !info->channel_mask || (info->channel_mask & (1U << ch));
+}
+
+static int pcm_chmap_ctl_info(struct snd_kcontrol *kcontrol,
+                             struct snd_ctl_elem_info *uinfo)
+{
+       struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
+
+       uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+       uinfo->count = 0;
+       uinfo->count = info->max_channels;
+       uinfo->value.integer.min = 0;
+       uinfo->value.integer.max = SNDRV_CHMAP_LAST;
+       return 0;
+}
+
+/* get callback for channel map ctl element
+ * stores the channel position firstly matching with the current channels
+ */
+static int pcm_chmap_ctl_get(struct snd_kcontrol *kcontrol,
+                            struct snd_ctl_elem_value *ucontrol)
+{
+       struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
+       unsigned int idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+       struct snd_pcm_substream *substream;
+       const struct snd_pcm_chmap_elem *map;
+
+       if (snd_BUG_ON(!info->chmap))
+               return -EINVAL;
+       substream = snd_pcm_chmap_substream(info, idx);
+       if (!substream)
+               return -ENODEV;
+       memset(ucontrol->value.integer.value, 0,
+              sizeof(ucontrol->value.integer.value));
+       if (!substream->runtime)
+               return 0; /* no channels set */
+       for (map = info->chmap; map->channels; map++) {
+               int i;
+               if (map->channels == substream->runtime->channels &&
+                   valid_chmap_channels(info, map->channels)) {
+                       for (i = 0; i < map->channels; i++)
+                               ucontrol->value.integer.value[i] = map->map[i];
+                       return 0;
+               }
+       }
+       return -EINVAL;
+}
+
+/* tlv callback for channel map ctl element
+ * expands the pre-defined channel maps in a form of TLV
+ */
+static int pcm_chmap_ctl_tlv(struct snd_kcontrol *kcontrol, int op_flag,
+                            unsigned int size, unsigned int __user *tlv)
+{
+       struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
+       const struct snd_pcm_chmap_elem *map;
+       unsigned int __user *dst;
+       int c, count = 0;
+
+       if (snd_BUG_ON(!info->chmap))
+               return -EINVAL;
+       if (size < 8)
+               return -ENOMEM;
+       if (put_user(SNDRV_CTL_TLVT_CONTAINER, tlv))
+               return -EFAULT;
+       size -= 8;
+       dst = tlv + 2;
+       for (map = info->chmap; map->channels; map++) {
+               int chs_bytes = map->channels * 4;
+               if (!valid_chmap_channels(info, map->channels))
+                       continue;
+               if (size < 8)
+                       return -ENOMEM;
+               if (put_user(SNDRV_CTL_TLVT_CHMAP_FIXED, dst) ||
+                   put_user(chs_bytes, dst + 1))
+                       return -EFAULT;
+               dst += 2;
+               size -= 8;
+               count += 8;
+               if (size < chs_bytes)
+                       return -ENOMEM;
+               size -= chs_bytes;
+               count += chs_bytes;
+               for (c = 0; c < map->channels; c++) {
+                       if (put_user(map->map[c], dst))
+                               return -EFAULT;
+                       dst++;
+               }
+       }
+       if (put_user(count, tlv + 1))
+               return -EFAULT;
+       return 0;
+}
+
+static void pcm_chmap_ctl_private_free(struct snd_kcontrol *kcontrol)
+{
+       struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
+       info->pcm->streams[info->stream].chmap_kctl = NULL;
+       kfree(info);
+}
+
+/**
+ * snd_pcm_add_chmap_ctls - create channel-mapping control elements
+ * @pcm: the assigned PCM instance
+ * @stream: stream direction
+ * @chmap: channel map elements (for query)
+ * @max_channels: the max number of channels for the stream
+ * @private_value: the value passed to each kcontrol's private_value field
+ * @info_ret: store struct snd_pcm_chmap instance if non-NULL
+ *
+ * Create channel-mapping control elements assigned to the given PCM stream(s).
+ * Returns zero if succeed, or a negative error value.
+ */
+int snd_pcm_add_chmap_ctls(struct snd_pcm *pcm, int stream,
+                          const struct snd_pcm_chmap_elem *chmap,
+                          int max_channels,
+                          unsigned long private_value,
+                          struct snd_pcm_chmap **info_ret)
+{
+       struct snd_pcm_chmap *info;
+       struct snd_kcontrol_new knew = {
+               .iface = SNDRV_CTL_ELEM_IFACE_PCM,
+               .access = SNDRV_CTL_ELEM_ACCESS_READ |
+                       SNDRV_CTL_ELEM_ACCESS_VOLATILE | /* no notification */
+                       SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+                       SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK,
+               .info = pcm_chmap_ctl_info,
+               .get = pcm_chmap_ctl_get,
+               .tlv.c = pcm_chmap_ctl_tlv,
+       };
+       int err;
+
+       info = kzalloc(sizeof(*info), GFP_KERNEL);
+       if (!info)
+               return -ENOMEM;
+       info->pcm = pcm;
+       info->stream = stream;
+       info->chmap = chmap;
+       info->max_channels = max_channels;
+       if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+               knew.name = "Playback Channel Map";
+       else
+               knew.name = "Capture Channel Map";
+       knew.device = pcm->device;
+       knew.count = pcm->streams[stream].substream_count;
+       knew.private_value = private_value;
+       info->kctl = snd_ctl_new1(&knew, info);
+       if (!info->kctl) {
+               kfree(info);
+               return -ENOMEM;
+       }
+       info->kctl->private_free = pcm_chmap_ctl_private_free;
+       err = snd_ctl_add(pcm->card, info->kctl);
+       if (err < 0)
+               return err;
+       pcm->streams[stream].chmap_kctl = info->kctl;
+       if (info_ret)
+               *info_ret = info;
+       return 0;
+}
+EXPORT_SYMBOL_GPL(snd_pcm_add_chmap_ctls);