annotate audioio/AudioPulseAudioTarget.cpp @ 154:386b02c926bf

* Merge from one-fftdataserver-per-fftmodel branch. This bit of reworking (which is not described very accurately by the title of the branch) turns the MatrixFile object into something that either reads or writes, but not both, and separates the FFT file cache reader and writer implementations separately. This allows the FFT data server to have a single thread owning writers and one reader per "customer" thread, and for all locking to be vastly simplified and concentrated in the data server alone (because none of the classes it makes use of is used in more than one thread at a time). The result is faster and more trustworthy code.
author Chris Cannam
date Tue, 27 Jan 2009 13:25:10 +0000
parents 4c9c04645685
children 3bd87e04f060
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@120 27 //#define DEBUG_AUDIO_PULSE_AUDIO_TARGET 1
Chris@117 28
Chris@117 29 AudioPulseAudioTarget::AudioPulseAudioTarget(AudioCallbackPlaySource *source) :
Chris@117 30 AudioCallbackPlayTarget(source),
Chris@117 31 m_mutex(QMutex::Recursive),
Chris@117 32 m_loop(0),
Chris@117 33 m_api(0),
Chris@117 34 m_context(0),
Chris@117 35 m_stream(0),
Chris@117 36 m_loopThread(0),
Chris@117 37 m_bufferSize(0),
Chris@117 38 m_sampleRate(0),
Chris@117 39 m_latency(0),
Chris@117 40 m_done(false)
Chris@117 41 {
Chris@117 42 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET
Chris@117 43 std::cerr << "AudioPulseAudioTarget: Initialising for PulseAudio" << std::endl;
Chris@117 44 #endif
Chris@117 45
Chris@117 46 m_loop = pa_mainloop_new();
Chris@117 47 if (!m_loop) {
Chris@117 48 std::cerr << "ERROR: AudioPulseAudioTarget: Failed to create main loop" << std::endl;
Chris@117 49 return;
Chris@117 50 }
Chris@117 51
Chris@117 52 m_api = pa_mainloop_get_api(m_loop);
Chris@117 53
Chris@117 54 //!!! handle signals how?
Chris@117 55
Chris@118 56 m_bufferSize = 20480;
Chris@117 57 m_sampleRate = 44100;
Chris@117 58 if (m_source && (m_source->getSourceSampleRate() != 0)) {
Chris@117 59 m_sampleRate = m_source->getSourceSampleRate();
Chris@117 60 }
Chris@117 61 m_spec.rate = m_sampleRate;
Chris@117 62 m_spec.channels = 2;
Chris@117 63 m_spec.format = PA_SAMPLE_FLOAT32NE;
Chris@117 64
Chris@117 65 m_context = pa_context_new(m_api, source->getClientName().toLocal8Bit().data());
Chris@117 66 if (!m_context) {
Chris@117 67 std::cerr << "ERROR: AudioPulseAudioTarget: Failed to create context object" << std::endl;
Chris@117 68 return;
Chris@117 69 }
Chris@117 70
Chris@117 71 pa_context_set_state_callback(m_context, contextStateChangedStatic, this);
Chris@117 72
Chris@117 73 pa_context_connect(m_context, 0, (pa_context_flags_t)0, 0); // default server
Chris@117 74
Chris@117 75 m_loopThread = new MainLoopThread(m_loop);
Chris@117 76 m_loopThread->start();
Chris@117 77
Chris@117 78 #ifdef DEBUG_PULSE_AUDIO_TARGET
Chris@117 79 std::cerr << "AudioPulseAudioTarget: initialised OK" << std::endl;
Chris@117 80 #endif
Chris@117 81 }
Chris@117 82
Chris@117 83 AudioPulseAudioTarget::~AudioPulseAudioTarget()
Chris@117 84 {
Chris@117 85 std::cerr << "AudioPulseAudioTarget::~AudioPulseAudioTarget()" << std::endl;
Chris@117 86
Chris@117 87 if (m_source) {
Chris@117 88 m_source->setTarget(0, m_bufferSize);
Chris@117 89 }
Chris@117 90
Chris@117 91 shutdown();
Chris@117 92
Chris@117 93 QMutexLocker locker(&m_mutex);
Chris@117 94
Chris@117 95 if (m_stream) pa_stream_unref(m_stream);
Chris@117 96
Chris@117 97 if (m_context) pa_context_unref(m_context);
Chris@117 98
Chris@117 99 if (m_loop) {
Chris@117 100 pa_signal_done();
Chris@117 101 pa_mainloop_free(m_loop);
Chris@117 102 }
Chris@117 103
Chris@117 104 m_stream = 0;
Chris@117 105 m_context = 0;
Chris@117 106 m_loop = 0;
Chris@117 107
Chris@117 108 std::cerr << "AudioPulseAudioTarget::~AudioPulseAudioTarget() done" << std::endl;
Chris@117 109 }
Chris@117 110
Chris@117 111 void
Chris@117 112 AudioPulseAudioTarget::shutdown()
Chris@117 113 {
Chris@117 114 m_done = true;
Chris@117 115 }
Chris@117 116
Chris@117 117 bool
Chris@117 118 AudioPulseAudioTarget::isOK() const
Chris@117 119 {
Chris@117 120 return (m_context != 0);
Chris@117 121 }
Chris@117 122
Chris@117 123 double
Chris@117 124 AudioPulseAudioTarget::getCurrentTime() const
Chris@117 125 {
Chris@117 126 if (!m_stream) return 0.0;
Chris@118 127
Chris@118 128 pa_usec_t usec = 0;
Chris@118 129 pa_stream_get_time(m_stream, &usec);
Chris@118 130 return usec / 1000000.f;
Chris@117 131 }
Chris@117 132
Chris@117 133 void
Chris@117 134 AudioPulseAudioTarget::sourceModelReplaced()
Chris@117 135 {
Chris@117 136 m_source->setTargetSampleRate(m_sampleRate);
Chris@117 137 }
Chris@117 138
Chris@117 139 void
Chris@117 140 AudioPulseAudioTarget::streamWriteStatic(pa_stream *stream,
Chris@117 141 size_t length,
Chris@117 142 void *data)
Chris@117 143 {
Chris@117 144 AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data;
Chris@117 145
Chris@117 146 assert(stream == target->m_stream);
Chris@117 147
Chris@117 148 target->streamWrite(length);
Chris@117 149 }
Chris@117 150
Chris@117 151 void
Chris@120 152 AudioPulseAudioTarget::streamWrite(size_t requested)
Chris@117 153 {
Chris@117 154 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET
Chris@120 155 std::cout << "AudioPulseAudioTarget::streamWrite(" << requested << ")" << std::endl;
Chris@117 156 #endif
Chris@117 157 if (m_done) return;
Chris@117 158
Chris@117 159 QMutexLocker locker(&m_mutex);
Chris@117 160
Chris@118 161 if (m_source->getTargetPlayLatency() == 0) { //!!! need better test
Chris@118 162 //!!!
Chris@118 163 pa_usec_t latency = 0;
Chris@118 164 int negative = 0;
Chris@118 165 if (pa_stream_get_latency(m_stream, &latency, &negative)) {
Chris@118 166 std::cerr << "AudioPulseAudioTarget::contextStateChanged: Failed to query latency" << std::endl;
Chris@118 167 }
Chris@154 168 // std::cerr << "Latency = " << latency << " usec" << std::endl;
Chris@118 169 int latframes = (latency / 1000000.f) * float(m_sampleRate);
Chris@154 170 // std::cerr << "that's " << latframes << " frames" << std::endl;
Chris@118 171 m_source->setTargetPlayLatency(latframes); //!!! buh
Chris@118 172 }
Chris@118 173
Chris@117 174 static float *output = 0;
Chris@117 175 static float **tmpbuf = 0;
Chris@117 176 static size_t tmpbufch = 0;
Chris@117 177 static size_t tmpbufsz = 0;
Chris@117 178
Chris@117 179 size_t sourceChannels = m_source->getSourceChannelCount();
Chris@117 180
Chris@117 181 // Because we offer pan, we always want at least 2 channels
Chris@117 182 if (sourceChannels < 2) sourceChannels = 2;
Chris@117 183
Chris@120 184 size_t nframes = requested / (sourceChannels * sizeof(float));
Chris@120 185
Chris@120 186 if (nframes > m_bufferSize) {
Chris@120 187 std::cerr << "WARNING: AudioPulseAudioTarget::streamWrite: nframes " << nframes << " > m_bufferSize " << m_bufferSize << std::endl;
Chris@120 188 }
Chris@120 189
Chris@120 190 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET
Chris@120 191 std::cout << "AudioPulseAudioTarget::streamWrite: nframes = " << nframes << std::endl;
Chris@120 192 #endif
Chris@120 193
Chris@117 194 if (!tmpbuf || tmpbufch != sourceChannels || int(tmpbufsz) < nframes) {
Chris@117 195
Chris@117 196 if (tmpbuf) {
Chris@117 197 for (size_t i = 0; i < tmpbufch; ++i) {
Chris@117 198 delete[] tmpbuf[i];
Chris@117 199 }
Chris@117 200 delete[] tmpbuf;
Chris@117 201 }
Chris@117 202
Chris@117 203 if (output) {
Chris@117 204 delete[] output;
Chris@117 205 }
Chris@117 206
Chris@117 207 tmpbufch = sourceChannels;
Chris@117 208 tmpbufsz = nframes;
Chris@117 209 tmpbuf = new float *[tmpbufch];
Chris@117 210
Chris@117 211 for (size_t i = 0; i < tmpbufch; ++i) {
Chris@117 212 tmpbuf[i] = new float[tmpbufsz];
Chris@117 213 }
Chris@117 214
Chris@117 215 output = new float[tmpbufsz * tmpbufch];
Chris@117 216 }
Chris@117 217
Chris@117 218 size_t received = m_source->getSourceSamples(nframes, tmpbuf);
Chris@117 219
Chris@120 220 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET
Chris@119 221 std::cerr << "requested " << nframes << ", received " << received << std::endl;
Chris@120 222
Chris@119 223 if (received < nframes) {
Chris@119 224 std::cerr << "*** WARNING: Wrong number of frames received" << std::endl;
Chris@119 225 }
Chris@120 226 #endif
Chris@119 227
Chris@117 228 float peakLeft = 0.0, peakRight = 0.0;
Chris@117 229
Chris@117 230 for (size_t ch = 0; ch < 2; ++ch) {
Chris@117 231
Chris@117 232 float peak = 0.0;
Chris@117 233
Chris@117 234 if (ch < sourceChannels) {
Chris@117 235
Chris@117 236 // PulseAudio samples are interleaved
Chris@117 237 for (size_t i = 0; i < nframes; ++i) {
Chris@117 238 if (i < received) {
Chris@117 239 output[i * 2 + ch] = tmpbuf[ch][i] * m_outputGain;
Chris@117 240 float sample = fabsf(output[i * 2 + ch]);
Chris@117 241 if (sample > peak) peak = sample;
Chris@117 242 } else {
Chris@117 243 output[i * 2 + ch] = 0;
Chris@117 244 }
Chris@117 245 }
Chris@117 246
Chris@117 247 } else if (ch == 1 && sourceChannels == 1) {
Chris@117 248
Chris@117 249 for (size_t i = 0; i < nframes; ++i) {
Chris@117 250 if (i < received) {
Chris@117 251 output[i * 2 + ch] = tmpbuf[0][i] * m_outputGain;
Chris@117 252 float sample = fabsf(output[i * 2 + ch]);
Chris@117 253 if (sample > peak) peak = sample;
Chris@117 254 } else {
Chris@117 255 output[i * 2 + ch] = 0;
Chris@117 256 }
Chris@117 257 }
Chris@117 258
Chris@117 259 } else {
Chris@117 260 for (size_t i = 0; i < nframes; ++i) {
Chris@117 261 output[i * 2 + ch] = 0;
Chris@117 262 }
Chris@117 263 }
Chris@117 264
Chris@117 265 if (ch == 0) peakLeft = peak;
Chris@117 266 if (ch > 0 || sourceChannels == 1) peakRight = peak;
Chris@117 267 }
Chris@117 268
Chris@120 269 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET
Chris@120 270 std::cerr << "calling pa_stream_write with "
Chris@120 271 << nframes * tmpbufch * sizeof(float) << " bytes" << std::endl;
Chris@120 272 #endif
Chris@120 273
Chris@117 274 pa_stream_write(m_stream, output, nframes * tmpbufch * sizeof(float),
Chris@117 275 0, 0, PA_SEEK_RELATIVE);
Chris@117 276
Chris@117 277 m_source->setOutputLevels(peakLeft, peakRight);
Chris@117 278
Chris@117 279 return;
Chris@117 280 }
Chris@117 281
Chris@117 282 void
Chris@117 283 AudioPulseAudioTarget::streamStateChangedStatic(pa_stream *stream,
Chris@117 284 void *data)
Chris@117 285 {
Chris@117 286 AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data;
Chris@117 287
Chris@117 288 assert(stream == target->m_stream);
Chris@117 289
Chris@117 290 target->streamStateChanged();
Chris@117 291 }
Chris@117 292
Chris@117 293 void
Chris@117 294 AudioPulseAudioTarget::streamStateChanged()
Chris@117 295 {
Chris@117 296 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET
Chris@117 297 std::cerr << "AudioPulseAudioTarget::streamStateChanged" << std::endl;
Chris@117 298 #endif
Chris@117 299 QMutexLocker locker(&m_mutex);
Chris@117 300
Chris@117 301 switch (pa_stream_get_state(m_stream)) {
Chris@117 302
Chris@117 303 case PA_STREAM_CREATING:
Chris@117 304 case PA_STREAM_TERMINATED:
Chris@117 305 break;
Chris@117 306
Chris@117 307 case PA_STREAM_READY:
Chris@117 308 std::cerr << "AudioPulseAudioTarget::streamStateChanged: Ready" << std::endl;
Chris@117 309 break;
Chris@117 310
Chris@117 311 case PA_STREAM_FAILED:
Chris@117 312 default:
Chris@117 313 std::cerr << "AudioPulseAudioTarget::streamStateChanged: Error: "
Chris@117 314 << pa_strerror(pa_context_errno(m_context)) << std::endl;
Chris@117 315 //!!! do something...
Chris@117 316 break;
Chris@117 317 }
Chris@117 318 }
Chris@117 319
Chris@117 320 void
Chris@117 321 AudioPulseAudioTarget::contextStateChangedStatic(pa_context *context,
Chris@117 322 void *data)
Chris@117 323 {
Chris@117 324 AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data;
Chris@117 325
Chris@117 326 assert(context == target->m_context);
Chris@117 327
Chris@117 328 target->contextStateChanged();
Chris@117 329 }
Chris@117 330
Chris@117 331 void
Chris@117 332 AudioPulseAudioTarget::contextStateChanged()
Chris@117 333 {
Chris@117 334 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET
Chris@117 335 std::cerr << "AudioPulseAudioTarget::contextStateChanged" << std::endl;
Chris@117 336 #endif
Chris@117 337 QMutexLocker locker(&m_mutex);
Chris@117 338
Chris@117 339 switch (pa_context_get_state(m_context)) {
Chris@117 340
Chris@117 341 case PA_CONTEXT_CONNECTING:
Chris@117 342 case PA_CONTEXT_AUTHORIZING:
Chris@117 343 case PA_CONTEXT_SETTING_NAME:
Chris@117 344 break;
Chris@117 345
Chris@117 346 case PA_CONTEXT_READY:
Chris@118 347 {
Chris@117 348 std::cerr << "AudioPulseAudioTarget::contextStateChanged: Ready"
Chris@117 349 << std::endl;
Chris@117 350
Chris@117 351 m_stream = pa_stream_new(m_context, "stream", &m_spec, 0);
Chris@117 352 assert(m_stream); //!!!
Chris@117 353
Chris@117 354 pa_stream_set_state_callback(m_stream, streamStateChangedStatic, this);
Chris@117 355 pa_stream_set_write_callback(m_stream, streamWriteStatic, this);
Chris@120 356 pa_stream_set_overflow_callback(m_stream, streamOverflowStatic, this);
Chris@120 357 pa_stream_set_underflow_callback(m_stream, streamUnderflowStatic, this);
Chris@117 358
Chris@118 359 if (pa_stream_connect_playback
Chris@118 360 (m_stream, 0, 0,
Chris@118 361 pa_stream_flags_t(PA_STREAM_INTERPOLATE_TIMING |
Chris@118 362 PA_STREAM_AUTO_TIMING_UPDATE),
Chris@118 363 0, 0)) { //??? return value
Chris@117 364 std::cerr << "AudioPulseAudioTarget: Failed to connect playback stream" << std::endl;
Chris@117 365 }
Chris@117 366
Chris@118 367 pa_usec_t latency = 0;
Chris@118 368 int negative = 0;
Chris@118 369 if (pa_stream_get_latency(m_stream, &latency, &negative)) {
Chris@118 370 std::cerr << "AudioPulseAudioTarget::contextStateChanged: Failed to query latency" << std::endl;
Chris@118 371 }
Chris@118 372 std::cerr << "Latency = " << latency << " usec" << std::endl;
Chris@118 373 int latframes = (latency / 1000000.f) * float(m_sampleRate);
Chris@118 374 std::cerr << "that's " << latframes << " frames" << std::endl;
Chris@118 375
Chris@117 376 const pa_buffer_attr *attr;
Chris@117 377 if (!(attr = pa_stream_get_buffer_attr(m_stream))) {
Chris@117 378 std::cerr << "AudioPulseAudioTarget::contextStateChanged: Cannot query stream buffer attributes" << std::endl;
Chris@117 379 m_source->setTarget(this, 4096);
Chris@117 380 m_source->setTargetSampleRate(m_sampleRate);
Chris@118 381 m_source->setTargetPlayLatency(latframes);
Chris@117 382 } else {
Chris@117 383 std::cerr << "AudioPulseAudioTarget::contextStateChanged: stream max length = " << attr->maxlength << std::endl;
Chris@117 384 int latency = attr->tlength;
Chris@117 385 std::cerr << "latency = " << latency << std::endl;
Chris@117 386 m_source->setTarget(this, attr->maxlength);
Chris@117 387 m_source->setTargetSampleRate(m_sampleRate);
Chris@118 388 m_source->setTargetPlayLatency(latframes);
Chris@117 389 }
Chris@117 390
Chris@117 391 break;
Chris@118 392 }
Chris@117 393
Chris@117 394 case PA_CONTEXT_TERMINATED:
Chris@117 395 std::cerr << "AudioPulseAudioTarget::contextStateChanged: Terminated" << std::endl;
Chris@117 396 //!!! do something...
Chris@117 397 break;
Chris@117 398
Chris@117 399 case PA_CONTEXT_FAILED:
Chris@117 400 default:
Chris@117 401 std::cerr << "AudioPulseAudioTarget::contextStateChanged: Error: "
Chris@117 402 << pa_strerror(pa_context_errno(m_context)) << std::endl;
Chris@117 403 //!!! do something...
Chris@117 404 break;
Chris@117 405 }
Chris@117 406 }
Chris@117 407
Chris@120 408 void
Chris@120 409 AudioPulseAudioTarget::streamOverflowStatic(pa_stream *, void *)
Chris@120 410 {
Chris@120 411 std::cerr << "AudioPulseAudioTarget::streamOverflowStatic: Overflow!" << std::endl;
Chris@120 412 }
Chris@120 413
Chris@120 414 void
Chris@130 415 AudioPulseAudioTarget::streamUnderflowStatic(pa_stream *, void *data)
Chris@120 416 {
Chris@120 417 std::cerr << "AudioPulseAudioTarget::streamUnderflowStatic: Underflow!" << std::endl;
Chris@130 418 AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data;
Chris@130 419 if (target && target->m_source) {
Chris@130 420 target->m_source->audioProcessingOverload();
Chris@130 421 }
Chris@120 422 }
Chris@120 423
Chris@117 424 #endif /* HAVE_PULSEAUDIO */
Chris@117 425