Discussion:
[PATCH] introduce channel mappings
Johannes Weißl
2011-05-13 15:13:43 UTC
Permalink
for >2 channels, it is not clear for the sound server which speaker
should receive which channel (there is no default mapping!). This
patch forwards the mapping which was specified by the input format
to the output plugin.
---
Makefile | 2 +-
aac.c | 26 +++++++++++++++-
aac.h | 41 +++++++++++++++++++++++++
alsa.c | 2 +-
ao.c | 55 ++++++++++++++++++++++++++++++++-
arts.c | 2 +-
channelmap.c | 63 +++++++++++++++++++++++++++++++++++++++
channelmap.h | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
ffmpeg.c | 1 +
flac.c | 12 +++++++
input.c | 9 +++++-
input.h | 2 +
ip.h | 2 +
mad.c | 1 +
mikmod.c | 1 +
modplug.c | 1 +
mp4.c | 32 +++++++++++++++++++-
mpc.c | 1 +
op.h | 3 +-
oss.c | 2 +-
output.c | 6 ++--
output.h | 3 +-
player.c | 32 ++++++++++++--------
pulse.c | 34 +++++++++++++++++----
roar.c | 2 +-
sun.c | 2 +-
vorbis.c | 47 +++++++++++++++++++++++++++++
wav.c | 8 ++--
waveout.c | 2 +-
wavpack.c | 3 +-
30 files changed, 450 insertions(+), 41 deletions(-)
create mode 100644 aac.h
create mode 100644 channelmap.c
create mode 100644 channelmap.h

diff --git a/Makefile b/Makefile
index bfb2088..db4a841 100644
--- a/Makefile
+++ b/Makefile
@@ -31,7 +31,7 @@ main.o server.o: CFLAGS += -DDEFAULT_PORT=3000
# programs {{{
cmus-y := \
ape.o browser.o buffer.o cache.o cmdline.o cmus.o command_mode.o comment.o \
- convert.lo debug.o editable.o expr.o filters.o \
+ channelmap.o convert.lo debug.o editable.o expr.o filters.o \
format_print.o gbuf.o glob.o help.o history.o http.o id3.o input.o job.o \
keys.o keyval.o lib.o load_dir.o locking.o mergesort.o misc.o options.o \
output.o pcm.o pl.o play_queue.o player.o \
diff --git a/aac.c b/aac.c
index 3b3552a..ce07a43 100644
--- a/aac.c
+++ b/aac.c
@@ -22,6 +22,7 @@
#include "id3.h"
#include "comment.h"
#include "read_wrapper.h"
+#include "aac.h"

#include <neaacdec.h>

@@ -186,6 +187,27 @@ static int buffer_fill_frame(struct input_plugin_data *ip_data)
/* not reached */
}

+static void aac_get_channel_map(struct input_plugin_data *ip_data)
+{
+ struct aac_private *priv = ip_data->private;
+ NeAACDecFrameInfo frame_info;
+ void *buf;
+ int i;
+
+ ip_data->channel_map[0] = CHANNEL_POSITION_INVALID;
+
+ if (buffer_fill_frame(ip_data) <= 0)
+ return;
+
+ buf = NeAACDecDecode(priv->decoder, &frame_info, buffer_data(ip_data), buffer_length(ip_data));
+ if (!buf || frame_info.error != 0 || frame_info.bytesconsumed <= 0
+ || frame_info.channels > CHANNELS_MAX)
+ return;
+
+ for (i = 0; i < frame_info.channels; i++)
+ ip_data->channel_map[i] = channel_position_aac(frame_info.channel_position[i]);
+}
+
static int aac_open(struct input_plugin_data *ip_data)
{
struct aac_private *priv;
@@ -205,7 +227,7 @@ static int aac_open(struct input_plugin_data *ip_data)
/* set decoder config */
neaac_cfg = NeAACDecGetCurrentConfiguration(priv->decoder);
neaac_cfg->outputFormat = FAAD_FMT_16BIT; /* force 16 bit audio */
- neaac_cfg->downMatrix = 1; /* 5.1 -> stereo */
+ neaac_cfg->downMatrix = 0; /* NOT 5.1 -> stereo */
neaac_cfg->dontUpSampleImplicitSBR = 0; /* upsample, please! */
NeAACDecSetConfiguration(priv->decoder, neaac_cfg);

@@ -250,6 +272,8 @@ static int aac_open(struct input_plugin_data *ip_data)
#if defined(WORDS_BIGENDIAN)
ip_data->sf |= sf_bigendian(1);
#endif
+ aac_get_channel_map(ip_data);
+
return 0;
out:
NeAACDecClose(priv->decoder);
diff --git a/aac.h b/aac.h
new file mode 100644
index 0000000..2ed01ca
--- /dev/null
+++ b/aac.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2011 Various Authors
+ * Copyright 2011 Johannes Weißl
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _AAC_H
+#define _AAC_H
+
+#include "channelmap.h"
+#include <neaacdec.h>
+
+static inline channel_position_t channel_position_aac(unsigned char c)
+{
+ switch (c) {
+ case FRONT_CHANNEL_CENTER: return CHANNEL_POSITION_FRONT_CENTER;
+ case FRONT_CHANNEL_LEFT: return CHANNEL_POSITION_FRONT_LEFT;
+ case FRONT_CHANNEL_RIGHT: return CHANNEL_POSITION_FRONT_RIGHT;
+ case SIDE_CHANNEL_LEFT: return CHANNEL_POSITION_SIDE_LEFT;
+ case SIDE_CHANNEL_RIGHT: return CHANNEL_POSITION_SIDE_RIGHT;
+ case BACK_CHANNEL_LEFT: return CHANNEL_POSITION_REAR_LEFT;
+ case BACK_CHANNEL_RIGHT: return CHANNEL_POSITION_REAR_RIGHT;
+ case BACK_CHANNEL_CENTER: return CHANNEL_POSITION_REAR_CENTER;
+ case LFE_CHANNEL: return CHANNEL_POSITION_LFE;
+ default: return CHANNEL_POSITION_INVALID;
+ }
+}
+
+#endif
diff --git a/alsa.c b/alsa.c
index 7acbbdd..a872d45 100644
--- a/alsa.c
+++ b/alsa.c
@@ -178,7 +178,7 @@ out:
return rc;
}

