Chris@117: /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*-  vi:set ts=8 sts=4 sw=4: */
Chris@117: 
Chris@117: /*
Chris@117:     Sonic Visualiser
Chris@117:     An audio file viewer and annotation editor.
Chris@117:     Centre for Digital Music, Queen Mary, University of London.
Chris@117:     This file copyright 2008 QMUL.
Chris@117:     
Chris@117:     This program is free software; you can redistribute it and/or
Chris@117:     modify it under the terms of the GNU General Public License as
Chris@117:     published by the Free Software Foundation; either version 2 of the
Chris@117:     License, or (at your option) any later version.  See the file
Chris@117:     COPYING included with this distribution for more information.
Chris@117: */
Chris@117: 
Chris@117: #ifdef HAVE_LIBPULSE
Chris@117: 
Chris@117: #include "AudioPulseAudioTarget.h"
Chris@117: #include "AudioCallbackPlaySource.h"
Chris@117: 
Chris@117: #include <QMutexLocker>
Chris@117: 
Chris@117: #include <iostream>
Chris@117: #include <cassert>
Chris@117: #include <cmath>
Chris@117: 
Chris@195: #define DEBUG_AUDIO_PULSE_AUDIO_TARGET 1
Chris@195: //#define DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY 1
Chris@117: 
Chris@117: AudioPulseAudioTarget::AudioPulseAudioTarget(AudioCallbackPlaySource *source) :
Chris@117:     AudioCallbackPlayTarget(source),
Chris@117:     m_mutex(QMutex::Recursive),
Chris@117:     m_loop(0),
Chris@117:     m_api(0),
Chris@117:     m_context(0),
Chris@117:     m_stream(0),
Chris@117:     m_loopThread(0),
Chris@117:     m_bufferSize(0),
Chris@117:     m_sampleRate(0),
Chris@117:     m_latency(0),
Chris@117:     m_done(false)
Chris@117: {
Chris@117: #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET
Chris@117:     std::cerr << "AudioPulseAudioTarget: Initialising for PulseAudio" << std::endl;
Chris@117: #endif
Chris@117: 
Chris@117:     m_loop = pa_mainloop_new();
Chris@117:     if (!m_loop) {
Chris@117:         std::cerr << "ERROR: AudioPulseAudioTarget: Failed to create main loop" << std::endl;
Chris@117:         return;
Chris@117:     }
Chris@117: 
Chris@117:     m_api = pa_mainloop_get_api(m_loop);
Chris@117: 
Chris@117:     //!!! handle signals how?
Chris@117: 
Chris@118:     m_bufferSize = 20480;
Chris@117:     m_sampleRate = 44100;
Chris@117:     if (m_source && (m_source->getSourceSampleRate() != 0)) {
Chris@117: 	m_sampleRate = m_source->getSourceSampleRate();
Chris@117:     }
Chris@117:     m_spec.rate = m_sampleRate;
Chris@117:     m_spec.channels = 2;
Chris@117:     m_spec.format = PA_SAMPLE_FLOAT32NE;
Chris@117: 
Chris@195: #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET
Chris@195:     std::cerr << "AudioPulseAudioTarget: Creating context" << std::endl;
Chris@195: #endif
Chris@195: 
Chris@117:     m_context = pa_context_new(m_api, source->getClientName().toLocal8Bit().data());
Chris@117:     if (!m_context) {
Chris@117:         std::cerr << "ERROR: AudioPulseAudioTarget: Failed to create context object" << std::endl;
Chris@117:         return;
Chris@117:     }
Chris@117: 
Chris@117:     pa_context_set_state_callback(m_context, contextStateChangedStatic, this);
Chris@117: 
Chris@195: #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET
Chris@195:     std::cerr << "AudioPulseAudioTarget: Connecting to default server..." << std::endl;
Chris@195: #endif
Chris@195: 
Chris@195:     pa_context_connect(m_context, 0, // default server
Chris@195:                        (pa_context_flags_t)PA_CONTEXT_NOAUTOSPAWN, 0);
Chris@195: 
Chris@195: #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET
Chris@195:     std::cerr << "AudioPulseAudioTarget: Starting main loop" << std::endl;
Chris@195: #endif
Chris@117: 
Chris@117:     m_loopThread = new MainLoopThread(m_loop);
Chris@117:     m_loopThread->start();
Chris@117: 
Chris@117: #ifdef DEBUG_PULSE_AUDIO_TARGET
Chris@117:     std::cerr << "AudioPulseAudioTarget: initialised OK" << std::endl;
Chris@117: #endif
Chris@117: }
Chris@117: 
Chris@117: AudioPulseAudioTarget::~AudioPulseAudioTarget()
Chris@117: {
Chris@233:     SVDEBUG << "AudioPulseAudioTarget::~AudioPulseAudioTarget()" << endl;
Chris@117: 
Chris@117:     if (m_source) {
Chris@117:         m_source->setTarget(0, m_bufferSize);
Chris@117:     }
Chris@117: 
Chris@117:     shutdown();
Chris@117: 
Chris@117:     QMutexLocker locker(&m_mutex);
Chris@117: 
Chris@117:     if (m_stream) pa_stream_unref(m_stream);
Chris@117: 
Chris@117:     if (m_context) pa_context_unref(m_context);
Chris@117: 
Chris@117:     if (m_loop) {
Chris@117:         pa_signal_done();
Chris@117:         pa_mainloop_free(m_loop);
Chris@117:     }
Chris@117: 
Chris@117:     m_stream = 0;
Chris@117:     m_context = 0;
Chris@117:     m_loop = 0;
Chris@117: 
Chris@233:     SVDEBUG << "AudioPulseAudioTarget::~AudioPulseAudioTarget() done" << endl;
Chris@117: }
Chris@117: 
Chris@117: void 
Chris@117: AudioPulseAudioTarget::shutdown()
Chris@117: {
Chris@117:     m_done = true;
Chris@117: }
Chris@117: 
Chris@117: bool
Chris@117: AudioPulseAudioTarget::isOK() const
Chris@117: {
Chris@117:     return (m_context != 0);
Chris@117: }
Chris@117: 
Chris@117: double
Chris@117: AudioPulseAudioTarget::getCurrentTime() const
Chris@117: {
Chris@117:     if (!m_stream) return 0.0;
Chris@118:     
Chris@118:     pa_usec_t usec = 0;
Chris@118:     pa_stream_get_time(m_stream, &usec);
Chris@118:     return usec / 1000000.f;
Chris@117: }
Chris@117: 
Chris@117: void
Chris@117: AudioPulseAudioTarget::sourceModelReplaced()
Chris@117: {
Chris@117:     m_source->setTargetSampleRate(m_sampleRate);
Chris@117: }
Chris@117: 
Chris@117: void
Chris@117: AudioPulseAudioTarget::streamWriteStatic(pa_stream *stream,
Chris@117:                                          size_t length,
Chris@117:                                          void *data)
Chris@117: {
Chris@117:     AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data;
Chris@117:     
Chris@117:     assert(stream == target->m_stream);
Chris@117: 
Chris@117:     target->streamWrite(length);
Chris@117: }
Chris@117: 
Chris@117: void
Chris@120: AudioPulseAudioTarget::streamWrite(size_t requested)
Chris@117: {
Chris@195: #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY
Chris@120:     std::cout << "AudioPulseAudioTarget::streamWrite(" << requested << ")" << std::endl;
Chris@117: #endif
Chris@117:     if (m_done) return;
Chris@117: 
Chris@117:     QMutexLocker locker(&m_mutex);
Chris@117: 
Chris@193:     pa_usec_t latency = 0;
Chris@193:     int negative = 0;
Chris@193:     if (!pa_stream_get_latency(m_stream, &latency, &negative)) {
Chris@193:         int latframes = (latency / 1000000.f) * float(m_sampleRate);
Chris@193:         if (latframes > 0) m_source->setTargetPlayLatency(latframes);
Chris@118:     }
Chris@118: 
Chris@117:     static float *output = 0;
Chris@117:     static float **tmpbuf = 0;
Chris@117:     static size_t tmpbufch = 0;
Chris@117:     static size_t tmpbufsz = 0;
Chris@117: 
Chris@117:     size_t sourceChannels = m_source->getSourceChannelCount();
Chris@117: 
Chris@117:     // Because we offer pan, we always want at least 2 channels
Chris@117:     if (sourceChannels < 2) sourceChannels = 2;
Chris@117: 
Chris@120:     size_t nframes = requested / (sourceChannels * sizeof(float));
Chris@120: 
Chris@120:     if (nframes > m_bufferSize) {
Chris@120:         std::cerr << "WARNING: AudioPulseAudioTarget::streamWrite: nframes " << nframes << " > m_bufferSize " << m_bufferSize << std::endl;
Chris@120:     }
Chris@120: 
Chris@195: #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY
Chris@120:     std::cout << "AudioPulseAudioTarget::streamWrite: nframes = " << nframes << std::endl;
Chris@120: #endif
Chris@120: 
Chris@117:     if (!tmpbuf || tmpbufch != sourceChannels || int(tmpbufsz) < nframes) {
Chris@117: 
Chris@117: 	if (tmpbuf) {
Chris@117: 	    for (size_t i = 0; i < tmpbufch; ++i) {
Chris@117: 		delete[] tmpbuf[i];
Chris@117: 	    }
Chris@117: 	    delete[] tmpbuf;
Chris@117: 	}
Chris@117: 
Chris@117:         if (output) {
Chris@117:             delete[] output;
Chris@117:         }
Chris@117: 
Chris@117: 	tmpbufch = sourceChannels;
Chris@117: 	tmpbufsz = nframes;
Chris@117: 	tmpbuf = new float *[tmpbufch];
Chris@117: 
Chris@117: 	for (size_t i = 0; i < tmpbufch; ++i) {
Chris@117: 	    tmpbuf[i] = new float[tmpbufsz];
Chris@117: 	}
Chris@117: 
Chris@117:         output = new float[tmpbufsz * tmpbufch];
Chris@117:     }
Chris@117: 	
Chris@117:     size_t received = m_source->getSourceSamples(nframes, tmpbuf);
Chris@117: 
Chris@195: #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY
Chris@119:     std::cerr << "requested " << nframes << ", received " << received << std::endl;
Chris@120: 
Chris@119:     if (received < nframes) {
Chris@119:         std::cerr << "*** WARNING: Wrong number of frames received" << std::endl;
Chris@119:     }
Chris@120: #endif
Chris@119: 
Chris@117:     float peakLeft = 0.0, peakRight = 0.0;
Chris@117: 
Chris@117:     for (size_t ch = 0; ch < 2; ++ch) {
Chris@117: 	
Chris@117: 	float peak = 0.0;
Chris@117: 
Chris@117: 	if (ch < sourceChannels) {
Chris@117: 
Chris@117: 	    // PulseAudio samples are interleaved
Chris@117: 	    for (size_t i = 0; i < nframes; ++i) {
Chris@117:                 if (i < received) {
Chris@117:                     output[i * 2 + ch] = tmpbuf[ch][i] * m_outputGain;
Chris@117:                     float sample = fabsf(output[i * 2 + ch]);
Chris@117:                     if (sample > peak) peak = sample;
Chris@117:                 } else {
Chris@117:                     output[i * 2 + ch] = 0;
Chris@117:                 }
Chris@117: 	    }
Chris@117: 
Chris@117: 	} else if (ch == 1 && sourceChannels == 1) {
Chris@117: 
Chris@117: 	    for (size_t i = 0; i < nframes; ++i) {
Chris@117:                 if (i < received) {
Chris@117:                     output[i * 2 + ch] = tmpbuf[0][i] * m_outputGain;
Chris@117:                     float sample = fabsf(output[i * 2 + ch]);
Chris@117:                     if (sample > peak) peak = sample;
Chris@117:                 } else {
Chris@117:                     output[i * 2 + ch] = 0;
Chris@117:                 }
Chris@117: 	    }
Chris@117: 
Chris@117: 	} else {
Chris@117: 	    for (size_t i = 0; i < nframes; ++i) {
Chris@117: 		output[i * 2 + ch] = 0;
Chris@117: 	    }
Chris@117: 	}
Chris@117: 
Chris@117: 	if (ch == 0) peakLeft = peak;
Chris@117: 	if (ch > 0 || sourceChannels == 1) peakRight = peak;
Chris@117:     }
Chris@117: 
Chris@195: #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY
Chris@233:     SVDEBUG << "calling pa_stream_write with "
Chris@229:               << nframes * tmpbufch * sizeof(float) << " bytes" << endl;
Chris@120: #endif
Chris@120: 
Chris@117:     pa_stream_write(m_stream, output, nframes * tmpbufch * sizeof(float),
Chris@117:                     0, 0, PA_SEEK_RELATIVE);
Chris@117: 
Chris@117:     m_source->setOutputLevels(peakLeft, peakRight);
Chris@117: 
Chris@117:     return;
Chris@117: }
Chris@117: 
Chris@117: void
Chris@117: AudioPulseAudioTarget::streamStateChangedStatic(pa_stream *stream,
Chris@117:                                                 void *data)
Chris@117: {
Chris@117:     AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data;
Chris@117:     
Chris@117:     assert(stream == target->m_stream);
Chris@117: 
Chris@117:     target->streamStateChanged();
Chris@117: }
Chris@117: 
Chris@117: void
Chris@117: AudioPulseAudioTarget::streamStateChanged()
Chris@117: {
Chris@117: #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET
Chris@233:     SVDEBUG << "AudioPulseAudioTarget::streamStateChanged" << endl;
Chris@117: #endif
Chris@117:     QMutexLocker locker(&m_mutex);
Chris@117: 
Chris@117:     switch (pa_stream_get_state(m_stream)) {
Chris@117: 
Chris@117:         case PA_STREAM_CREATING:
Chris@117:         case PA_STREAM_TERMINATED:
Chris@117:             break;
Chris@117: 
Chris@117:         case PA_STREAM_READY:
Chris@191:         {
Chris@233:             SVDEBUG << "AudioPulseAudioTarget::streamStateChanged: Ready" << endl;
Chris@191: 
Chris@191:             pa_usec_t latency = 0;
Chris@191:             int negative = 0;
Chris@191:             if (pa_stream_get_latency(m_stream, &latency, &negative)) {
Chris@191:                 std::cerr << "AudioPulseAudioTarget::streamStateChanged: Failed to query latency" << std::endl;
Chris@191:             }
Chris@191:             std::cerr << "Latency = " << latency << " usec" << std::endl;
Chris@191:             int latframes = (latency / 1000000.f) * float(m_sampleRate);
Chris@191:             std::cerr << "that's " << latframes << " frames" << std::endl;
Chris@191: 
Chris@191:             const pa_buffer_attr *attr;
Chris@191:             if (!(attr = pa_stream_get_buffer_attr(m_stream))) {
Chris@233:                 SVDEBUG << "AudioPulseAudioTarget::streamStateChanged: Cannot query stream buffer attributes" << endl;
Chris@191:                 m_source->setTarget(this, m_bufferSize);
Chris@191:                 m_source->setTargetSampleRate(m_sampleRate);
Chris@193:                 if (latframes != 0) m_source->setTargetPlayLatency(latframes);
Chris@191:             } else {
Chris@193:                 int targetLength = attr->tlength;
Chris@233:                 SVDEBUG << "AudioPulseAudioTarget::streamStateChanged: stream target length = " << targetLength << endl;
Chris@193:                 m_source->setTarget(this, targetLength);
Chris@191:                 m_source->setTargetSampleRate(m_sampleRate);
Chris@193:                 if (latframes == 0) latframes = targetLength;
Chris@193:                 std::cerr << "latency = " << latframes << std::endl;
Chris@191:                 m_source->setTargetPlayLatency(latframes);
Chris@191:             }
Chris@191:         }
Chris@117:             break;
Chris@117: 
Chris@117:         case PA_STREAM_FAILED:
Chris@117:         default:
Chris@117:             std::cerr << "AudioPulseAudioTarget::streamStateChanged: Error: "
Chris@117:                       << pa_strerror(pa_context_errno(m_context)) << std::endl;
Chris@117:             //!!! do something...
Chris@117:             break;
Chris@117:     }
Chris@117: }
Chris@117: 
Chris@117: void
Chris@117: AudioPulseAudioTarget::contextStateChangedStatic(pa_context *context,
Chris@117:                                                  void *data)
Chris@117: {
Chris@117:     AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data;
Chris@117:     
Chris@117:     assert(context == target->m_context);
Chris@117: 
Chris@117:     target->contextStateChanged();
Chris@117: }
Chris@117: 
Chris@117: void
Chris@117: AudioPulseAudioTarget::contextStateChanged()
Chris@117: {
Chris@117: #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET
Chris@233:     SVDEBUG << "AudioPulseAudioTarget::contextStateChanged" << endl;
Chris@117: #endif
Chris@117:     QMutexLocker locker(&m_mutex);
Chris@117: 
Chris@117:     switch (pa_context_get_state(m_context)) {
Chris@117: 
Chris@117:         case PA_CONTEXT_CONNECTING:
Chris@117:         case PA_CONTEXT_AUTHORIZING:
Chris@117:         case PA_CONTEXT_SETTING_NAME:
Chris@117:             break;
Chris@117: 
Chris@117:         case PA_CONTEXT_READY:
Chris@233:             SVDEBUG << "AudioPulseAudioTarget::contextStateChanged: Ready"
Chris@229:                       << endl;
Chris@117: 
Chris@117:             m_stream = pa_stream_new(m_context, "stream", &m_spec, 0);
Chris@117:             assert(m_stream); //!!!
Chris@117:             
Chris@117:             pa_stream_set_state_callback(m_stream, streamStateChangedStatic, this);
Chris@117:             pa_stream_set_write_callback(m_stream, streamWriteStatic, this);
Chris@120:             pa_stream_set_overflow_callback(m_stream, streamOverflowStatic, this);
Chris@120:             pa_stream_set_underflow_callback(m_stream, streamUnderflowStatic, this);
Chris@118:             if (pa_stream_connect_playback
Chris@118:                 (m_stream, 0, 0,
Chris@118:                  pa_stream_flags_t(PA_STREAM_INTERPOLATE_TIMING |
Chris@118:                                    PA_STREAM_AUTO_TIMING_UPDATE),
Chris@118:                  0, 0)) { //??? return value
Chris@117:                 std::cerr << "AudioPulseAudioTarget: Failed to connect playback stream" << std::endl;
Chris@117:             }
Chris@117: 
Chris@117:             break;
Chris@117: 
Chris@117:         case PA_CONTEXT_TERMINATED:
Chris@233:             SVDEBUG << "AudioPulseAudioTarget::contextStateChanged: Terminated" << endl;
Chris@117:             //!!! do something...
Chris@117:             break;
Chris@117: 
Chris@117:         case PA_CONTEXT_FAILED:
Chris@117:         default:
Chris@117:             std::cerr << "AudioPulseAudioTarget::contextStateChanged: Error: "
Chris@117:                       << pa_strerror(pa_context_errno(m_context)) << std::endl;
Chris@117:             //!!! do something...
Chris@117:             break;
Chris@117:     }
Chris@117: }
Chris@117: 
Chris@120: void
Chris@120: AudioPulseAudioTarget::streamOverflowStatic(pa_stream *, void *)
Chris@120: {
Chris@233:     SVDEBUG << "AudioPulseAudioTarget::streamOverflowStatic: Overflow!" << endl;
Chris@120: }
Chris@120: 
Chris@120: void
Chris@130: AudioPulseAudioTarget::streamUnderflowStatic(pa_stream *, void *data)
Chris@120: {
Chris@233:     SVDEBUG << "AudioPulseAudioTarget::streamUnderflowStatic: Underflow!" << endl;
Chris@130:     AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data;
Chris@130:     if (target && target->m_source) {
Chris@130:         target->m_source->audioProcessingOverload();
Chris@130:     }
Chris@120: }
Chris@120: 
Chris@117: #endif /* HAVE_PULSEAUDIO */
Chris@117: