annotate audioio/AudioPulseAudioTarget.cpp @ 451:dc1a360f2b69

Allow layers to be loaded without models if their layer class explicitly says it's OK (otherwise default template won't load, as it has an empty waveform layer)
author Chris Cannam
date Mon, 20 Apr 2015 10:10:26 +0100
parents 72c662fe7ea3
children ad998a2fe9e2
rev   line source
Chris@117 1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
Chris@117 2
Chris@117 3 /*
Chris@117 4 Sonic Visualiser
Chris@117 5 An audio file viewer and annotation editor.
Chris@117 6 Centre for Digital Music, Queen Mary, University of London.
Chris@117 7 This file copyright 2008 QMUL.
Chris@117 8
Chris@117 9 This program is free software; you can redistribute it and/or
Chris@117 10 modify it under the terms of the GNU General Public License as
Chris@117 11 published by the Free Software Foundation; either version 2 of the
Chris@117 12 License, or (at your option) any later version. See the file
Chris@117 13 COPYING included with this distribution for more information.
Chris@117 14 */
Chris@117 15
Chris@117 16 #ifdef HAVE_LIBPULSE
Chris@117 17
Chris@117 18 #include "AudioPulseAudioTarget.h"
Chris@117 19 #include "AudioCallbackPlaySource.h"
Chris@117 20
Chris@117 21 #include <QMutexLocker>
Chris@117 22
Chris@117 23 #include <iostream>
Chris@117 24 #include <cassert>
Chris@117 25 #include <cmath>
Chris@117 26
Chris@195 27 #define DEBUG_AUDIO_PULSE_AUDIO_TARGET 1
Chris@195 28 //#define DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY 1
Chris@117 29
Chris@117 30 AudioPulseAudioTarget::AudioPulseAudioTarget(AudioCallbackPlaySource *source) :
Chris@117 31 AudioCallbackPlayTarget(source),
Chris@117 32 m_mutex(QMutex::Recursive),
Chris@117 33 m_loop(0),
Chris@117 34 m_api(0),
Chris@117 35 m_context(0),
Chris@117 36 m_stream(0),
Chris@117 37 m_loopThread(0),
Chris@117 38 m_bufferSize(0),
Chris@117 39 m_sampleRate(0),
Chris@117 40 m_latency(0),
Chris@117 41 m_done(false)
Chris@117 42 {
Chris@117 43 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET
Chris@293 44 cerr << "AudioPulseAudioTarget: Initialising for PulseAudio" << endl;
Chris@117 45 #endif
Chris@117 46
Chris@117 47 m_loop = pa_mainloop_new();
Chris@117 48 if (!m_loop) {
Chris@293 49 cerr << "ERROR: AudioPulseAudioTarget: Failed to create main loop" << endl;
Chris@117 50 return;
Chris@117 51 }
Chris@117 52
Chris@117 53 m_api = pa_mainloop_get_api(m_loop);
Chris@117 54
Chris@117 55 //!!! handle signals how?
Chris@117 56
Chris@118 57 m_bufferSize = 20480;
Chris@117 58 m_sampleRate = 44100;
Chris@117 59 if (m_source && (m_source->getSourceSampleRate() != 0)) {
Chris@436 60 m_sampleRate = int(m_source->getSourceSampleRate());
Chris@117 61 }
Chris@117 62 m_spec.rate = m_sampleRate;
Chris@117 63 m_spec.channels = 2;
Chris@117 64 m_spec.format = PA_SAMPLE_FLOAT32NE;
Chris@117 65
Chris@195 66 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET
Chris@293 67 cerr << "AudioPulseAudioTarget: Creating context" << endl;
Chris@195 68 #endif
Chris@195 69
Chris@117 70 m_context = pa_context_new(m_api, source->getClientName().toLocal8Bit().data());
Chris@117 71 if (!m_context) {
Chris@293 72 cerr << "ERROR: AudioPulseAudioTarget: Failed to create context object" << endl;
Chris@117 73 return;
Chris@117 74 }
Chris@117 75
Chris@117 76 pa_context_set_state_callback(m_context, contextStateChangedStatic, this);
Chris@117 77
Chris@195 78 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET
Chris@293 79 cerr << "AudioPulseAudioTarget: Connecting to default server..." << endl;
Chris@195 80 #endif
Chris@195 81
Chris@195 82 pa_context_connect(m_context, 0, // default server
Chris@195 83 (pa_context_flags_t)PA_CONTEXT_NOAUTOSPAWN, 0);
Chris@195 84
Chris@195 85 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET
Chris@293 86 cerr << "AudioPulseAudioTarget: Starting main loop" << endl;
Chris@195 87 #endif
Chris@117 88
Chris@117 89 m_loopThread = new MainLoopThread(m_loop);
Chris@117 90 m_loopThread->start();
Chris@117 91
Chris@117 92 #ifdef DEBUG_PULSE_AUDIO_TARGET
Chris@293 93 cerr << "AudioPulseAudioTarget: initialised OK" << endl;
Chris@117 94 #endif
Chris@117 95 }
Chris@117 96
Chris@117 97 AudioPulseAudioTarget::~AudioPulseAudioTarget()
Chris@117 98 {
Chris@233 99 SVDEBUG << "AudioPulseAudioTarget::~AudioPulseAudioTarget()" << endl;
Chris@117 100
Chris@117 101 if (m_source) {
Chris@117 102 m_source->setTarget(0, m_bufferSize);
Chris@117 103 }
Chris@117 104
Chris@117 105 shutdown();
Chris@117 106
Chris@117 107 QMutexLocker locker(&m_mutex);
Chris@117 108
Chris@117 109 if (m_stream) pa_stream_unref(m_stream);
Chris@117 110
Chris@117 111 if (m_context) pa_context_unref(m_context);
Chris@117 112
Chris@117 113 if (m_loop) {
Chris@117 114 pa_signal_done();
Chris@117 115 pa_mainloop_free(m_loop);
Chris@117 116 }
Chris@117 117
Chris@117 118 m_stream = 0;
Chris@117 119 m_context = 0;
Chris@117 120 m_loop = 0;
Chris@117 121
Chris@233 122 SVDEBUG << "AudioPulseAudioTarget::~AudioPulseAudioTarget() done" << endl;
Chris@117 123 }
Chris@117 124
Chris@117 125 void
Chris@117 126 AudioPulseAudioTarget::shutdown()
Chris@117 127 {
Chris@117 128 m_done = true;
Chris@117 129 }
Chris@117 130
Chris@117 131 bool
Chris@117 132 AudioPulseAudioTarget::isOK() const
Chris@117 133 {
Chris@117 134 return (m_context != 0);
Chris@117 135 }
Chris@117 136
Chris@117 137 double
Chris@117 138 AudioPulseAudioTarget::getCurrentTime() const
Chris@117 139 {
Chris@117 140 if (!m_stream) return 0.0;
Chris@118 141
Chris@118 142 pa_usec_t usec = 0;
Chris@118 143 pa_stream_get_time(m_stream, &usec);
Chris@436 144 return double(usec) / 1000000.0;
Chris@117 145 }
Chris@117 146
Chris@117 147 void
Chris@117 148 AudioPulseAudioTarget::sourceModelReplaced()
Chris@117 149 {
Chris@117 150 m_source->setTargetSampleRate(m_sampleRate);
Chris@117 151 }
Chris@117 152
Chris@117 153 void
Chris@436 154 AudioPulseAudioTarget::streamWriteStatic(pa_stream *,
Chris@117 155 size_t length,
Chris@117 156 void *data)
Chris@117 157 {
Chris@117 158 AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data;
Chris@117 159
Chris@436 160 // assert(stream == target->m_stream);
Chris@117 161
Chris@117 162 target->streamWrite(length);
Chris@117 163 }
Chris@117 164
Chris@117 165 void
Chris@436 166 AudioPulseAudioTarget::streamWrite(sv_frame_t requested)
Chris@117 167 {
Chris@195 168 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY
Chris@293 169 cout << "AudioPulseAudioTarget::streamWrite(" << requested << ")" << endl;
Chris@117 170 #endif
Chris@117 171 if (m_done) return;
Chris@117 172
Chris@117 173 QMutexLocker locker(&m_mutex);
Chris@117 174
Chris@193 175 pa_usec_t latency = 0;
Chris@193 176 int negative = 0;
Chris@193 177 if (!pa_stream_get_latency(m_stream, &latency, &negative)) {
Chris@436 178 int latframes = int(double(latency) / 1000000.0 * double(m_sampleRate));
Chris@193 179 if (latframes > 0) m_source->setTargetPlayLatency(latframes);
Chris@118 180 }
Chris@118 181
Chris@117 182 static float *output = 0;
Chris@117 183 static float **tmpbuf = 0;
Chris@366 184 static int tmpbufch = 0;
Chris@436 185 static sv_frame_t tmpbufsz = 0;
Chris@117 186
Chris@366 187 int sourceChannels = m_source->getSourceChannelCount();
Chris@117 188
Chris@117 189 // Because we offer pan, we always want at least 2 channels
Chris@117 190 if (sourceChannels < 2) sourceChannels = 2;
Chris@117 191
Chris@436 192 sv_frame_t nframes = requested / (sourceChannels * sizeof(float));
Chris@120 193
Chris@120 194 if (nframes > m_bufferSize) {
Chris@293 195 cerr << "WARNING: AudioPulseAudioTarget::streamWrite: nframes " << nframes << " > m_bufferSize " << m_bufferSize << endl;
Chris@120 196 }
Chris@120 197
Chris@195 198 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY
Chris@293 199 cout << "AudioPulseAudioTarget::streamWrite: nframes = " << nframes << endl;
Chris@120 200 #endif
Chris@120 201
Chris@117 202 if (!tmpbuf || tmpbufch != sourceChannels || int(tmpbufsz) < nframes) {
Chris@117 203
Chris@117 204 if (tmpbuf) {
Chris@366 205 for (int i = 0; i < tmpbufch; ++i) {
Chris@117 206 delete[] tmpbuf[i];
Chris@117 207 }
Chris@117 208 delete[] tmpbuf;
Chris@117 209 }
Chris@117 210
Chris@117 211 if (output) {
Chris@117 212 delete[] output;
Chris@117 213 }
Chris@117 214
Chris@117 215 tmpbufch = sourceChannels;
Chris@117 216 tmpbufsz = nframes;
Chris@117 217 tmpbuf = new float *[tmpbufch];
Chris@117 218
Chris@366 219 for (int i = 0; i < tmpbufch; ++i) {
Chris@117 220 tmpbuf[i] = new float[tmpbufsz];
Chris@117 221 }
Chris@117 222
Chris@117 223 output = new float[tmpbufsz * tmpbufch];
Chris@117 224 }
Chris@117 225
Chris@436 226 sv_frame_t received = m_source->getSourceSamples(nframes, tmpbuf);
Chris@117 227
Chris@195 228 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY
Chris@293 229 cerr << "requested " << nframes << ", received " << received << endl;
Chris@120 230
Chris@119 231 if (received < nframes) {
Chris@293 232 cerr << "*** WARNING: Wrong number of frames received" << endl;
Chris@119 233 }
Chris@120 234 #endif
Chris@119 235
Chris@117 236 float peakLeft = 0.0, peakRight = 0.0;
Chris@117 237
Chris@366 238 for (int ch = 0; ch < 2; ++ch) {
Chris@117 239
Chris@117 240 float peak = 0.0;
Chris@117 241
Chris@411 242 // PulseAudio samples are interleaved
Chris@411 243 for (int i = 0; i < nframes; ++i) {
Chris@411 244 if (i < received) {
Chris@411 245 output[i * 2 + ch] = tmpbuf[ch][i] * m_outputGain;
Chris@411 246 float sample = fabsf(output[i * 2 + ch]);
Chris@411 247 if (sample > peak) peak = sample;
Chris@411 248 } else {
Chris@411 249 output[i * 2 + ch] = 0;
Chris@411 250 }
Chris@411 251 }
Chris@117 252
Chris@117 253 if (ch == 0) peakLeft = peak;
Chris@411 254 if (ch == 1) peakRight = peak;
Chris@117 255 }
Chris@117 256
Chris@195 257 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY
Chris@233 258 SVDEBUG << "calling pa_stream_write with "
Chris@229 259 << nframes * tmpbufch * sizeof(float) << " bytes" << endl;
Chris@120 260 #endif
Chris@120 261
Chris@117 262 pa_stream_write(m_stream, output, nframes * tmpbufch * sizeof(float),
Chris@117 263 0, 0, PA_SEEK_RELATIVE);
Chris@117 264
Chris@117 265 m_source->setOutputLevels(peakLeft, peakRight);
Chris@117 266
Chris@117 267 return;
Chris@117 268 }
Chris@117 269
Chris@117 270 void
Chris@436 271 AudioPulseAudioTarget::streamStateChangedStatic(pa_stream *,
Chris@117 272 void *data)
Chris@117 273 {
Chris@117 274 AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data;
Chris@117 275
Chris@436 276 // assert(stream == target->m_stream);
Chris@117 277
Chris@117 278 target->streamStateChanged();
Chris@117 279 }
Chris@117 280
Chris@117 281 void
Chris@117 282 AudioPulseAudioTarget::streamStateChanged()
Chris@117 283 {
Chris@117 284 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET
Chris@233 285 SVDEBUG << "AudioPulseAudioTarget::streamStateChanged" << endl;
Chris@117 286 #endif
Chris@117 287 QMutexLocker locker(&m_mutex);
Chris@117 288
Chris@117 289 switch (pa_stream_get_state(m_stream)) {
Chris@117 290
Chris@366 291 case PA_STREAM_UNCONNECTED:
Chris@366 292 case PA_STREAM_CREATING:
Chris@366 293 case PA_STREAM_TERMINATED:
Chris@366 294 break;
Chris@117 295
Chris@366 296 case PA_STREAM_READY:
Chris@366 297 {
Chris@366 298 SVDEBUG << "AudioPulseAudioTarget::streamStateChanged: Ready" << endl;
Chris@366 299
Chris@366 300 pa_usec_t latency = 0;
Chris@366 301 int negative = 0;
Chris@366 302 if (pa_stream_get_latency(m_stream, &latency, &negative)) {
Chris@366 303 cerr << "AudioPulseAudioTarget::streamStateChanged: Failed to query latency" << endl;
Chris@366 304 }
Chris@366 305 cerr << "Latency = " << latency << " usec" << endl;
Chris@436 306 int latframes = int(double(latency) / 1000000.0 * m_sampleRate);
Chris@366 307 cerr << "that's " << latframes << " frames" << endl;
Chris@191 308
Chris@366 309 const pa_buffer_attr *attr;
Chris@366 310 if (!(attr = pa_stream_get_buffer_attr(m_stream))) {
Chris@366 311 SVDEBUG << "AudioPulseAudioTarget::streamStateChanged: Cannot query stream buffer attributes" << endl;
Chris@366 312 m_source->setTarget(this, m_bufferSize);
Chris@366 313 m_source->setTargetSampleRate(m_sampleRate);
Chris@366 314 if (latframes != 0) m_source->setTargetPlayLatency(latframes);
Chris@366 315 } else {
Chris@366 316 int targetLength = attr->tlength;
Chris@366 317 SVDEBUG << "AudioPulseAudioTarget::streamStateChanged: stream target length = " << targetLength << endl;
Chris@366 318 m_source->setTarget(this, targetLength);
Chris@366 319 m_source->setTargetSampleRate(m_sampleRate);
Chris@366 320 if (latframes == 0) latframes = targetLength;
Chris@366 321 cerr << "latency = " << latframes << endl;
Chris@366 322 m_source->setTargetPlayLatency(latframes);
Chris@191 323 }
Chris@366 324 }
Chris@366 325 break;
Chris@366 326
Chris@366 327 case PA_STREAM_FAILED:
Chris@366 328 default:
Chris@366 329 cerr << "AudioPulseAudioTarget::streamStateChanged: Error: "
Chris@366 330 << pa_strerror(pa_context_errno(m_context)) << endl;
Chris@366 331 //!!! do something...
Chris@366 332 break;
Chris@117 333 }
Chris@117 334 }
Chris@117 335
Chris@117 336 void
Chris@436 337 AudioPulseAudioTarget::contextStateChangedStatic(pa_context *,
Chris@117 338 void *data)
Chris@117 339 {
Chris@117 340 AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data;
Chris@117 341
Chris@436 342 // assert(context == target->m_context);
Chris@117 343
Chris@117 344 target->contextStateChanged();
Chris@117 345 }
Chris@117 346
Chris@117 347 void
Chris@117 348 AudioPulseAudioTarget::contextStateChanged()
Chris@117 349 {
Chris@117 350 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET
Chris@233 351 SVDEBUG << "AudioPulseAudioTarget::contextStateChanged" << endl;
Chris@117 352 #endif
Chris@117 353 QMutexLocker locker(&m_mutex);
Chris@117 354
Chris@117 355 switch (pa_context_get_state(m_context)) {
Chris@117 356
Chris@366 357 case PA_CONTEXT_UNCONNECTED:
Chris@117 358 case PA_CONTEXT_CONNECTING:
Chris@117 359 case PA_CONTEXT_AUTHORIZING:
Chris@117 360 case PA_CONTEXT_SETTING_NAME:
Chris@117 361 break;
Chris@117 362
Chris@117 363 case PA_CONTEXT_READY:
Chris@233 364 SVDEBUG << "AudioPulseAudioTarget::contextStateChanged: Ready"
Chris@229 365 << endl;
Chris@117 366
Chris@117 367 m_stream = pa_stream_new(m_context, "stream", &m_spec, 0);
Chris@117 368 assert(m_stream); //!!!
Chris@117 369
Chris@117 370 pa_stream_set_state_callback(m_stream, streamStateChangedStatic, this);
Chris@117 371 pa_stream_set_write_callback(m_stream, streamWriteStatic, this);
Chris@120 372 pa_stream_set_overflow_callback(m_stream, streamOverflowStatic, this);
Chris@120 373 pa_stream_set_underflow_callback(m_stream, streamUnderflowStatic, this);
Chris@118 374 if (pa_stream_connect_playback
Chris@118 375 (m_stream, 0, 0,
Chris@118 376 pa_stream_flags_t(PA_STREAM_INTERPOLATE_TIMING |
Chris@118 377 PA_STREAM_AUTO_TIMING_UPDATE),
Chris@118 378 0, 0)) { //??? return value
Chris@293 379 cerr << "AudioPulseAudioTarget: Failed to connect playback stream" << endl;
Chris@117 380 }
Chris@117 381
Chris@117 382 break;
Chris@117 383
Chris@117 384 case PA_CONTEXT_TERMINATED:
Chris@233 385 SVDEBUG << "AudioPulseAudioTarget::contextStateChanged: Terminated" << endl;
Chris@117 386 //!!! do something...
Chris@117 387 break;
Chris@117 388
Chris@117 389 case PA_CONTEXT_FAILED:
Chris@117 390 default:
Chris@293 391 cerr << "AudioPulseAudioTarget::contextStateChanged: Error: "
Chris@293 392 << pa_strerror(pa_context_errno(m_context)) << endl;
Chris@117 393 //!!! do something...
Chris@117 394 break;
Chris@117 395 }
Chris@117 396 }
Chris@117 397
Chris@120 398 void
Chris@120 399 AudioPulseAudioTarget::streamOverflowStatic(pa_stream *, void *)
Chris@120 400 {
Chris@233 401 SVDEBUG << "AudioPulseAudioTarget::streamOverflowStatic: Overflow!" << endl;
Chris@120 402 }
Chris@120 403
Chris@120 404 void
Chris@130 405 AudioPulseAudioTarget::streamUnderflowStatic(pa_stream *, void *data)
Chris@120 406 {
Chris@233 407 SVDEBUG << "AudioPulseAudioTarget::streamUnderflowStatic: Underflow!" << endl;
Chris@130 408 AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data;
Chris@130 409 if (target && target->m_source) {
Chris@130 410 target->m_source->audioProcessingOverload();
Chris@130 411 }
Chris@120 412 }
Chris@120 413
Chris@117 414 #endif /* HAVE_PULSEAUDIO */
Chris@117 415