-static int op_alsa_open(sample_format_t sf)
+static int op_alsa_open(sample_format_t sf, const channel_position_t *channel_map)
{
int rc;

diff --git a/ao.c b/ao.c
index 0c79ae7..57f9f47 100644
--- a/ao.c
+++ b/ao.c
@@ -20,6 +20,7 @@
#include "xmalloc.h"
#include "utils.h"
#include "misc.h"
+#include "debug.h"

/*
* <ao/ao.h> uses FILE but doesn't include stdio.h.
@@ -55,13 +56,61 @@ static int op_ao_exit(void)
return 0;
}

-static int op_ao_open(sample_format_t sf)
+/* http://www.xiph.org/ao/doc/ao_sample_format.html */
+static const struct {
+ channel_position_t pos;
+ const char *str;
+} ao_channel_mapping[] = {
+ { CHANNEL_POSITION_LEFT, "L" },
+ { CHANNEL_POSITION_RIGHT, "R" },
+ { CHANNEL_POSITION_CENTER, "C" },
+ { CHANNEL_POSITION_MONO, "M" },
+ { CHANNEL_POSITION_FRONT_LEFT_OF_CENTER, "CL" },
+ { CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER, "CR" },
+ { CHANNEL_POSITION_REAR_LEFT, "BL" },
+ { CHANNEL_POSITION_REAR_RIGHT, "BR" },
+ { CHANNEL_POSITION_REAR_CENTER, "BC" },
+ { CHANNEL_POSITION_SIDE_LEFT, "SL" },
+ { CHANNEL_POSITION_SIDE_RIGHT, "SR" },
+ { CHANNEL_POSITION_LFE, "LFE" },
+ { CHANNEL_POSITION_INVALID, "X" },
+};
+
+static char *ao_channel_matrix(int channels, const channel_position_t *map)
+{
+ int i, j;
+ char buf[256] = "";
+
+ if (!map || !channel_map_valid(map))
+ return NULL;
+
+ for (i = 0; i < channels; i++) {
+ const channel_position_t pos = map[i];
+ int found = 0;
+ for (j = 0; j < N_ELEMENTS(ao_channel_mapping); j++) {
+ if (pos == ao_channel_mapping[j].pos) {
+ strcat(buf, ao_channel_mapping[j].str);
+ strcat(buf, ",");
+ found = 1;
+ break;
+ }
+ }
+ if (!found)
+ strcat(buf, "M,");
+ }
+ buf[strlen(buf)-1] = '\0';
+
+ return xstrdup(buf);
+}
+
+static int op_ao_open(sample_format_t sf, const channel_position_t *channel_map)
{
ao_sample_format format = {
.bits = sf_get_bits(sf),
.rate = sf_get_rate(sf),
.channels = sf_get_channels(sf),
- .byte_format = sf_get_bigendian(sf) ? AO_FMT_BIG : AO_FMT_LITTLE
+ .byte_format = sf_get_bigendian(sf) ? AO_FMT_BIG : AO_FMT_LITTLE,
+ .matrix = ao_channel_matrix(sf_get_channels(sf), channel_map)
};
int driver;

@@ -109,6 +158,8 @@ static int op_ao_open(sample_format_t sf)
return -OP_ERROR_INTERNAL;
}
}
+
+ d_print("channel matrix: %s\n", format.matrix ? format.matrix : "default");
return 0;
}

diff --git a/arts.c b/arts.c
index 3f4ef12..c61f183 100644
--- a/arts.c
+++ b/arts.c
@@ -43,7 +43,7 @@ static int op_arts_exit(void)
return 0;
}

