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@117: #define DEBUG_AUDIO_PULSE_AUDIO_TARGET 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@117: m_bufferSize = 2048; 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@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@117: pa_context_connect(m_context, 0, (pa_context_flags_t)0, 0); // default server 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@117: std::cerr << "AudioPulseAudioTarget::~AudioPulseAudioTarget()" << std::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@117: std::cerr << "AudioPulseAudioTarget::~AudioPulseAudioTarget() done" << std::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@117: //!!! else return Pa_GetStreamTime(m_stream); Chris@117: Chris@117: return 0.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@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@117: AudioPulseAudioTarget::streamWrite(size_t nframes) Chris@117: { Chris@117: #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET Chris@117: std::cout << "AudioPulseAudioTarget::streamWrite(" << nframes << ")" << std::endl; Chris@117: #endif Chris@117: if (m_done) return; Chris@117: Chris@117: QMutexLocker locker(&m_mutex); Chris@117: Chris@117: if (nframes > m_bufferSize) { Chris@117: std::cerr << "WARNING: AudioPulseAudioTarget::streamWrite: nframes " << nframes << " > m_bufferSize " << m_bufferSize << std::endl; Chris@117: } Chris@117: 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@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@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@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@117: std::cerr << "AudioPulseAudioTarget::streamStateChanged" << std::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@117: std::cerr << "AudioPulseAudioTarget::streamStateChanged: Ready" << std::endl; 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@117: std::cerr << "AudioPulseAudioTarget::contextStateChanged" << std::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@117: std::cerr << "AudioPulseAudioTarget::contextStateChanged: Ready" Chris@117: << std::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@117: Chris@117: if (!pa_stream_connect_playback(m_stream, 0, 0, pa_stream_flags_t(0), 0, 0)) { Chris@117: std::cerr << "AudioPulseAudioTarget: Failed to connect playback stream" << std::endl; Chris@117: break; Chris@117: } Chris@117: Chris@117: const pa_buffer_attr *attr; Chris@117: if (!(attr = pa_stream_get_buffer_attr(m_stream))) { Chris@117: std::cerr << "AudioPulseAudioTarget::contextStateChanged: Cannot query stream buffer attributes" << std::endl; Chris@117: m_source->setTarget(this, 4096); Chris@117: m_source->setTargetSampleRate(m_sampleRate); Chris@117: m_source->setTargetPlayLatency(4096); Chris@117: } else { Chris@117: std::cerr << "AudioPulseAudioTarget::contextStateChanged: stream max length = " << attr->maxlength << std::endl; Chris@117: int latency = attr->tlength; Chris@117: std::cerr << "latency = " << latency << std::endl; Chris@117: m_source->setTarget(this, attr->maxlength); Chris@117: m_source->setTargetSampleRate(m_sampleRate); Chris@117: m_source->setTargetPlayLatency(latency); Chris@117: } Chris@117: Chris@117: break; Chris@117: Chris@117: case PA_CONTEXT_TERMINATED: Chris@117: std::cerr << "AudioPulseAudioTarget::contextStateChanged: Terminated" << std::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@117: #endif /* HAVE_PULSEAUDIO */ Chris@117: