view audioio/AudioPulseAudioTarget.cpp @ 451:dc1a360f2b69

Allow layers to be loaded without models if their layer class explicitly says it's OK (otherwise default template won't load, as it has an empty waveform layer)
author Chris Cannam
date Mon, 20 Apr 2015 10:10:26 +0100
parents 72c662fe7ea3
children ad998a2fe9e2
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 = int(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 double(usec) / 1000000.0;
}

void
AudioPulseAudioTarget::sourceModelReplaced()
{
    m_source->setTargetSampleRate(m_sampleRate);
}

void
AudioPulseAudioTarget::streamWriteStatic(pa_stream *,
                                         size_t length,
                                         void *data)
{
    AudioPulseAudioTarget *target = (AudioPulseAudioTarget *)data;
    
//    assert(stream == target->m_stream);

    target->streamWrite(length);
}

void
AudioPulseAudioTarget::streamWrite(sv_frame_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 = int(double(latency) / 1000000.0 * double(m_sampleRate));
        if (latframes > 0) m_source->setTargetPlayLatency(latframes);
    }

    static float *output = 0;
    static float **tmpbuf = 0;
    static int tmpbufch = 0;
    static sv_frame_t tmpbufsz = 0;

    int sourceChannels = m_source->getSourceChannelCount();

    // Because we offer pan, we always want at least 2 channels
    if (sourceChannels < 2) sourceChannels = 2;

    sv_frame_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 (int i = 0; i < tmpbufch; ++i) {
		delete[] tmpbuf[i];
	    }
	    delete[] tmpbuf;
	}

        if (output) {
            delete[] output;
        }

	tmpbufch = sourceChannels;
	tmpbufsz = nframes;
	tmpbuf = new float *[tmpbufch];

	for (int i = 0; i < tmpbufch; ++i) {
	    tmpbuf[i] = new float[tmpbufsz];
	}

        output = new float[tmpbufsz * tmpbufch];
    }
	
    sv_frame_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 (int ch = 0; ch < 2; ++ch) {
	
	float peak = 0.0;

        // PulseAudio samples are interleaved
        for (int 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;
            }
        }

	if (ch == 0) peakLeft = peak;
	if (ch == 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 *,
                                                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_UNCONNECTED:
    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 = int(double(latency) / 1000000.0 * 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 *,
                                                 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_UNCONNECTED:
        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 */