-static int op_arts_open(sample_format_t sf)
+static int op_arts_open(sample_format_t sf, const channel_position_t *channel_map)
{
int buffer_time, server_latency, total_latency;
int blocking;
diff --git a/channelmap.c b/channelmap.c
new file mode 100644
index 0000000..c46a5bb
--- /dev/null
+++ b/channelmap.c
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2011 Various Authors
+ * Copyright 2011 Johannes Weißl
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "channelmap.h"
+#include "utils.h"
+
+void channel_map_init_waveex(int channels, unsigned int mask, channel_position_t *map)
+{
+ /* http://www.microsoft.com/whdc/device/audio/multichaud.mspx#EMLAC */
+ const channel_position_t channel_map_waveex[] = {
+ CHANNEL_POSITION_FRONT_LEFT,
+ CHANNEL_POSITION_FRONT_RIGHT,
+ CHANNEL_POSITION_FRONT_CENTER,
+ CHANNEL_POSITION_LFE,
+ CHANNEL_POSITION_REAR_LEFT,
+ CHANNEL_POSITION_REAR_RIGHT,
+ CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,
+ CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER,
+ CHANNEL_POSITION_REAR_CENTER,
+ CHANNEL_POSITION_SIDE_LEFT,
+ CHANNEL_POSITION_SIDE_RIGHT,
+ CHANNEL_POSITION_TOP_CENTER,
+ CHANNEL_POSITION_TOP_FRONT_LEFT,
+ CHANNEL_POSITION_TOP_FRONT_CENTER,
+ CHANNEL_POSITION_TOP_FRONT_RIGHT,
+ CHANNEL_POSITION_TOP_REAR_LEFT,
+ CHANNEL_POSITION_TOP_REAR_CENTER,
+ CHANNEL_POSITION_TOP_REAR_RIGHT
+ };
+
+ if (channels == 1) {
+ map[0] = CHANNEL_POSITION_MONO;
+ } else if (channels > 1 && channels < N_ELEMENTS(channel_map_waveex)) {
+ int i, j = 0;
+
+ if (!mask)
+ mask = (1 << channels) - 1;
+
+ for (i = 0; i < N_ELEMENTS(channel_map_waveex); i++) {
+ if (mask & (1 << i))
+ map[j++] = channel_map_waveex[i];
+ }
+ if (j != channels)
+ map[0] = CHANNEL_POSITION_INVALID;
+ } else {
+ map[0] = CHANNEL_POSITION_INVALID;
+ }
+}
diff --git a/channelmap.h b/channelmap.h
new file mode 100644
index 0000000..dd06acb
--- /dev/null
+++ b/channelmap.h
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2011 Various Authors
+ * Copyright 2011 Johannes Weißl
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU 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
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _CHANNELMAP_H
+#define _CHANNELMAP_H
+
+#include <string.h>
+
+#define CHANNELS_MAX 32
+
+/* Modelled after PulseAudio */
+enum channel_position {
+ CHANNEL_POSITION_INVALID = -1,
+ CHANNEL_POSITION_MONO = 0,
+ CHANNEL_POSITION_FRONT_LEFT,
+ CHANNEL_POSITION_FRONT_RIGHT,
+ CHANNEL_POSITION_FRONT_CENTER,
+
+ CHANNEL_POSITION_LEFT = CHANNEL_POSITION_FRONT_LEFT,
+ CHANNEL_POSITION_RIGHT = CHANNEL_POSITION_FRONT_RIGHT,
+ CHANNEL_POSITION_CENTER = CHANNEL_POSITION_FRONT_CENTER,
+
+ CHANNEL_POSITION_REAR_CENTER,
+ CHANNEL_POSITION_REAR_LEFT,
+ CHANNEL_POSITION_REAR_RIGHT,
+
+ CHANNEL_POSITION_LFE,
+ CHANNEL_POSITION_SUBWOOFER = CHANNEL_POSITION_LFE,
+
+ CHANNEL_POSITION_FRONT_LEFT_OF_CENTER,
+ CHANNEL_POSITION_FRONT_RIGHT_OF_CENTER,
+
+ CHANNEL_POSITION_SIDE_LEFT,
+ CHANNEL_POSITION_SIDE_RIGHT,
+
+ CHANNEL_POSITION_TOP_CENTER,
+
+ CHANNEL_POSITION_TOP_FRONT_LEFT,
+ CHANNEL_POSITION_TOP_FRONT_RIGHT,
+ CHANNEL_POSITION_TOP_FRONT_CENTER,
+
+ CHANNEL_POSITION_TOP_REAR_LEFT,
+ CHANNEL_POSITION_TOP_REAR_RIGHT,
+ CHANNEL_POSITION_TOP_REAR_CENTER,
+
+ CHANNEL_POSITION_MAX
+};
+
+typedef enum channel_position channel_position_t;
+
+#define CHANNEL_MAP_INIT { CHANNEL_POSITION_INVALID }
+
+#define CHANNEL_MAP(name) \
+ channel_position_t name[CHANNELS_MAX] = CHANNEL_MAP_INIT
+
+static inline int channel_map_valid(const channel_position_t *map)
+{
+ return map[0] != CHANNEL_POSITION_INVALID;
+}
+
+static inline int channel_map_equal(const channel_position_t *a, const channel_position_t *b, int channels)
+{
+ return memcmp(a, b, sizeof(*a) * channels) == 0;
+}
+
+static inline channel_position_t *channel_map_copy(channel_position_t *dst, const channel_position_t *src)
+{
+ return memcpy(dst, src, sizeof(*dst) * CHANNELS_MAX);
+}
+
+static inline void channel_map_init_stereo(channel_position_t *map)
+{
+ map[0] = CHANNEL_POSITION_LEFT;
+ map[1] = CHANNEL_POSITION_RIGHT;
+}
+
+void channel_map_init_waveex(int channels, unsigned int mask, channel_position_t *map);
+
+#endif
diff --git a/ffmpeg.c b/ffmpeg.c
index 8095abe..64d97f0 100644
--- a/ffmpeg.c
+++ b/ffmpeg.c
@@ -258,6 +258,7 @@ static int ffmpeg_open(struct input_plugin_data *ip_data)
#ifdef WORDS_BIGENDIAN
ip_data->sf |= sf_bigendian(1);
#endif
+ channel_map_init_waveex(cc->channels, cc->channel_layout, ip_data->channel_map);
return 0;
}

