annotate audioio/AudioPulseAudioTarget.cpp @ 256:f3f9e3d647c1

Give a dedicated key to toggling the centre line, and move it out of the overlay level setting -- reducing number of overlay levels to 3. Introduce two distinct vertical scale types (so that we can hide the spectrogram colour scale part easily)
author Chris Cannam
date Mon, 30 Jan 2012 16:02:14 +0000
parents 8aace2d9f1c2
children 068235cf5bf7
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@117 44 std::cerr << "AudioPulseAudioTarget: Initialising for PulseAudio" << std::endl;
Chris@117 45 #endif
Chris@117 46
Chris@117 47 m_loop = pa_mainloop_new();
Chris@117 48 if (!m_loop) {
Chris@117 49 std::cerr << "ERROR: AudioPulseAudioTarget: Failed to create main loop" << std::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@195 67 std::cerr << "AudioPulseAudioTarget: Creating context" << std::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@117 72 std::cerr << "ERROR: AudioPulseAudioTarget: Failed to create context object" << std::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@195 79 std::cerr << "AudioPulseAudioTarget: Connecting to default server..." << std::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@195 86 std::cerr << "AudioPulseAudioTarget: Starting main loop" << std::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@117 93 std::cerr << "AudioPulseAudioTarget: initialised OK" << std::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@120 166 AudioPulseAudioTarget::streamWrite(size_t requested)
Chris@117 167 {
Chris@195 168 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY
Chris@120 169 std::cout << "AudioPulseAudioTarget::streamWrite(" << requested << ")" << std::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@117 184 static size_t tmpbufch = 0;
Chris@117 185 static size_t tmpbufsz = 0;
Chris@117 186
Chris@117 187 size_t 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@120 192 size_t nframes = requested / (sourceChannels * sizeof(float));
Chris@120 193
Chris@120 194 if (nframes > m_bufferSize) {
Chris@120 195 std::cerr << "WARNING: AudioPulseAudioTarget::streamWrite: nframes " << nframes << " > m_bufferSize " << m_bufferSize << std::endl;
Chris@120 196 }
Chris@120 197
Chris@195 198 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY
Chris@120 199 std::cout << "AudioPulseAudioTarget::streamWrite: nframes = " << nframes << std::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@117 205 for (size_t 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@117 219 for (size_t 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@117 226 size_t received = m_source->getSourceSamples(nframes, tmpbuf);
Chris@117 227
Chris@195 228 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY
Chris@119 229 std::cerr << "requested " << nframes << ", received " << received << std::endl;
Chris@120 230
Chris@119 231 if (received < nframes) {
Chris@119 232 std::cerr << "*** WARNING: Wrong number of frames received" << std::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@117 238 for (size_t 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@117 245 for (size_t 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@117 257 for (size_t 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@117 268 for (size_t 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@117 311 case PA_STREAM_CREATING:
Chris@117 312 case PA_STREAM_TERMINATED:
Chris@117 313 break;
Chris@117 314
Chris@117 315 case PA_STREAM_READY:
Chris@191 316 {
Chris@233 317 SVDEBUG << "AudioPulseAudioTarget::streamStateChanged: Ready" << endl;
Chris@191 318
Chris@191 319 pa_usec_t latency = 0;
Chris@191 320 int negative = 0;
Chris@191 321 if (pa_stream_get_latency(m_stream, &latency, &negative)) {
Chris@191 322 std::cerr << "AudioPulseAudioTarget::streamStateChanged: Failed to query latency" << std::endl;
Chris@191 323 }
Chris@191 324 std::cerr << "Latency = " << latency << " usec" << std::endl;
Chris@191 325 int latframes = (latency / 1000000.f) * float(m_sampleRate);
Chris@191 326 std::cerr << "that's " << latframes << " frames" << std::endl;
Chris@191 327
Chris@191 328 const pa_buffer_attr *attr;
Chris@191 329 if (!(attr = pa_stream_get_buffer_attr(m_stream))) {
Chris@233 330 SVDEBUG << "AudioPulseAudioTarget::streamStateChanged: Cannot query stream buffer attributes" << endl;
Chris@191 331 m_source->setTarget(this, m_bufferSize);
Chris@191 332 m_source->setTargetSampleRate(m_sampleRate);
Chris@193 333 if (latframes != 0) m_source->setTargetPlayLatency(latframes);
Chris@191 334 } else {
Chris@193 335 int targetLength = attr->tlength;
Chris@233 336 SVDEBUG << "AudioPulseAudioTarget::streamStateChanged: stream target length = " << targetLength << endl;
Chris@193 337 m_source->setTarget(this, targetLength);
Chris@191 338 m_source->setTargetSampleRate(m_sampleRate);
Chris@193 339 if (latframes == 0) latframes = targetLength;
Chris@193 340 std::cerr << "latency = " << latframes << std::endl;
Chris@191 341 m_source->setTargetPlayLatency(latframes);
Chris@191 342 }
Chris@191 343 }
Chris@117 344 break;
Chris@117 345
Chris@117 346 case PA_STREAM_FAILED:
Chris@117 347 default:
Chris@117 348 std::cerr << "AudioPulseAudioTarget::streamStateChanged: Error: "
Chris@117 349 << pa_strerror(pa_context_errno(m_context)) << std::endl;
Chris@117 350 //!!! do something...
Chris@117 351 break;
Chris@117 352 }
Chris@117 353 }
Chris@117 354
Chris@117 355 void
Chris@117 356 AudioPulseAudioTarget::contextStateChangedStatic(pa_context *context,
Chris@117 357 void *data)
Chris@117 358 {
Chris@117 359 AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data;
Chris@117 360
Chris@117 361 assert(context == target->m_context);
Chris@117 362
Chris@117 363 target->contextStateChanged();
Chris@117 364 }
Chris@117 365
Chris@117 366 void
Chris@117 367 AudioPulseAudioTarget::contextStateChanged()
Chris@117 368 {
Chris@117 369 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET
Chris@233 370 SVDEBUG << "AudioPulseAudioTarget::contextStateChanged" << endl;
Chris@117 371 #endif
Chris@117 372 QMutexLocker locker(&m_mutex);
Chris@117 373
Chris@117 374 switch (pa_context_get_state(m_context)) {
Chris@117 375
Chris@117 376 case PA_CONTEXT_CONNECTING:
Chris@117 377 case PA_CONTEXT_AUTHORIZING:
Chris@117 378 case PA_CONTEXT_SETTING_NAME:
Chris@117 379 break;
Chris@117 380
Chris@117 381 case PA_CONTEXT_READY:
Chris@233 382 SVDEBUG << "AudioPulseAudioTarget::contextStateChanged: Ready"
Chris@229 383 << endl;
Chris@117 384
Chris@117 385 m_stream = pa_stream_new(m_context, "stream", &m_spec, 0);
Chris@117 386 assert(m_stream); //!!!
Chris@117 387
Chris@117 388 pa_stream_set_state_callback(m_stream, streamStateChangedStatic, this);
Chris@117 389 pa_stream_set_write_callback(m_stream, streamWriteStatic, this);
Chris@120 390 pa_stream_set_overflow_callback(m_stream, streamOverflowStatic, this);
Chris@120 391 pa_stream_set_underflow_callback(m_stream, streamUnderflowStatic, this);
Chris@118 392 if (pa_stream_connect_playback
Chris@118 393 (m_stream, 0, 0,
Chris@118 394 pa_stream_flags_t(PA_STREAM_INTERPOLATE_TIMING |
Chris@118 395 PA_STREAM_AUTO_TIMING_UPDATE),
Chris@118 396 0, 0)) { //??? return value
Chris@117 397 std::cerr << "AudioPulseAudioTarget: Failed to connect playback stream" << std::endl;
Chris@117 398 }
Chris@117 399
Chris@117 400 break;
Chris@117 401
Chris@117 402 case PA_CONTEXT_TERMINATED:
Chris@233 403 SVDEBUG << "AudioPulseAudioTarget::contextStateChanged: Terminated" << endl;
Chris@117 404 //!!! do something...
Chris@117 405 break;
Chris@117 406
Chris@117 407 case PA_CONTEXT_FAILED:
Chris@117 408 default:
Chris@117 409 std::cerr << "AudioPulseAudioTarget::contextStateChanged: Error: "
Chris@117 410 << pa_strerror(pa_context_errno(m_context)) << std::endl;
Chris@117 411 //!!! do something...
Chris@117 412 break;
Chris@117 413 }
Chris@117 414 }
Chris@117 415
Chris@120 416 void
Chris@120 417 AudioPulseAudioTarget::streamOverflowStatic(pa_stream *, void *)
Chris@120 418 {
Chris@233 419 SVDEBUG << "AudioPulseAudioTarget::streamOverflowStatic: Overflow!" << endl;
Chris@120 420 }
Chris@120 421
Chris@120 422 void
Chris@130 423 AudioPulseAudioTarget::streamUnderflowStatic(pa_stream *, void *data)
Chris@120 424 {
Chris@233 425 SVDEBUG << "AudioPulseAudioTarget::streamUnderflowStatic: Underflow!" << endl;
Chris@130 426 AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data;
Chris@130 427 if (target && target->m_source) {
Chris@130 428 target->m_source->audioProcessingOverload();
Chris@130 429 }
Chris@120 430 }
Chris@120 431
Chris@117 432 #endif /* HAVE_PULSEAUDIO */
Chris@117 433