From f492c78e2ef7c50e1799f493dac106fe2a535ca0 Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Wed, 17 Apr 2013 00:24:44 +0200 Subject: output/jack: convert to C++ --- Makefile.am | 2 +- src/OutputList.cxx | 2 +- src/output/JackOutputPlugin.cxx | 775 ++++++++++++++++++++++++++++++++++++++++ src/output/JackOutputPlugin.hxx | 25 ++ src/output/jack_output_plugin.c | 755 -------------------------------------- src/output/jack_output_plugin.h | 25 -- 6 files changed, 802 insertions(+), 782 deletions(-) create mode 100644 src/output/JackOutputPlugin.cxx create mode 100644 src/output/JackOutputPlugin.hxx delete mode 100644 src/output/jack_output_plugin.c delete mode 100644 src/output/jack_output_plugin.h diff --git a/Makefile.am b/Makefile.am index 6a4046498..3134af8d5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -839,7 +839,7 @@ endif if HAVE_JACK liboutput_plugins_a_SOURCES += \ - src/output/jack_output_plugin.c src/output/jack_output_plugin.h + src/output/JackOutputPlugin.cxx src/output/JackOutputPlugin.hxx endif if HAVE_MVP diff --git a/src/OutputList.cxx b/src/OutputList.cxx index 7210ff929..7744617b7 100644 --- a/src/OutputList.cxx +++ b/src/OutputList.cxx @@ -25,7 +25,7 @@ #include "output/ffado_output_plugin.h" #include "output/FifoOutputPlugin.hxx" #include "output/HttpdOutputPlugin.hxx" -#include "output/jack_output_plugin.h" +#include "output/JackOutputPlugin.hxx" #include "output/mvp_output_plugin.h" #include "output/NullOutputPlugin.hxx" #include "output/openal_output_plugin.h" diff --git a/src/output/JackOutputPlugin.cxx b/src/output/JackOutputPlugin.cxx new file mode 100644 index 000000000..ea351d5c9 --- /dev/null +++ b/src/output/JackOutputPlugin.cxx @@ -0,0 +1,775 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "JackOutputPlugin.hxx" +#include "output_api.h" + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#undef G_LOG_DOMAIN +#define G_LOG_DOMAIN "jack" + +enum { + MAX_PORTS = 16, +}; + +static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t); + +struct JackOutput { + struct audio_output base; + + /** + * libjack options passed to jack_client_open(). + */ + jack_options_t options; + + const char *name; + + const char *server_name; + + /* configuration */ + + char *source_ports[MAX_PORTS]; + unsigned num_source_ports; + + char *destination_ports[MAX_PORTS]; + unsigned num_destination_ports; + + size_t ringbuffer_size; + + /* the current audio format */ + struct audio_format audio_format; + + /* jack library stuff */ + jack_port_t *ports[MAX_PORTS]; + jack_client_t *client; + jack_ringbuffer_t *ringbuffer[MAX_PORTS]; + + bool shutdown; + + /** + * While this flag is set, the "process" callback generates + * silence. + */ + bool pause; + + bool Initialize(const config_param *param, GError **error_r) { + return ao_base_init(&base, &jack_output_plugin, param, + error_r); + } + + void Deinitialize() { + ao_base_finish(&base); + } +}; + +/** + * The quark used for GError.domain. + */ +static inline GQuark +jack_output_quark(void) +{ + return g_quark_from_static_string("jack_output"); +} + +/** + * Determine the number of frames guaranteed to be available on all + * channels. + */ +static jack_nframes_t +mpd_jack_available(const JackOutput *jd) +{ + size_t min = jack_ringbuffer_read_space(jd->ringbuffer[0]); + + for (unsigned i = 1; i < jd->audio_format.channels; ++i) { + size_t current = jack_ringbuffer_read_space(jd->ringbuffer[i]); + if (current < min) + min = current; + } + + assert(min % jack_sample_size == 0); + + return min / jack_sample_size; +} + +static int +mpd_jack_process(jack_nframes_t nframes, void *arg) +{ + JackOutput *jd = (JackOutput *) arg; + + if (nframes <= 0) + return 0; + + if (jd->pause) { + /* empty the ring buffers */ + + const jack_nframes_t available = mpd_jack_available(jd); + for (unsigned i = 0; i < jd->audio_format.channels; ++i) + jack_ringbuffer_read_advance(jd->ringbuffer[i], + available * jack_sample_size); + + /* generate silence while MPD is paused */ + + for (unsigned i = 0; i < jd->audio_format.channels; ++i) { + jack_default_audio_sample_t *out = + (jack_default_audio_sample_t *) + jack_port_get_buffer(jd->ports[i], nframes); + + for (jack_nframes_t f = 0; f < nframes; ++f) + out[f] = 0.0; + } + + return 0; + } + + jack_nframes_t available = mpd_jack_available(jd); + if (available > nframes) + available = nframes; + + for (unsigned i = 0; i < jd->audio_format.channels; ++i) { + jack_default_audio_sample_t *out = + (jack_default_audio_sample_t *) + jack_port_get_buffer(jd->ports[i], nframes); + if (out == nullptr) + /* workaround for libjack1 bug: if the server + connection fails, the process callback is + invoked anyway, but unable to get a + buffer */ + continue; + + jack_ringbuffer_read(jd->ringbuffer[i], + (char *)out, available * jack_sample_size); + + for (jack_nframes_t f = available; f < nframes; ++f) + /* ringbuffer underrun, fill with silence */ + out[f] = 0.0; + } + + /* generate silence for the unused source ports */ + + for (unsigned i = jd->audio_format.channels; + i < jd->num_source_ports; ++i) { + jack_default_audio_sample_t *out = + (jack_default_audio_sample_t *) + jack_port_get_buffer(jd->ports[i], nframes); + if (out == nullptr) + /* workaround for libjack1 bug: if the server + connection fails, the process callback is + invoked anyway, but unable to get a + buffer */ + continue; + + for (jack_nframes_t f = 0; f < nframes; ++f) + out[f] = 0.0; + } + + return 0; +} + +static void +mpd_jack_shutdown(void *arg) +{ + JackOutput *jd = (JackOutput *) arg; + jd->shutdown = true; +} + +static void +set_audioformat(JackOutput *jd, struct audio_format *audio_format) +{ + audio_format->sample_rate = jack_get_sample_rate(jd->client); + + 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->format != SAMPLE_FORMAT_S16 && + audio_format->format != SAMPLE_FORMAT_S24_P32) + audio_format->format = SAMPLE_FORMAT_S24_P32; +} + +static void +mpd_jack_error(const char *msg) +{ + g_warning("%s", msg); +} + +#ifdef HAVE_JACK_SET_INFO_FUNCTION +static void +mpd_jack_info(const char *msg) +{ + g_message("%s", msg); +} +#endif + +/** + * Disconnect the JACK client. + */ +static void +mpd_jack_disconnect(JackOutput *jd) +{ + assert(jd != nullptr); + assert(jd->client != nullptr); + + jack_deactivate(jd->client); + jack_client_close(jd->client); + jd->client = nullptr; +} + +/** + * Connect the JACK client and performs some basic setup + * (e.g. register callbacks). + */ +static bool +mpd_jack_connect(JackOutput *jd, GError **error_r) +{ + jack_status_t status; + + assert(jd != nullptr); + + jd->shutdown = false; + + jd->client = jack_client_open(jd->name, jd->options, &status, + jd->server_name); + if (jd->client == nullptr) { + g_set_error(error_r, jack_output_quark(), 0, + "Failed to connect to JACK server, status=%d", + status); + return false; + } + + jack_set_process_callback(jd->client, mpd_jack_process, jd); + jack_on_shutdown(jd->client, mpd_jack_shutdown, jd); + + 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] == nullptr) { + g_set_error(error_r, jack_output_quark(), 0, + "Cannot register output port \"%s\"", + jd->source_ports[i]); + mpd_jack_disconnect(jd); + return false; + } + } + + return true; +} + +static bool +mpd_jack_test_default_device(void) +{ + return true; +} + +static unsigned +parse_port_list(int line, const char *source, char **dest, GError **error_r) +{ + char **list = g_strsplit(source, ",", 0); + unsigned n = 0; + + for (n = 0; list[n] != nullptr; ++n) { + if (n >= MAX_PORTS) { + g_set_error(error_r, jack_output_quark(), 0, + "too many port names in line %d", + line); + return 0; + } + + dest[n] = list[n]; + } + + g_free(list); + + if (n == 0) { + g_set_error(error_r, jack_output_quark(), 0, + "at least one port name expected in line %d", + line); + return 0; + } + + return n; +} + +static struct audio_output * +mpd_jack_init(const config_param *param, GError **error_r) +{ + JackOutput *jd = new JackOutput(); + + if (!jd->Initialize(param, error_r)) { + delete jd; + return nullptr; + } + + const char *value; + + jd->options = JackNullOption; + + jd->name = config_get_block_string(param, "client_name", nullptr); + if (jd->name != nullptr) + jd->options = jack_options_t(jd->options | JackUseExactName); + else + /* if there's a no configured client name, we don't + care about the JackUseExactName option */ + jd->name = "Music Player Daemon"; + + jd->server_name = config_get_block_string(param, "server_name", nullptr); + if (jd->server_name != nullptr) + jd->options = jack_options_t(jd->options | JackServerName); + + if (!config_get_block_bool(param, "autostart", false)) + jd->options = jack_options_t(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 nullptr; + + /* configure the destination ports */ + + value = config_get_block_string(param, "destination_ports", nullptr); + if (value == nullptr) { + /* compatibility with MPD < 0.16 */ + value = config_get_block_string(param, "ports", nullptr); + if (value != nullptr) + g_warning("deprecated option 'ports' in line %d", + param->line); + } + + if (value != nullptr) { + jd->num_destination_ports = + parse_port_list(param->line, value, + jd->destination_ports, error_r); + if (jd->num_destination_ports == 0) + return nullptr; + } else { + 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); + + jack_set_error_function(mpd_jack_error); + +#ifdef HAVE_JACK_SET_INFO_FUNCTION + jack_set_info_function(mpd_jack_info); +#endif + + return &jd->base; +} + +static void +mpd_jack_finish(struct audio_output *ao) +{ + JackOutput *jd = (JackOutput *)ao; + + for (unsigned i = 0; i < jd->num_source_ports; ++i) + g_free(jd->source_ports[i]); + + for (unsigned i = 0; i < jd->num_destination_ports; ++i) + g_free(jd->destination_ports[i]); + + jd->Deinitialize(); + delete jd; +} + +static bool +mpd_jack_enable(struct audio_output *ao, GError **error_r) +{ + JackOutput *jd = (JackOutput *)ao; + + for (unsigned i = 0; i < jd->num_source_ports; ++i) + jd->ringbuffer[i] = nullptr; + + return mpd_jack_connect(jd, error_r); +} + +static void +mpd_jack_disable(struct audio_output *ao) +{ + JackOutput *jd = (JackOutput *)ao; + + if (jd->client != nullptr) + mpd_jack_disconnect(jd); + + for (unsigned i = 0; i < jd->num_source_ports; ++i) { + if (jd->ringbuffer[i] != nullptr) { + jack_ringbuffer_free(jd->ringbuffer[i]); + jd->ringbuffer[i] = nullptr; + } + } +} + +/** + * Stops the playback on the JACK connection. + */ +static void +mpd_jack_stop(JackOutput *jd) +{ + assert(jd != nullptr); + + if (jd->client == nullptr) + return; + + if (jd->shutdown) + /* the connection has failed; close it */ + mpd_jack_disconnect(jd); + else + /* the connection is alive: just stop playback */ + jack_deactivate(jd->client); +} + +static bool +mpd_jack_start(JackOutput *jd, GError **error_r) +{ + const char *destination_ports[MAX_PORTS], **jports; + const char *duplicate_port = nullptr; + unsigned num_destination_ports; + + assert(jd->client != nullptr); + 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 < jd->num_source_ports; ++i) { + if (jd->ringbuffer[i] == nullptr) + jd->ringbuffer[i] = + jack_ringbuffer_create(jd->ringbuffer_size); + + /* clear the ring buffer to be sure that data from + previous playbacks are gone */ + jack_ringbuffer_reset(jd->ringbuffer[i]); + } + + if ( jack_activate(jd->client) ) { + g_set_error(error_r, jack_output_quark(), 0, + "cannot activate client"); + mpd_jack_stop(jd); + return false; + } + + if (jd->num_destination_ports == 0) { + /* no output ports were configured - ask libjack for + defaults */ + jports = jack_get_ports(jd->client, nullptr, nullptr, + JackPortIsPhysical | JackPortIsInput); + if (jports == nullptr) { + g_set_error(error_r, jack_output_quark(), 0, + "no ports found"); + mpd_jack_stop(jd); + return false; + } + + assert(*jports != nullptr); + + for (num_destination_ports = 0; + num_destination_ports < MAX_PORTS && + jports[num_destination_ports] != nullptr; + ++num_destination_ports) { + g_debug("destination_port[%u] = '%s'\n", + num_destination_ports, + jports[num_destination_ports]); + destination_ports[num_destination_ports] = + jports[num_destination_ports]; + } + } else { + /* use the configured output ports */ + + num_destination_ports = jd->num_destination_ports; + memcpy(destination_ports, jd->destination_ports, + num_destination_ports * sizeof(*destination_ports)); + + jports = nullptr; + } + + assert(num_destination_ports > 0); + + if (jd->audio_format.channels >= 2 && num_destination_ports == 1) { + /* mix stereo signal on one speaker */ + + 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(num_destination_ports <= jd->num_source_ports); + + for (unsigned i = 0; i < num_destination_ports; ++i) { + int ret; + + ret = jack_connect(jd->client, jack_port_name(jd->ports[i]), + destination_ports[i]); + if (ret != 0) { + g_set_error(error_r, jack_output_quark(), 0, + "Not a valid JACK port: %s", + destination_ports[i]); + + if (jports != nullptr) + free(jports); + + mpd_jack_stop(jd); + return false; + } + } + + if (duplicate_port != nullptr) { + /* 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]), + duplicate_port); + if (ret != 0) { + g_set_error(error_r, jack_output_quark(), 0, + "Not a valid JACK port: %s", + duplicate_port); + + if (jports != nullptr) + free(jports); + + mpd_jack_stop(jd); + return false; + } + } + + if (jports != nullptr) + free(jports); + + return true; +} + +static bool +mpd_jack_open(struct audio_output *ao, struct audio_format *audio_format, + GError **error_r) +{ + JackOutput *jd = (JackOutput *)ao; + + assert(jd != nullptr); + + jd->pause = false; + + if (jd->client != nullptr && jd->shutdown) + mpd_jack_disconnect(jd); + + if (jd->client == nullptr && !mpd_jack_connect(jd, error_r)) + return false; + + set_audioformat(jd, audio_format); + jd->audio_format = *audio_format; + + if (!mpd_jack_start(jd, error_r)) + return false; + + return true; +} + +static void +mpd_jack_close(G_GNUC_UNUSED struct audio_output *ao) +{ + JackOutput *jd = (JackOutput *)ao; + + mpd_jack_stop(jd); +} + +static unsigned +mpd_jack_delay(struct audio_output *ao) +{ + JackOutput *jd = (JackOutput *)ao; + + return jd->base.pause && jd->pause && !jd->shutdown + ? 1000 + : 0; +} + +static inline jack_default_audio_sample_t +sample_16_to_jack(int16_t sample) +{ + return sample / (jack_default_audio_sample_t)(1 << (16 - 1)); +} + +static void +mpd_jack_write_samples_16(JackOutput *jd, const int16_t *src, + unsigned num_samples) +{ + jack_default_audio_sample_t sample; + unsigned i; + + while (num_samples-- > 0) { + for (i = 0; i < jd->audio_format.channels; ++i) { + sample = sample_16_to_jack(*src++); + jack_ringbuffer_write(jd->ringbuffer[i], + (const char *)&sample, + sizeof(sample)); + } + } +} + +static inline jack_default_audio_sample_t +sample_24_to_jack(int32_t sample) +{ + return sample / (jack_default_audio_sample_t)(1 << (24 - 1)); +} + +static void +mpd_jack_write_samples_24(JackOutput *jd, const int32_t *src, + unsigned num_samples) +{ + jack_default_audio_sample_t sample; + unsigned i; + + while (num_samples-- > 0) { + for (i = 0; i < jd->audio_format.channels; ++i) { + sample = sample_24_to_jack(*src++); + jack_ringbuffer_write(jd->ringbuffer[i], + (const char *)&sample, + sizeof(sample)); + } + } +} + +static void +mpd_jack_write_samples(JackOutput *jd, const void *src, + unsigned num_samples) +{ + switch (jd->audio_format.format) { + case SAMPLE_FORMAT_S16: + mpd_jack_write_samples_16(jd, (const int16_t*)src, + num_samples); + break; + + case SAMPLE_FORMAT_S24_P32: + mpd_jack_write_samples_24(jd, (const int32_t*)src, + num_samples); + break; + + default: + assert(false); + } +} + +static size_t +mpd_jack_play(struct audio_output *ao, const void *chunk, size_t size, + GError **error_r) +{ + JackOutput *jd = (JackOutput *)ao; + const size_t frame_size = audio_format_frame_size(&jd->audio_format); + size_t space = 0, space1; + + jd->pause = false; + + assert(size % frame_size == 0); + size /= frame_size; + + while (true) { + if (jd->shutdown) { + g_set_error(error_r, jack_output_quark(), 0, + "Refusing to play, because " + "there is no client thread"); + return 0; + } + + space = jack_ringbuffer_write_space(jd->ringbuffer[0]); + 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 >= jack_sample_size) + break; + + /* XXX do something more intelligent to + synchronize */ + g_usleep(1000); + } + + space /= jack_sample_size; + if (space < size) + size = space; + + mpd_jack_write_samples(jd, chunk, size); + return size * frame_size; +} + +static bool +mpd_jack_pause(struct audio_output *ao) +{ + JackOutput *jd = (JackOutput *)ao; + + if (jd->shutdown) + return false; + + jd->pause = true; + + return true; +} + +const struct audio_output_plugin jack_output_plugin = { + "jack", + mpd_jack_test_default_device, + mpd_jack_init, + mpd_jack_finish, + mpd_jack_enable, + mpd_jack_disable, + mpd_jack_open, + mpd_jack_close, + mpd_jack_delay, + nullptr, + mpd_jack_play, + nullptr, + nullptr, + mpd_jack_pause, + nullptr, +}; diff --git a/src/output/JackOutputPlugin.hxx b/src/output/JackOutputPlugin.hxx new file mode 100644 index 000000000..908105ad2 --- /dev/null +++ b/src/output/JackOutputPlugin.hxx @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * 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, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_JACK_OUTPUT_PLUGIN_HXX +#define MPD_JACK_OUTPUT_PLUGIN_HXX + +extern const struct audio_output_plugin jack_output_plugin; + +#endif diff --git a/src/output/jack_output_plugin.c b/src/output/jack_output_plugin.c deleted file mode 100644 index d5c8ca412..000000000 --- a/src/output/jack_output_plugin.c +++ /dev/null @@ -1,755 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#include "config.h" -#include "jack_output_plugin.h" -#include "output_api.h" - -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#undef G_LOG_DOMAIN -#define G_LOG_DOMAIN "jack" - -enum { - MAX_PORTS = 16, -}; - -static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t); - -struct jack_data { - struct audio_output base; - - /** - * libjack options passed to jack_client_open(). - */ - jack_options_t options; - - const char *name; - - const char *server_name; - - /* configuration */ - - char *source_ports[MAX_PORTS]; - unsigned num_source_ports; - - char *destination_ports[MAX_PORTS]; - unsigned num_destination_ports; - - size_t ringbuffer_size; - - /* the current audio format */ - struct audio_format audio_format; - - /* jack library stuff */ - jack_port_t *ports[MAX_PORTS]; - jack_client_t *client; - jack_ringbuffer_t *ringbuffer[MAX_PORTS]; - - bool shutdown; - - /** - * While this flag is set, the "process" callback generates - * silence. - */ - bool pause; -}; - -/** - * The quark used for GError.domain. - */ -static inline GQuark -jack_output_quark(void) -{ - return g_quark_from_static_string("jack_output"); -} - -/** - * Determine the number of frames guaranteed to be available on all - * channels. - */ -static jack_nframes_t -mpd_jack_available(const struct jack_data *jd) -{ - size_t min = jack_ringbuffer_read_space(jd->ringbuffer[0]); - - for (unsigned i = 1; i < jd->audio_format.channels; ++i) { - size_t current = jack_ringbuffer_read_space(jd->ringbuffer[i]); - if (current < min) - min = current; - } - - assert(min % jack_sample_size == 0); - - return min / jack_sample_size; -} - -static int -mpd_jack_process(jack_nframes_t nframes, void *arg) -{ - struct jack_data *jd = (struct jack_data *) arg; - jack_default_audio_sample_t *out; - - if (nframes <= 0) - return 0; - - if (jd->pause) { - /* empty the ring buffers */ - - const jack_nframes_t available = mpd_jack_available(jd); - for (unsigned i = 0; i < jd->audio_format.channels; ++i) - jack_ringbuffer_read_advance(jd->ringbuffer[i], - available * jack_sample_size); - - /* generate silence while MPD is paused */ - - for (unsigned i = 0; i < jd->audio_format.channels; ++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; - } - - jack_nframes_t available = mpd_jack_available(jd); - if (available > nframes) - available = nframes; - - for (unsigned i = 0; i < jd->audio_format.channels; ++i) { - out = jack_port_get_buffer(jd->ports[i], nframes); - if (out == NULL) - /* workaround for libjack1 bug: if the server - connection fails, the process callback is - invoked anyway, but unable to get a - buffer */ - continue; - - jack_ringbuffer_read(jd->ringbuffer[i], - (char *)out, available * jack_sample_size); - - for (jack_nframes_t f = available; f < nframes; ++f) - /* ringbuffer underrun, fill with silence */ - out[f] = 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); - if (out == NULL) - /* workaround for libjack1 bug: if the server - connection fails, the process callback is - invoked anyway, but unable to get a - buffer */ - continue; - - for (jack_nframes_t f = 0; f < nframes; ++f) - out[f] = 0.0; - } - - return 0; -} - -static void -mpd_jack_shutdown(void *arg) -{ - struct jack_data *jd = (struct jack_data *) arg; - jd->shutdown = true; -} - -static void -set_audioformat(struct jack_data *jd, struct audio_format *audio_format) -{ - audio_format->sample_rate = jack_get_sample_rate(jd->client); - - 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->format != SAMPLE_FORMAT_S16 && - audio_format->format != SAMPLE_FORMAT_S24_P32) - audio_format->format = SAMPLE_FORMAT_S24_P32; -} - -static void -mpd_jack_error(const char *msg) -{ - g_warning("%s", msg); -} - -#ifdef HAVE_JACK_SET_INFO_FUNCTION -static void -mpd_jack_info(const char *msg) -{ - g_message("%s", msg); -} -#endif - -/** - * Disconnect the JACK client. - */ -static void -mpd_jack_disconnect(struct jack_data *jd) -{ - assert(jd != NULL); - assert(jd->client != NULL); - - jack_deactivate(jd->client); - jack_client_close(jd->client); - jd->client = NULL; -} - -/** - * Connect the JACK client and performs some basic setup - * (e.g. register callbacks). - */ -static bool -mpd_jack_connect(struct jack_data *jd, GError **error_r) -{ - jack_status_t status; - - assert(jd != NULL); - - jd->shutdown = false; - - jd->client = jack_client_open(jd->name, jd->options, &status, - jd->server_name); - if (jd->client == NULL) { - g_set_error(error_r, jack_output_quark(), 0, - "Failed to connect to JACK server, status=%d", - status); - return false; - } - - jack_set_process_callback(jd->client, mpd_jack_process, jd); - jack_on_shutdown(jd->client, mpd_jack_shutdown, jd); - - 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\"", - jd->source_ports[i]); - mpd_jack_disconnect(jd); - return false; - } - } - - return true; -} - -static bool -mpd_jack_test_default_device(void) -{ - return true; -} - -static unsigned -parse_port_list(int line, const char *source, char **dest, GError **error_r) -{ - char **list = g_strsplit(source, ",", 0); - unsigned n = 0; - - for (n = 0; list[n] != NULL; ++n) { - if (n >= MAX_PORTS) { - g_set_error(error_r, jack_output_quark(), 0, - "too many port names in line %d", - line); - return 0; - } - - dest[n] = list[n]; - } - - g_free(list); - - if (n == 0) { - g_set_error(error_r, jack_output_quark(), 0, - "at least one port name expected in line %d", - line); - return 0; - } - - return n; -} - -static struct audio_output * -mpd_jack_init(const struct config_param *param, GError **error_r) -{ - struct jack_data *jd = g_new(struct jack_data, 1); - - if (!ao_base_init(&jd->base, &jack_output_plugin, param, error_r)) { - g_free(jd); - return NULL; - } - - const char *value; - - jd->options = JackNullOption; - - jd->name = config_get_block_string(param, "client_name", NULL); - if (jd->name != NULL) - jd->options |= JackUseExactName; - else - /* if there's a no configured client name, we don't - care about the JackUseExactName option */ - jd->name = "Music Player Daemon"; - - jd->server_name = config_get_block_string(param, "server_name", NULL); - if (jd->server_name != NULL) - jd->options |= JackServerName; - - 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 */ - value = config_get_block_string(param, "ports", NULL); - if (value != NULL) - g_warning("deprecated option 'ports' in line %d", - param->line); - } - - if (value != NULL) { - jd->num_destination_ports = - parse_port_list(param->line, value, - jd->destination_ports, error_r); - if (jd->num_destination_ports == 0) - return NULL; - } else { - 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); - - jack_set_error_function(mpd_jack_error); - -#ifdef HAVE_JACK_SET_INFO_FUNCTION - jack_set_info_function(mpd_jack_info); -#endif - - return &jd->base; -} - -static void -mpd_jack_finish(struct audio_output *ao) -{ - struct jack_data *jd = (struct jack_data *)ao; - - for (unsigned i = 0; i < jd->num_source_ports; ++i) - g_free(jd->source_ports[i]); - - for (unsigned i = 0; i < jd->num_destination_ports; ++i) - g_free(jd->destination_ports[i]); - - ao_base_finish(&jd->base); - g_free(jd); -} - -static bool -mpd_jack_enable(struct audio_output *ao, GError **error_r) -{ - struct jack_data *jd = (struct jack_data *)ao; - - for (unsigned i = 0; i < jd->num_source_ports; ++i) - jd->ringbuffer[i] = NULL; - - return mpd_jack_connect(jd, error_r); -} - -static void -mpd_jack_disable(struct audio_output *ao) -{ - struct jack_data *jd = (struct jack_data *)ao; - - if (jd->client != NULL) - mpd_jack_disconnect(jd); - - 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; - } - } -} - -/** - * Stops the playback on the JACK connection. - */ -static void -mpd_jack_stop(struct jack_data *jd) -{ - assert(jd != NULL); - - if (jd->client == NULL) - return; - - if (jd->shutdown) - /* the connection has failed; close it */ - mpd_jack_disconnect(jd); - else - /* the connection is alive: just stop playback */ - jack_deactivate(jd->client); -} - -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 < jd->num_source_ports; ++i) { - if (jd->ringbuffer[i] == NULL) - jd->ringbuffer[i] = - jack_ringbuffer_create(jd->ringbuffer_size); - - /* clear the ring buffer to be sure that data from - previous playbacks are gone */ - jack_ringbuffer_reset(jd->ringbuffer[i]); - } - - if ( jack_activate(jd->client) ) { - g_set_error(error_r, jack_output_quark(), 0, - "cannot activate client"); - mpd_jack_stop(jd); - return false; - } - - if (jd->num_destination_ports == 0) { - /* no output ports were configured - ask libjack for - defaults */ - jports = jack_get_ports(jd->client, NULL, NULL, - JackPortIsPhysical | JackPortIsInput); - if (jports == NULL) { - g_set_error(error_r, jack_output_quark(), 0, - "no ports found"); - mpd_jack_stop(jd); - return false; - } - - assert(*jports != NULL); - - for (num_destination_ports = 0; - num_destination_ports < MAX_PORTS && - jports[num_destination_ports] != NULL; - ++num_destination_ports) { - g_debug("destination_port[%u] = '%s'\n", - num_destination_ports, - jports[num_destination_ports]); - destination_ports[num_destination_ports] = - jports[num_destination_ports]; - } - } else { - /* use the configured output ports */ - - num_destination_ports = jd->num_destination_ports; - memcpy(destination_ports, jd->destination_ports, - num_destination_ports * sizeof(*destination_ports)); - - jports = NULL; - } - - assert(num_destination_ports > 0); - - if (jd->audio_format.channels >= 2 && num_destination_ports == 1) { - /* mix stereo signal on one speaker */ - - 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(num_destination_ports <= jd->num_source_ports); - - for (unsigned i = 0; i < num_destination_ports; ++i) { - int ret; - - ret = jack_connect(jd->client, jack_port_name(jd->ports[i]), - destination_ports[i]); - if (ret != 0) { - g_set_error(error_r, jack_output_quark(), 0, - "Not a valid JACK port: %s", - destination_ports[i]); - - if (jports != NULL) - free(jports); - - mpd_jack_stop(jd); - return false; - } - } - - 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]), - duplicate_port); - if (ret != 0) { - g_set_error(error_r, jack_output_quark(), 0, - "Not a valid JACK port: %s", - duplicate_port); - - if (jports != NULL) - free(jports); - - mpd_jack_stop(jd); - return false; - } - } - - if (jports != NULL) - free(jports); - - return true; -} - -static bool -mpd_jack_open(struct audio_output *ao, struct audio_format *audio_format, - GError **error_r) -{ - struct jack_data *jd = (struct jack_data *)ao; - - assert(jd != NULL); - - jd->pause = false; - - if (jd->client != NULL && jd->shutdown) - mpd_jack_disconnect(jd); - - if (jd->client == NULL && !mpd_jack_connect(jd, error_r)) - return false; - - set_audioformat(jd, audio_format); - jd->audio_format = *audio_format; - - if (!mpd_jack_start(jd, error_r)) - return false; - - return true; -} - -static void -mpd_jack_close(G_GNUC_UNUSED struct audio_output *ao) -{ - struct jack_data *jd = (struct jack_data *)ao; - - mpd_jack_stop(jd); -} - -static unsigned -mpd_jack_delay(struct audio_output *ao) -{ - struct jack_data *jd = (struct jack_data *)ao; - - return jd->base.pause && jd->pause && !jd->shutdown - ? 1000 - : 0; -} - -static inline jack_default_audio_sample_t -sample_16_to_jack(int16_t sample) -{ - return sample / (jack_default_audio_sample_t)(1 << (16 - 1)); -} - -static void -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) { - for (i = 0; i < jd->audio_format.channels; ++i) { - sample = sample_16_to_jack(*src++); - jack_ringbuffer_write(jd->ringbuffer[i], (void*)&sample, - sizeof(sample)); - } - } -} - -static inline jack_default_audio_sample_t -sample_24_to_jack(int32_t sample) -{ - return sample / (jack_default_audio_sample_t)(1 << (24 - 1)); -} - -static void -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) { - for (i = 0; i < jd->audio_format.channels; ++i) { - sample = sample_24_to_jack(*src++); - jack_ringbuffer_write(jd->ringbuffer[i], (void*)&sample, - sizeof(sample)); - } - } -} - -static void -mpd_jack_write_samples(struct jack_data *jd, const void *src, - unsigned num_samples) -{ - switch (jd->audio_format.format) { - case SAMPLE_FORMAT_S16: - mpd_jack_write_samples_16(jd, (const int16_t*)src, - num_samples); - break; - - case SAMPLE_FORMAT_S24_P32: - mpd_jack_write_samples_24(jd, (const int32_t*)src, - num_samples); - break; - - default: - assert(false); - } -} - -static size_t -mpd_jack_play(struct audio_output *ao, const void *chunk, size_t size, - GError **error_r) -{ - struct jack_data *jd = (struct jack_data *)ao; - const size_t frame_size = audio_format_frame_size(&jd->audio_format); - size_t space = 0, space1; - - jd->pause = false; - - assert(size % frame_size == 0); - size /= frame_size; - - while (true) { - if (jd->shutdown) { - g_set_error(error_r, jack_output_quark(), 0, - "Refusing to play, because " - "there is no client thread"); - return 0; - } - - space = jack_ringbuffer_write_space(jd->ringbuffer[0]); - 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 >= jack_sample_size) - break; - - /* XXX do something more intelligent to - synchronize */ - g_usleep(1000); - } - - space /= jack_sample_size; - if (space < size) - size = space; - - mpd_jack_write_samples(jd, chunk, size); - return size * frame_size; -} - -static bool -mpd_jack_pause(struct audio_output *ao) -{ - struct jack_data *jd = (struct jack_data *)ao; - - if (jd->shutdown) - return false; - - jd->pause = true; - - return true; -} - -const struct audio_output_plugin jack_output_plugin = { - .name = "jack", - .test_default_device = mpd_jack_test_default_device, - .init = mpd_jack_init, - .finish = mpd_jack_finish, - .enable = mpd_jack_enable, - .disable = mpd_jack_disable, - .open = mpd_jack_open, - .delay = mpd_jack_delay, - .play = mpd_jack_play, - .pause = mpd_jack_pause, - .close = mpd_jack_close, -}; diff --git a/src/output/jack_output_plugin.h b/src/output/jack_output_plugin.h deleted file mode 100644 index 2f94ae7dc..000000000 --- a/src/output/jack_output_plugin.h +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (C) 2003-2011 The Music Player Daemon Project - * http://www.musicpd.org - * - * 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, write to the Free Software Foundation, Inc., - * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. - */ - -#ifndef MPD_JACK_OUTPUT_PLUGIN_H -#define MPD_JACK_OUTPUT_PLUGIN_H - -extern const struct audio_output_plugin jack_output_plugin; - -#endif -- cgit v1.2.3