diff --git a/flac.c b/flac.c
index 5d82833..53fdde0 100644
--- a/flac.c
+++ b/flac.c
@@ -375,6 +375,17 @@ static void free_priv(struct input_plugin_data *ip_data)
errno = save;
}

+/* http://flac.sourceforge.net/format.html#frame_header */
+static void channel_map_init_flac(int channels, channel_position_t *map)
+{
+ unsigned int mask = 0;
+ if (channels == 4)
+ mask = 0x33; // 0b110011, without center and lfe
+ else if (channels == 5)
+ mask = 0x37; // 0b110111, without lfe
+ channel_map_init_waveex(channels, mask, map);
+}
+
static int flac_open(struct input_plugin_data *ip_data)
{
struct flac_private *priv;
@@ -458,6 +469,7 @@ static int flac_open(struct input_plugin_data *ip_data)
return -IP_ERROR_SAMPLE_FORMAT;
}

+ channel_map_init_flac(sf_get_channels(ip_data->sf), ip_data->channel_map);
d_print("sr: %d, ch: %d, bits: %d\n",
sf_get_rate(ip_data->sf),
sf_get_channels(ip_data->sf),
diff --git a/input.c b/input.c
index 93c3aaa..2f5d43d 100644
--- a/input.c
+++ b/input.c
@@ -386,7 +386,8 @@ static void ip_init(struct input_plugin *ip, char *filename)
.data = {
.fd = -1,
.filename = filename,
- .remote = is_url(filename)
+ .remote = is_url(filename),
+ .channel_map = CHANNEL_MAP_INIT
}
};
*ip = t;
@@ -742,6 +743,12 @@ sample_format_t ip_get_sf(struct input_plugin *ip)
return ip->data.sf;
}

+void ip_get_channel_map(struct input_plugin *ip, channel_position_t *channel_map)
+{
+ BUG_ON(!ip->open);
+ channel_map_copy(channel_map, ip->data.channel_map);
+}
+
const char *ip_get_filename(struct input_plugin *ip)
{
return ip->data.filename;
diff --git a/input.h b/input.h
index 7fd4a63..549ed55 100644
--- a/input.h
+++ b/input.h
@@ -21,6 +21,7 @@

#include "keyval.h"
#include "sf.h"
+#include "channelmap.h"

struct input_plugin;

@@ -70,6 +71,7 @@ char *ip_codec(struct input_plugin *ip);
char *ip_codec_profile(struct input_plugin *ip);

sample_format_t ip_get_sf(struct input_plugin *ip);
+void ip_get_channel_map(struct input_plugin *ip, channel_position_t *channel_map);
const char *ip_get_filename(struct input_plugin *ip);
const char *ip_get_metadata(struct input_plugin *ip);
int ip_is_remote(struct input_plugin *ip);
diff --git a/ip.h b/ip.h
index 29f1975..71117f1 100644
--- a/ip.h
+++ b/ip.h
@@ -21,6 +21,7 @@

#include "keyval.h"
#include "sf.h"
+#include "channelmap.h"

#ifndef __GNUC__
#include <fcntl.h>
@@ -72,6 +73,7 @@ struct input_plugin_data {

/* filled by plugin */
sample_format_t sf;
+ channel_position_t channel_map[CHANNELS_MAX];
void *private;
};

diff --git a/mad.c b/mad.c
index 2d868ce..0d2c475 100644
--- a/mad.c
+++ b/mad.c
@@ -86,6 +86,7 @@ static int mad_open(struct input_plugin_data *ip_data)
/* always 16-bit signed little-endian */
ip_data->sf = sf_rate(info->sample_rate) | sf_channels(info->channels) |
sf_bits(16) | sf_signed(1);
+ channel_map_init_waveex(info->channels, 0, ip_data->channel_map);
return 0;
}

