/* * Copyright (C) 2003-2014 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 "../OutputAPI.hxx" #include "config/ConfigError.hxx" #include "util/SplitString.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" #include #include #include #include #include /* for usleep() */ #include #include static constexpr unsigned MAX_PORTS = 16; static constexpr size_t jack_sample_size = sizeof(jack_default_audio_sample_t); struct JackOutput { AudioOutput base; /** * libjack options passed to jack_client_open(). */ jack_options_t options; const char *name; const char *server_name; /* configuration */ std::string source_ports[MAX_PORTS]; unsigned num_source_ports; std::string destination_ports[MAX_PORTS]; unsigned num_destination_ports; size_t ringbuffer_size; /* the current audio format */ AudioFormat 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; JackOutput() :base(jack_output_plugin) {} bool Configure(const config_param ¶m, Error &error); bool Connect(Error &error); /** * Disconnect the JACK client. */ void Disconnect(); void Shutdown() { shutdown = true; } bool Enable(Error &error); void Disable(); bool Open(AudioFormat &new_audio_format, Error &error); bool Start(Error &error); void Stop(); /** * Determine the number of frames guaranteed to be available * on all channels. */ gcc_pure jack_nframes_t GetAvailable() const; void Process(jack_nframes_t nframes); /** * @return the number of frames that were written */ size_t WriteSamples(const float *src, size_t n_frames); size_t Play(const void *chunk, size_t size, Error &error); }; static constexpr Domain jack_output_domain("jack_output"); inline jack_nframes_t JackOutput::GetAvailable() const { size_t min = jack_ringbuffer_read_space(ringbuffer[0]); for (unsigned i = 1; i < audio_format.channels; ++i) { size_t current = jack_ringbuffer_read_space(ringbuffer[i]); if (current < min) min = current; } assert(min % jack_sample_size == 0); return min / jack_sample_size; } inline void JackOutput::Process(jack_nframes_t nframes) { if (nframes <= 0) return; jack_nframes_t available = GetAvailable(); if (pause) { /* empty the ring buffers */ for (unsigned i = 0; i < audio_format.channels; ++i) jack_ringbuffer_read_advance(ringbuffer[i], available * jack_sample_size); /* generate silence while MPD is paused */ for (unsigned i = 0; i < audio_format.channels; ++i) { jack_default_audio_sample_t *out = (jack_default_audio_sample_t *) jack_port_get_buffer(ports[i], nframes); std::fill_n(out, nframes, 0.0); } return; } if (available > nframes) available = nframes; for (unsigned i = 0; i < audio_format.channels; ++i) { jack_default_audio_sample_t *out = (jack_default_audio_sample_t *) jack_port_get_buffer(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(ringbuffer[i], (char *)out, available * jack_sample_size); /* ringbuffer underrun, fill with silence */ std::fill(out + available, out + nframes, 0.0); } /* generate silence for the unused source ports */ for (unsigned i = audio_format.channels; i < num_source_ports; ++i) { jack_default_audio_sample_t *out = (jack_default_audio_sample_t *) jack_port_get_buffer(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; std::fill_n(out, nframes, 0.0); } } static int mpd_jack_process(jack_nframes_t nframes, void *arg) { JackOutput &jo = *(JackOutput *) arg; jo.Process(nframes); return 0; } static void mpd_jack_shutdown(void *arg) { JackOutput &jo = *(JackOutput *) arg; jo.Shutdown(); } static void set_audioformat(JackOutput *jd, AudioFormat &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; /* JACK uses 32 bit float in the range [-1 .. 1] - just like MPD's SampleFormat::FLOAT*/ static_assert(jack_sample_size == sizeof(float), "Expected float32"); audio_format.format = SampleFormat::FLOAT; } static void mpd_jack_error(const char *msg) { LogError(jack_output_domain, msg); } #ifdef HAVE_JACK_SET_INFO_FUNCTION static void mpd_jack_info(const char *msg) { LogDefault(jack_output_domain, msg); } #endif void JackOutput::Disconnect() { assert(client != nullptr); jack_deactivate(client); jack_client_close(client); client = nullptr; } /** * Connect the JACK client and performs some basic setup * (e.g. register callbacks). */ bool JackOutput::Connect(Error &error) { shutdown = false; jack_status_t status; client = jack_client_open(name, options, &status, server_name); if (client == nullptr) { error.Format(jack_output_domain, status, "Failed to connect to JACK server, status=%d", status); return false; } jack_set_process_callback(client, mpd_jack_process, this); jack_on_shutdown(client, mpd_jack_shutdown, this); for (unsigned i = 0; i < num_source_ports; ++i) { ports[i] = jack_port_register(client, source_ports[i].c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); if (ports[i] == nullptr) { error.Format(jack_output_domain, "Cannot register output port \"%s\"", source_ports[i].c_str()); Disconnect(); return false; } } return true; } static bool mpd_jack_test_default_device(void) { return true; } static unsigned parse_port_list(const char *source, std::string dest[], Error &error) { unsigned n = 0; for (auto &&i : SplitString(source, ',')) { if (n >= MAX_PORTS) { error.Set(config_domain, "too many port names"); return 0; } dest[n++] = std::move(i); } if (n == 0) { error.Format(config_domain, "at least one port name expected"); return 0; } return n; } bool JackOutput::Configure(const config_param ¶m, Error &error) { if (!base.Configure(param, error)) return false; options = JackNullOption; name = param.GetBlockValue("client_name", nullptr); if (name != nullptr) options = jack_options_t(options | JackUseExactName); else /* if there's a no configured client name, we don't care about the JackUseExactName option */ name = "Music Player Daemon"; server_name = param.GetBlockValue("server_name", nullptr); if (server_name != nullptr) options = jack_options_t(options | JackServerName); if (!param.GetBlockValue("autostart", false)) options = jack_options_t(options | JackNoStartServer); /* configure the source ports */ const char *value = param.GetBlockValue("source_ports", "left,right"); num_source_ports = parse_port_list(value, source_ports, error); if (num_source_ports == 0) return nullptr; /* configure the destination ports */ value = param.GetBlockValue("destination_ports", nullptr); if (value == nullptr) { /* compatibility with MPD < 0.16 */ value = param.GetBlockValue("ports", nullptr); if (value != nullptr) FormatWarning(jack_output_domain, "deprecated option 'ports' in line %d", param.line); } if (value != nullptr) { num_destination_ports = parse_port_list(value, destination_ports, error); if (num_destination_ports == 0) return nullptr; } else { num_destination_ports = 0; } if (num_destination_ports > 0 && num_destination_ports != num_source_ports) FormatWarning(jack_output_domain, "number of source ports (%u) mismatches the " "number of destination ports (%u) in line %d", num_source_ports, num_destination_ports, param.line); ringbuffer_size = param.GetBlockValue("ringbuffer_size", 32768u); return true; } inline bool JackOutput::Enable(Error &error) { for (unsigned i = 0; i < num_source_ports; ++i) ringbuffer[i] = nullptr; return Connect(error); } inline void JackOutput::Disable() { if (client != nullptr) Disconnect(); for (unsigned i = 0; i < num_source_ports; ++i) { if (ringbuffer[i] != nullptr) { jack_ringbuffer_free(ringbuffer[i]); ringbuffer[i] = nullptr; } } } static AudioOutput * mpd_jack_init(const config_param ¶m, Error &error) { JackOutput *jd = new JackOutput(); if (!jd->Configure(param, error)) { delete jd; return nullptr; } 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(AudioOutput *ao) { JackOutput *jd = (JackOutput *)ao; delete jd; } static bool mpd_jack_enable(AudioOutput *ao, Error &error) { JackOutput &jo = *(JackOutput *)ao; return jo.Enable(error); } static void mpd_jack_disable(AudioOutput *ao) { JackOutput &jo = *(JackOutput *)ao; jo.Disable(); } /** * Stops the playback on the JACK connection. */ void JackOutput::Stop() { if (client == nullptr) return; if (shutdown) /* the connection has failed; close it */ Disconnect(); else /* the connection is alive: just stop playback */ jack_deactivate(client); } inline bool JackOutput::Start(Error &error) { assert(client != nullptr); assert(audio_format.channels <= 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 < num_source_ports; ++i) { if (ringbuffer[i] == nullptr) ringbuffer[i] = jack_ringbuffer_create(ringbuffer_size); /* clear the ring buffer to be sure that data from previous playbacks are gone */ jack_ringbuffer_reset(ringbuffer[i]); } if ( jack_activate(client) ) { error.Set(jack_output_domain, "cannot activate client"); Stop(); return false; } const char *dports[MAX_PORTS], **jports; unsigned num_dports; if (num_destination_ports == 0) { /* no output ports were configured - ask libjack for defaults */ jports = jack_get_ports(client, nullptr, nullptr, JackPortIsPhysical | JackPortIsInput); if (jports == nullptr) { error.Set(jack_output_domain, "no ports found"); Stop(); return false; } assert(*jports != nullptr); for (num_dports = 0; num_dports < MAX_PORTS && jports[num_dports] != nullptr; ++num_dports) { FormatDebug(jack_output_domain, "destination_port[%u] = '%s'\n", num_dports, jports[num_dports]); dports[num_dports] = jports[num_dports]; } } else { /* use the configured output ports */ num_dports = num_destination_ports; for (unsigned i = 0; i < num_dports; ++i) dports[i] = destination_ports[i].c_str(); jports = nullptr; } assert(num_dports > 0); const char *duplicate_port = nullptr; if (audio_format.channels >= 2 && num_dports == 1) { /* mix stereo signal on one speaker */ std::fill(dports + num_dports, dports + audio_format.channels, dports[0]); } else if (num_dports > audio_format.channels) { if (audio_format.channels == 1 && num_dports > 2) { /* mono input file: connect the one source channel to the both destination channels */ duplicate_port = dports[1]; num_dports = 1; } else /* connect only as many ports as we need */ num_dports = audio_format.channels; } assert(num_dports <= num_source_ports); for (unsigned i = 0; i < num_dports; ++i) { int ret = jack_connect(client, jack_port_name(ports[i]), dports[i]); if (ret != 0) { error.Format(jack_output_domain, "Not a valid JACK port: %s", dports[i]); if (jports != nullptr) free(jports); Stop(); return false; } } if (duplicate_port != nullptr) { /* mono input file: connect the one source channel to the both destination channels */ int ret; ret = jack_connect(client, jack_port_name(ports[0]), duplicate_port); if (ret != 0) { error.Format(jack_output_domain, "Not a valid JACK port: %s", duplicate_port); if (jports != nullptr) free(jports); Stop(); return false; } } if (jports != nullptr) free(jports); return true; } inline bool JackOutput::Open(AudioFormat &new_audio_format, Error &error) { pause = false; if (client != nullptr && shutdown) Disconnect(); if (client == nullptr && !Connect(error)) return false; set_audioformat(this, new_audio_format); audio_format = new_audio_format; return Start(error); } static bool mpd_jack_open(AudioOutput *ao, AudioFormat &audio_format, Error &error) { JackOutput &jo = *(JackOutput *)ao; return jo.Open(audio_format, error); } static void mpd_jack_close(AudioOutput *ao) { JackOutput &jo = *(JackOutput *)ao; jo.Stop(); } static unsigned mpd_jack_delay(AudioOutput *ao) { JackOutput *jd = (JackOutput *)ao; return jd->base.pause && jd->pause && !jd->shutdown ? 1000 : 0; } inline size_t JackOutput::WriteSamples(const float *src, size_t n_frames) { const unsigned n_channels = audio_format.channels; const size_t result = n_frames; while (n_frames-- > 0) for (unsigned i = 0; i < n_channels; ++i, ++src) jack_ringbuffer_write(ringbuffer[i], (const char *)src, sizeof(*src)); return result; } inline size_t JackOutput::Play(const void *chunk, size_t size, Error &error) { pause = false; const size_t frame_size = audio_format.GetFrameSize(); assert(size % frame_size == 0); size /= frame_size; size_t space = 0; while (true) { if (shutdown) { error.Set(jack_output_domain, "Refusing to play, because " "there is no client thread"); return 0; } space = jack_ringbuffer_write_space(ringbuffer[0]); for (unsigned i = 1; i < audio_format.channels; ++i) { unsigned space1 = jack_ringbuffer_write_space(ringbuffer[i]); if (space > space1) /* send data symmetrically */ space = space1; } if (space >= jack_sample_size) break; /* XXX do something more intelligent to synchronize */ usleep(1000); } space /= jack_sample_size; if (space < size) size = space; return WriteSamples((const float *)chunk, size) * frame_size; } static size_t mpd_jack_play(AudioOutput *ao, const void *chunk, size_t size, Error &error) { JackOutput &jo = *(JackOutput *)ao; return jo.Play(chunk, size, error); } static bool mpd_jack_pause(AudioOutput *ao) { JackOutput *jd = (JackOutput *)ao; if (jd->shutdown) return false; jd->pause = true; return true; } const struct AudioOutputPlugin 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, };