Mercurial > hg > svapp
view audioio/AudioPulseAudioTarget.cpp @ 304:c837368b1faf
Remove OSC option from constructor -- call startOSCQueue() after construction if you want it.
This needs to be handled this way in SV because we need to ask for network permission before opening the OSC port (that's the thing that gets the app blocked by e.g. Windows Firewall).
author | Chris Cannam |
---|---|
date | Thu, 12 Dec 2013 15:20:14 +0000 |
parents | 068235cf5bf7 |
children | 0876ea394902 |
line wrap: on
line source
/* -*- 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 //#define DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY 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 cerr << "AudioPulseAudioTarget: Initialising for PulseAudio" << endl; #endif m_loop = pa_mainloop_new(); if (!m_loop) { cerr << "ERROR: AudioPulseAudioTarget: Failed to create main loop" << endl; return; } m_api = pa_mainloop_get_api(m_loop); //!!! handle signals how? m_bufferSize = 20480; 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; #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET cerr << "AudioPulseAudioTarget: Creating context" << endl; #endif m_context = pa_context_new(m_api, source->getClientName().toLocal8Bit().data()); if (!m_context) { cerr << "ERROR: AudioPulseAudioTarget: Failed to create context object" << endl; return; } pa_context_set_state_callback(m_context, contextStateChangedStatic, this); #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET cerr << "AudioPulseAudioTarget: Connecting to default server..." << endl; #endif pa_context_connect(m_context, 0, // default server (pa_context_flags_t)PA_CONTEXT_NOAUTOSPAWN, 0); #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET cerr << "AudioPulseAudioTarget: Starting main loop" << endl; #endif m_loopThread = new MainLoopThread(m_loop); m_loopThread->start(); #ifdef DEBUG_PULSE_AUDIO_TARGET cerr << "AudioPulseAudioTarget: initialised OK" << endl; #endif } AudioPulseAudioTarget::~AudioPulseAudioTarget() { SVDEBUG << "AudioPulseAudioTarget::~AudioPulseAudioTarget()" << 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; SVDEBUG << "AudioPulseAudioTarget::~AudioPulseAudioTarget() done" << 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; pa_usec_t usec = 0; pa_stream_get_time(m_stream, &usec); return usec / 1000000.f; } 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 requested) { #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY cout << "AudioPulseAudioTarget::streamWrite(" << requested << ")" << endl; #endif if (m_done) return; QMutexLocker locker(&m_mutex); pa_usec_t latency = 0; int negative = 0; if (!pa_stream_get_latency(m_stream, &latency, &negative)) { int latframes = (latency / 1000000.f) * float(m_sampleRate); if (latframes > 0) m_source->setTargetPlayLatency(latframes); } 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; size_t nframes = requested / (sourceChannels * sizeof(float)); if (nframes > m_bufferSize) { cerr << "WARNING: AudioPulseAudioTarget::streamWrite: nframes " << nframes << " > m_bufferSize " << m_bufferSize << endl; } #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY cout << "AudioPulseAudioTarget::streamWrite: nframes = " << nframes << endl; #endif 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); #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY cerr << "requested " << nframes << ", received " << received << endl; if (received < nframes) { cerr << "*** WARNING: Wrong number of frames received" << endl; } #endif 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; } #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY SVDEBUG << "calling pa_stream_write with " << nframes * tmpbufch * sizeof(float) << " bytes" << endl; #endif 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 SVDEBUG << "AudioPulseAudioTarget::streamStateChanged" << 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: { SVDEBUG << "AudioPulseAudioTarget::streamStateChanged: Ready" << endl; pa_usec_t latency = 0; int negative = 0; if (pa_stream_get_latency(m_stream, &latency, &negative)) { cerr << "AudioPulseAudioTarget::streamStateChanged: Failed to query latency" << endl; } cerr << "Latency = " << latency << " usec" << endl; int latframes = (latency / 1000000.f) * float(m_sampleRate); cerr << "that's " << latframes << " frames" << endl; const pa_buffer_attr *attr; if (!(attr = pa_stream_get_buffer_attr(m_stream))) { SVDEBUG << "AudioPulseAudioTarget::streamStateChanged: Cannot query stream buffer attributes" << endl; m_source->setTarget(this, m_bufferSize); m_source->setTargetSampleRate(m_sampleRate); if (latframes != 0) m_source->setTargetPlayLatency(latframes); } else { int targetLength = attr->tlength; SVDEBUG << "AudioPulseAudioTarget::streamStateChanged: stream target length = " << targetLength << endl; m_source->setTarget(this, targetLength); m_source->setTargetSampleRate(m_sampleRate); if (latframes == 0) latframes = targetLength; cerr << "latency = " << latframes << endl; m_source->setTargetPlayLatency(latframes); } } break; case PA_STREAM_FAILED: default: cerr << "AudioPulseAudioTarget::streamStateChanged: Error: " << pa_strerror(pa_context_errno(m_context)) << 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 SVDEBUG << "AudioPulseAudioTarget::contextStateChanged" << 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: SVDEBUG << "AudioPulseAudioTarget::contextStateChanged: Ready" << 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); pa_stream_set_overflow_callback(m_stream, streamOverflowStatic, this); pa_stream_set_underflow_callback(m_stream, streamUnderflowStatic, this); if (pa_stream_connect_playback (m_stream, 0, 0, pa_stream_flags_t(PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_AUTO_TIMING_UPDATE), 0, 0)) { //??? return value cerr << "AudioPulseAudioTarget: Failed to connect playback stream" << endl; } break; case PA_CONTEXT_TERMINATED: SVDEBUG << "AudioPulseAudioTarget::contextStateChanged: Terminated" << endl; //!!! do something... break; case PA_CONTEXT_FAILED: default: cerr << "AudioPulseAudioTarget::contextStateChanged: Error: " << pa_strerror(pa_context_errno(m_context)) << endl; //!!! do something... break; } } void AudioPulseAudioTarget::streamOverflowStatic(pa_stream *, void *) { SVDEBUG << "AudioPulseAudioTarget::streamOverflowStatic: Overflow!" << endl; } void AudioPulseAudioTarget::streamUnderflowStatic(pa_stream *, void *data) { SVDEBUG << "AudioPulseAudioTarget::streamUnderflowStatic: Underflow!" << endl; AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data; if (target && target->m_source) { target->m_source->audioProcessingOverload(); } } #endif /* HAVE_PULSEAUDIO */