diff --git a/mikmod.c b/mikmod.c
index 259bd9c..a6c22f4 100644
--- a/mikmod.c
+++ b/mikmod.c
@@ -76,6 +76,7 @@ static int mik_open(struct input_plugin_data *ip_data)
#ifdef WORDS_BIGENDIAN
ip_data->sf |= sf_bigendian(1);
#endif
+ channel_map_init_stereo(ip_data->channel_map);
return 0;
}

diff --git a/modplug.c b/modplug.c
index c00ecea..6a6f272 100644
--- a/modplug.c
+++ b/modplug.c
@@ -93,6 +93,7 @@ static int mod_open(struct input_plugin_data *ip_data)
#ifdef WORDS_BIGENDIAN
ip_data->sf |= sf_bigendian(1);
#endif
+ channel_map_init_stereo(ip_data->channel_map);
return 0;
}

diff --git a/mp4.c b/mp4.c
index 53aa574..2eb12bc 100644
--- a/mp4.c
+++ b/mp4.c
@@ -22,6 +22,7 @@
#include "file.h"
#include "config/mp4.h"
#include "comment.h"
+#include "aac.h"

#if USE_MPEG4IP
#include <mp4.h>
@@ -92,6 +93,34 @@ static MP4TrackId mp4_get_track(MP4FileHandle *handle)
return MP4_INVALID_TRACK_ID;
}

+static void mp4_get_channel_map(struct input_plugin_data *ip_data)
+{
+ struct mp4_private *priv = ip_data->private;
+ unsigned char *aac_data = NULL;
+ unsigned int aac_data_len = 0;
+ NeAACDecFrameInfo frame_info;
+ int i;
+
+ ip_data->channel_map[0] = CHANNEL_POSITION_INVALID;
+
+ if (MP4ReadSample(priv->mp4.handle, priv->mp4.track, priv->mp4.sample,
+ &aac_data, &aac_data_len, NULL, NULL, NULL, NULL) == 0)
+ return;
+
+ if (!aac_data)
+ return;
+
+ NeAACDecDecode(priv->decoder, &frame_info, aac_data, aac_data_len);
+ free(aac_data);
+
+ if (frame_info.error != 0 || frame_info.bytesconsumed <= 0
+ || frame_info.channels > CHANNELS_MAX)
+ return;
+
+ for (i = 0; i < frame_info.channels; i++)
+ ip_data->channel_map[i] = channel_position_aac(frame_info.channel_position[i]);
+}
+
static int mp4_open(struct input_plugin_data *ip_data)
{
struct mp4_private *priv;
@@ -118,7 +147,7 @@ static int mp4_open(struct input_plugin_data *ip_data)
/* set decoder config */
neaac_cfg = NeAACDecGetCurrentConfiguration(priv->decoder);
neaac_cfg->outputFormat = FAAD_FMT_16BIT; /* force 16 bit audio */
- neaac_cfg->downMatrix = 1; /* 5.1 -> stereo */
+ neaac_cfg->downMatrix = 0; /* NOT 5.1 -> stereo */
NeAACDecSetConfiguration(priv->decoder, neaac_cfg);

/* open mpeg-4 file */
@@ -165,6 +194,7 @@ static int mp4_open(struct input_plugin_data *ip_data)
#if defined(WORDS_BIGENDIAN)
ip_data->sf |= sf_bigendian(1);
#endif
+ mp4_get_channel_map(ip_data);

return 0;

diff --git a/mpc.c b/mpc.c
index 6561328..a178e60 100644
--- a/mpc.c
+++ b/mpc.c
@@ -177,6 +177,7 @@ static int mpc_open(struct input_plugin_data *ip_data)

ip_data->sf = sf_rate(priv->info.sample_freq) | sf_channels(priv->info.channels) |
sf_bits(16) | sf_signed(1);
+ channel_map_init_waveex(priv->info.channels, 0, ip_data->channel_map);
return 0;
}

diff --git a/op.h b/op.h
index 9647dad..0290ff2 100644
--- a/op.h
+++ b/op.h
@@ -20,6 +20,7 @@
#define _OP_H

#include "sf.h"
+#include "channelmap.h"

