| 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@117 | 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@117 | 56     m_bufferSize = 2048; | 
| 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@117 | 127 //!!!    else return Pa_GetStreamTime(m_stream); | 
| Chris@117 | 128 | 
| Chris@117 | 129     return 0.0;//!!! | 
| Chris@117 | 130 } | 
| Chris@117 | 131 | 
| Chris@117 | 132 void | 
| Chris@117 | 133 AudioPulseAudioTarget::sourceModelReplaced() | 
| Chris@117 | 134 { | 
| Chris@117 | 135     m_source->setTargetSampleRate(m_sampleRate); | 
| Chris@117 | 136 } | 
| Chris@117 | 137 | 
| Chris@117 | 138 void | 
| Chris@117 | 139 AudioPulseAudioTarget::streamWriteStatic(pa_stream *stream, | 
| Chris@117 | 140                                          size_t length, | 
| Chris@117 | 141                                          void *data) | 
| Chris@117 | 142 { | 
| Chris@117 | 143     AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data; | 
| Chris@117 | 144 | 
| Chris@117 | 145     assert(stream == target->m_stream); | 
| Chris@117 | 146 | 
| Chris@117 | 147     target->streamWrite(length); | 
| Chris@117 | 148 } | 
| Chris@117 | 149 | 
| Chris@117 | 150 void | 
| Chris@117 | 151 AudioPulseAudioTarget::streamWrite(size_t nframes) | 
| Chris@117 | 152 { | 
| Chris@117 | 153 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET | 
| Chris@117 | 154     std::cout << "AudioPulseAudioTarget::streamWrite(" << nframes << ")" << std::endl; | 
| Chris@117 | 155 #endif | 
| Chris@117 | 156     if (m_done) return; | 
| Chris@117 | 157 | 
| Chris@117 | 158     QMutexLocker locker(&m_mutex); | 
| Chris@117 | 159 | 
| Chris@117 | 160     if (nframes > m_bufferSize) { | 
| Chris@117 | 161         std::cerr << "WARNING: AudioPulseAudioTarget::streamWrite: nframes " << nframes << " > m_bufferSize " << m_bufferSize << std::endl; | 
| Chris@117 | 162     } | 
| Chris@117 | 163 | 
| Chris@117 | 164     static float *output = 0; | 
| Chris@117 | 165     static float **tmpbuf = 0; | 
| Chris@117 | 166     static size_t tmpbufch = 0; | 
| Chris@117 | 167     static size_t tmpbufsz = 0; | 
| Chris@117 | 168 | 
| Chris@117 | 169     size_t sourceChannels = m_source->getSourceChannelCount(); | 
| Chris@117 | 170 | 
| Chris@117 | 171     // Because we offer pan, we always want at least 2 channels | 
| Chris@117 | 172     if (sourceChannels < 2) sourceChannels = 2; | 
| Chris@117 | 173 | 
| Chris@117 | 174     if (!tmpbuf || tmpbufch != sourceChannels || int(tmpbufsz) < nframes) { | 
| Chris@117 | 175 | 
| Chris@117 | 176 	if (tmpbuf) { | 
| Chris@117 | 177 	    for (size_t i = 0; i < tmpbufch; ++i) { | 
| Chris@117 | 178 		delete[] tmpbuf[i]; | 
| Chris@117 | 179 	    } | 
| Chris@117 | 180 	    delete[] tmpbuf; | 
| Chris@117 | 181 	} | 
| Chris@117 | 182 | 
| Chris@117 | 183         if (output) { | 
| Chris@117 | 184             delete[] output; | 
| Chris@117 | 185         } | 
| Chris@117 | 186 | 
| Chris@117 | 187 	tmpbufch = sourceChannels; | 
| Chris@117 | 188 	tmpbufsz = nframes; | 
| Chris@117 | 189 	tmpbuf = new float *[tmpbufch]; | 
| Chris@117 | 190 | 
| Chris@117 | 191 	for (size_t i = 0; i < tmpbufch; ++i) { | 
| Chris@117 | 192 	    tmpbuf[i] = new float[tmpbufsz]; | 
| Chris@117 | 193 	} | 
| Chris@117 | 194 | 
| Chris@117 | 195         output = new float[tmpbufsz * tmpbufch]; | 
| Chris@117 | 196     } | 
| Chris@117 | 197 | 
| Chris@117 | 198     size_t received = m_source->getSourceSamples(nframes, tmpbuf); | 
| Chris@117 | 199 | 
| Chris@117 | 200     float peakLeft = 0.0, peakRight = 0.0; | 
| Chris@117 | 201 | 
| Chris@117 | 202     for (size_t ch = 0; ch < 2; ++ch) { | 
| Chris@117 | 203 | 
| Chris@117 | 204 	float peak = 0.0; | 
| Chris@117 | 205 | 
| Chris@117 | 206 	if (ch < sourceChannels) { | 
| Chris@117 | 207 | 
| Chris@117 | 208 	    // PulseAudio samples are interleaved | 
| Chris@117 | 209 	    for (size_t i = 0; i < nframes; ++i) { | 
| Chris@117 | 210                 if (i < received) { | 
| Chris@117 | 211                     output[i * 2 + ch] = tmpbuf[ch][i] * m_outputGain; | 
| Chris@117 | 212                     float sample = fabsf(output[i * 2 + ch]); | 
| Chris@117 | 213                     if (sample > peak) peak = sample; | 
| Chris@117 | 214                 } else { | 
| Chris@117 | 215                     output[i * 2 + ch] = 0; | 
| Chris@117 | 216                 } | 
| Chris@117 | 217 	    } | 
| Chris@117 | 218 | 
| Chris@117 | 219 	} else if (ch == 1 && sourceChannels == 1) { | 
| Chris@117 | 220 | 
| Chris@117 | 221 	    for (size_t i = 0; i < nframes; ++i) { | 
| Chris@117 | 222                 if (i < received) { | 
| Chris@117 | 223                     output[i * 2 + ch] = tmpbuf[0][i] * m_outputGain; | 
| Chris@117 | 224                     float sample = fabsf(output[i * 2 + ch]); | 
| Chris@117 | 225                     if (sample > peak) peak = sample; | 
| Chris@117 | 226                 } else { | 
| Chris@117 | 227                     output[i * 2 + ch] = 0; | 
| Chris@117 | 228                 } | 
| Chris@117 | 229 	    } | 
| Chris@117 | 230 | 
| Chris@117 | 231 	} else { | 
| Chris@117 | 232 	    for (size_t i = 0; i < nframes; ++i) { | 
| Chris@117 | 233 		output[i * 2 + ch] = 0; | 
| Chris@117 | 234 	    } | 
| Chris@117 | 235 	} | 
| Chris@117 | 236 | 
| Chris@117 | 237 	if (ch == 0) peakLeft = peak; | 
| Chris@117 | 238 	if (ch > 0 || sourceChannels == 1) peakRight = peak; | 
| Chris@117 | 239     } | 
| Chris@117 | 240 | 
| Chris@117 | 241     pa_stream_write(m_stream, output, nframes * tmpbufch * sizeof(float), | 
| Chris@117 | 242                     0, 0, PA_SEEK_RELATIVE); | 
| Chris@117 | 243 | 
| Chris@117 | 244     m_source->setOutputLevels(peakLeft, peakRight); | 
| Chris@117 | 245 | 
| Chris@117 | 246     return; | 
| Chris@117 | 247 } | 
| Chris@117 | 248 | 
| Chris@117 | 249 void | 
| Chris@117 | 250 AudioPulseAudioTarget::streamStateChangedStatic(pa_stream *stream, | 
| Chris@117 | 251                                                 void *data) | 
| Chris@117 | 252 { | 
| Chris@117 | 253     AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data; | 
| Chris@117 | 254 | 
| Chris@117 | 255     assert(stream == target->m_stream); | 
| Chris@117 | 256 | 
| Chris@117 | 257     target->streamStateChanged(); | 
| Chris@117 | 258 } | 
| Chris@117 | 259 | 
| Chris@117 | 260 void | 
| Chris@117 | 261 AudioPulseAudioTarget::streamStateChanged() | 
| Chris@117 | 262 { | 
| Chris@117 | 263 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET | 
| Chris@117 | 264     std::cerr << "AudioPulseAudioTarget::streamStateChanged" << std::endl; | 
| Chris@117 | 265 #endif | 
| Chris@117 | 266     QMutexLocker locker(&m_mutex); | 
| Chris@117 | 267 | 
| Chris@117 | 268     switch (pa_stream_get_state(m_stream)) { | 
| Chris@117 | 269 | 
| Chris@117 | 270         case PA_STREAM_CREATING: | 
| Chris@117 | 271         case PA_STREAM_TERMINATED: | 
| Chris@117 | 272             break; | 
| Chris@117 | 273 | 
| Chris@117 | 274         case PA_STREAM_READY: | 
| Chris@117 | 275             std::cerr << "AudioPulseAudioTarget::streamStateChanged: Ready" << std::endl; | 
| Chris@117 | 276             break; | 
| Chris@117 | 277 | 
| Chris@117 | 278         case PA_STREAM_FAILED: | 
| Chris@117 | 279         default: | 
| Chris@117 | 280             std::cerr << "AudioPulseAudioTarget::streamStateChanged: Error: " | 
| Chris@117 | 281                       << pa_strerror(pa_context_errno(m_context)) << std::endl; | 
| Chris@117 | 282             //!!! do something... | 
| Chris@117 | 283             break; | 
| Chris@117 | 284     } | 
| Chris@117 | 285 } | 
| Chris@117 | 286 | 
| Chris@117 | 287 void | 
| Chris@117 | 288 AudioPulseAudioTarget::contextStateChangedStatic(pa_context *context, | 
| Chris@117 | 289                                                  void *data) | 
| Chris@117 | 290 { | 
| Chris@117 | 291     AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data; | 
| Chris@117 | 292 | 
| Chris@117 | 293     assert(context == target->m_context); | 
| Chris@117 | 294 | 
| Chris@117 | 295     target->contextStateChanged(); | 
| Chris@117 | 296 } | 
| Chris@117 | 297 | 
| Chris@117 | 298 void | 
| Chris@117 | 299 AudioPulseAudioTarget::contextStateChanged() | 
| Chris@117 | 300 { | 
| Chris@117 | 301 #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET | 
| Chris@117 | 302     std::cerr << "AudioPulseAudioTarget::contextStateChanged" << std::endl; | 
| Chris@117 | 303 #endif | 
| Chris@117 | 304     QMutexLocker locker(&m_mutex); | 
| Chris@117 | 305 | 
| Chris@117 | 306     switch (pa_context_get_state(m_context)) { | 
| Chris@117 | 307 | 
| Chris@117 | 308         case PA_CONTEXT_CONNECTING: | 
| Chris@117 | 309         case PA_CONTEXT_AUTHORIZING: | 
| Chris@117 | 310         case PA_CONTEXT_SETTING_NAME: | 
| Chris@117 | 311             break; | 
| Chris@117 | 312 | 
| Chris@117 | 313         case PA_CONTEXT_READY: | 
| Chris@117 | 314             std::cerr << "AudioPulseAudioTarget::contextStateChanged: Ready" | 
| Chris@117 | 315                       << std::endl; | 
| Chris@117 | 316 | 
| Chris@117 | 317             m_stream = pa_stream_new(m_context, "stream", &m_spec, 0); | 
| Chris@117 | 318             assert(m_stream); //!!! | 
| Chris@117 | 319 | 
| Chris@117 | 320             pa_stream_set_state_callback(m_stream, streamStateChangedStatic, this); | 
| Chris@117 | 321             pa_stream_set_write_callback(m_stream, streamWriteStatic, this); | 
| Chris@117 | 322 | 
| Chris@117 | 323             if (!pa_stream_connect_playback(m_stream, 0, 0, pa_stream_flags_t(0), 0, 0)) { | 
| Chris@117 | 324                 std::cerr << "AudioPulseAudioTarget: Failed to connect playback stream" << std::endl; | 
| Chris@117 | 325                 break; | 
| Chris@117 | 326             } | 
| Chris@117 | 327 | 
| Chris@117 | 328             const pa_buffer_attr *attr; | 
| Chris@117 | 329             if (!(attr = pa_stream_get_buffer_attr(m_stream))) { | 
| Chris@117 | 330                 std::cerr << "AudioPulseAudioTarget::contextStateChanged: Cannot query stream buffer attributes" << std::endl; | 
| Chris@117 | 331                 m_source->setTarget(this, 4096); | 
| Chris@117 | 332                 m_source->setTargetSampleRate(m_sampleRate); | 
| Chris@117 | 333                 m_source->setTargetPlayLatency(4096); | 
| Chris@117 | 334             } else { | 
| Chris@117 | 335                 std::cerr << "AudioPulseAudioTarget::contextStateChanged: stream max length = " << attr->maxlength << std::endl; | 
| Chris@117 | 336                 int latency = attr->tlength; | 
| Chris@117 | 337                 std::cerr << "latency = " << latency << std::endl; | 
| Chris@117 | 338                 m_source->setTarget(this, attr->maxlength); | 
| Chris@117 | 339                 m_source->setTargetSampleRate(m_sampleRate); | 
| Chris@117 | 340                 m_source->setTargetPlayLatency(latency); | 
| Chris@117 | 341             } | 
| Chris@117 | 342 | 
| Chris@117 | 343             break; | 
| Chris@117 | 344 | 
| Chris@117 | 345         case PA_CONTEXT_TERMINATED: | 
| Chris@117 | 346             std::cerr << "AudioPulseAudioTarget::contextStateChanged: Terminated" << std::endl; | 
| Chris@117 | 347             //!!! do something... | 
| Chris@117 | 348             break; | 
| Chris@117 | 349 | 
| Chris@117 | 350         case PA_CONTEXT_FAILED: | 
| Chris@117 | 351         default: | 
| Chris@117 | 352             std::cerr << "AudioPulseAudioTarget::contextStateChanged: Error: " | 
| Chris@117 | 353                       << pa_strerror(pa_context_errno(m_context)) << std::endl; | 
| Chris@117 | 354             //!!! do something... | 
| Chris@117 | 355             break; | 
| Chris@117 | 356     } | 
| Chris@117 | 357 } | 
| Chris@117 | 358 | 
| Chris@117 | 359 #endif /* HAVE_PULSEAUDIO */ | 
| Chris@117 | 360 |