aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTakashi Iwai <tiwai@suse.de>2013-10-11 18:03:36 +0200
committerTakashi Iwai <tiwai@suse.de>2013-10-11 18:03:36 +0200
commitc7e9869fa6efac046f30fb20f6985fd0a4e51702 (patch)
treee2a6487ec17f21b029bbbe05b78872e6ae64852c
parent60f5a833368c2656923344ccae172ab64b3ba1a3 (diff)
downloadsalsa-lib-c7e9869fa6efac046f30fb20f6985fd0a4e51702.tar.gz
Add PCM chmap support
Not fully tested, though... Signed-off-by: Takashi Iwai <tiwai@suse.de>
-rw-r--r--README3
-rw-r--r--configure.ac14
-rw-r--r--src/asound.h71
-rw-r--r--src/pcm.c347
-rw-r--r--src/pcm_func.h12
-rw-r--r--src/pcm_macros.h31
-rw-r--r--src/recipe.h.in3
7 files changed, 481 insertions, 0 deletions
diff --git a/README b/README
index 89907fa..f006f4a 100644
--- a/README
+++ b/README
@@ -103,6 +103,9 @@ it can be disabled via --disable-user-elem configure option, too.
The async handler support can be enabled via --enable-async option.
It's disabled as default.
+The PCM chmap API support is also optional, can be enabled via
+--enable-chmap option.
+
With option --enable-libasound, libasound.so will be created as an
opt-in ABI-compatible library with the genuine ALSA-lib.
diff --git a/configure.ac b/configure.ac
index fbf49a7..9c64760 100644
--- a/configure.ac
+++ b/configure.ac
@@ -92,6 +92,11 @@ AC_ARG_ENABLE(async,
[enable async handler support]),
async="$enableval", async="no")
+AC_ARG_ENABLE(chmap,
+ AS_HELP_STRING([--enable-chmap],
+ [enable chmap API support]),
+ chmap="$enableval", chmap="no")
+
AC_ARG_ENABLE(libasound,
AS_HELP_STRING([--enable-libasound],
[build a ABI-compatible libasound.so]),
@@ -148,6 +153,7 @@ if test "$everything" = "yes"; then
tlv="yes"
user_elem="yes"
async="yes"
+ chmap="yes"
libasound="yes"
symfuncs="yes"
output_buffer="yes"
@@ -188,6 +194,13 @@ else
fi
AC_SUBST(SALSA_HAS_ASYNC_SUPPORT)
+if test "$chmap" = "yes"; then
+ SALSA_HAS_CHMAP_SUPPORT=1
+else
+ SALSA_HAS_CHMAP_SUPPORT=0
+fi
+AC_SUBST(SALSA_HAS_CHMAP_SUPPORT)
+
if test "$sndconf" = "yes"; then
SALSA_HAS_DUMMY_CONF=1
else
@@ -286,6 +299,7 @@ echo " - ALSA-sequencer dummy interface: $sndseq"
echo " - TLV (dB) support: $tlv"
echo " - User-space control element support: $user_elem"
echo " - Async handler support: $async"
+echo " - PCM chmap API support: $chmap"
echo " - Make ABI-compatible libasound.so: $libasound"
echo " - Mark deprecated attribute: $markdeprecated"
echo " - Support string-output via snd_output: $output_buffer"
diff --git a/src/asound.h b/src/asound.h
index efa472a..b85640c 100644
--- a/src/asound.h
+++ b/src/asound.h
@@ -450,6 +450,74 @@ enum {
/* Trick to make alsa-lib/acinclude.m4 happy */
#define SNDRV_PCM_IOCTL_REWIND SNDRV_PCM_IOCTL_REWIND
+#if SALSA_HAS_CHMAP_SUPPORT
+#define SND_CHMAP_API_VERSION ((1 << 16) | (0 << 8) | 1)
+
+enum snd_pcm_chmap_type {
+ SND_CHMAP_TYPE_NONE = 0,
+ SND_CHMAP_TYPE_FIXED,
+ SND_CHMAP_TYPE_VAR,
+ SND_CHMAP_TYPE_PAIRED,
+ SND_CHMAP_TYPE_LAST = SND_CHMAP_TYPE_PAIRED,
+};
+
+enum snd_pcm_chmap_position {
+ SND_CHMAP_UNKNOWN = 0,
+ SND_CHMAP_NA,
+ SND_CHMAP_MONO,
+ SND_CHMAP_FL,
+ SND_CHMAP_FR,
+ SND_CHMAP_RL,
+ SND_CHMAP_RR,
+ SND_CHMAP_FC,
+ SND_CHMAP_LFE,
+ SND_CHMAP_SL,
+ SND_CHMAP_SR,
+ SND_CHMAP_RC,
+ SND_CHMAP_FLC,
+ SND_CHMAP_FRC,
+ SND_CHMAP_RLC,
+ SND_CHMAP_RRC,
+ SND_CHMAP_FLW,
+ SND_CHMAP_FRW,
+ SND_CHMAP_FLH,
+ SND_CHMAP_FCH,
+ SND_CHMAP_FRH,
+ SND_CHMAP_TC,
+ SND_CHMAP_TFL,
+ SND_CHMAP_TFR,
+ SND_CHMAP_TFC,
+ SND_CHMAP_TRL,
+ SND_CHMAP_TRR,
+ SND_CHMAP_TRC,
+ SND_CHMAP_TFLC,
+ SND_CHMAP_TFRC,
+ SND_CHMAP_TSL,
+ SND_CHMAP_TSR,
+ SND_CHMAP_LLFE,
+ SND_CHMAP_RLFE,
+ SND_CHMAP_BC,
+ SND_CHMAP_BLC,
+ SND_CHMAP_BRC,
+ SND_CHMAP_LAST = SND_CHMAP_BRC,
+};
+
+#define SND_CHMAP_POSITION_MASK 0xffff
+
+#define SND_CHMAP_PHASE_INVERSE (0x01 << 16)
+#define SND_CHMAP_DRIVER_SPEC (0x02 << 16)
+
+typedef struct snd_pcm_chmap {
+ unsigned int channels;
+ unsigned int pos[0];
+} snd_pcm_chmap_t;
+
+typedef struct snd_pcm_chmap_query {
+ enum snd_pcm_chmap_type type;
+ snd_pcm_chmap_t map;
+} snd_pcm_chmap_query_t;
+#endif /* SALSA_HAS_CHMAP_SUPPORT */
+
/* RAW MIDI inteface */
#define SNDRV_RAWMIDI_VERSION SNDRV_PROTOCOL_VERSION(2, 0, 0)
@@ -869,6 +937,9 @@ typedef struct snd_ctl_event {
#define SND_CTL_TLVT_DB_RANGE 3
#define SND_CTL_TLVT_DB_MINMAX 4
#define SND_CTL_TLVT_DB_MINMAX_MUTE 5
+#define SND_CTL_TLVT_CHMAP_FIXED 0x101 /* fixed channel position */
+#define SND_CTL_TLVT_CHMAP_VAR 0x102 /* channels freely swappable */
+#define SND_CTL_TLVT_CHMAP_PAIRED 0x103 /* pair-wise swappable */
/* Mute state */
#define SND_CTL_TLV_DB_GAIN_MUTE -9999999
diff --git a/src/pcm.c b/src/pcm.c
index 0c4363b..7f562ab 100644
--- a/src/pcm.c
+++ b/src/pcm.c
@@ -27,6 +27,7 @@
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
+#include <ctype.h>
#include "pcm.h"
#include "control.h"
#include "local.h"
@@ -1106,3 +1107,349 @@ int snd_pcm_recover(snd_pcm_t *pcm, int err, int silent)
return err;
}
+#if SALSA_HAS_CHMAP_SUPPORT
+/*
+ * channel mapping API
+ */
+
+static int do_ctl_chmap(int card, int dev, int subdev, int stream,
+ int (*func)(snd_ctl_t *, snd_ctl_elem_id_t *, void *),
+ void *arg)
+{
+ char ctlname[32];
+ snd_ctl_t *ctl;
+ snd_ctl_elem_id_t id = {
+ .iface = SND_CTL_ELEM_IFACE_PCM,
+ .device = dev,
+ .index = subdev,
+ };
+ int ret;
+
+ sprintf(ctlname, "hw:%d", card);
+ ret = snd_ctl_open(&ctl, ctlname, 0);
+ if (ret < 0)
+ return ret;
+
+ if (stream == SND_PCM_STREAM_PLAYBACK)
+ strcpy((char *)id.name, "Playback Channel Map");
+ else
+ strcpy((char *)id.name, "Capture Channel Map");
+ ret = func(ctl, &id, arg);
+ snd_ctl_close(ctl);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+#define TLV_SIZE 256
+
+static int do_read_tlv(snd_ctl_t *ctl, snd_ctl_elem_id_t *id, void *tlv)
+{
+ return snd_ctl_elem_tlv_read(ctl, id, tlv, TLV_SIZE * 4);
+}
+
+static inline int is_chmap_type(int type)
+{
+ return (type >= SND_CTL_TLVT_CHMAP_FIXED &&
+ type <= SND_CTL_TLVT_CHMAP_PAIRED);
+}
+
+snd_pcm_chmap_query_t **
+snd_pcm_query_chmaps_from_hw(int card, int dev, int subdev,
+ snd_pcm_stream_t stream)
+{
+ unsigned int tlv[TLV_SIZE], *start;
+ snd_pcm_chmap_query_t **map;
+ int i, nums;
+
+ if (do_ctl_chmap(card, dev, subdev, stream, do_read_tlv, tlv) < 0)
+ return NULL;
+
+ if (tlv[0] != SND_CTL_TLVT_CONTAINER) {
+ if (!is_chmap_type(tlv[0]))
+ return NULL;
+ start = tlv;
+ nums = 1;
+ } else {
+ unsigned int *p;
+ int size;
+ start = tlv + 2;
+ size = tlv[1];
+ nums = 0;
+ for (p = start; size > 0; ) {
+ if (!is_chmap_type(p[0]))
+ return NULL;
+ nums++;
+ size -= p[1] + 8;
+ p += p[1] / 4 + 2;
+ }
+ }
+ map = calloc(nums + 1, sizeof(int *));
+ if (!map)
+ return NULL;
+ for (i = 0; i < nums; i++) {
+ map[i] = malloc(start[1] + 8);
+ if (!map[i]) {
+ snd_pcm_free_chmaps(map);
+ return NULL;
+ }
+ map[i]->type = start[0] - 0x100;
+ map[i]->map.channels = start[1] / 4;
+ memcpy(map[i]->map.pos, start + 2, start[1]);
+ start += start[1] / 4 + 2;
+ }
+ return map;
+}
+
+void snd_pcm_free_chmaps(snd_pcm_chmap_query_t **maps)
+{
+ snd_pcm_chmap_query_t **p = maps;
+ if (!maps)
+ return;
+ for (p = maps; *p; p++)
+ free(*p);
+ free(maps);
+}
+
+static int do_get_chmap(snd_ctl_t *ctl, snd_ctl_elem_id_t *id, void *arg)
+{
+ snd_ctl_elem_value_t val = { .id = *id };
+ snd_pcm_chmap_t *map = arg;
+ int i, ret;
+
+ ret = snd_ctl_elem_read(ctl, &val);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < map->channels; i++)
+ map->pos[i] = snd_ctl_elem_value_get_integer(&val, i);
+ return 0;
+}
+
+snd_pcm_chmap_t *snd_pcm_get_chmap(snd_pcm_t *pcm)
+{
+ snd_pcm_chmap_t *map;
+
+ map = malloc(pcm->channels * sizeof(map->pos[0]) + sizeof(*map));
+ if (!map)
+ return NULL;
+ map->channels = pcm->channels;
+
+ if (do_ctl_chmap(pcm->card, pcm->device, pcm->subdevice, pcm->stream,
+ do_get_chmap, map) < 0) {
+ free(map);
+ return NULL;
+ }
+ return map;
+}
+
+static int do_set_chmap(snd_ctl_t *ctl, snd_ctl_elem_id_t *id, void *arg)
+{
+ snd_ctl_elem_value_t val = { .id = *id };
+ snd_pcm_chmap_t *map = arg;
+ int i;
+
+ for (i = 0; i < map->channels; i++)
+ snd_ctl_elem_value_set_integer(&val, i, map->pos[i]);
+ return snd_ctl_elem_write(ctl, &val);
+}
+
+int snd_pcm_set_chmap(snd_pcm_t *pcm, const snd_pcm_chmap_t *map)
+{
+ int ret;
+
+ if (map->channels > 128)
+ return -EINVAL;
+
+ ret = do_ctl_chmap(pcm->card, pcm->device, pcm->subdevice, pcm->stream,
+ do_set_chmap, (void *)map);
+ if (ret == -ENOENT || ret == -EPERM )
+ ret = -ENXIO;
+ return ret;
+}
+
+#define _NAME(n) [SND_CHMAP_TYPE_##n] = #n
+const char *_snd_chmap_type_names[SND_CHMAP_TYPE_LAST + 1] = {
+ _NAME(NONE), _NAME(FIXED), _NAME(VAR), _NAME(PAIRED),
+};
+#undef _NAME
+
+#define _NAME(n) [SND_CHMAP_##n] = #n
+const char *_snd_chmap_names[SND_CHMAP_LAST + 1] = {
+ _NAME(UNKNOWN), _NAME(NA), _NAME(MONO),
+ _NAME(FL), _NAME(FR),
+ _NAME(RL), _NAME(RR),
+ _NAME(FC), _NAME(LFE),
+ _NAME(SL), _NAME(SR),
+ _NAME(RC), _NAME(FLC), _NAME(FRC), _NAME(RLC), _NAME(RRC),
+ _NAME(FLW), _NAME(FRW),
+ _NAME(FLH), _NAME(FCH), _NAME(FRH), _NAME(TC),
+ _NAME(TFL), _NAME(TFR), _NAME(TFC),
+ _NAME(TRL), _NAME(TRR), _NAME(TRC),
+ _NAME(TFLC), _NAME(TFRC), _NAME(TSL), _NAME(TSR),
+ _NAME(LLFE), _NAME(RLFE),
+ _NAME(BC), _NAME(BLC), _NAME(BRC),
+};
+#undef _NAME
+
+const char *_snd_chmap_long_names[SND_CHMAP_LAST + 1] = {
+ [SND_CHMAP_UNKNOWN] = "Unknown",
+ [SND_CHMAP_NA] = "Unused",
+ [SND_CHMAP_MONO] = "Mono",
+ [SND_CHMAP_FL] = "Front Left",
+ [SND_CHMAP_FR] = "Front Right",
+ [SND_CHMAP_RL] = "Rear Left",
+ [SND_CHMAP_RR] = "Rear Right",
+ [SND_CHMAP_FC] = "Front Center",
+ [SND_CHMAP_LFE] = "LFE",
+ [SND_CHMAP_SL] = "Side Left",
+ [SND_CHMAP_SR] = "Side Right",
+ [SND_CHMAP_RC] = "Rear Center",
+ [SND_CHMAP_FLC] = "Front Left Center",
+ [SND_CHMAP_FRC] = "Front Right Center",
+ [SND_CHMAP_RLC] = "Rear Left Center",
+ [SND_CHMAP_RRC] = "Rear Right Center",
+ [SND_CHMAP_FLW] = "Front Left Wide",
+ [SND_CHMAP_FRW] = "Front Right Wide",
+ [SND_CHMAP_FLH] = "Front Left High",
+ [SND_CHMAP_FCH] = "Front Center High",
+ [SND_CHMAP_FRH] = "Front Right High",
+ [SND_CHMAP_TC] = "Top Center",
+ [SND_CHMAP_TFL] = "Top Front Left",
+ [SND_CHMAP_TFR] = "Top Front Right",
+ [SND_CHMAP_TFC] = "Top Front Center",
+ [SND_CHMAP_TRL] = "Top Rear Left",
+ [SND_CHMAP_TRR] = "Top Rear Right",
+ [SND_CHMAP_TRC] = "Top Rear Center",
+ [SND_CHMAP_TFLC] = "Top Front Left Center",
+ [SND_CHMAP_TFRC] = "Top Front Right Center",
+ [SND_CHMAP_TSL] = "Top Side Left",
+ [SND_CHMAP_TSR] = "Top Side Right",
+ [SND_CHMAP_LLFE] = "Left LFE",
+ [SND_CHMAP_RLFE] = "Right LFE",
+ [SND_CHMAP_BC] = "Bottom Center",
+ [SND_CHMAP_BLC] = "Bottom Left Center",
+ [SND_CHMAP_BRC] = "Bottom Right Center",
+};
+
+int snd_pcm_chmap_print(const snd_pcm_chmap_t *map, size_t maxlen, char *buf)
+{
+ unsigned int i, len = 0;
+
+ for (i = 0; i < map->channels; i++) {
+ unsigned int p = map->pos[i] & SND_CHMAP_POSITION_MASK;
+ if (i > 0) {
+ len += snprintf(buf + len, maxlen - len, " ");
+ if (len >= maxlen)
+ return -ENOMEM;
+ }
+ if (map->pos[i] & SND_CHMAP_DRIVER_SPEC)
+ len += snprintf(buf + len, maxlen, "%d", p);
+ else {
+ const char *name = _snd_chmap_names[p];
+ if (name)
+ len += snprintf(buf + len, maxlen - len,
+ "%s", name);
+ else
+ len += snprintf(buf + len, maxlen - len,
+ "Ch%d", p);
+ }
+ if (len >= maxlen)
+ return -ENOMEM;
+ if (map->pos[i] & SND_CHMAP_PHASE_INVERSE) {
+ len += snprintf(buf + len, maxlen - len, "[INV]");
+ if (len >= maxlen)
+ return -ENOMEM;
+ }
+ }
+ return len;
+}
+
+static int str_to_chmap(const char *str, int len)
+{
+ int val;
+ unsigned long v;
+ char *p;
+
+ if (isdigit(*str)) {
+ v = strtoul(str, &p, 0);
+ if (v == (unsigned long)-1)
+ return -1;
+ val = v;
+ val |= SND_CHMAP_DRIVER_SPEC;
+ str = p;
+ } else if (!strncasecmp(str, "ch", 2)) {
+ v = strtoul(str + 2, &p, 0);
+ if (v == (unsigned long)-1)
+ return -1;
+ val = v;
+ str = p;
+ } else {
+ for (val = 0; val <= SND_CHMAP_LAST; val++) {
+ int slen;
+ slen = strlen(_snd_chmap_names[val]);
+ if (slen > len)
+ continue;
+ if (!strncasecmp(str, _snd_chmap_names[val], slen) &&
+ !isalpha(str[slen])) {
+ str += slen;
+ break;
+ }
+ }
+ if (val > SND_CHMAP_LAST)
+ return -1;
+ }
+ if (str && !strncasecmp(str, "[INV]", 5))
+ val |= SND_CHMAP_PHASE_INVERSE;
+ return val;
+}
+
+unsigned int snd_pcm_chmap_from_string(const char *str)
+{
+ return str_to_chmap(str, strlen(str));
+}
+
+snd_pcm_chmap_t *snd_pcm_chmap_parse_string(const char *str)
+{
+ int i, ch = 0;
+ int tmp_map[64];
+ snd_pcm_chmap_t *map;
+
+ for (;;) {
+ const char *p;
+ int len, val;
+
+ if (ch >= (int)(sizeof(tmp_map) / sizeof(tmp_map[0])))
+ return NULL;
+ for (p = str; *p && isalnum(*p); p++)
+ ;
+ len = p - str;
+ if (!len)
+ return NULL;
+ val = str_to_chmap(str, len);
+ if (val < 0)
+ return NULL;
+ str += len;
+ if (*str == '[') {
+ if (!strncmp(str, "[INV]", 5)) {
+ val |= SND_CHMAP_PHASE_INVERSE;
+ str += 5;
+ }
+ }
+ tmp_map[ch] = val;
+ ch++;
+ for (; *str && !isalnum(*str); str++)
+ ;
+ if (!*str)
+ break;
+ }
+ map = malloc(sizeof(*map) + ch * sizeof(int));
+ if (!map)
+ return NULL;
+ map->channels = ch;
+ for (i = 0; i < ch; i++)
+ map->pos[i] = tmp_map[i];
+ return map;
+}
+
+#endif /* SALSA_HAS_CHMAP_SUPPORT */
diff --git a/src/pcm_func.h b/src/pcm_func.h
index d1c6b42..3224853 100644
--- a/src/pcm_func.h
+++ b/src/pcm_func.h
@@ -153,3 +153,15 @@ int snd_async_add_pcm_handler(snd_async_handler_t **handler, snd_pcm_t *pcm,
snd_async_callback_t callback,
void *private_data);
#endif
+
+#if SALSA_HAS_CHMAP_SUPPORT
+void snd_pcm_free_chmaps(snd_pcm_chmap_query_t **maps);
+snd_pcm_chmap_query_t **
+snd_pcm_query_chmaps_from_hw(int card, int dev, int subdev,
+ snd_pcm_stream_t stream);
+snd_pcm_chmap_t *snd_pcm_get_chmap(snd_pcm_t *pcm);
+int snd_pcm_set_chmap(snd_pcm_t *pcm, const snd_pcm_chmap_t *map);
+int snd_pcm_chmap_print(const snd_pcm_chmap_t *map, size_t maxlen, char *buf);
+unsigned int snd_pcm_chmap_from_string(const char *str);
+snd_pcm_chmap_t *snd_pcm_chmap_parse_string(const char *str);
+#endif
diff --git a/src/pcm_macros.h b/src/pcm_macros.h
index 04c8b0a..cec909d 100644
--- a/src/pcm_macros.h
+++ b/src/pcm_macros.h
@@ -2335,4 +2335,35 @@ u_int8_t snd_pcm_format_silence(snd_pcm_format_t format)
return (u_int8_t)snd_pcm_format_silence_64(format);
}
+#if SALSA_HAS_CHMAP_SUPPORT
+__SALSA_EXPORT_FUNC
+snd_pcm_chmap_query_t **snd_pcm_query_chmaps(snd_pcm_t *pcm)
+{
+ return snd_pcm_query_chmaps_from_hw(pcm->card, pcm->device,
+ pcm->subdevice, pcm->stream);
+}
+
+extern const char *_snd_chmap_type_names[];
+extern const char *_snd_chmap_names[];
+extern const char *_snd_chmap_long_names[];
+
+__SALSA_EXPORT_FUNC
+const char *snd_pcm_chmap_type_name(enum snd_pcm_chmap_type val)
+{
+ return _snd_chmap_type_names[val];
+}
+
+__SALSA_EXPORT_FUNC
+const char *snd_pcm_chmap_name(enum snd_pcm_chmap_position val)
+{
+ return _snd_chmap_names[val];
+}
+
+__SALSA_EXPORT_FUNC
+const char *snd_pcm_chmap_long_name(enum snd_pcm_chmap_position val)
+{
+ return _snd_chmap_long_names[val];
+}
+#endif /* SALSA_HAS_CHMAP_SUPPORT */
+
#endif /* __ALSA_PCM_MACROS_H */
diff --git a/src/recipe.h.in b/src/recipe.h.in
index c444f13..bbbdee3 100644
--- a/src/recipe.h.in
+++ b/src/recipe.h.in
@@ -10,6 +10,9 @@
/* Build with user-space control element support */
#define SALSA_HAS_USER_ELEM_SUPPORT @SALSA_HAS_USER_ELEM_SUPPORT@
+/* Build with chmap API support */
+#define SALSA_HAS_CHMAP_SUPPORT @SALSA_HAS_CHMAP_SUPPORT@
+
/* Build with dummy conf support */
#define SALSA_HAS_DUMMY_CONF @SALSA_HAS_DUMMY_CONF@