#ifndef __GNUC__
#include <fcntl.h>
@@ -49,7 +50,7 @@ enum {
struct output_plugin_ops {
int (*init)(void);
int (*exit)(void);
- int (*open)(sample_format_t sf);
+ int (*open)(sample_format_t sf, const channel_position_t *channel_map);
int (*close)(void);
int (*drop)(void);
int (*write)(const char *buffer, int count);
diff --git a/oss.c b/oss.c
index 57ec3f5..0a84d20 100644
--- a/oss.c
+++ b/oss.c
@@ -170,7 +170,7 @@ static int oss_exit(void)
return 0;
}

-static int oss_open(sample_format_t sf)
+static int oss_open(sample_format_t sf, const channel_position_t *channel_map)
{
int oss_version = 0;
oss_fd = open(oss_dsp_device, O_WRONLY);
diff --git a/output.c b/output.c
index 949786e..e94f279 100644
--- a/output.c
+++ b/output.c
@@ -233,7 +233,7 @@ int op_select_any(void)
rc = select_plugin(o);
if (rc != 0)
continue;
- rc = o->pcm_ops->open(sf);
+ rc = o->pcm_ops->open(sf, NULL);
if (rc == 0) {
o->pcm_ops->close();
break;
@@ -242,11 +242,11 @@ int op_select_any(void)
return rc;
}

-int op_open(sample_format_t sf)
+int op_open(sample_format_t sf, const channel_position_t *channel_map)
{
if (op == NULL)
return -OP_ERROR_NOT_INITIALIZED;
- return op->pcm_ops->open(sf);
+ return op->pcm_ops->open(sf, channel_map);
}

int op_drop(void)
diff --git a/output.h b/output.h
index 8445d7c..06ea894 100644
--- a/output.h
+++ b/output.h
@@ -20,6 +20,7 @@
#define _OUTPUT_H

#include "sf.h"
+#include "channelmap.h"

extern int volume_max;
extern int volume_l;
@@ -41,7 +42,7 @@ int op_select_any(void);
*
* errors: OP_ERROR_{}
*/
-int op_open(sample_format_t sf);
+int op_open(sample_format_t sf, const channel_position_t *channel_map);

/*
* drop pcm data
diff --git a/player.c b/player.c
index 674641f..2c3751f 100644
--- a/player.c
+++ b/player.c
@@ -83,6 +83,7 @@ int soft_vol_r;
static const struct player_callbacks *player_cbs = NULL;

static sample_format_t buffer_sf;
+static CHANNEL_MAP(buffer_channel_map);

static pthread_t producer_thread;
static pthread_mutex_t producer_mutex = CMUS_MUTEX_INITIALIZER;
@@ -134,9 +135,10 @@ static void reset_buffer(void)
pthread_cond_broadcast(&producer_playing);
}

-static void set_buffer_sf(sample_format_t sf)
+static void set_buffer_sf(void)
{
- buffer_sf = sf;
+ buffer_sf = ip_get_sf(ip);
+ ip_get_channel_map(ip, buffer_channel_map);

/* ip_read converts samples to this format */
if (sf_get_channels(buffer_sf) <= 2 && sf_get_bits(buffer_sf) <= 16) {
@@ -145,6 +147,7 @@ static void set_buffer_sf(sample_format_t sf)
#ifdef WORDS_BIGENDIAN
buffer_sf |= sf_bigendian(1);
#endif
+ channel_map_init_stereo(buffer_channel_map);
}
}

@@ -703,8 +706,8 @@ static void __consumer_play(void)
} else if (consumer_status == CS_STOPPED) {
int rc;

- set_buffer_sf(ip_get_sf(ip));
- rc = op_open(buffer_sf);
+ set_buffer_sf();
+ rc = op_open(buffer_sf, buffer_channel_map);
if (rc) {
player_op_error(rc, "opening audio device");
} else {
@@ -746,19 +749,22 @@ static void __consumer_pause(void)

/* setting consumer status }}} */

-static int change_sf(sample_format_t sf, int drop)
+
+static int change_sf(int drop)
{
int old_sf = buffer_sf;
+ CHANNEL_MAP(old_channel_map);
+ channel_map_copy(old_channel_map, buffer_channel_map);

- set_buffer_sf(sf);
- if (buffer_sf != old_sf) {
+ set_buffer_sf();
+ if (buffer_sf != old_sf || !channel_map_equal(buffer_channel_map, old_channel_map, sf_get_channels(buffer_sf))) {
/* reopen */
int rc;

if (drop)
op_drop();
op_close();
- rc = op_open(buffer_sf);
+ rc = op_open(buffer_sf, buffer_channel_map);
if (rc) {
player_op_error(rc, "opening audio device");
__consumer_status_update(CS_STOPPED);
@@ -810,7 +816,7 @@ static void __consumer_handle_eof(void)
} else {
/* PS_PLAYING */
file_changed(ti);
- if (!change_sf(ip_get_sf(ip), 0))
+ if (!change_sf(0))
__prebuffer();
}
} else {
@@ -1137,7 +1143,7 @@ void player_set_file(struct track_info *ti)
__consumer_stop();
goto out;
}
- change_sf(ip_get_sf(ip), 1);
+ change_sf(1);
}
out:
__player_status_changed();
@@ -1171,7 +1177,7 @@ void player_play_file(struct track_info *ti)
__producer_stop();
} else {
op_drop();
- change_sf(ip_get_sf(ip), 1);
+ change_sf(1);
}
out:
__player_status_changed();
@@ -1302,8 +1308,8 @@ void player_set_op(const char *name)
}

if (consumer_status == CS_PLAYING || consumer_status == CS_PAUSED) {
- set_buffer_sf(ip_get_sf(ip));
- rc = op_open(buffer_sf);
+ set_buffer_sf();
+ rc = op_open(buffer_sf, buffer_channel_map);
if (rc) {
__consumer_status_update(CS_STOPPED);
__producer_stop();
diff --git a/pulse.c b/pulse.c
index aef2f88..984a19c 100644
--- a/pulse.c
+++ b/pulse.c
@@ -313,10 +313,29 @@ static int op_pulse_exit(void)
return OP_ERROR_SUCCESS;
}

-static int op_pulse_open(sample_format_t sf)
+#define RET_IF(x) case CHANNEL_POSITION_ ## x: return PA_CHANNEL_POSITION_ ## x
+
+static pa_channel_position_t pulse_channel_position(channel_position_t p)
+{
+ switch (p) {
+ RET_IF(MONO);
+ RET_IF(FRONT_LEFT); RET_IF(FRONT_RIGHT); RET_IF(FRONT_CENTER);
+ RET_IF(REAR_CENTER); RET_IF(REAR_LEFT); RET_IF(REAR_RIGHT);
+ RET_IF(LFE);
+ RET_IF(FRONT_LEFT_OF_CENTER); RET_IF(FRONT_RIGHT_OF_CENTER);
+ RET_IF(SIDE_LEFT); RET_IF(SIDE_RIGHT);
+ RET_IF(TOP_CENTER);
+ RET_IF(TOP_FRONT_LEFT); RET_IF(TOP_FRONT_RIGHT); RET_IF(TOP_FRONT_CENTER);
+ RET_IF(TOP_REAR_LEFT); RET_IF(TOP_REAR_RIGHT); RET_IF(TOP_REAR_CENTER);
+ default:
+ return PA_CHANNEL_POSITION_INVALID;
+ }
+}
+
+static int op_pulse_open(sample_format_t sf, const channel_position_t *channel_map)
{
pa_proplist *pl;
- int rc;
+ int rc, i;

const pa_sample_spec ss = {
.format = __pa_sample_format(sf),
@@ -328,10 +347,13 @@ static int op_pulse_open(sample_format_t sf)
return -OP_ERROR_SAMPLE_FORMAT;

pa_ss = ss;
- /* can't be done in op_pulse_mixer_open() since channels is not known
- * TODO: use correct channel map once sample_format_t is updated */
- if (!pa_channel_map_init_auto(&pa_cmap, ss.channels, PA_CHANNEL_MAP_ALSA))
- ret_pa_last_error();
+ if (channel_map && channel_map_valid(channel_map)) {
+ pa_channel_map_init(&pa_cmap);
+ pa_cmap.channels = ss.channels;
+ for (i = 0; i < pa_cmap.channels; i++)
+ pa_cmap.map[i] = pulse_channel_position(channel_map[i]);
+ } else
+ pa_channel_map_init_auto(&pa_cmap, ss.channels, PA_CHANNEL_MAP_ALSA);

rc = __pa_create_context();
if (rc)
diff --git a/roar.c b/roar.c
index be4c00b..e37fc63 100644
--- a/roar.c
+++ b/roar.c
@@ -107,7 +107,7 @@ static int _set_role(void)
return 0;
}

-static int op_roar_open(sample_format_t sf)
+static int op_roar_open(sample_format_t sf, const channel_position_t *channel_map)
{
int codec = -1;
int ret;
diff --git a/sun.c b/sun.c
index eba111d..f71c187 100644
--- a/sun.c
+++ b/sun.c
@@ -142,7 +142,7 @@ static int sun_close(void)
return 0;
}

-static int sun_open(sample_format_t sf)
+static int sun_open(sample_format_t sf, const channel_position_t *channel_map)
{
sun_fd = open(sun_audio_device, O_WRONLY);
if (sun_fd == -1)
diff --git a/vorbis.c b/vorbis.c
index a086d65..93f3f01 100644
--- a/vorbis.c
+++ b/vorbis.c
@@ -104,6 +104,52 @@ static ov_callbacks callbacks = {
.tell_func = tell_func
};

+/* http://xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-800004.3.9 */
+static void channel_map_init_vorbis(int channels, channel_position_t *map)
+{
+ switch (channels) {
+ case 8:
+ channel_map_init_vorbis(7, map);
+ map[5] = CHANNEL_POSITION_REAR_LEFT;
+ map[6] = CHANNEL_POSITION_REAR_RIGHT;
+ map[7] = CHANNEL_POSITION_LFE;
+ break;
+ case 7:
+ channel_map_init_vorbis(3, map);
+ map[3] = CHANNEL_POSITION_SIDE_LEFT;
+ map[4] = CHANNEL_POSITION_SIDE_RIGHT;
+ map[5] = CHANNEL_POSITION_REAR_CENTER;
+ map[6] = CHANNEL_POSITION_LFE;
+ break;
+ case 6:
+ map[5] = CHANNEL_POSITION_LFE;
+ /* Fall through */
+ case 5:
+ map[3] = CHANNEL_POSITION_REAR_LEFT;
+ map[4] = CHANNEL_POSITION_REAR_RIGHT;
+ /* Fall through */
+ case 3:
+ map[0] = CHANNEL_POSITION_FRONT_LEFT;
+ map[1] = CHANNEL_POSITION_CENTER;
+ map[2] = CHANNEL_POSITION_FRONT_RIGHT;
+ break;
+ case 4:
+ map[2] = CHANNEL_POSITION_REAR_LEFT;
+ map[3] = CHANNEL_POSITION_REAR_RIGHT;
+ /* Fall through */
+ case 2:
+ map[0] = CHANNEL_POSITION_FRONT_LEFT;
+ map[1] = CHANNEL_POSITION_FRONT_RIGHT;
+ break;
+ case 1:
+ map[0] = CHANNEL_POSITION_MONO;
+ break;
+ default:
+ map[0] = CHANNEL_POSITION_INVALID;
+ break;
+ }
+}
+
static int vorbis_open(struct input_plugin_data *ip_data)
{
struct vorbis_private *priv;
@@ -129,6 +175,7 @@ static int vorbis_open(struct input_plugin_data *ip_data)
#ifdef WORDS_BIGENDIAN
ip_data->sf |= sf_bigendian(1);
#endif
+ channel_map_init_vorbis(vi->channels, ip_data->channel_map);
return 0;
}

diff --git a/wav.c b/wav.c
index 70efc98..5035f1e 100644
--- a/wav.c
+++ b/wav.c
@@ -141,7 +141,7 @@ static int wav_open(struct input_plugin_data *ip_data)
goto error_exit;
}
{
- unsigned int format_tag, channels, rate, bits;
+ unsigned int format_tag, channels, rate, bits, channel_mask = 0;

format_tag = read_le16(fmt + 0);
channels = read_le16(fmt + 2);
@@ -171,8 +171,7 @@ static int wav_open(struct input_plugin_data *ip_data)
rc = -IP_ERROR_FILE_FORMAT;
goto error_exit;
}
- /* speaker position mask */
- read_le32(fmt + 20);
+ channel_mask = read_le32(fmt + 20);
format_tag = read_le16(fmt + 24);
/* ignore rest of extension tag */
}
@@ -183,12 +182,13 @@ static int wav_open(struct input_plugin_data *ip_data)
rc = -IP_ERROR_UNSUPPORTED_FILE_TYPE;
goto error_exit;
}
- if ((bits != 8 && bits != 16 && bits != 24 && bits != 32) || channels < 1 || channels > 2) {
+ if ((bits != 8 && bits != 16 && bits != 24 && bits != 32) || channels < 1) {
rc = -IP_ERROR_SAMPLE_FORMAT;
goto error_exit;
}
ip_data->sf = sf_channels(channels) | sf_rate(rate) | sf_bits(bits) |
sf_signed(bits > 8);
+ channel_map_init_waveex(channels, channel_mask, ip_data->channel_map);
}

rc = find_chunk(ip_data->fd, "data", &priv->pcm_size);
diff --git a/waveout.c b/waveout.c
index 9c2a326..8813c3f 100644
--- a/waveout.c
+++ b/waveout.c
@@ -75,7 +75,7 @@ static void clean_buffers(void)
}
}

