diff --git a/NEWS b/NEWS index a9a846f70..74e975f31 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,7 @@ ver 0.22.7 (not yet released) - simple: fix database corruption bug * output - fix crash when pausing with multiple partitions + - jack: enable on Windows - httpd: send header "Access-Control-Allow-Origin: *" - wasapi: add algorithm for finding usable audio format - wasapi: use default device only if none was configured diff --git a/doc/plugins.rst b/doc/plugins.rst index 71b9ad004..3a4cf4b1a 100644 --- a/doc/plugins.rst +++ b/doc/plugins.rst @@ -910,6 +910,10 @@ jack The jack plugin connects to a `JACK server `_. +On Windows, this plugin loads :file:`libjack64.dll` at runtime. This +means you need to `download and install the JACK windows build +`_. + .. list-table:: :widths: 20 80 :header-rows: 1 diff --git a/python/build/jack.py b/python/build/jack.py new file mode 100644 index 000000000..6e71e7f1d --- /dev/null +++ b/python/build/jack.py @@ -0,0 +1,47 @@ +import os, shutil +import re + +from .project import Project + +# This class installs just the public headers and a fake pkg-config +# file which defines the macro "DYNAMIC_JACK". This tells MPD's JACK +# output plugin to load the libjack64.dll dynamically using +# LoadLibrary(). This kludge avoids the runtime DLL dependency for +# users who don't use JACK, but still allows using the system JACK +# client library. +# +# The problem with JACK is that it uses an extremely fragile shared +# memory protocol to communicate with the daemon. One needs to use +# daemon and client library from the same build. That's why we don't +# build libjack statically here; it would probably not be compatible +# with the user's JACK daemon. + +class JackProject(Project): + def __init__(self, url, md5, installed, + **kwargs): + m = re.match(r'.*/v([\d.]+)\.tar\.gz$', url) + self.version = m.group(1) + Project.__init__(self, url, md5, installed, + name='jack2', version=self.version, + base='jack2-' + self.version, + **kwargs) + + def build(self, toolchain): + src = self.unpack(toolchain) + + includes = ['jack.h', 'ringbuffer.h', 'systemdeps.h', 'transport.h', 'types.h', 'weakmacros.h'] + includedir = os.path.join(toolchain.install_prefix, 'include', 'jack') + os.makedirs(includedir, exist_ok=True) + + for i in includes: + shutil.copyfile(os.path.join(src, 'common', 'jack', i), + os.path.join(includedir, i)) + + with open(os.path.join(toolchain.install_prefix, 'lib', 'pkgconfig', 'jack.pc'), 'w') as f: + print("prefix=" + toolchain.install_prefix, file=f) + print("", file=f) + print("Name: jack", file=f) + print("Description: dummy", file=f) + print("Version: " + self.version, file=f) + print("Libs: ", file=f) + print("Cflags: -DDYNAMIC_JACK", file=f) diff --git a/python/build/libs.py b/python/build/libs.py index e5277856c..aa64db05c 100644 --- a/python/build/libs.py +++ b/python/build/libs.py @@ -9,6 +9,7 @@ from build.autotools import AutotoolsProject from build.ffmpeg import FfmpegProject from build.openssl import OpenSSLProject from build.boost import BoostProject +from build.jack import JackProject libmpdclient = MesonProject( 'https://www.musicpd.org/download/libmpdclient/2/libmpdclient-2.19.tar.xz', @@ -443,6 +444,12 @@ libnfs = AutotoolsProject( autoreconf=True, ) +jack = JackProject( + 'https://github.com/jackaudio/jack2/archive/v1.9.17.tar.gz', + '38f674bbc57852a8eb3d9faa1f96a0912d26f7d5df14c11005ad499c8ae352f2', + 'lib/pkgconfig/jack.pc', +) + boost = BoostProject( 'https://dl.bintray.com/boostorg/release/1.75.0/source/boost_1_75_0.tar.bz2', '953db31e016db7bb207f11432bef7df100516eeb746843fa0486a222e3fd49cb', diff --git a/src/lib/jack/Dynamic.hxx b/src/lib/jack/Dynamic.hxx new file mode 100644 index 000000000..92d4d72c9 --- /dev/null +++ b/src/lib/jack/Dynamic.hxx @@ -0,0 +1,182 @@ +/* + * Copyright 2003-2021 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 "system/Error.hxx" + +/* sorry for this horrible piece of code - there's no elegant way to + load DLLs at runtime */ + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-function-type" +#endif + +using jack_set_error_function_t = std::add_pointer_t; +static jack_set_error_function_t _jack_set_error_function; + +using jack_set_info_function_t = std::add_pointer_t; +static jack_set_info_function_t _jack_set_info_function; + +using jack_client_open_t = std::add_pointer_t; +static jack_client_open_t _jack_client_open; + +using jack_client_close_t = std::add_pointer_t; +static jack_client_close_t _jack_client_close; + +using jack_connect_t = std::add_pointer_t; +static jack_connect_t _jack_connect; + +using jack_activate_t = std::add_pointer_t; +static jack_activate_t _jack_activate; + +using jack_deactivate_t = std::add_pointer_t; +static jack_deactivate_t _jack_deactivate; + +using jack_get_sample_rate_t = std::add_pointer_t; +static jack_get_sample_rate_t _jack_get_sample_rate; + +using jack_set_process_callback_t = std::add_pointer_t; +static jack_set_process_callback_t _jack_set_process_callback; + +using jack_on_info_shutdown_t = std::add_pointer_t; +static jack_on_info_shutdown_t _jack_on_info_shutdown; + +using jack_free_t = std::add_pointer_t; +static jack_free_t _jack_free; + +using jack_get_ports_t = std::add_pointer_t; +static jack_get_ports_t _jack_get_ports; + +using jack_port_register_t = std::add_pointer_t; +static jack_port_register_t _jack_port_register; + +using jack_port_name_t = std::add_pointer_t; +static jack_port_name_t _jack_port_name; + +using jack_port_get_buffer_t = std::add_pointer_t; +static jack_port_get_buffer_t _jack_port_get_buffer; + +using jack_ringbuffer_create_t = std::add_pointer_t; +static jack_ringbuffer_create_t _jack_ringbuffer_create; + +using jack_ringbuffer_free_t = std::add_pointer_t; +static jack_ringbuffer_free_t _jack_ringbuffer_free; + +using jack_ringbuffer_get_write_vector_t = std::add_pointer_t; +static jack_ringbuffer_get_write_vector_t _jack_ringbuffer_get_write_vector; + +using jack_ringbuffer_write_advance_t = std::add_pointer_t; +static jack_ringbuffer_write_advance_t _jack_ringbuffer_write_advance; + +using jack_ringbuffer_read_space_t = std::add_pointer_t; +static jack_ringbuffer_read_space_t _jack_ringbuffer_read_space; + +using jack_ringbuffer_read_t = std::add_pointer_t; +static jack_ringbuffer_read_t _jack_ringbuffer_read; + +using jack_ringbuffer_read_advance_t = std::add_pointer_t; +static jack_ringbuffer_read_advance_t _jack_ringbuffer_read_advance; + +using jack_ringbuffer_reset_t = std::add_pointer_t; +static jack_ringbuffer_reset_t _jack_ringbuffer_reset; + +template +static void +GetFunction(HMODULE h, const char *name, T &result) +{ + auto f = GetProcAddress(h, name); + if (f == nullptr) + throw FormatRuntimeError("No such libjack function: %s", name); + + result = reinterpret_cast(f); +} + +static void +LoadJackLibrary() +{ +#ifdef _WIN64 +#define LIBJACK "libjack64" +#else +#define LIBJACK "libjack" +#endif + + auto libjack = LoadLibraryA(LIBJACK); + if (!libjack) + throw FormatLastError("Failed to load " LIBJACK ".dll"); + + GetFunction(libjack, "jack_set_error_function", _jack_set_error_function); + GetFunction(libjack, "jack_set_info_function", _jack_set_info_function); + + GetFunction(libjack, "jack_client_open", _jack_client_open); + GetFunction(libjack, "jack_client_close", _jack_client_close); + GetFunction(libjack, "jack_connect", _jack_connect); + GetFunction(libjack, "jack_activate", _jack_activate); + GetFunction(libjack, "jack_deactivate", _jack_deactivate); + GetFunction(libjack, "jack_free", _jack_free); + + GetFunction(libjack, "jack_get_sample_rate", _jack_get_sample_rate); + GetFunction(libjack, "jack_set_process_callback", _jack_set_process_callback); + GetFunction(libjack, "jack_on_info_shutdown", _jack_on_info_shutdown); + + GetFunction(libjack, "jack_get_ports", _jack_get_ports); + GetFunction(libjack, "jack_port_register", _jack_port_register); + GetFunction(libjack, "jack_port_name", _jack_port_name); + GetFunction(libjack, "jack_port_get_buffer", _jack_port_get_buffer); + + GetFunction(libjack, "jack_ringbuffer_create", _jack_ringbuffer_create); + GetFunction(libjack, "jack_ringbuffer_free", _jack_ringbuffer_free); + GetFunction(libjack, "jack_ringbuffer_get_write_vector", _jack_ringbuffer_get_write_vector); + GetFunction(libjack, "jack_ringbuffer_write_advance", _jack_ringbuffer_write_advance); + GetFunction(libjack, "jack_ringbuffer_read_space", _jack_ringbuffer_read_space); + GetFunction(libjack, "jack_ringbuffer_read", _jack_ringbuffer_read); + GetFunction(libjack, "jack_ringbuffer_read_advance", _jack_ringbuffer_read_advance); + GetFunction(libjack, "jack_ringbuffer_reset", _jack_ringbuffer_reset); +} + +#define jack_set_error_function _jack_set_error_function +#define jack_set_info_function _jack_set_info_function + +#define jack_client_open _jack_client_open +#define jack_client_close _jack_client_close +#define jack_connect _jack_connect +#define jack_activate _jack_activate +#define jack_deactivate _jack_deactivate +#define jack_free _jack_free + +#define jack_get_sample_rate _jack_get_sample_rate +#define jack_set_process_callback _jack_set_process_callback +#define jack_on_info_shutdown _jack_on_info_shutdown + +#define jack_get_ports _jack_get_ports +#define jack_port_register _jack_port_register +#define jack_port_name _jack_port_name +#define jack_port_get_buffer _jack_port_get_buffer + +#define jack_ringbuffer_create _jack_ringbuffer_create +#define jack_ringbuffer_free _jack_ringbuffer_free +#define jack_ringbuffer_get_write_vector _jack_ringbuffer_get_write_vector +#define jack_ringbuffer_write_advance _jack_ringbuffer_write_advance +#define jack_ringbuffer_read_space _jack_ringbuffer_read_space +#define jack_ringbuffer_read _jack_ringbuffer_read +#define jack_ringbuffer_read_advance _jack_ringbuffer_read_advance +#define jack_ringbuffer_reset _jack_ringbuffer_reset + +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif diff --git a/src/output/plugins/JackOutputPlugin.cxx b/src/output/plugins/JackOutputPlugin.cxx index 5f58f93f1..b0cb910d6 100644 --- a/src/output/plugins/JackOutputPlugin.cxx +++ b/src/output/plugins/JackOutputPlugin.cxx @@ -44,6 +44,10 @@ static constexpr unsigned MAX_PORTS = 16; static constexpr size_t jack_sample_size = sizeof(jack_default_audio_sample_t); +#ifdef DYNAMIC_JACK +#include "lib/jack/Dynamic.hxx" +#endif // _WIN32 + class JackOutput final : public AudioOutput { /** * libjack options passed to jack_client_open(). @@ -463,6 +467,10 @@ JackOutput::Disable() noexcept static AudioOutput * mpd_jack_init(EventLoop &, const ConfigBlock &block) { +#ifdef DYNAMIC_JACK + LoadJackLibrary(); +#endif + jack_set_error_function(mpd_jack_error); #ifdef HAVE_JACK_SET_INFO_FUNCTION diff --git a/win32/build.py b/win32/build.py index 8aa87e288..9f3011831 100755 --- a/win32/build.py +++ b/win32/build.py @@ -108,6 +108,7 @@ thirdparty_libs = [ curl, libexpat, libnfs, + jack, boost, ]