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 Chris@117: Chris@117: #include Chris@117: #include Chris@117: #include 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@293: cerr << "AudioPulseAudioTarget: Initialising for PulseAudio" << endl; Chris@117: #endif Chris@117: Chris@117: m_loop = pa_mainloop_new(); Chris@117: if (!m_loop) { Chris@293: cerr << "ERROR: AudioPulseAudioTarget: Failed to create main loop" << 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@436: m_sampleRate = int(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@293: cerr << "AudioPulseAudioTarget: Creating context" << 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@293: cerr << "ERROR: AudioPulseAudioTarget: Failed to create context object" << 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@293: cerr << "AudioPulseAudioTarget: Connecting to default server..." << 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@293: cerr << "AudioPulseAudioTarget: Starting main loop" << 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@293: cerr << "AudioPulseAudioTarget: initialised OK" << 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@436: return double(usec) / 1000000.0; 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@436: AudioPulseAudioTarget::streamWriteStatic(pa_stream *, Chris@117: size_t length, Chris@117: void *data) Chris@117: { Chris@117: AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data; Chris@117: Chris@436: // assert(stream == target->m_stream); Chris@117: Chris@117: target->streamWrite(length); Chris@117: } Chris@117: Chris@117: void Chris@436: AudioPulseAudioTarget::streamWrite(sv_frame_t requested) Chris@117: { Chris@195: #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY Chris@293: cout << "AudioPulseAudioTarget::streamWrite(" << requested << ")" << 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@436: int latframes = int(double(latency) / 1000000.0 * double(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@366: static int tmpbufch = 0; Chris@436: static sv_frame_t tmpbufsz = 0; Chris@117: Chris@366: int 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@436: sv_frame_t nframes = requested / (sourceChannels * sizeof(float)); Chris@120: Chris@120: if (nframes > m_bufferSize) { Chris@293: cerr << "WARNING: AudioPulseAudioTarget::streamWrite: nframes " << nframes << " > m_bufferSize " << m_bufferSize << endl; Chris@120: } Chris@120: Chris@195: #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY Chris@293: cout << "AudioPulseAudioTarget::streamWrite: nframes = " << nframes << endl; Chris@120: #endif Chris@120: Chris@117: if (!tmpbuf || tmpbufch != sourceChannels || int(tmpbufsz) < nframes) { Chris@117: Chris@117: if (tmpbuf) { Chris@366: for (int 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@366: for (int 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@436: sv_frame_t received = m_source->getSourceSamples(nframes, tmpbuf); Chris@117: Chris@195: #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY Chris@293: cerr << "requested " << nframes << ", received " << received << endl; Chris@120: Chris@119: if (received < nframes) { Chris@293: cerr << "*** WARNING: Wrong number of frames received" << endl; Chris@119: } Chris@120: #endif Chris@119: Chris@117: float peakLeft = 0.0, peakRight = 0.0; Chris@117: Chris@366: for (int ch = 0; ch < 2; ++ch) { Chris@117: Chris@117: float peak = 0.0; Chris@117: Chris@411: // PulseAudio samples are interleaved Chris@411: for (int i = 0; i < nframes; ++i) { Chris@411: if (i < received) { Chris@411: output[i * 2 + ch] = tmpbuf[ch][i] * m_outputGain; Chris@411: float sample = fabsf(output[i * 2 + ch]); Chris@411: if (sample > peak) peak = sample; Chris@411: } else { Chris@411: output[i * 2 + ch] = 0; Chris@411: } Chris@411: } Chris@117: Chris@117: if (ch == 0) peakLeft = peak; Chris@411: if (ch == 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@436: AudioPulseAudioTarget::streamStateChangedStatic(pa_stream *, Chris@117: void *data) Chris@117: { Chris@117: AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data; Chris@117: Chris@436: // 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@366: case PA_STREAM_UNCONNECTED: Chris@366: case PA_STREAM_CREATING: Chris@366: case PA_STREAM_TERMINATED: Chris@366: break; Chris@117: Chris@366: case PA_STREAM_READY: Chris@366: { Chris@366: SVDEBUG << "AudioPulseAudioTarget::streamStateChanged: Ready" << endl; Chris@366: Chris@366: pa_usec_t latency = 0; Chris@366: int negative = 0; Chris@366: if (pa_stream_get_latency(m_stream, &latency, &negative)) { Chris@366: cerr << "AudioPulseAudioTarget::streamStateChanged: Failed to query latency" << endl; Chris@366: } Chris@366: cerr << "Latency = " << latency << " usec" << endl; Chris@436: int latframes = int(double(latency) / 1000000.0 * m_sampleRate); Chris@366: cerr << "that's " << latframes << " frames" << endl; Chris@191: Chris@366: const pa_buffer_attr *attr; Chris@366: if (!(attr = pa_stream_get_buffer_attr(m_stream))) { Chris@366: SVDEBUG << "AudioPulseAudioTarget::streamStateChanged: Cannot query stream buffer attributes" << endl; Chris@366: m_source->setTarget(this, m_bufferSize); Chris@366: m_source->setTargetSampleRate(m_sampleRate); Chris@366: if (latframes != 0) m_source->setTargetPlayLatency(latframes); Chris@366: } else { Chris@366: int targetLength = attr->tlength; Chris@366: SVDEBUG << "AudioPulseAudioTarget::streamStateChanged: stream target length = " << targetLength << endl; Chris@366: m_source->setTarget(this, targetLength); Chris@366: m_source->setTargetSampleRate(m_sampleRate); Chris@366: if (latframes == 0) latframes = targetLength; Chris@366: cerr << "latency = " << latframes << endl; Chris@366: m_source->setTargetPlayLatency(latframes); Chris@191: } Chris@366: } Chris@366: break; Chris@366: Chris@366: case PA_STREAM_FAILED: Chris@366: default: Chris@366: cerr << "AudioPulseAudioTarget::streamStateChanged: Error: " Chris@366: << pa_strerror(pa_context_errno(m_context)) << endl; Chris@366: //!!! do something... Chris@366: break; Chris@117: } Chris@117: } Chris@117: Chris@117: void Chris@436: AudioPulseAudioTarget::contextStateChangedStatic(pa_context *, Chris@117: void *data) Chris@117: { Chris@117: AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data; Chris@117: Chris@436: // 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@366: case PA_CONTEXT_UNCONNECTED: 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@293: cerr << "AudioPulseAudioTarget: Failed to connect playback stream" << 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@293: cerr << "AudioPulseAudioTarget::contextStateChanged: Error: " Chris@293: << pa_strerror(pa_context_errno(m_context)) << 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: