From f9208e1276573c0577f21cf6ecf3cfa580fca8b1 Mon Sep 17 00:00:00 2001 From: "Devin J. Pohly" Date: Tue, 6 Aug 2013 19:24:55 -0400 Subject: [PATCH] Add skeletal sound driver for JACK This doesn't make any sound yet, but it creates the JACK client and connects the requested ports if any. --- configure.ac | 11 ++ src/Makefile.am | 4 + src/arch/Sound/RageSoundDriver_JACK.cpp | 184 ++++++++++++++++++++++++ src/arch/Sound/RageSoundDriver_JACK.h | 58 ++++++++ src/arch/arch.cpp | 3 + 5 files changed, 260 insertions(+) create mode 100644 src/arch/Sound/RageSoundDriver_JACK.cpp create mode 100644 src/arch/Sound/RageSoundDriver_JACK.h diff --git a/configure.ac b/configure.ac index bd19709a49..cf6fab3b69 100644 --- a/configure.ac +++ b/configure.ac @@ -265,6 +265,16 @@ if test x$have_pulse = xyes; then LIBS="$LIBS -lpulse" fi +dnl Search for JACK unless --without-jack or --with-jack=no is given; require it for --with-jack +AC_ARG_WITH([jack], [AS_HELP_STRING([--without-jack], [Disable JACK sound driver])]) +AS_IF([test "x$with_jack" != "xno"], [ + AC_SEARCH_LIBS([jack_client_open], [jack], [have_jack=yes], [ + AS_IF([test "x$with_jack" != x], [ + AC_MSG_ERROR([--with-jack was specified but JACK was not found]) + ]) + ]) +]) + AC_MSG_CHECKING(if cstdlib breaks llabs) AC_LANG_PUSH(C++) AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include @@ -283,6 +293,7 @@ AC_DEFINE(__STDC_FORMAT_MACROS, 1, [Use PRId64 and similar]) AM_CONDITIONAL(HAVE_ALSA, test x$alsa != xfalse ) AM_CONDITIONAL(HAVE_GTK, test "$enable_gtk2" != "no" ) +AM_CONDITIONAL(HAVE_JACK, test x$have_jack = xyes) AM_CONDITIONAL(HAVE_OSS, test x$ac_cv_header_sys_soundcard_h = xyes ) AM_CONDITIONAL(HAVE_PULSE, test x$have_pulse = xyes) AM_CONDITIONAL(USE_CRASH_HANDLER, test "$use_crash_handler" = "yes" ) diff --git a/src/Makefile.am b/src/Makefile.am index 7c86fa913f..8a8e08b25c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -225,6 +225,10 @@ if HAVE_PULSE Sound += arch/Sound/RageSoundDriver_PulseAudio.cpp arch/Sound/RageSoundDriver_PulseAudio.h endif +if HAVE_JACK +Sound += arch/Sound/RageSoundDriver_JACK.cpp arch/Sound/RageSoundDriver_JACK.h +endif + if HAVE_OSS Sound += arch/Sound/RageSoundDriver_OSS.cpp arch/Sound/RageSoundDriver_OSS.h endif diff --git a/src/arch/Sound/RageSoundDriver_JACK.cpp b/src/arch/Sound/RageSoundDriver_JACK.cpp new file mode 100644 index 0000000000..678bd2d599 --- /dev/null +++ b/src/arch/Sound/RageSoundDriver_JACK.cpp @@ -0,0 +1,184 @@ +#include "global.h" +#include "RageSoundDriver_JACK.h" +#include "RageLog.h" +#include "RageUtil.h" +#include "PrefsManager.h" +#include "ProductInfo.h" + +REGISTER_SOUND_DRIVER_CLASS( JACK ); + +RageSoundDriver_JACK::RageSoundDriver_JACK() : + RageSoundDriver() +{ + client = NULL; + port_l = NULL; + port_r = NULL; +} + +RageSoundDriver_JACK::~RageSoundDriver_JACK() +{ + // Shut down client + jack_client_close(client); +} + +RString RageSoundDriver_JACK::Init() +{ + jack_status_t status; + RString error; + + // Open JACK client and call it "StepMania" or whatever + client = jack_client_open(PRODUCT_FAMILY, JackNullOption, &status); + if (client == NULL) + return "Couldn't connect to JACK server"; + + LOG->Trace("JACK connected at %u Hz", jack_get_sample_rate(client)); + + // Set callback for processing audio + if (jack_set_process_callback(client, ProcessTrampoline, this)) + { + error = "Couldn't set JACK process callback"; + goto out_close; + } + + // TODO Set a jack_on_shutdown callback as well? Probably just stop + // caring about sound altogether if that happens. + + // Create output ports + port_l = jack_port_register(client, "out_l", JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + if (port_l == NULL) + { + error = "Couldn't create JACK port out_l"; + goto out_close; + } + + port_r = jack_port_register(client, "out_r", JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + if (port_r == NULL) + { + error = "Couldn't create JACK port out_r"; + goto out_unreg_l; + } + + // Go! + if (jack_activate(client)) + { + error = "Couldn't activate JACK client"; + goto out_unreg_r; + } + + // Connect to playback port. This currently requires that you specify + // the client using the SoundDevice preference. The alternative is to + // connect automatically to something like `playback' if none is + // specified, which is not friendly JACK behavior. + error = ConnectPorts(); + if (!error.empty()) + goto out_deactivate; + + // Success! + return RString(); + + + // Not success! +out_deactivate: + jack_deactivate(client); +out_unreg_r: + jack_port_unregister(client, port_r); +out_unreg_l: + jack_port_unregister(client, port_l); +out_close: + jack_client_close(client); + return error; +} + +RString RageSoundDriver_JACK::ConnectPorts() +{ + vector portNames; + split(PREFSMAN->m_iSoundDevice.Get(), ",", portNames, true); + + switch (portNames.size()) + { + case 0: + // No ports specified + return RString(); + case 1: + // HACK: Pointing both channels at one port is probably the + // wrong way to mix to mono. For now, it works. + portNames.push_back(portNames[0]); + break; + case 2: + break; + default: + LOG->Warn("More than two JACK ports specified; using the first two only."); + break; + } + + ASSERT(portNames.size() >= 2); + + // Use jack_port_by_name to ensure ports exist, then jack_port_name to + // use their canonical name. (I'm not sure if that second step is + // necessary, I've seen something about "aliases" in the docs.) + jack_port_t *port_out = jack_port_by_name(client, portNames[0]); + if (port_out == NULL || jack_connect(client, jack_port_name(port_l), jack_port_name(port_out))) + return "Couldn't connect left JACK port"; + + port_out = jack_port_by_name(client, portNames[1]); + if (port_out == NULL || jack_connect(client, jack_port_name(port_r), jack_port_name(port_out))) + return "Couldn't connect right JACK port"; + + return RString(); +} + +int64_t RageSoundDriver_JACK::GetPosition() const +{ + return jack_frame_time(client); +} + +int RageSoundDriver_JACK::GetSampleRate() const +{ + return jack_get_sample_rate(client); +} + +int RageSoundDriver_JACK::ProcessCallback(jack_nframes_t nframes) +{ + jack_default_audio_sample_t *out_l = + (jack_default_audio_sample_t *) jack_port_get_buffer(port_l, nframes); + jack_default_audio_sample_t *out_r = + (jack_default_audio_sample_t *) jack_port_get_buffer(port_r, nframes); + + memset(out_l, 0, nframes * sizeof(jack_default_audio_sample_t)); + memset(out_r, 0, nframes * sizeof(jack_default_audio_sample_t)); + + return 0; +} + +// Static callback trampoline +int RageSoundDriver_JACK::ProcessTrampoline(jack_nframes_t nframes, void *arg) +{ + return ((RageSoundDriver_JACK *) arg)->ProcessCallback(nframes); +} + +/* + * (c) 2013 Devin J. Pohly + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF + * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS + * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT + * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ diff --git a/src/arch/Sound/RageSoundDriver_JACK.h b/src/arch/Sound/RageSoundDriver_JACK.h new file mode 100644 index 0000000000..d0e67ad1b7 --- /dev/null +++ b/src/arch/Sound/RageSoundDriver_JACK.h @@ -0,0 +1,58 @@ +#ifndef RAGE_SOUND_JACK +#define RAGE_SOUND_JACK + +#include "RageSoundDriver.h" +#include + +#define USE_RAGE_SOUND_JACK + +class RageSoundDriver_JACK: public RageSoundDriver +{ +public: + RageSoundDriver_JACK(); + ~RageSoundDriver_JACK(); + + RString Init(); + + int GetSampleRate() const; + int64_t GetPosition() const; + +private: + jack_client_t *client; + jack_port_t *port_l; + jack_port_t *port_r; + + // Helper for Init() + RString ConnectPorts(); + + // JACK callbacks and trampolines + int ProcessCallback(jack_nframes_t nframes); + static int ProcessTrampoline(jack_nframes_t nframes, void *arg); +}; + +#endif + +/* + * (c) 2013 Devin J. Pohly + * All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, and/or sell copies of the Software, and to permit persons to + * whom the Software is furnished to do so, provided that the above + * copyright notice(s) and this permission notice appear in all copies of + * the Software and that both the above copyright notice(s) and this + * permission notice appear in supporting documentation. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF + * THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS + * INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT + * OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS + * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ diff --git a/src/arch/arch.cpp b/src/arch/arch.cpp index be0e2e3e65..28802d5f79 100644 --- a/src/arch/arch.cpp +++ b/src/arch/arch.cpp @@ -264,6 +264,9 @@ RageSoundDriver *MakeRageSoundDriver( const RString &drivers ) #ifdef USE_RAGE_SOUND_DSOUND_SOFTWARE if( !DriversToTry[i].CompareNoCase("DirectSound-sw") ) ret = new RageSound_DSound_Software; #endif +#ifdef USE_RAGE_SOUND_JACK + if( !DriversToTry[i].CompareNoCase("JACK") ) ret = new RageSound_JACK; +#endif #ifdef USE_RAGE_SOUND_NULL if( !DriversToTry[i].CompareNoCase("Null") ) ret = new RageSound_Null; #endif