diff --git a/NEWS b/NEWS
index b181a9a87..a920d5015 100644
--- a/NEWS
+++ b/NEWS
@@ -39,7 +39,7 @@ ver 0.16 (20??/??/??)
- jack: added option "client_name"
- jack: clear ring buffers before activating
- jack: renamed option "ports" to "destination_ports"
- - jack: support mono input
+ - jack: support more than two audio channels
- httpd: bind port when output is enabled
- wildcards allowed in audio_format configuration
- consistently lock audio output objects
diff --git a/doc/user.xml b/doc/user.xml
index 6eba599ed..aabbd33f6 100644
--- a/doc/user.xml
+++ b/doc/user.xml
@@ -756,6 +756,18 @@ cd mpd-version
launch the JACK daemon. Disabled by default.
+
+
+ source_ports
+ A,B
+
+
+ The names of the JACK source ports to be created.
+ By default, the ports "left" and "right" are
+ created. To use more ports, you have to tweak this
+ option.
+
+
destination_ports
diff --git a/src/output/jack_output_plugin.c b/src/output/jack_output_plugin.c
index 14493715f..d2f7747dc 100644
--- a/src/output/jack_output_plugin.c
+++ b/src/output/jack_output_plugin.c
@@ -42,10 +42,6 @@ enum {
static const size_t sample_size = sizeof(jack_default_audio_sample_t);
-static const char *const port_names[2] = {
- "left", "right",
-};
-
struct jack_data {
/**
* libjack options passed to jack_client_open().
@@ -56,6 +52,9 @@ struct jack_data {
/* configuration */
+ char *source_ports[MAX_PORTS];
+ unsigned num_source_ports;
+
char *destination_ports[MAX_PORTS];
unsigned num_destination_ports;
@@ -65,9 +64,9 @@ struct jack_data {
struct audio_format audio_format;
/* jack library stuff */
- jack_port_t *ports[2];
+ jack_port_t *ports[MAX_PORTS];
jack_client_t *client;
- jack_ringbuffer_t *ringbuffer[2];
+ jack_ringbuffer_t *ringbuffer[MAX_PORTS];
bool shutdown;
@@ -126,6 +125,16 @@ mpd_jack_process(jack_nframes_t nframes, void *arg)
out[available++] = 0.0;
}
+ /* generate silence for the unused source ports */
+
+ for (unsigned i = jd->audio_format.channels;
+ i < jd->num_source_ports; ++i) {
+ out = jack_port_get_buffer(jd->ports[i], nframes);
+
+ for (jack_nframes_t f = 0; f < nframes; ++f)
+ out[f] = 0.0;
+ }
+
return 0;
}
@@ -141,7 +150,9 @@ set_audioformat(struct jack_data *jd, struct audio_format *audio_format)
{
audio_format->sample_rate = jack_get_sample_rate(jd->client);
- if (audio_format->channels > 2)
+ if (jd->num_source_ports == 1)
+ audio_format->channels = 1;
+ else if (audio_format->channels > jd->num_source_ports)
audio_format->channels = 2;
if (audio_format->bits != 16 && audio_format->bits != 24)
@@ -200,14 +211,15 @@ mpd_jack_connect(struct jack_data *jd, GError **error_r)
jack_set_process_callback(jd->client, mpd_jack_process, jd);
jack_on_shutdown(jd->client, mpd_jack_shutdown, jd);
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->ports); ++i) {
- jd->ports[i] = jack_port_register(jd->client, port_names[i],
+ for (unsigned i = 0; i < jd->num_source_ports; ++i) {
+ jd->ports[i] = jack_port_register(jd->client,
+ jd->source_ports[i],
JACK_DEFAULT_AUDIO_TYPE,
JackPortIsOutput, 0);
if (jd->ports[i] == NULL) {
g_set_error(error_r, jack_output_quark(), 0,
"Cannot register output port \"%s\"",
- port_names[i]);
+ jd->source_ports[i]);
mpd_jack_disconnect(jd);
return false;
}
@@ -272,6 +284,16 @@ mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format,
if (!config_get_block_bool(param, "autostart", false))
jd->options |= JackNoStartServer;
+ /* configure the source ports */
+
+ value = config_get_block_string(param, "source_ports", "left,right");
+ jd->num_source_ports = parse_port_list(param->line, value,
+ jd->source_ports, error_r);
+ if (jd->num_source_ports == 0)
+ return NULL;
+
+ /* configure the destination ports */
+
value = config_get_block_string(param, "destination_ports", NULL);
if (value == NULL) {
/* compatibility with MPD < 0.16 */
@@ -291,6 +313,13 @@ mpd_jack_init(G_GNUC_UNUSED const struct audio_format *audio_format,
jd->num_destination_ports = 0;
}
+ if (jd->num_destination_ports > 0 &&
+ jd->num_destination_ports != jd->num_source_ports)
+ g_warning("number of source ports (%u) mismatches the "
+ "number of destination ports (%u) in line %d",
+ jd->num_source_ports, jd->num_destination_ports,
+ param->line);
+
jd->ringbuffer_size =
config_get_block_unsigned(param, "ringbuffer_size", 32768);
@@ -319,7 +348,7 @@ mpd_jack_enable(void *data, GError **error_r)
{
struct jack_data *jd = (struct jack_data *)data;
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i)
+ for (unsigned i = 0; i < jd->num_source_ports; ++i)
jd->ringbuffer[i] = NULL;
return mpd_jack_connect(jd, error_r);
@@ -333,7 +362,7 @@ mpd_jack_disable(void *data)
if (jd->client != NULL)
mpd_jack_disconnect(jd);
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i) {
+ for (unsigned i = 0; i < jd->num_source_ports; ++i) {
if (jd->ringbuffer[i] != NULL) {
jack_ringbuffer_free(jd->ringbuffer[i]);
jd->ringbuffer[i] = NULL;
@@ -364,15 +393,17 @@ static bool
mpd_jack_start(struct jack_data *jd, GError **error_r)
{
const char *destination_ports[MAX_PORTS], **jports;
+ const char *duplicate_port = NULL;
unsigned num_destination_ports;
assert(jd->client != NULL);
+ assert(jd->audio_format.channels <= jd->num_source_ports);
/* allocate the ring buffers on the first open(); these
persist until MPD exits. It's too unsafe to delete them
because we can never know when mpd_jack_process() gets
called */
- for (unsigned i = 0; i < G_N_ELEMENTS(jd->ringbuffer); ++i) {
+ for (unsigned i = 0; i < jd->num_source_ports; ++i) {
if (jd->ringbuffer[i] == NULL)
jd->ringbuffer[i] =
jack_ringbuffer_create(jd->ringbuffer_size);
@@ -425,16 +456,26 @@ mpd_jack_start(struct jack_data *jd, GError **error_r)
assert(num_destination_ports > 0);
- if (jd->audio_format.channels == 2 && num_destination_ports == 1) {
+ if (jd->audio_format.channels >= 2 && num_destination_ports == 1) {
/* mix stereo signal on one speaker */
- destination_ports[1] = destination_ports[0];
- num_destination_ports = 2;
+ while (num_destination_ports < jd->audio_format.channels)
+ destination_ports[num_destination_ports++] =
+ destination_ports[0];
+ } else if (num_destination_ports > jd->audio_format.channels) {
+ if (jd->audio_format.channels == 1 && num_destination_ports > 2) {
+ /* mono input file: connect the one source
+ channel to the both destination channels */
+ duplicate_port = destination_ports[1];
+ num_destination_ports = 1;
+ } else
+ /* connect only as many ports as we need */
+ num_destination_ports = jd->audio_format.channels;
}
- assert(jd->audio_format.channels <= num_destination_ports);
+ assert(num_destination_ports <= jd->num_source_ports);
- for (unsigned i = 0; i < jd->audio_format.channels; ++i) {
+ for (unsigned i = 0; i < num_destination_ports; ++i) {
int ret;
ret = jack_connect(jd->client, jack_port_name(jd->ports[i]),
@@ -452,17 +493,17 @@ mpd_jack_start(struct jack_data *jd, GError **error_r)
}
}
- if (jd->audio_format.channels == 1 && num_destination_ports == 2) {
+ if (duplicate_port != NULL) {
/* mono input file: connect the one source channel to
the both destination channels */
int ret;
ret = jack_connect(jd->client, jack_port_name(jd->ports[0]),
- destination_ports[1]);
+ duplicate_port);
if (ret != 0) {
g_set_error(error_r, jack_output_quark(), 0,
"Not a valid JACK port: %s",
- destination_ports[1]);
+ duplicate_port);
if (jports != NULL)
free(jports);
@@ -518,15 +559,12 @@ mpd_jack_write_samples_16(struct jack_data *jd, const int16_t *src,
unsigned num_samples)
{
jack_default_audio_sample_t sample;
+ unsigned i;
while (num_samples-- > 0) {
- sample = sample_16_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[0], (void*)&sample,
- sizeof(sample));
-
- if (jd->audio_format.channels >= 2) {
+ for (i = 0; i < jd->audio_format.channels; ++i) {
sample = sample_16_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[1], (void*)&sample,
+ jack_ringbuffer_write(jd->ringbuffer[i], (void*)&sample,
sizeof(sample));
}
}
@@ -543,15 +581,12 @@ mpd_jack_write_samples_24(struct jack_data *jd, const int32_t *src,
unsigned num_samples)
{
jack_default_audio_sample_t sample;
+ unsigned i;
while (num_samples-- > 0) {
- sample = sample_24_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[0], (void*)&sample,
- sizeof(sample));
-
- if (jd->audio_format.channels >= 2) {
+ for (i = 0; i < jd->audio_format.channels; ++i) {
sample = sample_24_to_jack(*src++);
- jack_ringbuffer_write(jd->ringbuffer[1], (void*)&sample,
+ jack_ringbuffer_write(jd->ringbuffer[i], (void*)&sample,
sizeof(sample));
}
}
@@ -598,10 +633,12 @@ mpd_jack_play(void *data, const void *chunk, size_t size, GError **error_r)
}
space = jack_ringbuffer_write_space(jd->ringbuffer[0]);
- space1 = jack_ringbuffer_write_space(jd->ringbuffer[1]);
- if (space > space1)
- /* send data symmetrically */
- space = space1;
+ for (unsigned i = 1; i < jd->audio_format.channels; ++i) {
+ space1 = jack_ringbuffer_write_space(jd->ringbuffer[i]);
+ if (space > space1)
+ /* send data symmetrically */
+ space = space1;
+ }
if (space >= frame_size)
break;