Chris@43: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ Chris@43: Chris@43: /* Chris@43: Sonic Visualiser Chris@43: An audio file viewer and annotation editor. Chris@43: Centre for Digital Music, Queen Mary, University of London. Chris@43: This file copyright 2006 Chris Cannam. Chris@43: Chris@43: This program is free software; you can redistribute it and/or Chris@43: modify it under the terms of the GNU General Public License as Chris@43: published by the Free Software Foundation; either version 2 of the Chris@43: License, or (at your option) any later version. See the file Chris@43: COPYING included with this distribution for more information. Chris@43: */ Chris@43: Chris@43: #ifdef HAVE_JACK Chris@43: Chris@43: #include "AudioJACKTarget.h" Chris@43: #include "AudioCallbackPlaySource.h" Chris@43: Chris@43: #include Chris@43: #include Chris@43: Chris@43: //#define DEBUG_AUDIO_JACK_TARGET 1 Chris@43: Chris@43: #ifdef BUILD_STATIC Chris@43: #ifdef Q_OS_LINUX Chris@43: Chris@43: // Some lunacy to enable JACK support in static builds. JACK isn't Chris@43: // supposed to be linked statically, because it depends on a Chris@43: // consistent shared memory layout between client library and daemon, Chris@43: // so it's very fragile in the face of version mismatches. Chris@43: // Chris@43: // Therefore for static builds on Linux we avoid linking against JACK Chris@43: // at all during the build, instead using dlopen and runtime symbol Chris@43: // lookup to switch on JACK support at runtime. The following big Chris@43: // mess (down to the #endifs) is the code that implements this. Chris@43: Chris@43: static void *symbol(const char *name) Chris@43: { Chris@43: static bool attempted = false; Chris@43: static void *library = 0; Chris@43: static std::map symbols; Chris@43: if (symbols.find(name) != symbols.end()) return symbols[name]; Chris@43: if (!library) { Chris@43: if (!attempted) { Chris@43: library = ::dlopen("libjack.so.1", RTLD_NOW); Chris@43: if (!library) library = ::dlopen("libjack.so.0", RTLD_NOW); Chris@43: if (!library) library = ::dlopen("libjack.so", RTLD_NOW); Chris@43: if (!library) { Chris@43: std::cerr << "WARNING: AudioJACKTarget: Failed to load JACK library: " Chris@43: << ::dlerror() << " (tried .so, .so.0, .so.1)" Chris@43: << std::endl; Chris@43: } Chris@43: attempted = true; Chris@43: } Chris@43: if (!library) return 0; Chris@43: } Chris@43: void *symbol = ::dlsym(library, name); Chris@43: if (!symbol) { Chris@43: std::cerr << "WARNING: AudioJACKTarget: Failed to locate symbol " Chris@43: << name << ": " << ::dlerror() << std::endl; Chris@43: } Chris@43: symbols[name] = symbol; Chris@43: return symbol; Chris@43: } Chris@43: Chris@56: static jack_client_t *dynamic_jack_client_open(const char *client_name, Chris@56: jack_options_t options, Chris@56: jack_status_t *status, ...) Chris@56: { Chris@67: typedef jack_client_t *(*func)(const char *client_name, Chris@67: jack_options_t options, Chris@67: jack_status_t *status, ...); Chris@56: void *s = symbol("jack_client_open"); Chris@56: if (!s) return 0; Chris@56: func f = (func)s; Chris@56: return f(client_name, options, status); // varargs not supported here Chris@56: } Chris@56: Chris@43: static int dynamic_jack_set_process_callback(jack_client_t *client, Chris@43: JackProcessCallback process_callback, Chris@43: void *arg) Chris@43: { Chris@43: typedef int (*func)(jack_client_t *client, Chris@43: JackProcessCallback process_callback, Chris@43: void *arg); Chris@43: void *s = symbol("jack_set_process_callback"); Chris@43: if (!s) return 1; Chris@43: func f = (func)s; Chris@43: return f(client, process_callback, arg); Chris@43: } Chris@43: Chris@43: static int dynamic_jack_set_xrun_callback(jack_client_t *client, Chris@43: JackXRunCallback xrun_callback, Chris@43: void *arg) Chris@43: { Chris@43: typedef int (*func)(jack_client_t *client, Chris@43: JackXRunCallback xrun_callback, Chris@43: void *arg); Chris@43: void *s = symbol("jack_set_xrun_callback"); Chris@43: if (!s) return 1; Chris@43: func f = (func)s; Chris@43: return f(client, xrun_callback, arg); Chris@43: } Chris@43: Chris@43: static const char **dynamic_jack_get_ports(jack_client_t *client, Chris@43: const char *port_name_pattern, Chris@43: const char *type_name_pattern, Chris@43: unsigned long flags) Chris@43: { Chris@43: typedef const char **(*func)(jack_client_t *client, Chris@43: const char *port_name_pattern, Chris@43: const char *type_name_pattern, Chris@43: unsigned long flags); Chris@43: void *s = symbol("jack_get_ports"); Chris@43: if (!s) return 0; Chris@43: func f = (func)s; Chris@43: return f(client, port_name_pattern, type_name_pattern, flags); Chris@43: } Chris@43: Chris@43: static jack_port_t *dynamic_jack_port_register(jack_client_t *client, Chris@43: const char *port_name, Chris@43: const char *port_type, Chris@43: unsigned long flags, Chris@43: unsigned long buffer_size) Chris@43: { Chris@43: typedef jack_port_t *(*func)(jack_client_t *client, Chris@43: const char *port_name, Chris@43: const char *port_type, Chris@43: unsigned long flags, Chris@43: unsigned long buffer_size); Chris@43: void *s = symbol("jack_port_register"); Chris@43: if (!s) return 0; Chris@43: func f = (func)s; Chris@43: return f(client, port_name, port_type, flags, buffer_size); Chris@43: } Chris@43: Chris@43: static int dynamic_jack_connect(jack_client_t *client, Chris@43: const char *source, Chris@43: const char *dest) Chris@43: { Chris@43: typedef int (*func)(jack_client_t *client, Chris@43: const char *source, Chris@43: const char *dest); Chris@43: void *s = symbol("jack_connect"); Chris@43: if (!s) return 1; Chris@43: func f = (func)s; Chris@43: return f(client, source, dest); Chris@43: } Chris@43: Chris@43: static void *dynamic_jack_port_get_buffer(jack_port_t *port, Chris@43: jack_nframes_t sz) Chris@43: { Chris@43: typedef void *(*func)(jack_port_t *, jack_nframes_t); Chris@43: void *s = symbol("jack_port_get_buffer"); Chris@43: if (!s) return 0; Chris@43: func f = (func)s; Chris@43: return f(port, sz); Chris@43: } Chris@43: Chris@43: static int dynamic_jack_port_unregister(jack_client_t *client, Chris@43: jack_port_t *port) Chris@43: { Chris@43: typedef int(*func)(jack_client_t *, jack_port_t *); Chris@43: void *s = symbol("jack_port_unregister"); Chris@43: if (!s) return 0; Chris@43: func f = (func)s; Chris@43: return f(client, port); Chris@43: } Chris@43: Chris@43: #define dynamic1(rv, name, argtype, failval) \ Chris@43: static rv dynamic_##name(argtype arg) { \ Chris@43: typedef rv (*func) (argtype); \ Chris@43: void *s = symbol(#name); \ Chris@43: if (!s) return failval; \ Chris@43: func f = (func) s; \ Chris@43: return f(arg); \ Chris@43: } Chris@43: Chris@43: dynamic1(jack_client_t *, jack_client_new, const char *, 0); Chris@43: dynamic1(jack_nframes_t, jack_get_buffer_size, jack_client_t *, 0); Chris@43: dynamic1(jack_nframes_t, jack_get_sample_rate, jack_client_t *, 0); Chris@43: dynamic1(int, jack_activate, jack_client_t *, 1); Chris@43: dynamic1(int, jack_deactivate, jack_client_t *, 1); Chris@43: dynamic1(int, jack_client_close, jack_client_t *, 1); Chris@43: dynamic1(jack_nframes_t, jack_port_get_latency, jack_port_t *, 0); Chris@43: dynamic1(const char *, jack_port_name, const jack_port_t *, 0); Chris@43: Chris@43: #define jack_client_new dynamic_jack_client_new Chris@56: #define jack_client_open dynamic_jack_client_open Chris@43: #define jack_get_buffer_size dynamic_jack_get_buffer_size Chris@43: #define jack_get_sample_rate dynamic_jack_get_sample_rate Chris@43: #define jack_set_process_callback dynamic_jack_set_process_callback Chris@43: #define jack_set_xrun_callback dynamic_jack_set_xrun_callback Chris@43: #define jack_activate dynamic_jack_activate Chris@43: #define jack_deactivate dynamic_jack_deactivate Chris@43: #define jack_client_close dynamic_jack_client_close Chris@43: #define jack_get_ports dynamic_jack_get_ports Chris@43: #define jack_port_register dynamic_jack_port_register Chris@43: #define jack_port_unregister dynamic_jack_port_unregister Chris@43: #define jack_port_get_latency dynamic_jack_port_get_latency Chris@43: #define jack_port_name dynamic_jack_port_name Chris@43: #define jack_connect dynamic_jack_connect Chris@43: #define jack_port_get_buffer dynamic_jack_port_get_buffer Chris@43: Chris@43: #endif Chris@43: #endif Chris@43: Chris@43: AudioJACKTarget::AudioJACKTarget(AudioCallbackPlaySource *source) : Chris@43: AudioCallbackPlayTarget(source), Chris@43: m_client(0), Chris@43: m_bufferSize(0), Chris@71: m_sampleRate(0), Chris@71: m_done(false) Chris@43: { Chris@56: JackOptions options = JackNullOption; Chris@56: #ifdef HAVE_PORTAUDIO Chris@56: options = JackNoStartServer; Chris@56: #endif Chris@56: Chris@56: JackStatus status = JackStatus(0); Chris@57: m_client = jack_client_open(source->getClientName().toLocal8Bit().data(), Chris@57: options, &status); Chris@57: Chris@43: if (!m_client) { Chris@56: std::cerr << "AudioJACKTarget: Failed to connect to JACK server: status code " Chris@56: << status << std::endl; Chris@56: return; Chris@43: } Chris@43: Chris@43: m_bufferSize = jack_get_buffer_size(m_client); Chris@43: m_sampleRate = jack_get_sample_rate(m_client); Chris@43: Chris@43: jack_set_xrun_callback(m_client, xrunStatic, this); Chris@43: jack_set_process_callback(m_client, processStatic, this); Chris@43: Chris@43: if (jack_activate(m_client)) { Chris@43: std::cerr << "ERROR: AudioJACKTarget: Failed to activate JACK client" Chris@43: << std::endl; Chris@43: } Chris@43: Chris@43: if (m_source) { Chris@43: sourceModelReplaced(); Chris@43: } Chris@43: } Chris@43: Chris@43: AudioJACKTarget::~AudioJACKTarget() Chris@43: { Chris@43: std::cerr << "AudioJACKTarget::~AudioJACKTarget()" << std::endl; Chris@71: Chris@71: shutdown(); Chris@71: Chris@43: if (m_client) { Chris@71: Chris@71: while (m_outputs.size() > 0) { Chris@71: std::vector::iterator itr = m_outputs.end(); Chris@71: --itr; Chris@71: jack_port_t *port = *itr; Chris@71: std::cerr << "unregister " << m_outputs.size() << std::endl; Chris@71: if (port) jack_port_unregister(m_client, port); Chris@71: m_outputs.erase(itr); Chris@71: } Chris@71: std::cerr << "Deactivating... "; Chris@43: jack_deactivate(m_client); Chris@71: std::cerr << "done\nClosing... "; Chris@43: jack_client_close(m_client); Chris@71: std::cerr << "done" << std::endl; Chris@43: } Chris@71: Chris@71: m_client = 0; Chris@71: Chris@43: std::cerr << "AudioJACKTarget::~AudioJACKTarget() done" << std::endl; Chris@43: } Chris@43: Chris@71: void Chris@71: AudioJACKTarget::shutdown() Chris@71: { Chris@71: m_done = true; Chris@71: } Chris@71: Chris@43: bool Chris@43: AudioJACKTarget::isOK() const Chris@43: { Chris@43: return (m_client != 0); Chris@43: } Chris@43: Chris@43: int Chris@43: AudioJACKTarget::processStatic(jack_nframes_t nframes, void *arg) Chris@43: { Chris@43: return ((AudioJACKTarget *)arg)->process(nframes); Chris@43: } Chris@43: Chris@43: int Chris@43: AudioJACKTarget::xrunStatic(void *arg) Chris@43: { Chris@43: return ((AudioJACKTarget *)arg)->xrun(); Chris@43: } Chris@43: Chris@43: void Chris@43: AudioJACKTarget::sourceModelReplaced() Chris@43: { Chris@43: m_mutex.lock(); Chris@43: Chris@43: m_source->setTargetBlockSize(m_bufferSize); Chris@43: m_source->setTargetSampleRate(m_sampleRate); Chris@43: Chris@43: size_t channels = m_source->getSourceChannelCount(); Chris@43: Chris@43: // Because we offer pan, we always want at least 2 channels Chris@43: if (channels < 2) channels = 2; Chris@43: Chris@43: if (channels == m_outputs.size() || !m_client) { Chris@43: m_mutex.unlock(); Chris@43: return; Chris@43: } Chris@43: Chris@43: const char **ports = Chris@43: jack_get_ports(m_client, NULL, NULL, Chris@43: JackPortIsPhysical | JackPortIsInput); Chris@43: size_t physicalPortCount = 0; Chris@43: while (ports[physicalPortCount]) ++physicalPortCount; Chris@43: Chris@43: #ifdef DEBUG_AUDIO_JACK_TARGET Chris@43: std::cerr << "AudioJACKTarget::sourceModelReplaced: have " << channels << " channels and " << physicalPortCount << " physical ports" << std::endl; Chris@43: #endif Chris@43: Chris@43: while (m_outputs.size() < channels) { Chris@43: Chris@43: char name[20]; Chris@43: jack_port_t *port; Chris@43: Chris@43: sprintf(name, "out %d", m_outputs.size() + 1); Chris@43: Chris@43: port = jack_port_register(m_client, Chris@43: name, Chris@43: JACK_DEFAULT_AUDIO_TYPE, Chris@43: JackPortIsOutput, Chris@43: 0); Chris@43: Chris@43: if (!port) { Chris@43: std::cerr Chris@43: << "ERROR: AudioJACKTarget: Failed to create JACK output port " Chris@43: << m_outputs.size() << std::endl; Chris@43: } else { Chris@43: m_source->setTargetPlayLatency(jack_port_get_latency(port)); Chris@43: } Chris@43: Chris@43: if (m_outputs.size() < physicalPortCount) { Chris@43: jack_connect(m_client, jack_port_name(port), ports[m_outputs.size()]); Chris@43: } Chris@43: Chris@43: m_outputs.push_back(port); Chris@43: } Chris@43: Chris@43: while (m_outputs.size() > channels) { Chris@43: std::vector::iterator itr = m_outputs.end(); Chris@43: --itr; Chris@43: jack_port_t *port = *itr; Chris@43: if (port) jack_port_unregister(m_client, port); Chris@43: m_outputs.erase(itr); Chris@43: } Chris@43: Chris@43: m_mutex.unlock(); Chris@43: } Chris@43: Chris@43: int Chris@43: AudioJACKTarget::process(jack_nframes_t nframes) Chris@43: { Chris@71: if (m_done) return 0; Chris@71: Chris@43: if (!m_mutex.tryLock()) { Chris@43: return 0; Chris@43: } Chris@43: Chris@43: if (m_outputs.empty()) { Chris@43: m_mutex.unlock(); Chris@43: return 0; Chris@43: } Chris@43: Chris@43: #ifdef DEBUG_AUDIO_JACK_TARGET Chris@43: std::cout << "AudioJACKTarget::process(" << nframes << "): have a source" << std::endl; Chris@43: #endif Chris@43: Chris@43: #ifdef DEBUG_AUDIO_JACK_TARGET Chris@43: if (m_bufferSize != nframes) { Chris@43: std::cerr << "WARNING: m_bufferSize != nframes (" << m_bufferSize << " != " << nframes << ")" << std::endl; Chris@43: } Chris@43: #endif Chris@43: Chris@43: float **buffers = (float **)alloca(m_outputs.size() * sizeof(float *)); Chris@43: Chris@43: for (size_t ch = 0; ch < m_outputs.size(); ++ch) { Chris@43: buffers[ch] = (float *)jack_port_get_buffer(m_outputs[ch], nframes); Chris@43: } Chris@43: Chris@43: size_t received = 0; Chris@43: Chris@43: if (m_source) { Chris@43: received = m_source->getSourceSamples(nframes, buffers); Chris@43: } Chris@43: Chris@43: for (size_t ch = 0; ch < m_outputs.size(); ++ch) { Chris@43: for (size_t i = received; i < nframes; ++i) { Chris@43: buffers[ch][i] = 0.0; Chris@43: } Chris@43: } Chris@43: Chris@43: float peakLeft = 0.0, peakRight = 0.0; Chris@43: Chris@43: for (size_t ch = 0; ch < m_outputs.size(); ++ch) { Chris@43: Chris@43: float peak = 0.0; Chris@43: Chris@43: for (size_t i = 0; i < nframes; ++i) { Chris@43: buffers[ch][i] *= m_outputGain; Chris@43: float sample = fabsf(buffers[ch][i]); Chris@43: if (sample > peak) peak = sample; Chris@43: } Chris@43: Chris@43: if (ch == 0) peakLeft = peak; Chris@43: if (ch > 0 || m_outputs.size() == 1) peakRight = peak; Chris@43: } Chris@43: Chris@43: if (m_source) { Chris@43: m_source->setOutputLevels(peakLeft, peakRight); Chris@43: } Chris@43: Chris@43: m_mutex.unlock(); Chris@43: return 0; Chris@43: } Chris@43: Chris@43: int Chris@43: AudioJACKTarget::xrun() Chris@43: { Chris@43: std::cerr << "AudioJACKTarget: xrun!" << std::endl; Chris@43: if (m_source) m_source->audioProcessingOverload(); Chris@43: return 0; Chris@43: } Chris@43: Chris@43: #endif /* HAVE_JACK */ Chris@43: