annotate audioio/AudioPulseAudioTarget.cpp @ 166:2c2029007618

* "ffwd-similar" * stop when ffwding to end
author Chris Cannam
date Tue, 03 Mar 2009 21:48:03 +0000
parents 386b02c926bf
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