-static int waveout_open(sample_format_t sf)
+static int waveout_open(sample_format_t sf, const channel_position_t *channel_map)
{
WAVEFORMATEX format = {
.cbSize = sizeof(format),
diff --git a/wavpack.c b/wavpack.c
index 6694cba..dee0364 100644
--- a/wavpack.c
+++ b/wavpack.c
@@ -200,7 +200,7 @@ static int wavpack_open(struct input_plugin_data *ip_data)

priv->wpc = WavpackOpenFileInputEx(&callbacks, &priv->wv_file,
priv->has_wvc ? &priv->wvc_file : NULL, msg,
- OPEN_2CH_MAX | OPEN_NORMALIZE, 0);
+ OPEN_NORMALIZE, 0);

if (!priv->wpc) {
d_print("WavpackOpenFileInputEx failed: %s\n", msg);
@@ -212,6 +212,7 @@ static int wavpack_open(struct input_plugin_data *ip_data)
| sf_channels(WavpackGetReducedChannels(priv->wpc))
| sf_bits(WavpackGetBitsPerSample(priv->wpc))
| sf_signed(1);
+ channel_map_init_waveex(sf_get_channels(ip_data->sf), WavpackGetChannelMask(priv->wpc), ip_data->channel_map);
return 0;
}
--
1.7.5.1
Gregory Petrosyan
2011-05-15 09:23:59 UTC
Permalink
Post by Johannes Weißl
for >2 channels, it is not clear for the sound server which speaker
should receive which channel (there is no default mapping!). This
patch forwards the mapping which was specified by the input format
to the output plugin.
---
30 files changed, 450 insertions(+), 41 deletions(-)
Fantastic work! Pushed to -pu.

Everyone with more than stereo audio systems -- please give it a try and report
how it works for you!

Gregory

Loading...