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@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@118: m_bufferSize = 20480; 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@195: #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET Chris@195: std::cerr << "AudioPulseAudioTarget: Creating context" << std::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@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@195: #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET Chris@195: std::cerr << "AudioPulseAudioTarget: Connecting to default server..." << std::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@195: std::cerr << "AudioPulseAudioTarget: Starting main loop" << std::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@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@118: Chris@118: pa_usec_t usec = 0; Chris@118: pa_stream_get_time(m_stream, &usec); Chris@118: return usec / 1000000.f; 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@120: AudioPulseAudioTarget::streamWrite(size_t requested) Chris@117: { Chris@195: #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY Chris@120: std::cout << "AudioPulseAudioTarget::streamWrite(" << requested << ")" << std::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@193: int latframes = (latency / 1000000.f) * float(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@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@120: size_t nframes = requested / (sourceChannels * sizeof(float)); Chris@120: Chris@120: if (nframes > m_bufferSize) { Chris@120: std::cerr << "WARNING: AudioPulseAudioTarget::streamWrite: nframes " << nframes << " > m_bufferSize " << m_bufferSize << std::endl; Chris@120: } Chris@120: Chris@195: #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY Chris@120: std::cout << "AudioPulseAudioTarget::streamWrite: nframes = " << nframes << std::endl; Chris@120: #endif Chris@120: 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@195: #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY Chris@119: std::cerr << "requested " << nframes << ", received " << received << std::endl; Chris@120: Chris@119: if (received < nframes) { Chris@119: std::cerr << "*** WARNING: Wrong number of frames received" << std::endl; Chris@119: } Chris@120: #endif Chris@119: 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@195: #ifdef DEBUG_AUDIO_PULSE_AUDIO_TARGET_PLAY Chris@120: std::cerr << "calling pa_stream_write with " Chris@120: << nframes * tmpbufch * sizeof(float) << " bytes" << std::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@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@191: { Chris@117: std::cerr << "AudioPulseAudioTarget::streamStateChanged: Ready" << std::endl; Chris@191: Chris@191: pa_usec_t latency = 0; Chris@191: int negative = 0; Chris@191: if (pa_stream_get_latency(m_stream, &latency, &negative)) { Chris@191: std::cerr << "AudioPulseAudioTarget::streamStateChanged: Failed to query latency" << std::endl; Chris@191: } Chris@191: std::cerr << "Latency = " << latency << " usec" << std::endl; Chris@191: int latframes = (latency / 1000000.f) * float(m_sampleRate); Chris@191: std::cerr << "that's " << latframes << " frames" << std::endl; Chris@191: Chris@191: const pa_buffer_attr *attr; Chris@191: if (!(attr = pa_stream_get_buffer_attr(m_stream))) { Chris@191: std::cerr << "AudioPulseAudioTarget::streamStateChanged: Cannot query stream buffer attributes" << std::endl; Chris@191: m_source->setTarget(this, m_bufferSize); Chris@191: m_source->setTargetSampleRate(m_sampleRate); Chris@193: if (latframes != 0) m_source->setTargetPlayLatency(latframes); Chris@191: } else { Chris@193: int targetLength = attr->tlength; Chris@193: std::cerr << "AudioPulseAudioTarget::streamStateChanged: stream target length = " << targetLength << std::endl; Chris@193: m_source->setTarget(this, targetLength); Chris@191: m_source->setTargetSampleRate(m_sampleRate); Chris@193: if (latframes == 0) latframes = targetLength; Chris@193: std::cerr << "latency = " << latframes << std::endl; Chris@191: m_source->setTargetPlayLatency(latframes); Chris@191: } Chris@191: } 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@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@117: std::cerr << "AudioPulseAudioTarget: Failed to connect playback stream" << std::endl; 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@120: void Chris@120: AudioPulseAudioTarget::streamOverflowStatic(pa_stream *, void *) Chris@120: { Chris@120: std::cerr << "AudioPulseAudioTarget::streamOverflowStatic: Overflow!" << std::endl; Chris@120: } Chris@120: Chris@120: void Chris@130: AudioPulseAudioTarget::streamUnderflowStatic(pa_stream *, void *data) Chris@120: { Chris@120: std::cerr << "AudioPulseAudioTarget::streamUnderflowStatic: Underflow!" << std::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: