Mercurial > hg > svapp
changeset 117:2bc8bf6d016c
* Provisional PulseAudio output driver. No latency handling yet, and
some other things missing. The very basic basics work.
author | Chris Cannam |
---|---|
date | Wed, 21 May 2008 16:54:24 +0000 |
parents | 9554c19c42fd |
children | c41e340dfe8d |
files | audioio/AudioJACKTarget.cpp audioio/AudioPulseAudioTarget.cpp audioio/AudioPulseAudioTarget.h audioio/AudioTargetFactory.cpp audioio/audioio.pro |
diffstat | 5 files changed, 466 insertions(+), 2 deletions(-) [+] |
line wrap: on
line diff
--- a/audioio/AudioJACKTarget.cpp Tue May 20 10:14:15 2008 +0000 +++ b/audioio/AudioJACKTarget.cpp Wed May 21 16:54:24 2008 +0000 @@ -219,7 +219,10 @@ m_done(false) { JackOptions options = JackNullOption; -#ifdef HAVE_PORTAUDIO +#ifdef HAVE_PORTAUDIO_2_0 + options = JackNoStartServer; +#endif +#ifdef HAVE_LIBPULSE options = JackNoStartServer; #endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/audioio/AudioPulseAudioTarget.cpp Wed May 21 16:54:24 2008 +0000 @@ -0,0 +1,360 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2008 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifdef HAVE_LIBPULSE + +#include "AudioPulseAudioTarget.h" +#include "AudioCallbackPlaySource.h" + +#include <QMutexLocker> + +#include <iostream> +#include <cassert> +#include <cmath> + +#define DEBUG_AUDIO_PULSE_AUDIO_TARGET 1 + +AudioPulseAudioTarget::AudioPulseAudioTarget(AudioCallbackPlaySource *source) : + AudioCallbackPlayTarget(source), + m_mutex(QMutex::Recursive), + m_loop(0), + m_api(0), + m_context(0), + m_stream(0), + m_loopThread(0), + m_bufferSize(0), + m_sampleRate(0), + m_latency(0), + m_done(false) +{ +#ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET + std::cerr << "AudioPulseAudioTarget: Initialising for PulseAudio" << std::endl; +#endif + + m_loop = pa_mainloop_new(); + if (!m_loop) { + std::cerr << "ERROR: AudioPulseAudioTarget: Failed to create main loop" << std::endl; + return; + } + + m_api = pa_mainloop_get_api(m_loop); + + //!!! handle signals how? + + m_bufferSize = 2048; + m_sampleRate = 44100; + if (m_source && (m_source->getSourceSampleRate() != 0)) { + m_sampleRate = m_source->getSourceSampleRate(); + } + m_spec.rate = m_sampleRate; + m_spec.channels = 2; + m_spec.format = PA_SAMPLE_FLOAT32NE; + + m_context = pa_context_new(m_api, source->getClientName().toLocal8Bit().data()); + if (!m_context) { + std::cerr << "ERROR: AudioPulseAudioTarget: Failed to create context object" << std::endl; + return; + } + + pa_context_set_state_callback(m_context, contextStateChangedStatic, this); + + pa_context_connect(m_context, 0, (pa_context_flags_t)0, 0); // default server + + m_loopThread = new MainLoopThread(m_loop); + m_loopThread->start(); + +#ifdef DEBUG_PULSE_AUDIO_TARGET + std::cerr << "AudioPulseAudioTarget: initialised OK" << std::endl; +#endif +} + +AudioPulseAudioTarget::~AudioPulseAudioTarget() +{ + std::cerr << "AudioPulseAudioTarget::~AudioPulseAudioTarget()" << std::endl; + + if (m_source) { + m_source->setTarget(0, m_bufferSize); + } + + shutdown(); + + QMutexLocker locker(&m_mutex); + + if (m_stream) pa_stream_unref(m_stream); + + if (m_context) pa_context_unref(m_context); + + if (m_loop) { + pa_signal_done(); + pa_mainloop_free(m_loop); + } + + m_stream = 0; + m_context = 0; + m_loop = 0; + + std::cerr << "AudioPulseAudioTarget::~AudioPulseAudioTarget() done" << std::endl; +} + +void +AudioPulseAudioTarget::shutdown() +{ + m_done = true; +} + +bool +AudioPulseAudioTarget::isOK() const +{ + return (m_context != 0); +} + +double +AudioPulseAudioTarget::getCurrentTime() const +{ + if (!m_stream) return 0.0; +//!!! else return Pa_GetStreamTime(m_stream); + + return 0.0;//!!! +} + +void +AudioPulseAudioTarget::sourceModelReplaced() +{ + m_source->setTargetSampleRate(m_sampleRate); +} + +void +AudioPulseAudioTarget::streamWriteStatic(pa_stream *stream, + size_t length, + void *data) +{ + AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data; + + assert(stream == target->m_stream); + + target->streamWrite(length); +} + +void +AudioPulseAudioTarget::streamWrite(size_t nframes) +{ +#ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET + std::cout << "AudioPulseAudioTarget::streamWrite(" << nframes << ")" << std::endl; +#endif + if (m_done) return; + + QMutexLocker locker(&m_mutex); + + if (nframes > m_bufferSize) { + std::cerr << "WARNING: AudioPulseAudioTarget::streamWrite: nframes " << nframes << " > m_bufferSize " << m_bufferSize << std::endl; + } + + static float *output = 0; + static float **tmpbuf = 0; + static size_t tmpbufch = 0; + static size_t tmpbufsz = 0; + + size_t sourceChannels = m_source->getSourceChannelCount(); + + // Because we offer pan, we always want at least 2 channels + if (sourceChannels < 2) sourceChannels = 2; + + if (!tmpbuf || tmpbufch != sourceChannels || int(tmpbufsz) < nframes) { + + if (tmpbuf) { + for (size_t i = 0; i < tmpbufch; ++i) { + delete[] tmpbuf[i]; + } + delete[] tmpbuf; + } + + if (output) { + delete[] output; + } + + tmpbufch = sourceChannels; + tmpbufsz = nframes; + tmpbuf = new float *[tmpbufch]; + + for (size_t i = 0; i < tmpbufch; ++i) { + tmpbuf[i] = new float[tmpbufsz]; + } + + output = new float[tmpbufsz * tmpbufch]; + } + + size_t received = m_source->getSourceSamples(nframes, tmpbuf); + + float peakLeft = 0.0, peakRight = 0.0; + + for (size_t ch = 0; ch < 2; ++ch) { + + float peak = 0.0; + + if (ch < sourceChannels) { + + // PulseAudio samples are interleaved + for (size_t i = 0; i < nframes; ++i) { + if (i < received) { + output[i * 2 + ch] = tmpbuf[ch][i] * m_outputGain; + float sample = fabsf(output[i * 2 + ch]); + if (sample > peak) peak = sample; + } else { + output[i * 2 + ch] = 0; + } + } + + } else if (ch == 1 && sourceChannels == 1) { + + for (size_t i = 0; i < nframes; ++i) { + if (i < received) { + output[i * 2 + ch] = tmpbuf[0][i] * m_outputGain; + float sample = fabsf(output[i * 2 + ch]); + if (sample > peak) peak = sample; + } else { + output[i * 2 + ch] = 0; + } + } + + } else { + for (size_t i = 0; i < nframes; ++i) { + output[i * 2 + ch] = 0; + } + } + + if (ch == 0) peakLeft = peak; + if (ch > 0 || sourceChannels == 1) peakRight = peak; + } + + pa_stream_write(m_stream, output, nframes * tmpbufch * sizeof(float), + 0, 0, PA_SEEK_RELATIVE); + + m_source->setOutputLevels(peakLeft, peakRight); + + return; +} + +void +AudioPulseAudioTarget::streamStateChangedStatic(pa_stream *stream, + void *data) +{ + AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data; + + assert(stream == target->m_stream); + + target->streamStateChanged(); +} + +void +AudioPulseAudioTarget::streamStateChanged() +{ +#ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET + std::cerr << "AudioPulseAudioTarget::streamStateChanged" << std::endl; +#endif + QMutexLocker locker(&m_mutex); + + switch (pa_stream_get_state(m_stream)) { + + case PA_STREAM_CREATING: + case PA_STREAM_TERMINATED: + break; + + case PA_STREAM_READY: + std::cerr << "AudioPulseAudioTarget::streamStateChanged: Ready" << std::endl; + break; + + case PA_STREAM_FAILED: + default: + std::cerr << "AudioPulseAudioTarget::streamStateChanged: Error: " + << pa_strerror(pa_context_errno(m_context)) << std::endl; + //!!! do something... + break; + } +} + +void +AudioPulseAudioTarget::contextStateChangedStatic(pa_context *context, + void *data) +{ + AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data; + + assert(context == target->m_context); + + target->contextStateChanged(); +} + +void +AudioPulseAudioTarget::contextStateChanged() +{ +#ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET + std::cerr << "AudioPulseAudioTarget::contextStateChanged" << std::endl; +#endif + QMutexLocker locker(&m_mutex); + + switch (pa_context_get_state(m_context)) { + + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + break; + + case PA_CONTEXT_READY: + std::cerr << "AudioPulseAudioTarget::contextStateChanged: Ready" + << std::endl; + + m_stream = pa_stream_new(m_context, "stream", &m_spec, 0); + assert(m_stream); //!!! + + pa_stream_set_state_callback(m_stream, streamStateChangedStatic, this); + pa_stream_set_write_callback(m_stream, streamWriteStatic, this); + + if (!pa_stream_connect_playback(m_stream, 0, 0, pa_stream_flags_t(0), 0, 0)) { + std::cerr << "AudioPulseAudioTarget: Failed to connect playback stream" << std::endl; + break; + } + + const pa_buffer_attr *attr; + if (!(attr = pa_stream_get_buffer_attr(m_stream))) { + std::cerr << "AudioPulseAudioTarget::contextStateChanged: Cannot query stream buffer attributes" << std::endl; + m_source->setTarget(this, 4096); + m_source->setTargetSampleRate(m_sampleRate); + m_source->setTargetPlayLatency(4096); + } else { + std::cerr << "AudioPulseAudioTarget::contextStateChanged: stream max length = " << attr->maxlength << std::endl; + int latency = attr->tlength; + std::cerr << "latency = " << latency << std::endl; + m_source->setTarget(this, attr->maxlength); + m_source->setTargetSampleRate(m_sampleRate); + m_source->setTargetPlayLatency(latency); + } + + break; + + case PA_CONTEXT_TERMINATED: + std::cerr << "AudioPulseAudioTarget::contextStateChanged: Terminated" << std::endl; + //!!! do something... + break; + + case PA_CONTEXT_FAILED: + default: + std::cerr << "AudioPulseAudioTarget::contextStateChanged: Error: " + << pa_strerror(pa_context_errno(m_context)) << std::endl; + //!!! do something... + break; + } +} + +#endif /* HAVE_PULSEAUDIO */ +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/audioio/AudioPulseAudioTarget.h Wed May 21 16:54:24 2008 +0000 @@ -0,0 +1,89 @@ +/* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */ + +/* + Sonic Visualiser + An audio file viewer and annotation editor. + Centre for Digital Music, Queen Mary, University of London. + This file copyright 2008 QMUL. + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of the + License, or (at your option) any later version. See the file + COPYING included with this distribution for more information. +*/ + +#ifndef _AUDIO_PULSE_AUDIO_TARGET_H_ +#define _AUDIO_PULSE_AUDIO_TARGET_H_ + +#ifdef HAVE_LIBPULSE + +#include <pulse/pulseaudio.h> + +#include <QObject> +#include <QMutex> +#include <QThread> + +#include "AudioCallbackPlayTarget.h" + +class AudioCallbackPlaySource; + +class AudioPulseAudioTarget : public AudioCallbackPlayTarget +{ + Q_OBJECT + +public: + AudioPulseAudioTarget(AudioCallbackPlaySource *source); + virtual ~AudioPulseAudioTarget(); + + virtual void shutdown(); + + virtual bool isOK() const; + + virtual double getCurrentTime() const; + +public slots: + virtual void sourceModelReplaced(); + +protected: + void streamWrite(size_t); + void streamStateChanged(); + void contextStateChanged(); + + static void streamWriteStatic(pa_stream *, size_t, void *); + static void streamStateChangedStatic(pa_stream *, void *); + static void contextStateChangedStatic(pa_context *, void *); + + QMutex m_mutex; + + class MainLoopThread : public QThread + { + public: + MainLoopThread(pa_mainloop *loop) : m_loop(loop) { } + virtual void run() { + int rv = 0; + pa_mainloop_run(m_loop, &rv); //!!! check return value from this, and rv + } + + private: + pa_mainloop *m_loop; + }; + + pa_mainloop *m_loop; + pa_mainloop_api *m_api; + pa_context *m_context; + pa_stream *m_stream; + pa_sample_spec m_spec; + + MainLoopThread *m_loopThread; + + int m_bufferSize; + int m_sampleRate; + int m_latency; + bool m_done; +}; + +#endif /* HAVE_PULSEAUDIO */ + +#endif +
--- a/audioio/AudioTargetFactory.cpp Tue May 20 10:14:15 2008 +0000 +++ b/audioio/AudioTargetFactory.cpp Wed May 21 16:54:24 2008 +0000 @@ -18,6 +18,7 @@ #include "AudioJACKTarget.h" #include "AudioCoreAudioTarget.h" #include "AudioPortAudioTarget.h" +#include "AudioPulseAudioTarget.h" #include <iostream> @@ -34,6 +35,15 @@ delete target; } #endif + +#ifdef HAVE_LIBPULSE + target = new AudioPulseAudioTarget(source); + if (target->isOK()) return target; + else { + std::cerr << "WARNING: AudioTargetFactory::createCallbackTarget: Failed to open PulseAudio target" << std::endl; + delete target; + } +#endif #ifdef HAVE_COREAUDIO target = new AudioCoreAudioTarget(source);
--- a/audioio/audioio.pro Tue May 20 10:14:15 2008 +0000 +++ b/audioio/audioio.pro Wed May 21 16:54:24 2008 +0000 @@ -1,6 +1,6 @@ TEMPLATE = lib -SV_UNIT_PACKAGES = fftw3f samplerate jack portaudio-2.0 rubberband +SV_UNIT_PACKAGES = fftw3f samplerate jack portaudio-2.0 libpulse rubberband load(../sv.prf) CONFIG += sv staticlib qt thread warn_on stl rtti exceptions @@ -19,6 +19,7 @@ AudioGenerator.h \ AudioJACKTarget.h \ AudioPortAudioTarget.h \ + AudioPulseAudioTarget.h \ AudioTargetFactory.h \ PlaySpeedRangeMapper.h SOURCES += AudioCallbackPlaySource.cpp \ @@ -27,5 +28,6 @@ AudioGenerator.cpp \ AudioJACKTarget.cpp \ AudioPortAudioTarget.cpp \ + AudioPulseAudioTarget.cpp \ AudioTargetFactory.cpp \ PlaySpeedRangeMapper.cpp