/* -*- 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 #include #include #include //#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 = 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; 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; 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 std::cout << "AudioPulseAudioTarget::streamWrite(" << requested << ")" << std::endl; #endif if (m_done) return; QMutexLocker locker(&m_mutex); if (m_source->getTargetPlayLatency() == 0) { //!!! need better test //!!! pa_usec_t latency = 0; int negative = 0; if (pa_stream_get_latency(m_stream, &latency, &negative)) { std::cerr << "AudioPulseAudioTarget::contextStateChanged: Failed to query latency" << std::endl; } // std::cerr << "Latency = " << latency << " usec" << std::endl; int latframes = (latency / 1000000.f) * float(m_sampleRate); // std::cerr << "that's " << latframes << " frames" << std::endl; m_source->setTargetPlayLatency(latframes); //!!! buh } 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) { std::cerr << "WARNING: AudioPulseAudioTarget::streamWrite: nframes " << nframes << " > m_bufferSize " << m_bufferSize << std::endl; } #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET std::cout << "AudioPulseAudioTarget::streamWrite: nframes = " << nframes << std::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 std::cerr << "requested " << nframes << ", received " << received << std::endl; if (received < nframes) { std::cerr << "*** WARNING: Wrong number of frames received" << std::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 std::cerr << "calling pa_stream_write with " << nframes * tmpbufch * sizeof(float) << " bytes" << std::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 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); 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 std::cerr << "AudioPulseAudioTarget: Failed to connect playback stream" << std::endl; } pa_usec_t latency = 0; int negative = 0; if (pa_stream_get_latency(m_stream, &latency, &negative)) { std::cerr << "AudioPulseAudioTarget::contextStateChanged: Failed to query latency" << std::endl; } std::cerr << "Latency = " << latency << " usec" << std::endl; int latframes = (latency / 1000000.f) * float(m_sampleRate); std::cerr << "that's " << latframes << " frames" << std::endl; 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(latframes); } 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(latframes); } 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; } } void AudioPulseAudioTarget::streamOverflowStatic(pa_stream *, void *) { std::cerr << "AudioPulseAudioTarget::streamOverflowStatic: Overflow!" << std::endl; } void AudioPulseAudioTarget::streamUnderflowStatic(pa_stream *, void *data) { std::cerr << "AudioPulseAudioTarget::streamUnderflowStatic: Underflow!" << std::endl; AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data; if (target && target->m_source) { target->m_source->audioProcessingOverload(); } } #endif /* HAVE_PULSEAUDIO */