annotate audioio/AudioPulseAudioTarget.cpp @ 403:eb84b06301da

Restore the old prev/next layer commands (that were never enabled because they didn't work) using the new fixed order layer list (so they now do work)
author Chris Cannam
date Tue, 02 Sep 2014 16:06:41 +0100
parents 0876ea394902
children 53fee450891e
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@117 60 m_sampleRate = 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@118 144 return usec / 1000000.f;
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@117 154 AudioPulseAudioTarget::streamWriteStatic(pa_stream *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@117 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@366 166 AudioPulseAudioTarget::streamWrite(int 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@193 178 int latframes = (latency / 1000000.f) * float(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@366 185 static int 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@366 192 int 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@366 226 int 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@117 242 if (ch < sourceChannels) {
Chris@117 243
Chris@117 244 // PulseAudio samples are interleaved
Chris@366 245 for (int i = 0; i < nframes; ++i) {
Chris@117 246 if (i < received) {
Chris@117 247 output[i * 2 + ch] = tmpbuf[ch][i] * m_outputGain;
Chris@117 248 float sample = fabsf(output[i * 2 + ch]);
Chris@117 249 if (sample > peak) peak = sample;
Chris@117 250 } else {
Chris@117 251 output[i * 2 + ch] = 0;
Chris@117 252 }
Chris@117 253 }
Chris@117 254
Chris@117 255 } else if (ch == 1 && sourceChannels == 1) {
Chris@117 256
Chris@366 257 for (int i = 0; i < nframes; ++i) {
Chris@117 258 if (i < received) {
Chris@117 259 output[i * 2 + ch] = tmpbuf[0][i] * m_outputGain;
Chris@117 260 float sample = fabsf(output[i * 2 + ch]);
Chris@117 261 if (sample > peak) peak = sample;
Chris@117 262 } else {
Chris@117 263 output[i * 2 + ch] = 0;
Chris@117 264 }
Chris@117 265 }
Chris@117 266
Chris@117 267 } else {
Chris@366 268 for (int i = 0; i < nframes; ++i) {
Chris@117 269 output[i * 2 + ch] = 0;
Chris@117 270 }
Chris@117 271 }
Chris@117 272
Chris@117 273 if (ch == 0) peakLeft = peak;
Chris@117 274 if (ch > 0 || sourceChannels == 1) peakRight = peak;
Chris@117 275 }
Chris@117 276
Chris@195 277 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY
Chris@233 278 SVDEBUG << "calling pa_stream_write with "
Chris@229 279 << nframes * tmpbufch * sizeof(float) << " bytes" << endl;
Chris@120 280 #endif
Chris@120 281
Chris@117 282 pa_stream_write(m_stream, output, nframes * tmpbufch * sizeof(float),
Chris@117 283 0, 0, PA_SEEK_RELATIVE);
Chris@117 284
Chris@117 285 m_source->setOutputLevels(peakLeft, peakRight);
Chris@117 286
Chris@117 287 return;
Chris@117 288 }
Chris@117 289
Chris@117 290 void
Chris@117 291 AudioPulseAudioTarget::streamStateChangedStatic(pa_stream *stream,
Chris@117 292 void *data)
Chris@117 293 {
Chris@117 294 AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data;
Chris@117 295
Chris@117 296 assert(stream == target->m_stream);
Chris@117 297
Chris@117 298 target->streamStateChanged();
Chris@117 299 }
Chris@117 300
Chris@117 301 void
Chris@117 302 AudioPulseAudioTarget::streamStateChanged()
Chris@117 303 {
Chris@117 304 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET
Chris@233 305 SVDEBUG << "AudioPulseAudioTarget::streamStateChanged" << endl;
Chris@117 306 #endif
Chris@117 307 QMutexLocker locker(&m_mutex);
Chris@117 308
Chris@117 309 switch (pa_stream_get_state(m_stream)) {
Chris@117 310
Chris@366 311 case PA_STREAM_UNCONNECTED:
Chris@366 312 case PA_STREAM_CREATING:
Chris@366 313 case PA_STREAM_TERMINATED:
Chris@366 314 break;
Chris@117 315
Chris@366 316 case PA_STREAM_READY:
Chris@366 317 {
Chris@366 318 SVDEBUG << "AudioPulseAudioTarget::streamStateChanged: Ready" << endl;
Chris@366 319
Chris@366 320 pa_usec_t latency = 0;
Chris@366 321 int negative = 0;
Chris@366 322 if (pa_stream_get_latency(m_stream, &latency, &negative)) {
Chris@366 323 cerr << "AudioPulseAudioTarget::streamStateChanged: Failed to query latency" << endl;
Chris@366 324 }
Chris@366 325 cerr << "Latency = " << latency << " usec" << endl;
Chris@366 326 int latframes = (latency / 1000000.f) * float(m_sampleRate);
Chris@366 327 cerr << "that's " << latframes << " frames" << endl;
Chris@191 328
Chris@366 329 const pa_buffer_attr *attr;
Chris@366 330 if (!(attr = pa_stream_get_buffer_attr(m_stream))) {
Chris@366 331 SVDEBUG << "AudioPulseAudioTarget::streamStateChanged: Cannot query stream buffer attributes" << endl;
Chris@366 332 m_source->setTarget(this, m_bufferSize);
Chris@366 333 m_source->setTargetSampleRate(m_sampleRate);
Chris@366 334 if (latframes != 0) m_source->setTargetPlayLatency(latframes);
Chris@366 335 } else {
Chris@366 336 int targetLength = attr->tlength;
Chris@366 337 SVDEBUG << "AudioPulseAudioTarget::streamStateChanged: stream target length = " << targetLength << endl;
Chris@366 338 m_source->setTarget(this, targetLength);
Chris@366 339 m_source->setTargetSampleRate(m_sampleRate);
Chris@366 340 if (latframes == 0) latframes = targetLength;
Chris@366 341 cerr << "latency = " << latframes << endl;
Chris@366 342 m_source->setTargetPlayLatency(latframes);
Chris@191 343 }
Chris@366 344 }
Chris@366 345 break;
Chris@366 346
Chris@366 347 case PA_STREAM_FAILED:
Chris@366 348 default:
Chris@366 349 cerr << "AudioPulseAudioTarget::streamStateChanged: Error: "
Chris@366 350 << pa_strerror(pa_context_errno(m_context)) << endl;
Chris@366 351 //!!! do something...
Chris@366 352 break;
Chris@117 353 }
Chris@117 354 }
Chris@117 355
Chris@117 356 void
Chris@117 357 AudioPulseAudioTarget::contextStateChangedStatic(pa_context *context,
Chris@117 358 void *data)
Chris@117 359 {
Chris@117 360 AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data;
Chris@117 361
Chris@117 362 assert(context == target->m_context);
Chris@117 363
Chris@117 364 target->contextStateChanged();
Chris@117 365 }
Chris@117 366
Chris@117 367 void
Chris@117 368 AudioPulseAudioTarget::contextStateChanged()
Chris@117 369 {
Chris@117 370 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET
Chris@233 371 SVDEBUG << "AudioPulseAudioTarget::contextStateChanged" << endl;
Chris@117 372 #endif
Chris@117 373 QMutexLocker locker(&m_mutex);
Chris@117 374
Chris@117 375 switch (pa_context_get_state(m_context)) {
Chris@117 376
Chris@366 377 case PA_CONTEXT_UNCONNECTED:
Chris@117 378 case PA_CONTEXT_CONNECTING:
Chris@117 379 case PA_CONTEXT_AUTHORIZING:
Chris@117 380 case PA_CONTEXT_SETTING_NAME:
Chris@117 381 break;
Chris@117 382
Chris@117 383 case PA_CONTEXT_READY:
Chris@233 384 SVDEBUG << "AudioPulseAudioTarget::contextStateChanged: Ready"
Chris@229 385 << endl;
Chris@117 386
Chris@117 387 m_stream = pa_stream_new(m_context, "stream", &m_spec, 0);
Chris@117 388 assert(m_stream); //!!!
Chris@117 389
Chris@117 390 pa_stream_set_state_callback(m_stream, streamStateChangedStatic, this);
Chris@117 391 pa_stream_set_write_callback(m_stream, streamWriteStatic, this);
Chris@120 392 pa_stream_set_overflow_callback(m_stream, streamOverflowStatic, this);
Chris@120 393 pa_stream_set_underflow_callback(m_stream, streamUnderflowStatic, this);
Chris@118 394 if (pa_stream_connect_playback
Chris@118 395 (m_stream, 0, 0,
Chris@118 396 pa_stream_flags_t(PA_STREAM_INTERPOLATE_TIMING |
Chris@118 397 PA_STREAM_AUTO_TIMING_UPDATE),
Chris@118 398 0, 0)) { //??? return value
Chris@293 399 cerr << "AudioPulseAudioTarget: Failed to connect playback stream" << endl;
Chris@117 400 }
Chris@117 401
Chris@117 402 break;
Chris@117 403
Chris@117 404 case PA_CONTEXT_TERMINATED:
Chris@233 405 SVDEBUG << "AudioPulseAudioTarget::contextStateChanged: Terminated" << endl;
Chris@117 406 //!!! do something...
Chris@117 407 break;
Chris@117 408
Chris@117 409 case PA_CONTEXT_FAILED:
Chris@117 410 default:
Chris@293 411 cerr << "AudioPulseAudioTarget::contextStateChanged: Error: "
Chris@293 412 << pa_strerror(pa_context_errno(m_context)) << endl;
Chris@117 413 //!!! do something...
Chris@117 414 break;
Chris@117 415 }
Chris@117 416 }
Chris@117 417
Chris@120 418 void
Chris@120 419 AudioPulseAudioTarget::streamOverflowStatic(pa_stream *, void *)
Chris@120 420 {
Chris@233 421 SVDEBUG << "AudioPulseAudioTarget::streamOverflowStatic: Overflow!" << endl;
Chris@120 422 }
Chris@120 423
Chris@120 424 void
Chris@130 425 AudioPulseAudioTarget::streamUnderflowStatic(pa_stream *, void *data)
Chris@120 426 {
Chris@233 427 SVDEBUG << "AudioPulseAudioTarget::streamUnderflowStatic: Underflow!" << endl;
Chris@130 428 AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data;
Chris@130 429 if (target && target->m_source) {
Chris@130 430 target->m_source->audioProcessingOverload();
Chris@130 431 }
Chris@120 432 }
Chris@120 433
Chris@117 434 #endif /* HAVE_PULSEAUDIO */
Chris@117 435