annotate audioio/AudioPulseAudioTarget.cpp @ 191:3bd87e04f060

* Move query for latency and other stream attributes from contextStateChanged to streamStateChanged (they did not work previously, as they were trying to query the stream too soon)
author Chris Cannam
date Tue, 15 Jun 2010 11:36:02 +0000
parents 386b02c926bf
children 2b1869fccec1
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@191 308 {
Chris@117 309 std::cerr << "AudioPulseAudioTarget::streamStateChanged: Ready" << std::endl;
Chris@191 310
Chris@191 311 pa_usec_t latency = 0;
Chris@191 312 int negative = 0;
Chris@191 313 if (pa_stream_get_latency(m_stream, &latency, &negative)) {
Chris@191 314 std::cerr << "AudioPulseAudioTarget::streamStateChanged: Failed to query latency" << std::endl;
Chris@191 315 }
Chris@191 316 std::cerr << "Latency = " << latency << " usec" << std::endl;
Chris@191 317 int latframes = (latency / 1000000.f) * float(m_sampleRate);
Chris@191 318 std::cerr << "that's " << latframes << " frames" << std::endl;
Chris@191 319
Chris@191 320 const pa_buffer_attr *attr;
Chris@191 321 if (!(attr = pa_stream_get_buffer_attr(m_stream))) {
Chris@191 322 std::cerr << "AudioPulseAudioTarget::streamStateChanged: Cannot query stream buffer attributes" << std::endl;
Chris@191 323 m_source->setTarget(this, m_bufferSize);
Chris@191 324 m_source->setTargetSampleRate(m_sampleRate);
Chris@191 325 m_source->setTargetPlayLatency(latframes);
Chris@191 326 } else {
Chris@191 327 std::cerr << "AudioPulseAudioTarget::streamStateChanged: stream max length = " << attr->maxlength << std::endl;
Chris@191 328 int latency = attr->tlength;
Chris@191 329 std::cerr << "latency = " << latency << std::endl;
Chris@191 330 m_source->setTarget(this, attr->maxlength);
Chris@191 331 m_source->setTargetSampleRate(m_sampleRate);
Chris@191 332 m_source->setTargetPlayLatency(latframes);
Chris@191 333 }
Chris@191 334 }
Chris@117 335 break;
Chris@117 336
Chris@117 337 case PA_STREAM_FAILED:
Chris@117 338 default:
Chris@117 339 std::cerr << "AudioPulseAudioTarget::streamStateChanged: Error: "
Chris@117 340 << pa_strerror(pa_context_errno(m_context)) << std::endl;
Chris@117 341 //!!! do something...
Chris@117 342 break;
Chris@117 343 }
Chris@117 344 }
Chris@117 345
Chris@117 346 void
Chris@117 347 AudioPulseAudioTarget::contextStateChangedStatic(pa_context *context,
Chris@117 348 void *data)
Chris@117 349 {
Chris@117 350 AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data;
Chris@117 351
Chris@117 352 assert(context == target->m_context);
Chris@117 353
Chris@117 354 target->contextStateChanged();
Chris@117 355 }
Chris@117 356
Chris@117 357 void
Chris@117 358 AudioPulseAudioTarget::contextStateChanged()
Chris@117 359 {
Chris@117 360 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET
Chris@117 361 std::cerr << "AudioPulseAudioTarget::contextStateChanged" << std::endl;
Chris@117 362 #endif
Chris@117 363 QMutexLocker locker(&m_mutex);
Chris@117 364
Chris@117 365 switch (pa_context_get_state(m_context)) {
Chris@117 366
Chris@117 367 case PA_CONTEXT_CONNECTING:
Chris@117 368 case PA_CONTEXT_AUTHORIZING:
Chris@117 369 case PA_CONTEXT_SETTING_NAME:
Chris@117 370 break;
Chris@117 371
Chris@117 372 case PA_CONTEXT_READY:
Chris@117 373 std::cerr << "AudioPulseAudioTarget::contextStateChanged: Ready"
Chris@117 374 << std::endl;
Chris@117 375
Chris@117 376 m_stream = pa_stream_new(m_context, "stream", &m_spec, 0);
Chris@117 377 assert(m_stream); //!!!
Chris@117 378
Chris@117 379 pa_stream_set_state_callback(m_stream, streamStateChangedStatic, this);
Chris@117 380 pa_stream_set_write_callback(m_stream, streamWriteStatic, this);
Chris@120 381 pa_stream_set_overflow_callback(m_stream, streamOverflowStatic, this);
Chris@120 382 pa_stream_set_underflow_callback(m_stream, streamUnderflowStatic, this);
Chris@118 383 if (pa_stream_connect_playback
Chris@118 384 (m_stream, 0, 0,
Chris@118 385 pa_stream_flags_t(PA_STREAM_INTERPOLATE_TIMING |
Chris@118 386 PA_STREAM_AUTO_TIMING_UPDATE),
Chris@118 387 0, 0)) { //??? return value
Chris@117 388 std::cerr << "AudioPulseAudioTarget: Failed to connect playback stream" << std::endl;
Chris@117 389 }
Chris@117 390
Chris@117 391 break;
Chris@117 392
Chris@117 393 case PA_CONTEXT_TERMINATED:
Chris@117 394 std::cerr << "AudioPulseAudioTarget::contextStateChanged: Terminated" << std::endl;
Chris@117 395 //!!! do something...
Chris@117 396 break;
Chris@117 397
Chris@117 398 case PA_CONTEXT_FAILED:
Chris@117 399 default:
Chris@117 400 std::cerr << "AudioPulseAudioTarget::contextStateChanged: Error: "
Chris@117 401 << pa_strerror(pa_context_errno(m_context)) << std::endl;
Chris@117 402 //!!! do something...
Chris@117 403 break;
Chris@117 404 }
Chris@117 405 }
Chris@117 406
Chris@120 407 void
Chris@120 408 AudioPulseAudioTarget::streamOverflowStatic(pa_stream *, void *)
Chris@120 409 {
Chris@120 410 std::cerr << "AudioPulseAudioTarget::streamOverflowStatic: Overflow!" << std::endl;
Chris@120 411 }
Chris@120 412
Chris@120 413 void
Chris@130 414 AudioPulseAudioTarget::streamUnderflowStatic(pa_stream *, void *data)
Chris@120 415 {
Chris@120 416 std::cerr << "AudioPulseAudioTarget::streamUnderflowStatic: Underflow!" << std::endl;
Chris@130 417 AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data;
Chris@130 418 if (target && target->m_source) {
Chris@130 419 target->m_source->audioProcessingOverload();
Chris@130 420 }
Chris@120 421 }
Chris@120 422
Chris@117 423 #endif /* HAVE_PULSEAUDIO */
Chris@117 424