annotate audioio/AudioJACKTarget.cpp @ 77:0535c49069ba

* Various fixes to object lifetime management, particularly in the spectrum layer and for notification of main model deletion. The main purpose of this is to improve the behaviour of the spectrum, but I think it may also help with #1840922 Various crashes in Layer Summary window.
author Chris Cannam
date Wed, 23 Jan 2008 15:43:27 +0000
parents 716e9d2f91c7
children 19142c58cc4c
rev   line source
Chris@43 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@43 2
Chris@43 3 /*
Chris@43 4 Sonic Visualiser
Chris@43 5 An audio file viewer and annotation editor.
Chris@43 6 Centre for Digital Music, Queen Mary, University of London.
Chris@43 7 This file copyright 2006 Chris Cannam.
Chris@43 8
Chris@43 9 This program is free software; you can redistribute it and/or
Chris@43 10 modify it under the terms of the GNU General Public License as
Chris@43 11 published by the Free Software Foundation; either version 2 of the
Chris@43 12 License, or (at your option) any later version. See the file
Chris@43 13 COPYING included with this distribution for more information.
Chris@43 14 */
Chris@43 15
Chris@43 16 #ifdef HAVE_JACK
Chris@43 17
Chris@43 18 #include "AudioJACKTarget.h"
Chris@43 19 #include "AudioCallbackPlaySource.h"
Chris@43 20
Chris@43 21 #include <iostream>
Chris@43 22 #include <cmath>
Chris@43 23
Chris@43 24 //#define DEBUG_AUDIO_JACK_TARGET 1
Chris@43 25
Chris@43 26 #ifdef BUILD_STATIC
Chris@43 27 #ifdef Q_OS_LINUX
Chris@43 28
Chris@43 29 // Some lunacy to enable JACK support in static builds. JACK isn't
Chris@43 30 // supposed to be linked statically, because it depends on a
Chris@43 31 // consistent shared memory layout between client library and daemon,
Chris@43 32 // so it's very fragile in the face of version mismatches.
Chris@43 33 //
Chris@43 34 // Therefore for static builds on Linux we avoid linking against JACK
Chris@43 35 // at all during the build, instead using dlopen and runtime symbol
Chris@43 36 // lookup to switch on JACK support at runtime. The following big
Chris@43 37 // mess (down to the #endifs) is the code that implements this.
Chris@43 38
Chris@43 39 static void *symbol(const char *name)
Chris@43 40 {
Chris@43 41 static bool attempted = false;
Chris@43 42 static void *library = 0;
Chris@43 43 static std::map<const char *, void *> symbols;
Chris@43 44 if (symbols.find(name) != symbols.end()) return symbols[name];
Chris@43 45 if (!library) {
Chris@43 46 if (!attempted) {
Chris@43 47 library = ::dlopen("libjack.so.1", RTLD_NOW);
Chris@43 48 if (!library) library = ::dlopen("libjack.so.0", RTLD_NOW);
Chris@43 49 if (!library) library = ::dlopen("libjack.so", RTLD_NOW);
Chris@43 50 if (!library) {
Chris@43 51 std::cerr << "WARNING: AudioJACKTarget: Failed to load JACK library: "
Chris@43 52 << ::dlerror() << " (tried .so, .so.0, .so.1)"
Chris@43 53 << std::endl;
Chris@43 54 }
Chris@43 55 attempted = true;
Chris@43 56 }
Chris@43 57 if (!library) return 0;
Chris@43 58 }
Chris@43 59 void *symbol = ::dlsym(library, name);
Chris@43 60 if (!symbol) {
Chris@43 61 std::cerr << "WARNING: AudioJACKTarget: Failed to locate symbol "
Chris@43 62 << name << ": " << ::dlerror() << std::endl;
Chris@43 63 }
Chris@43 64 symbols[name] = symbol;
Chris@43 65 return symbol;
Chris@43 66 }
Chris@43 67
Chris@56 68 static jack_client_t *dynamic_jack_client_open(const char *client_name,
Chris@56 69 jack_options_t options,
Chris@56 70 jack_status_t *status, ...)
Chris@56 71 {
Chris@70 72 typedef jack_client_t *(*func)(const char *client_name,
Chris@70 73 jack_options_t options,
Chris@70 74 jack_status_t *status, ...);
Chris@56 75 void *s = symbol("jack_client_open");
Chris@56 76 if (!s) return 0;
Chris@56 77 func f = (func)s;
Chris@56 78 return f(client_name, options, status); // varargs not supported here
Chris@56 79 }
Chris@56 80
Chris@43 81 static int dynamic_jack_set_process_callback(jack_client_t *client,
Chris@43 82 JackProcessCallback process_callback,
Chris@43 83 void *arg)
Chris@43 84 {
Chris@43 85 typedef int (*func)(jack_client_t *client,
Chris@43 86 JackProcessCallback process_callback,
Chris@43 87 void *arg);
Chris@43 88 void *s = symbol("jack_set_process_callback");
Chris@43 89 if (!s) return 1;
Chris@43 90 func f = (func)s;
Chris@43 91 return f(client, process_callback, arg);
Chris@43 92 }
Chris@43 93
Chris@43 94 static int dynamic_jack_set_xrun_callback(jack_client_t *client,
Chris@43 95 JackXRunCallback xrun_callback,
Chris@43 96 void *arg)
Chris@43 97 {
Chris@43 98 typedef int (*func)(jack_client_t *client,
Chris@43 99 JackXRunCallback xrun_callback,
Chris@43 100 void *arg);
Chris@43 101 void *s = symbol("jack_set_xrun_callback");
Chris@43 102 if (!s) return 1;
Chris@43 103 func f = (func)s;
Chris@43 104 return f(client, xrun_callback, arg);
Chris@43 105 }
Chris@43 106
Chris@43 107 static const char **dynamic_jack_get_ports(jack_client_t *client,
Chris@43 108 const char *port_name_pattern,
Chris@43 109 const char *type_name_pattern,
Chris@43 110 unsigned long flags)
Chris@43 111 {
Chris@43 112 typedef const char **(*func)(jack_client_t *client,
Chris@43 113 const char *port_name_pattern,
Chris@43 114 const char *type_name_pattern,
Chris@43 115 unsigned long flags);
Chris@43 116 void *s = symbol("jack_get_ports");
Chris@43 117 if (!s) return 0;
Chris@43 118 func f = (func)s;
Chris@43 119 return f(client, port_name_pattern, type_name_pattern, flags);
Chris@43 120 }
Chris@43 121
Chris@43 122 static jack_port_t *dynamic_jack_port_register(jack_client_t *client,
Chris@43 123 const char *port_name,
Chris@43 124 const char *port_type,
Chris@43 125 unsigned long flags,
Chris@43 126 unsigned long buffer_size)
Chris@43 127 {
Chris@43 128 typedef jack_port_t *(*func)(jack_client_t *client,
Chris@43 129 const char *port_name,
Chris@43 130 const char *port_type,
Chris@43 131 unsigned long flags,
Chris@43 132 unsigned long buffer_size);
Chris@43 133 void *s = symbol("jack_port_register");
Chris@43 134 if (!s) return 0;
Chris@43 135 func f = (func)s;
Chris@43 136 return f(client, port_name, port_type, flags, buffer_size);
Chris@43 137 }
Chris@43 138
Chris@43 139 static int dynamic_jack_connect(jack_client_t *client,
Chris@43 140 const char *source,
Chris@43 141 const char *dest)
Chris@43 142 {
Chris@43 143 typedef int (*func)(jack_client_t *client,
Chris@43 144 const char *source,
Chris@43 145 const char *dest);
Chris@43 146 void *s = symbol("jack_connect");
Chris@43 147 if (!s) return 1;
Chris@43 148 func f = (func)s;
Chris@43 149 return f(client, source, dest);
Chris@43 150 }
Chris@43 151
Chris@43 152 static void *dynamic_jack_port_get_buffer(jack_port_t *port,
Chris@43 153 jack_nframes_t sz)
Chris@43 154 {
Chris@43 155 typedef void *(*func)(jack_port_t *, jack_nframes_t);
Chris@43 156 void *s = symbol("jack_port_get_buffer");
Chris@43 157 if (!s) return 0;
Chris@43 158 func f = (func)s;
Chris@43 159 return f(port, sz);
Chris@43 160 }
Chris@43 161
Chris@43 162 static int dynamic_jack_port_unregister(jack_client_t *client,
Chris@43 163 jack_port_t *port)
Chris@43 164 {
Chris@43 165 typedef int(*func)(jack_client_t *, jack_port_t *);
Chris@43 166 void *s = symbol("jack_port_unregister");
Chris@43 167 if (!s) return 0;
Chris@43 168 func f = (func)s;
Chris@43 169 return f(client, port);
Chris@43 170 }
Chris@43 171
Chris@43 172 #define dynamic1(rv, name, argtype, failval) \
Chris@43 173 static rv dynamic_##name(argtype arg) { \
Chris@43 174 typedef rv (*func) (argtype); \
Chris@43 175 void *s = symbol(#name); \
Chris@43 176 if (!s) return failval; \
Chris@43 177 func f = (func) s; \
Chris@43 178 return f(arg); \
Chris@43 179 }
Chris@43 180
Chris@43 181 dynamic1(jack_client_t *, jack_client_new, const char *, 0);
Chris@43 182 dynamic1(jack_nframes_t, jack_get_buffer_size, jack_client_t *, 0);
Chris@43 183 dynamic1(jack_nframes_t, jack_get_sample_rate, jack_client_t *, 0);
Chris@43 184 dynamic1(int, jack_activate, jack_client_t *, 1);
Chris@43 185 dynamic1(int, jack_deactivate, jack_client_t *, 1);
Chris@43 186 dynamic1(int, jack_client_close, jack_client_t *, 1);
Chris@43 187 dynamic1(jack_nframes_t, jack_port_get_latency, jack_port_t *, 0);
Chris@43 188 dynamic1(const char *, jack_port_name, const jack_port_t *, 0);
Chris@43 189
Chris@43 190 #define jack_client_new dynamic_jack_client_new
Chris@56 191 #define jack_client_open dynamic_jack_client_open
Chris@43 192 #define jack_get_buffer_size dynamic_jack_get_buffer_size
Chris@43 193 #define jack_get_sample_rate dynamic_jack_get_sample_rate
Chris@43 194 #define jack_set_process_callback dynamic_jack_set_process_callback
Chris@43 195 #define jack_set_xrun_callback dynamic_jack_set_xrun_callback
Chris@43 196 #define jack_activate dynamic_jack_activate
Chris@43 197 #define jack_deactivate dynamic_jack_deactivate
Chris@43 198 #define jack_client_close dynamic_jack_client_close
Chris@43 199 #define jack_get_ports dynamic_jack_get_ports
Chris@43 200 #define jack_port_register dynamic_jack_port_register
Chris@43 201 #define jack_port_unregister dynamic_jack_port_unregister
Chris@43 202 #define jack_port_get_latency dynamic_jack_port_get_latency
Chris@43 203 #define jack_port_name dynamic_jack_port_name
Chris@43 204 #define jack_connect dynamic_jack_connect
Chris@43 205 #define jack_port_get_buffer dynamic_jack_port_get_buffer
Chris@43 206
Chris@43 207 #endif
Chris@43 208 #endif
Chris@43 209
Chris@43 210 AudioJACKTarget::AudioJACKTarget(AudioCallbackPlaySource *source) :
Chris@43 211 AudioCallbackPlayTarget(source),
Chris@43 212 m_client(0),
Chris@43 213 m_bufferSize(0),
Chris@70 214 m_sampleRate(0),
Chris@70 215 m_done(false)
Chris@43 216 {
Chris@56 217 JackOptions options = JackNullOption;
Chris@56 218 #ifdef HAVE_PORTAUDIO
Chris@56 219 options = JackNoStartServer;
Chris@56 220 #endif
Chris@56 221
Chris@56 222 JackStatus status = JackStatus(0);
Chris@57 223 m_client = jack_client_open(source->getClientName().toLocal8Bit().data(),
Chris@57 224 options, &status);
Chris@57 225
Chris@43 226 if (!m_client) {
Chris@56 227 std::cerr << "AudioJACKTarget: Failed to connect to JACK server: status code "
Chris@56 228 << status << std::endl;
Chris@56 229 return;
Chris@43 230 }
Chris@43 231
Chris@43 232 m_bufferSize = jack_get_buffer_size(m_client);
Chris@43 233 m_sampleRate = jack_get_sample_rate(m_client);
Chris@43 234
Chris@43 235 jack_set_xrun_callback(m_client, xrunStatic, this);
Chris@43 236 jack_set_process_callback(m_client, processStatic, this);
Chris@43 237
Chris@43 238 if (jack_activate(m_client)) {
Chris@43 239 std::cerr << "ERROR: AudioJACKTarget: Failed to activate JACK client"
Chris@43 240 << std::endl;
Chris@43 241 }
Chris@43 242
Chris@43 243 if (m_source) {
Chris@43 244 sourceModelReplaced();
Chris@43 245 }
Chris@43 246 }
Chris@43 247
Chris@43 248 AudioJACKTarget::~AudioJACKTarget()
Chris@43 249 {
Chris@43 250 std::cerr << "AudioJACKTarget::~AudioJACKTarget()" << std::endl;
Chris@70 251
Chris@70 252 shutdown();
Chris@70 253
Chris@43 254 if (m_client) {
Chris@70 255
Chris@70 256 while (m_outputs.size() > 0) {
Chris@70 257 std::vector<jack_port_t *>::iterator itr = m_outputs.end();
Chris@70 258 --itr;
Chris@70 259 jack_port_t *port = *itr;
Chris@70 260 std::cerr << "unregister " << m_outputs.size() << std::endl;
Chris@70 261 if (port) jack_port_unregister(m_client, port);
Chris@70 262 m_outputs.erase(itr);
Chris@70 263 }
Chris@70 264 std::cerr << "Deactivating... ";
Chris@43 265 jack_deactivate(m_client);
Chris@70 266 std::cerr << "done\nClosing... ";
Chris@43 267 jack_client_close(m_client);
Chris@70 268 std::cerr << "done" << std::endl;
Chris@43 269 }
Chris@70 270
Chris@70 271 m_client = 0;
Chris@70 272
Chris@43 273 std::cerr << "AudioJACKTarget::~AudioJACKTarget() done" << std::endl;
Chris@43 274 }
Chris@43 275
Chris@70 276 void
Chris@70 277 AudioJACKTarget::shutdown()
Chris@70 278 {
Chris@70 279 m_done = true;
Chris@70 280 }
Chris@70 281
Chris@43 282 bool
Chris@43 283 AudioJACKTarget::isOK() const
Chris@43 284 {
Chris@43 285 return (m_client != 0);
Chris@43 286 }
Chris@43 287
Chris@43 288 int
Chris@43 289 AudioJACKTarget::processStatic(jack_nframes_t nframes, void *arg)
Chris@43 290 {
Chris@43 291 return ((AudioJACKTarget *)arg)->process(nframes);
Chris@43 292 }
Chris@43 293
Chris@43 294 int
Chris@43 295 AudioJACKTarget::xrunStatic(void *arg)
Chris@43 296 {
Chris@43 297 return ((AudioJACKTarget *)arg)->xrun();
Chris@43 298 }
Chris@43 299
Chris@43 300 void
Chris@43 301 AudioJACKTarget::sourceModelReplaced()
Chris@43 302 {
Chris@43 303 m_mutex.lock();
Chris@43 304
Chris@43 305 m_source->setTargetBlockSize(m_bufferSize);
Chris@43 306 m_source->setTargetSampleRate(m_sampleRate);
Chris@43 307
Chris@43 308 size_t channels = m_source->getSourceChannelCount();
Chris@43 309
Chris@43 310 // Because we offer pan, we always want at least 2 channels
Chris@43 311 if (channels < 2) channels = 2;
Chris@43 312
Chris@43 313 if (channels == m_outputs.size() || !m_client) {
Chris@43 314 m_mutex.unlock();
Chris@43 315 return;
Chris@43 316 }
Chris@43 317
Chris@43 318 const char **ports =
Chris@43 319 jack_get_ports(m_client, NULL, NULL,
Chris@43 320 JackPortIsPhysical | JackPortIsInput);
Chris@43 321 size_t physicalPortCount = 0;
Chris@43 322 while (ports[physicalPortCount]) ++physicalPortCount;
Chris@43 323
Chris@43 324 #ifdef DEBUG_AUDIO_JACK_TARGET
Chris@43 325 std::cerr << "AudioJACKTarget::sourceModelReplaced: have " << channels << " channels and " << physicalPortCount << " physical ports" << std::endl;
Chris@43 326 #endif
Chris@43 327
Chris@43 328 while (m_outputs.size() < channels) {
Chris@43 329
Chris@43 330 char name[20];
Chris@43 331 jack_port_t *port;
Chris@43 332
Chris@43 333 sprintf(name, "out %d", m_outputs.size() + 1);
Chris@43 334
Chris@43 335 port = jack_port_register(m_client,
Chris@43 336 name,
Chris@43 337 JACK_DEFAULT_AUDIO_TYPE,
Chris@43 338 JackPortIsOutput,
Chris@43 339 0);
Chris@43 340
Chris@43 341 if (!port) {
Chris@43 342 std::cerr
Chris@43 343 << "ERROR: AudioJACKTarget: Failed to create JACK output port "
Chris@43 344 << m_outputs.size() << std::endl;
Chris@43 345 } else {
Chris@43 346 m_source->setTargetPlayLatency(jack_port_get_latency(port));
Chris@43 347 }
Chris@43 348
Chris@43 349 if (m_outputs.size() < physicalPortCount) {
Chris@43 350 jack_connect(m_client, jack_port_name(port), ports[m_outputs.size()]);
Chris@43 351 }
Chris@43 352
Chris@43 353 m_outputs.push_back(port);
Chris@43 354 }
Chris@43 355
Chris@43 356 while (m_outputs.size() > channels) {
Chris@43 357 std::vector<jack_port_t *>::iterator itr = m_outputs.end();
Chris@43 358 --itr;
Chris@43 359 jack_port_t *port = *itr;
Chris@43 360 if (port) jack_port_unregister(m_client, port);
Chris@43 361 m_outputs.erase(itr);
Chris@43 362 }
Chris@43 363
Chris@43 364 m_mutex.unlock();
Chris@43 365 }
Chris@43 366
Chris@43 367 int
Chris@43 368 AudioJACKTarget::process(jack_nframes_t nframes)
Chris@43 369 {
Chris@70 370 if (m_done) return 0;
Chris@70 371
Chris@43 372 if (!m_mutex.tryLock()) {
Chris@43 373 return 0;
Chris@43 374 }
Chris@43 375
Chris@43 376 if (m_outputs.empty()) {
Chris@43 377 m_mutex.unlock();
Chris@43 378 return 0;
Chris@43 379 }
Chris@43 380
Chris@43 381 #ifdef DEBUG_AUDIO_JACK_TARGET
Chris@43 382 std::cout << "AudioJACKTarget::process(" << nframes << "): have a source" << std::endl;
Chris@43 383 #endif
Chris@43 384
Chris@43 385 #ifdef DEBUG_AUDIO_JACK_TARGET
Chris@43 386 if (m_bufferSize != nframes) {
Chris@43 387 std::cerr << "WARNING: m_bufferSize != nframes (" << m_bufferSize << " != " << nframes << ")" << std::endl;
Chris@43 388 }
Chris@43 389 #endif
Chris@43 390
Chris@43 391 float **buffers = (float **)alloca(m_outputs.size() * sizeof(float *));
Chris@43 392
Chris@43 393 for (size_t ch = 0; ch < m_outputs.size(); ++ch) {
Chris@43 394 buffers[ch] = (float *)jack_port_get_buffer(m_outputs[ch], nframes);
Chris@43 395 }
Chris@43 396
Chris@43 397 size_t received = 0;
Chris@43 398
Chris@43 399 if (m_source) {
Chris@43 400 received = m_source->getSourceSamples(nframes, buffers);
Chris@43 401 }
Chris@43 402
Chris@43 403 for (size_t ch = 0; ch < m_outputs.size(); ++ch) {
Chris@43 404 for (size_t i = received; i < nframes; ++i) {
Chris@43 405 buffers[ch][i] = 0.0;
Chris@43 406 }
Chris@43 407 }
Chris@43 408
Chris@43 409 float peakLeft = 0.0, peakRight = 0.0;
Chris@43 410
Chris@43 411 for (size_t ch = 0; ch < m_outputs.size(); ++ch) {
Chris@43 412
Chris@43 413 float peak = 0.0;
Chris@43 414
Chris@43 415 for (size_t i = 0; i < nframes; ++i) {
Chris@43 416 buffers[ch][i] *= m_outputGain;
Chris@43 417 float sample = fabsf(buffers[ch][i]);
Chris@43 418 if (sample > peak) peak = sample;
Chris@43 419 }
Chris@43 420
Chris@43 421 if (ch == 0) peakLeft = peak;
Chris@43 422 if (ch > 0 || m_outputs.size() == 1) peakRight = peak;
Chris@43 423 }
Chris@43 424
Chris@43 425 if (m_source) {
Chris@43 426 m_source->setOutputLevels(peakLeft, peakRight);
Chris@43 427 }
Chris@43 428
Chris@43 429 m_mutex.unlock();
Chris@43 430 return 0;
Chris@43 431 }
Chris@43 432
Chris@43 433 int
Chris@43 434 AudioJACKTarget::xrun()
Chris@43 435 {
Chris@43 436 std::cerr << "AudioJACKTarget: xrun!" << std::endl;
Chris@43 437 if (m_source) m_source->audioProcessingOverload();
Chris@43 438 return 0;
Chris@43 439 }
Chris@43 440
Chris@43 441 #endif /* HAVE_JACK */
Chris@43 442