diff options
author | Max Kellermann <max@musicpd.org> | 2021-04-07 17:29:58 +0200 |
---|---|---|
committer | Max Kellermann <max@musicpd.org> | 2021-04-26 21:47:20 +0200 |
commit | 0f02bbc2fe4fa5e359dd3da7bb6fe9e87e96a98b (patch) | |
tree | 824508159b1c1736659d84d1304db9cb6f678a68 | |
parent | b885f358a51d1ea6b126372a3d122b26c19dbe46 (diff) |
output/jack: enable on Windows
This enables the JACK output plugin on Windows, but doesn't link
against libjack64.dll, instead loads the DLL at runtime with
LoadLibrary(). This kludge avoids the extremely fragile JACK shared
memory protocol by using the system's libjack64.dll, without requiring
the same DLL at build time.
-rw-r--r-- | NEWS | 1 | ||||
-rw-r--r-- | doc/plugins.rst | 4 | ||||
-rw-r--r-- | python/build/jack.py | 47 | ||||
-rw-r--r-- | python/build/libs.py | 7 | ||||
-rw-r--r-- | src/lib/jack/Dynamic.hxx | 182 | ||||
-rw-r--r-- | src/output/plugins/JackOutputPlugin.cxx | 8 | ||||
-rwxr-xr-x | win32/build.py | 1 |
7 files changed, 250 insertions, 0 deletions
@@ -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 <http://jackaudio.org/>`_. +On Windows, this plugin loads :file:`libjack64.dll` at runtime. This +means you need to `download and install the JACK windows build +<https://jackaudio.org/downloads/>`_. + .. 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<decltype(jack_set_error_function)>; +static jack_set_error_function_t _jack_set_error_function; + +using jack_set_info_function_t = std::add_pointer_t<decltype(jack_set_info_function)>; +static jack_set_info_function_t _jack_set_info_function; + +using jack_client_open_t = std::add_pointer_t<decltype(jack_client_open)>; +static jack_client_open_t _jack_client_open; + +using jack_client_close_t = std::add_pointer_t<decltype(jack_client_close)>; +static jack_client_close_t _jack_client_close; + +using jack_connect_t = std::add_pointer_t<decltype(jack_connect)>; +static jack_connect_t _jack_connect; + +using jack_activate_t = std::add_pointer_t<decltype(jack_activate)>; +static jack_activate_t _jack_activate; + +using jack_deactivate_t = std::add_pointer_t<decltype(jack_deactivate)>; +static jack_deactivate_t _jack_deactivate; + +using jack_get_sample_rate_t = std::add_pointer_t<decltype(jack_get_sample_rate)>; +static jack_get_sample_rate_t _jack_get_sample_rate; + +using jack_set_process_callback_t = std::add_pointer_t<decltype(jack_set_process_callback)>; +static jack_set_process_callback_t _jack_set_process_callback; + +using jack_on_info_shutdown_t = std::add_pointer_t<decltype(jack_on_info_shutdown)>; +static jack_on_info_shutdown_t _jack_on_info_shutdown; + +using jack_free_t = std::add_pointer_t<decltype(jack_free)>; +static jack_free_t _jack_free; + +using jack_get_ports_t = std::add_pointer_t<decltype(jack_get_ports)>; +static jack_get_ports_t _jack_get_ports; + +using jack_port_register_t = std::add_pointer_t<decltype(jack_port_register)>; +static jack_port_register_t _jack_port_register; + +using jack_port_name_t = std::add_pointer_t<decltype(jack_port_name)>; +static jack_port_name_t _jack_port_name; + +using jack_port_get_buffer_t = std::add_pointer_t<decltype(jack_port_get_buffer)>; +static jack_port_get_buffer_t _jack_port_get_buffer; + +using jack_ringbuffer_create_t = std::add_pointer_t<decltype(jack_ringbuffer_create)>; +static jack_ringbuffer_create_t _jack_ringbuffer_create; + +using jack_ringbuffer_free_t = std::add_pointer_t<decltype(jack_ringbuffer_free)>; +static jack_ringbuffer_free_t _jack_ringbuffer_free; + +using jack_ringbuffer_get_write_vector_t = std::add_pointer_t<decltype(jack_ringbuffer_get_write_vector)>; +static jack_ringbuffer_get_write_vector_t _jack_ringbuffer_get_write_vector; + +using jack_ringbuffer_write_advance_t = std::add_pointer_t<decltype(jack_ringbuffer_write_advance)>; +static jack_ringbuffer_write_advance_t _jack_ringbuffer_write_advance; + +using jack_ringbuffer_read_space_t = std::add_pointer_t<decltype(jack_ringbuffer_read_space)>; +static jack_ringbuffer_read_space_t _jack_ringbuffer_read_space; + +using jack_ringbuffer_read_t = std::add_pointer_t<decltype(jack_ringbuffer_read)>; +static jack_ringbuffer_read_t _jack_ringbuffer_read; + +using jack_ringbuffer_read_advance_t = std::add_pointer_t<decltype(jack_ringbuffer_read_advance)>; +static jack_ringbuffer_read_advance_t _jack_ringbuffer_read_advance; + +using jack_ringbuffer_reset_t = std::add_pointer_t<decltype(jack_ringbuffer_reset)>; +static jack_ringbuffer_reset_t _jack_ringbuffer_reset; + +template<typename T> +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<T>(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, ] |