Since sound effects are something short and mixed with other audio, do not acquire resources explicitly. Follow the resources availability information to determine when it is ok to play the sound effects. When client has registered itself to resource manager, client's streams are classified properly. If no higher priority client has acquired the resources, isAvailable() is true, and sound effects can be played. We do not explicitly acquire the resources, since then other clients with the same resource class would lose the resources, thus not possible to have second client play music with QMediaPlayer class while our client would just want to play simple sound effects. Change-Id: Ib5589349dca6900a8bee616b8ad77e7cb5ec9533 Done-with: Juho Hämäläinen <juho.hamalainen@tieto.com> Reviewed-by: Andrew den Exter <andrew.den.exter@qinetic.com.au>
1223 lines
36 KiB
C++
1223 lines
36 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
|
|
** Contact: http://www.qt-project.org/legal
|
|
**
|
|
** This file is part of the Qt Toolkit.
|
|
**
|
|
** $QT_BEGIN_LICENSE:LGPL$
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and Digia. For licensing terms and
|
|
** conditions see http://qt.digia.com/licensing. For further information
|
|
** use the contact form at http://qt.digia.com/contact-us.
|
|
**
|
|
** GNU Lesser General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
** General Public License version 2.1 as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.LGPL included in the
|
|
** packaging of this file. Please review the following information to
|
|
** ensure the GNU Lesser General Public License version 2.1 requirements
|
|
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|
**
|
|
** In addition, as a special exception, Digia gives you certain additional
|
|
** rights. These rights are described in the Digia Qt LGPL Exception
|
|
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 3.0 as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.GPL included in the
|
|
** packaging of this file. Please review the following information to
|
|
** ensure the GNU General Public License version 3.0 requirements will be
|
|
** met: http://www.gnu.org/copyleft/gpl.html.
|
|
**
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
//
|
|
// W A R N I N G
|
|
// -------------
|
|
//
|
|
// This file is not part of the Qt API. It exists purely as an
|
|
// implementation detail. This header file may change from version to
|
|
// version without notice, or even be removed.
|
|
//
|
|
// INTERNAL USE ONLY: Do NOT use for any other purpose.
|
|
//
|
|
|
|
#include <QtCore/qcoreapplication.h>
|
|
#include <qaudioformat.h>
|
|
#include <QtNetwork>
|
|
#include <QTime>
|
|
|
|
#include "qsoundeffect_pulse_p.h"
|
|
|
|
#if defined(Q_WS_MAEMO_6)
|
|
#include <pulse/ext-stream-restore.h>
|
|
#endif
|
|
|
|
#include <private/qmediaresourcepolicy_p.h>
|
|
#include <private/qmediaresourceset_p.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
//#define QT_PA_DEBUG
|
|
#ifndef QTM_PULSEAUDIO_DEFAULTBUFFER
|
|
#define QT_PA_STREAM_BUFFER_SIZE_MAX (1024 * 64) //64KB is a trade-off for balancing control latency and uploading overhead
|
|
#endif
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
namespace
|
|
{
|
|
inline pa_sample_spec audioFormatToSampleSpec(const QAudioFormat &format)
|
|
{
|
|
pa_sample_spec spec;
|
|
|
|
spec.rate = format.sampleRate();
|
|
spec.channels = format.channelCount();
|
|
|
|
if (format.sampleSize() == 8)
|
|
spec.format = PA_SAMPLE_U8;
|
|
else if (format.sampleSize() == 16) {
|
|
switch (format.byteOrder()) {
|
|
case QAudioFormat::BigEndian: spec.format = PA_SAMPLE_S16BE; break;
|
|
case QAudioFormat::LittleEndian: spec.format = PA_SAMPLE_S16LE; break;
|
|
}
|
|
}
|
|
else if (format.sampleSize() == 32) {
|
|
switch (format.byteOrder()) {
|
|
case QAudioFormat::BigEndian: spec.format = PA_SAMPLE_S32BE; break;
|
|
case QAudioFormat::LittleEndian: spec.format = PA_SAMPLE_S32LE; break;
|
|
}
|
|
}
|
|
|
|
return spec;
|
|
}
|
|
|
|
class PulseDaemon : public QObject
|
|
{
|
|
Q_OBJECT
|
|
public:
|
|
PulseDaemon(): m_prepared(false)
|
|
{
|
|
prepare();
|
|
}
|
|
|
|
~PulseDaemon()
|
|
{
|
|
if (m_prepared)
|
|
release();
|
|
}
|
|
|
|
inline void lock()
|
|
{
|
|
if (m_mainLoop)
|
|
pa_threaded_mainloop_lock(m_mainLoop);
|
|
}
|
|
|
|
inline void unlock()
|
|
{
|
|
if (m_mainLoop)
|
|
pa_threaded_mainloop_unlock(m_mainLoop);
|
|
}
|
|
|
|
inline pa_context *context() const
|
|
{
|
|
return m_context;
|
|
}
|
|
|
|
inline pa_cvolume * calcVolume(pa_cvolume *dest, int soundEffectVolume)
|
|
{
|
|
pa_volume_t v = m_vol * soundEffectVolume / 100;
|
|
for (int i = 0; i < dest->channels; ++i)
|
|
dest->values[i] = v;
|
|
return dest;
|
|
}
|
|
|
|
void updateStatus(const pa_cvolume& volume)
|
|
{
|
|
if (m_vol != pa_cvolume_max(&volume)) {
|
|
m_vol = pa_cvolume_max(&volume);
|
|
emit volumeChanged();
|
|
}
|
|
}
|
|
|
|
Q_SIGNALS:
|
|
void contextReady();
|
|
void contextFailed();
|
|
void volumeChanged();
|
|
|
|
private Q_SLOTS:
|
|
void onContextFailed()
|
|
{
|
|
release();
|
|
|
|
// Try to reconnect later
|
|
QTimer::singleShot(30000, this, SLOT(prepare()));
|
|
|
|
emit contextFailed();
|
|
}
|
|
|
|
void prepare()
|
|
{
|
|
m_vol = PA_VOLUME_NORM;
|
|
|
|
m_context = 0;
|
|
m_mainLoop = pa_threaded_mainloop_new();
|
|
if (m_mainLoop == 0) {
|
|
qWarning("PulseAudioService: unable to create pulseaudio mainloop");
|
|
return;
|
|
}
|
|
|
|
if (pa_threaded_mainloop_start(m_mainLoop) != 0) {
|
|
qWarning("PulseAudioService: unable to start pulseaudio mainloop");
|
|
pa_threaded_mainloop_free(m_mainLoop);
|
|
return;
|
|
}
|
|
|
|
m_mainLoopApi = pa_threaded_mainloop_get_api(m_mainLoop);
|
|
|
|
lock();
|
|
m_context = pa_context_new(m_mainLoopApi, QString(QLatin1String("QtPulseAudio:%1")).arg(::getpid()).toLatin1().constData());
|
|
|
|
if (m_context == 0) {
|
|
qWarning("PulseAudioService: Unable to create new pulseaudio context");
|
|
pa_threaded_mainloop_unlock(m_mainLoop);
|
|
pa_threaded_mainloop_free(m_mainLoop);
|
|
m_mainLoop = 0;
|
|
onContextFailed();
|
|
return;
|
|
}
|
|
|
|
pa_context_set_state_callback(m_context, context_state_callback, this);
|
|
|
|
if (pa_context_connect(m_context, 0, (pa_context_flags_t)0, 0) < 0) {
|
|
qWarning("PulseAudioService: pa_context_connect() failed");
|
|
pa_context_unref(m_context);
|
|
pa_threaded_mainloop_unlock(m_mainLoop);
|
|
pa_threaded_mainloop_free(m_mainLoop);
|
|
m_mainLoop = 0;
|
|
m_context = 0;
|
|
return;
|
|
}
|
|
unlock();
|
|
|
|
m_prepared = true;
|
|
}
|
|
|
|
private:
|
|
void release()
|
|
{
|
|
if (!m_prepared)
|
|
return;
|
|
|
|
if (m_context) {
|
|
pa_context_unref(m_context);
|
|
m_context = 0;
|
|
}
|
|
|
|
if (m_mainLoop) {
|
|
pa_threaded_mainloop_stop(m_mainLoop);
|
|
pa_threaded_mainloop_free(m_mainLoop);
|
|
m_mainLoop = 0;
|
|
}
|
|
|
|
m_prepared = false;
|
|
}
|
|
|
|
static void context_state_callback(pa_context *c, void *userdata)
|
|
{
|
|
PulseDaemon *self = reinterpret_cast<PulseDaemon*>(userdata);
|
|
switch (pa_context_get_state(c)) {
|
|
case PA_CONTEXT_CONNECTING:
|
|
case PA_CONTEXT_AUTHORIZING:
|
|
case PA_CONTEXT_SETTING_NAME:
|
|
break;
|
|
case PA_CONTEXT_READY:
|
|
#if defined(Q_WS_MAEMO_6)
|
|
pa_ext_stream_restore_read(c, &stream_restore_info_callback, self);
|
|
pa_ext_stream_restore_set_subscribe_cb(c, &stream_restore_monitor_callback, self);
|
|
pa_ext_stream_restore_subscribe(c, 1, 0, self);
|
|
#endif
|
|
QMetaObject::invokeMethod(self, "contextReady", Qt::QueuedConnection);
|
|
break;
|
|
case PA_CONTEXT_FAILED:
|
|
QMetaObject::invokeMethod(self, "onContextFailed", Qt::QueuedConnection);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
#if defined(Q_WS_MAEMO_6)
|
|
|
|
static void stream_restore_monitor_callback(pa_context *c, void *userdata)
|
|
{
|
|
PulseDaemon *self = reinterpret_cast<PulseDaemon*>(userdata);
|
|
pa_ext_stream_restore_read(c, &stream_restore_info_callback, self);
|
|
}
|
|
|
|
static void stream_restore_info_callback(pa_context *c,
|
|
const pa_ext_stream_restore_info *info,
|
|
int eol, void *userdata)
|
|
{
|
|
Q_UNUSED(c)
|
|
|
|
PulseDaemon *self = reinterpret_cast<PulseDaemon*>(userdata);
|
|
|
|
if (!eol) {
|
|
if (QString(info->name).startsWith(QLatin1String("sink-input-by-media-role:x-maemo"))) {
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << "x-maemo volume =(" << info->volume.values[0] * 100 / PA_VOLUME_NORM << ","
|
|
<< info->volume.values[1] * 100 / PA_VOLUME_NORM << "), "
|
|
<< "mute = " << info->mute;
|
|
#endif
|
|
self->updateStatus(info->volume);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
pa_volume_t m_vol;
|
|
|
|
bool m_prepared;
|
|
pa_context *m_context;
|
|
pa_threaded_mainloop *m_mainLoop;
|
|
pa_mainloop_api *m_mainLoopApi;
|
|
};
|
|
|
|
}
|
|
|
|
Q_GLOBAL_STATIC(PulseDaemon, pulseDaemon)
|
|
Q_GLOBAL_STATIC(QSampleCache, sampleCache)
|
|
|
|
namespace
|
|
{
|
|
class PulseDaemonLocker
|
|
{
|
|
public:
|
|
PulseDaemonLocker()
|
|
{
|
|
pulseDaemon()->lock();
|
|
}
|
|
|
|
~PulseDaemonLocker()
|
|
{
|
|
pulseDaemon()->unlock();
|
|
}
|
|
};
|
|
}
|
|
|
|
class QSoundEffectRef
|
|
{
|
|
public:
|
|
QSoundEffectRef(QSoundEffectPrivate *target)
|
|
: m_ref(1)
|
|
, m_target(target)
|
|
{
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << "QSoundEffectRef(" << this << ") ctor";
|
|
#endif
|
|
}
|
|
|
|
QSoundEffectRef *getRef()
|
|
{
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << "QSoundEffectRef(" << this << ") getRef";
|
|
#endif
|
|
QMutexLocker locker(&m_mutex);
|
|
m_ref++;
|
|
return this;
|
|
}
|
|
|
|
void release()
|
|
{
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << "QSoundEffectRef(" << this << ") Release";
|
|
#endif
|
|
m_mutex.lock();
|
|
--m_ref;
|
|
if (m_ref == 0) {
|
|
m_mutex.unlock();
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << "QSoundEffectRef(" << this << ") deleted";
|
|
#endif
|
|
delete this;
|
|
return;
|
|
}
|
|
m_mutex.unlock();
|
|
}
|
|
|
|
QSoundEffectPrivate* soundEffect() const
|
|
{
|
|
QMutexLocker locker(&m_mutex);
|
|
return m_target;
|
|
}
|
|
|
|
void notifyDeleted()
|
|
{
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << "QSoundEffectRef(" << this << ") notifyDeleted";
|
|
#endif
|
|
QMutexLocker locker(&m_mutex);
|
|
m_target = NULL;
|
|
}
|
|
|
|
private:
|
|
int m_ref;
|
|
mutable QMutex m_mutex;
|
|
QSoundEffectPrivate *m_target;
|
|
};
|
|
|
|
QSoundEffectPrivate::QSoundEffectPrivate(QObject* parent):
|
|
QObject(parent),
|
|
m_pulseStream(0),
|
|
m_sinkInputId(-1),
|
|
m_emptying(false),
|
|
m_sampleReady(false),
|
|
m_playing(false),
|
|
m_status(QSoundEffect::Null),
|
|
m_muted(false),
|
|
m_playQueued(false),
|
|
m_stopping(false),
|
|
m_volume(1.0),
|
|
m_loopCount(1),
|
|
m_runningCount(0),
|
|
m_reloadCategory(false),
|
|
m_sample(0),
|
|
m_position(0),
|
|
m_resourcesAvailable(false)
|
|
{
|
|
m_ref = new QSoundEffectRef(this);
|
|
pa_sample_spec_init(&m_pulseSpec);
|
|
|
|
m_resources = QMediaResourcePolicy::createResourceSet<QMediaPlayerResourceSetInterface>();
|
|
Q_ASSERT(m_resources);
|
|
m_resourcesAvailable = m_resources->isAvailable();
|
|
connect(m_resources, SIGNAL(availabilityChanged(bool)), SLOT(handleAvailabilityChanged(bool)));
|
|
}
|
|
|
|
void QSoundEffectPrivate::handleAvailabilityChanged(bool available)
|
|
{
|
|
m_resourcesAvailable = available;
|
|
#ifdef DEBUG_RESOURCE
|
|
qDebug() << Q_FUNC_INFO << "Resource availability changed " << m_resourcesAvailable;
|
|
#endif
|
|
if (!m_resourcesAvailable)
|
|
stop();
|
|
}
|
|
|
|
void QSoundEffectPrivate::release()
|
|
{
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << this << "release";
|
|
#endif
|
|
m_ref->notifyDeleted();
|
|
unloadPulseStream();
|
|
if (m_sample) {
|
|
m_sample->release();
|
|
m_sample = 0;
|
|
}
|
|
|
|
this->deleteLater();
|
|
}
|
|
|
|
QString QSoundEffectPrivate::category() const
|
|
{
|
|
return m_category;
|
|
}
|
|
|
|
void QSoundEffectPrivate::setCategory(const QString &category)
|
|
{
|
|
if (m_category != category) {
|
|
m_category = category;
|
|
if (m_playing || m_playQueued) {
|
|
// Currently playing, we need to disconnect when
|
|
// playback stops
|
|
m_reloadCategory = true;
|
|
} else if (m_pulseStream) {
|
|
// We have to disconnect and reconnect
|
|
unloadPulseStream();
|
|
createPulseStream();
|
|
} else {
|
|
// Well, next time we create the pulse stream
|
|
// it should be set
|
|
}
|
|
|
|
emit categoryChanged();
|
|
}
|
|
}
|
|
|
|
QSoundEffectPrivate::~QSoundEffectPrivate()
|
|
{
|
|
QMediaResourcePolicy::destroyResourceSet(m_resources);
|
|
m_resources = 0;
|
|
m_ref->release();
|
|
}
|
|
|
|
QStringList QSoundEffectPrivate::supportedMimeTypes()
|
|
{
|
|
QStringList supportedTypes;
|
|
supportedTypes << QLatin1String("audio/x-wav") << QLatin1String("audio/vnd.wave") ;
|
|
return supportedTypes;
|
|
}
|
|
|
|
QUrl QSoundEffectPrivate::source() const
|
|
{
|
|
return m_source;
|
|
}
|
|
|
|
void QSoundEffectPrivate::setSource(const QUrl &url)
|
|
{
|
|
Q_ASSERT(m_source != url);
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << this << "setSource =" << url;
|
|
#endif
|
|
stop();
|
|
if (m_sample) {
|
|
if (!m_sampleReady) {
|
|
disconnect(m_sample, SIGNAL(error()), this, SLOT(decoderError()));
|
|
disconnect(m_sample, SIGNAL(ready()), this, SLOT(sampleReady()));
|
|
}
|
|
m_sample->release();
|
|
m_sample = 0;
|
|
}
|
|
|
|
m_source = url;
|
|
m_sampleReady = false;
|
|
|
|
PulseDaemonLocker locker;
|
|
setLoopsRemaining(0);
|
|
if (m_pulseStream && !pa_stream_is_corked(m_pulseStream)) {
|
|
pa_stream_set_write_callback(m_pulseStream, 0, 0);
|
|
pa_stream_set_underflow_callback(m_pulseStream, 0, 0);
|
|
pa_operation_unref(pa_stream_cork(m_pulseStream, 1, 0, 0));
|
|
}
|
|
setPlaying(false);
|
|
|
|
if (url.isEmpty()) {
|
|
setStatus(QSoundEffect::Null);
|
|
return;
|
|
}
|
|
|
|
setStatus(QSoundEffect::Loading);
|
|
m_sample = sampleCache()->requestSample(url);
|
|
connect(m_sample, SIGNAL(error()), this, SLOT(decoderError()));
|
|
connect(m_sample, SIGNAL(ready()), this, SLOT(sampleReady()));
|
|
switch(m_sample->state()) {
|
|
case QSample::Ready:
|
|
sampleReady();
|
|
break;
|
|
case QSample::Error:
|
|
decoderError();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
int QSoundEffectPrivate::loopCount() const
|
|
{
|
|
return m_loopCount;
|
|
}
|
|
|
|
int QSoundEffectPrivate::loopsRemaining() const
|
|
{
|
|
return m_runningCount;
|
|
}
|
|
|
|
void QSoundEffectPrivate::setLoopCount(int loopCount)
|
|
{
|
|
if (loopCount == 0)
|
|
loopCount = 1;
|
|
m_loopCount = loopCount;
|
|
if (m_playing)
|
|
setLoopsRemaining(loopCount);
|
|
}
|
|
|
|
qreal QSoundEffectPrivate::volume() const
|
|
{
|
|
return m_volume;
|
|
}
|
|
|
|
void QSoundEffectPrivate::setVolume(qreal volume)
|
|
{
|
|
m_volume = volume;
|
|
emit volumeChanged();
|
|
updateVolume();
|
|
}
|
|
|
|
void QSoundEffectPrivate::updateVolume()
|
|
{
|
|
if (m_sinkInputId < 0)
|
|
return;
|
|
PulseDaemonLocker locker;
|
|
pa_cvolume volume;
|
|
volume.channels = m_pulseSpec.channels;
|
|
if (pulseDaemon()->context())
|
|
pa_operation_unref(pa_context_set_sink_input_volume(pulseDaemon()->context(), m_sinkInputId, pulseDaemon()->calcVolume(&volume, qRound(m_volume * 100)), setvolume_callback, m_ref->getRef()));
|
|
Q_ASSERT(pa_cvolume_valid(&volume));
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << this << "updateVolume =" << pa_cvolume_max(&volume);
|
|
#endif
|
|
}
|
|
|
|
bool QSoundEffectPrivate::isMuted() const
|
|
{
|
|
return m_muted;
|
|
}
|
|
|
|
void QSoundEffectPrivate::setMuted(bool muted)
|
|
{
|
|
m_muted = muted;
|
|
emit mutedChanged();
|
|
updateMuted();
|
|
}
|
|
|
|
void QSoundEffectPrivate::updateMuted()
|
|
{
|
|
if (m_sinkInputId < 0)
|
|
return;
|
|
PulseDaemonLocker locker;
|
|
if (pulseDaemon()->context())
|
|
pa_operation_unref(pa_context_set_sink_input_mute(pulseDaemon()->context(), m_sinkInputId, m_muted, setmuted_callback, m_ref->getRef()));
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << this << "updateMuted = " << m_muted;
|
|
#endif
|
|
}
|
|
|
|
bool QSoundEffectPrivate::isLoaded() const
|
|
{
|
|
return m_status == QSoundEffect::Ready;
|
|
}
|
|
|
|
bool QSoundEffectPrivate::isPlaying() const
|
|
{
|
|
return m_playing;
|
|
}
|
|
|
|
QSoundEffect::Status QSoundEffectPrivate::status() const
|
|
{
|
|
return m_status;
|
|
}
|
|
|
|
void QSoundEffectPrivate::setPlaying(bool playing)
|
|
{
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << this << "setPlaying(" << playing << ")";
|
|
#endif
|
|
if (m_playing == playing)
|
|
return;
|
|
if (!playing)
|
|
m_playQueued = false;
|
|
m_playing = playing;
|
|
emit playingChanged();
|
|
}
|
|
|
|
void QSoundEffectPrivate::setStatus(QSoundEffect::Status status)
|
|
{
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << this << "setStatus" << status;
|
|
#endif
|
|
if (m_status == status)
|
|
return;
|
|
bool oldLoaded = isLoaded();
|
|
m_status = status;
|
|
emit statusChanged();
|
|
if (oldLoaded != isLoaded())
|
|
emit loadedChanged();
|
|
}
|
|
|
|
void QSoundEffectPrivate::setLoopsRemaining(int loopsRemaining)
|
|
{
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << this << "setLoopsRemaining " << loopsRemaining;
|
|
#endif
|
|
if (m_runningCount == loopsRemaining)
|
|
return;
|
|
m_runningCount = loopsRemaining;
|
|
emit loopsRemainingChanged();
|
|
}
|
|
|
|
void QSoundEffectPrivate::play()
|
|
{
|
|
if (!m_resourcesAvailable)
|
|
return;
|
|
|
|
playAvailable();
|
|
}
|
|
|
|
void QSoundEffectPrivate::playAvailable()
|
|
{
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << this << "play";
|
|
#endif
|
|
if (m_status == QSoundEffect::Null || m_status == QSoundEffect::Error || m_playQueued)
|
|
return;
|
|
|
|
PulseDaemonLocker locker;
|
|
if (!m_pulseStream || m_status != QSoundEffect::Ready || m_stopping || m_emptying) {
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << this << "play deferred";
|
|
#endif
|
|
m_playQueued = true;
|
|
} else {
|
|
if (m_playing) { //restart playing from the beginning
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << this << "restart playing";
|
|
#endif
|
|
setLoopsRemaining(0);
|
|
m_playQueued = true;
|
|
Q_ASSERT(m_pulseStream);
|
|
emptyStream();
|
|
return;
|
|
}
|
|
setLoopsRemaining(m_loopCount);
|
|
playSample();
|
|
}
|
|
|
|
setPlaying(true);
|
|
}
|
|
|
|
void QSoundEffectPrivate::emptyStream()
|
|
{
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << this << "emptyStream";
|
|
#endif
|
|
m_emptying = true;
|
|
pa_stream_set_write_callback(m_pulseStream, 0, 0);
|
|
pa_stream_set_underflow_callback(m_pulseStream, 0, 0);
|
|
pa_operation_unref(pa_stream_flush(m_pulseStream, stream_flush_callback, m_ref->getRef()));
|
|
}
|
|
|
|
void QSoundEffectPrivate::emptyComplete(void *stream)
|
|
{
|
|
PulseDaemonLocker locker;
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << this << "emptyComplete";
|
|
#endif
|
|
|
|
m_emptying = false;
|
|
|
|
if ((pa_stream *)stream == m_pulseStream)
|
|
pa_operation_unref(pa_stream_cork(m_pulseStream, 1, stream_cork_callback, m_ref->getRef()));
|
|
}
|
|
|
|
void QSoundEffectPrivate::sampleReady()
|
|
{
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << this << "sampleReady";
|
|
#endif
|
|
disconnect(m_sample, SIGNAL(error()), this, SLOT(decoderError()));
|
|
disconnect(m_sample, SIGNAL(ready()), this, SLOT(sampleReady()));
|
|
pa_sample_spec newFormatSpec = audioFormatToSampleSpec(m_sample->format());
|
|
|
|
if (m_pulseStream && !pa_sample_spec_equal(&m_pulseSpec, &newFormatSpec)) {
|
|
unloadPulseStream();
|
|
}
|
|
m_pulseSpec = newFormatSpec;
|
|
|
|
m_sampleReady = true;
|
|
m_position = 0;
|
|
|
|
if (m_name.isNull())
|
|
m_name = QString(QLatin1String("QtPulseSample-%1-%2")).arg(::getpid()).arg(quintptr(this)).toUtf8();
|
|
|
|
PulseDaemonLocker locker;
|
|
if (m_pulseStream) {
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << this << "reuse existing pulsestream";
|
|
#endif
|
|
#ifdef QTM_PULSEAUDIO_DEFAULTBUFFER
|
|
const pa_buffer_attr *bufferAttr = pa_stream_get_buffer_attr(m_pulseStream);
|
|
if (bufferAttr->prebuf > uint32_t(m_sample->data().size())) {
|
|
pa_buffer_attr newBufferAttr;
|
|
newBufferAttr = *bufferAttr;
|
|
newBufferAttr.prebuf = m_sample->data().size();
|
|
pa_operation_unref(pa_stream_set_buffer_attr(m_pulseStream, &newBufferAttr, stream_adjust_prebuffer_callback, m_ref->getRef()));
|
|
} else {
|
|
streamReady();
|
|
}
|
|
#else
|
|
const pa_buffer_attr *bufferAttr = pa_stream_get_buffer_attr(m_pulseStream);
|
|
if (bufferAttr->tlength < m_sample->data().size() && bufferAttr->tlength < QT_PA_STREAM_BUFFER_SIZE_MAX) {
|
|
pa_buffer_attr newBufferAttr;
|
|
newBufferAttr.maxlength = -1;
|
|
newBufferAttr.tlength = qMin(m_sample->data().size(), QT_PA_STREAM_BUFFER_SIZE_MAX);
|
|
newBufferAttr.minreq = bufferAttr->tlength / 2;
|
|
newBufferAttr.prebuf = -1;
|
|
newBufferAttr.fragsize = -1;
|
|
pa_operation_unref(pa_stream_set_buffer_attr(m_pulseStream, &newBufferAttr, stream_reset_buffer_callback, m_ref->getRef()));
|
|
} else if (bufferAttr->prebuf > uint32_t(m_sample->data().size())) {
|
|
pa_buffer_attr newBufferAttr;
|
|
newBufferAttr = *bufferAttr;
|
|
newBufferAttr.prebuf = m_sample->data().size();
|
|
pa_operation_unref(pa_stream_set_buffer_attr(m_pulseStream, &newBufferAttr, stream_adjust_prebuffer_callback, m_ref->getRef()));
|
|
} else {
|
|
streamReady();
|
|
}
|
|
#endif
|
|
} else {
|
|
if (!pulseDaemon()->context() || pa_context_get_state(pulseDaemon()->context()) != PA_CONTEXT_READY) {
|
|
connect(pulseDaemon(), SIGNAL(contextReady()), SLOT(contextReady()));
|
|
return;
|
|
}
|
|
createPulseStream();
|
|
}
|
|
}
|
|
|
|
void QSoundEffectPrivate::decoderError()
|
|
{
|
|
qWarning("QSoundEffect(pulseaudio): Error decoding source");
|
|
disconnect(m_sample, SIGNAL(error()), this, SLOT(decoderError()));
|
|
bool playingDirty = false;
|
|
if (m_playing) {
|
|
m_playing = false;
|
|
playingDirty = true;
|
|
}
|
|
setStatus(QSoundEffect::Error);
|
|
if (playingDirty)
|
|
emit playingChanged();
|
|
}
|
|
|
|
void QSoundEffectPrivate::unloadPulseStream()
|
|
{
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << this << "unloadPulseStream";
|
|
#endif
|
|
m_sinkInputId = -1;
|
|
PulseDaemonLocker locker;
|
|
if (m_pulseStream) {
|
|
pa_stream_set_state_callback(m_pulseStream, 0, 0);
|
|
pa_stream_set_write_callback(m_pulseStream, 0, 0);
|
|
pa_stream_set_underflow_callback(m_pulseStream, 0, 0);
|
|
pa_stream_disconnect(m_pulseStream);
|
|
pa_stream_unref(m_pulseStream);
|
|
disconnect(pulseDaemon(), SIGNAL(volumeChanged()), this, SLOT(updateVolume()));
|
|
disconnect(pulseDaemon(), SIGNAL(contextFailed()), this, SLOT(contextFailed()));
|
|
m_pulseStream = 0;
|
|
m_reloadCategory = false; // category will be reloaded when we connect anyway
|
|
}
|
|
}
|
|
|
|
void QSoundEffectPrivate::prepare()
|
|
{
|
|
if (!m_pulseStream || !m_sampleReady)
|
|
return;
|
|
PulseDaemonLocker locker;
|
|
pa_stream_set_write_callback(m_pulseStream, stream_write_callback, this);
|
|
pa_stream_set_underflow_callback(m_pulseStream, stream_underrun_callback, this);
|
|
m_stopping = false;
|
|
size_t writeBytes = size_t(qMin(m_pulseBufferSize, m_sample->data().size()));
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << this << "prepare(): writable size =" << pa_stream_writable_size(m_pulseStream)
|
|
<< "actual writeBytes =" << writeBytes
|
|
<< "m_playQueued =" << m_playQueued;
|
|
#endif
|
|
m_position = int(writeBytes);
|
|
if (pa_stream_write(m_pulseStream, reinterpret_cast<void *>(const_cast<char*>(m_sample->data().data())), writeBytes,
|
|
stream_write_done_callback, 0, PA_SEEK_RELATIVE) != 0) {
|
|
qWarning("QSoundEffect(pulseaudio): pa_stream_write, error = %s", pa_strerror(pa_context_errno(pulseDaemon()->context())));
|
|
}
|
|
if (m_playQueued) {
|
|
m_playQueued = false;
|
|
setLoopsRemaining(m_loopCount);
|
|
playSample();
|
|
}
|
|
}
|
|
|
|
void QSoundEffectPrivate::uploadSample()
|
|
{
|
|
if (m_runningCount == 0) {
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << this << "uploadSample: return due to 0 m_runningCount";
|
|
#endif
|
|
return;
|
|
}
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << this << "uploadSample: m_runningCount =" << m_runningCount;
|
|
#endif
|
|
if (m_position == m_sample->data().size()) {
|
|
m_position = 0;
|
|
if (m_runningCount > 0)
|
|
setLoopsRemaining(m_runningCount - 1);
|
|
if (m_runningCount == 0) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
int writtenBytes = 0;
|
|
int writableSize = int(pa_stream_writable_size(m_pulseStream));
|
|
int firstPartLength = qMin(m_sample->data().size() - m_position, writableSize);
|
|
if (pa_stream_write(m_pulseStream, reinterpret_cast<void *>(const_cast<char*>(m_sample->data().data()) + m_position),
|
|
firstPartLength, stream_write_done_callback, 0, PA_SEEK_RELATIVE) != 0) {
|
|
qWarning("QSoundEffect(pulseaudio): pa_stream_write, error = %s", pa_strerror(pa_context_errno(pulseDaemon()->context())));
|
|
}
|
|
writtenBytes = firstPartLength;
|
|
m_position += firstPartLength;
|
|
if (m_position == m_sample->data().size()) {
|
|
m_position = 0;
|
|
if (m_runningCount > 0)
|
|
setLoopsRemaining(m_runningCount - 1);
|
|
if (m_runningCount != 0 && firstPartLength < writableSize)
|
|
{
|
|
while (writtenBytes < writableSize) {
|
|
int writeSize = qMin(writableSize - writtenBytes, m_sample->data().size());
|
|
if (pa_stream_write(m_pulseStream, reinterpret_cast<void *>(const_cast<char*>(m_sample->data().data())),
|
|
writeSize, stream_write_done_callback, 0, PA_SEEK_RELATIVE) != 0) {
|
|
qWarning("QSoundEffect(pulseaudio): pa_stream_write, error = %s", pa_strerror(pa_context_errno(pulseDaemon()->context())));
|
|
}
|
|
writtenBytes += writeSize;
|
|
if (writeSize < m_sample->data().size()) {
|
|
m_position = writeSize;
|
|
break;
|
|
}
|
|
if (m_runningCount > 0)
|
|
setLoopsRemaining(m_runningCount - 1);
|
|
if (m_runningCount == 0)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << this << "uploadSample: use direct write, writeable size =" << writableSize
|
|
<< "actual writtenBytes =" << writtenBytes;
|
|
#endif
|
|
}
|
|
|
|
void QSoundEffectPrivate::playSample()
|
|
{
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << this << "playSample";
|
|
#endif
|
|
Q_ASSERT(m_pulseStream);
|
|
Q_ASSERT(pa_stream_get_state(m_pulseStream) == PA_STREAM_READY);
|
|
pa_operation *o = pa_stream_cork(m_pulseStream, 0, 0, 0);
|
|
if (o)
|
|
pa_operation_unref(o);
|
|
}
|
|
|
|
void QSoundEffectPrivate::stop()
|
|
{
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << this << "stop";
|
|
#endif
|
|
if (!m_playing)
|
|
return;
|
|
setPlaying(false);
|
|
PulseDaemonLocker locker;
|
|
m_stopping = true;
|
|
if (m_pulseStream) {
|
|
emptyStream();
|
|
if (m_reloadCategory) {
|
|
unloadPulseStream(); // upon play we reconnect anyway
|
|
}
|
|
}
|
|
setLoopsRemaining(0);
|
|
m_position = 0;
|
|
m_playQueued = false;
|
|
m_reloadCategory = false;
|
|
}
|
|
|
|
void QSoundEffectPrivate::underRun()
|
|
{
|
|
stop();
|
|
}
|
|
|
|
void QSoundEffectPrivate::streamReady()
|
|
{
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << this << "streamReady";
|
|
#endif
|
|
PulseDaemonLocker locker;
|
|
m_sinkInputId = pa_stream_get_index(m_pulseStream);
|
|
updateMuted();
|
|
updateVolume();
|
|
#ifdef QT_PA_DEBUG
|
|
const pa_buffer_attr *realBufAttr = pa_stream_get_buffer_attr(m_pulseStream);
|
|
qDebug() << this << "m_sinkInputId =" << m_sinkInputId
|
|
<< "tlength =" << realBufAttr->tlength << "maxlength =" << realBufAttr->maxlength
|
|
<< "minreq = " << realBufAttr->minreq << "prebuf =" << realBufAttr->prebuf;
|
|
#endif
|
|
prepare();
|
|
setStatus(QSoundEffect::Ready);
|
|
}
|
|
|
|
void QSoundEffectPrivate::createPulseStream()
|
|
{
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << this << "createPulseStream";
|
|
#endif
|
|
|
|
if (!pulseDaemon()->context())
|
|
return;
|
|
|
|
pa_proplist *propList = pa_proplist_new();
|
|
if (!m_category.isNull())
|
|
pa_proplist_sets(propList, PA_PROP_MEDIA_ROLE, m_category.toLatin1().constData());
|
|
pa_stream *stream = pa_stream_new_with_proplist(pulseDaemon()->context(), m_name.constData(), &m_pulseSpec, 0, propList);
|
|
pa_proplist_free(propList);
|
|
|
|
connect(pulseDaemon(), SIGNAL(volumeChanged()), this, SLOT(updateVolume()));
|
|
connect(pulseDaemon(), SIGNAL(contextFailed()), this, SLOT(contextFailed()));
|
|
|
|
if (stream == 0) {
|
|
qWarning("QSoundEffect(pulseaudio): Failed to create stream");
|
|
m_pulseStream = 0;
|
|
setStatus(QSoundEffect::Error);
|
|
setPlaying(false);
|
|
return;
|
|
}
|
|
else {
|
|
pa_stream_set_state_callback(stream, stream_state_callback, this);
|
|
pa_stream_set_write_callback(stream, stream_write_callback, this);
|
|
pa_stream_set_underflow_callback(stream, stream_underrun_callback, this);
|
|
}
|
|
m_pulseStream = stream;
|
|
|
|
#ifndef QTM_PULSEAUDIO_DEFAULTBUFFER
|
|
pa_buffer_attr bufferAttr;
|
|
bufferAttr.tlength = qMin(m_sample->data().size(), QT_PA_STREAM_BUFFER_SIZE_MAX);
|
|
bufferAttr.maxlength = -1;
|
|
bufferAttr.minreq = bufferAttr.tlength / 2;
|
|
bufferAttr.prebuf = -1;
|
|
bufferAttr.fragsize = -1;
|
|
if (pa_stream_connect_playback(m_pulseStream, 0, &bufferAttr,
|
|
#else
|
|
if (pa_stream_connect_playback(m_pulseStream, 0, 0,
|
|
#endif
|
|
m_muted ? pa_stream_flags_t(PA_STREAM_START_MUTED | PA_STREAM_START_CORKED)
|
|
: pa_stream_flags_t(PA_STREAM_START_UNMUTED | PA_STREAM_START_CORKED),
|
|
0, 0) < 0) {
|
|
qWarning("QSoundEffect(pulseaudio): Failed to connect stream, error = %s",
|
|
pa_strerror(pa_context_errno(pulseDaemon()->context())));
|
|
}
|
|
}
|
|
|
|
void QSoundEffectPrivate::contextReady()
|
|
{
|
|
disconnect(pulseDaemon(), SIGNAL(contextReady()), this, SLOT(contextReady()));
|
|
PulseDaemonLocker locker;
|
|
createPulseStream();
|
|
}
|
|
|
|
void QSoundEffectPrivate::contextFailed()
|
|
{
|
|
unloadPulseStream();
|
|
connect(pulseDaemon(), SIGNAL(contextReady()), this, SLOT(contextReady()));
|
|
}
|
|
|
|
void QSoundEffectPrivate::stream_write_callback(pa_stream *s, size_t length, void *userdata)
|
|
{
|
|
Q_UNUSED(length);
|
|
Q_UNUSED(s);
|
|
|
|
QSoundEffectPrivate *self = reinterpret_cast<QSoundEffectPrivate*>(userdata);
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << self << "stream_write_callback";
|
|
#endif
|
|
self->uploadSample();
|
|
}
|
|
|
|
void QSoundEffectPrivate::stream_state_callback(pa_stream *s, void *userdata)
|
|
{
|
|
QSoundEffectPrivate *self = reinterpret_cast<QSoundEffectPrivate*>(userdata);
|
|
switch (pa_stream_get_state(s)) {
|
|
case PA_STREAM_READY:
|
|
{
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << self << "pulse stream ready";
|
|
#endif
|
|
const pa_buffer_attr *bufferAttr = pa_stream_get_buffer_attr(self->m_pulseStream);
|
|
self->m_pulseBufferSize = bufferAttr->tlength;
|
|
if (bufferAttr->prebuf > uint32_t(self->m_sample->data().size())) {
|
|
pa_buffer_attr newBufferAttr;
|
|
newBufferAttr = *bufferAttr;
|
|
newBufferAttr.prebuf = self->m_sample->data().size();
|
|
pa_stream_set_buffer_attr(self->m_pulseStream, &newBufferAttr, stream_adjust_prebuffer_callback, self->m_ref->getRef());
|
|
} else {
|
|
QMetaObject::invokeMethod(self, "streamReady", Qt::QueuedConnection);
|
|
}
|
|
break;
|
|
}
|
|
case PA_STREAM_CREATING:
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << self << "pulse stream creating";
|
|
#endif
|
|
break;
|
|
case PA_STREAM_TERMINATED:
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << self << "pulse stream terminated";
|
|
#endif
|
|
break;
|
|
|
|
case PA_STREAM_FAILED:
|
|
default:
|
|
qWarning("QSoundEffect(pulseaudio): Error in pulse audio stream");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void QSoundEffectPrivate::stream_reset_buffer_callback(pa_stream *s, int success, void *userdata)
|
|
{
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << "stream_reset_buffer_callback";
|
|
#endif
|
|
Q_UNUSED(s);
|
|
QSoundEffectRef *ref = reinterpret_cast<QSoundEffectRef*>(userdata);
|
|
QSoundEffectPrivate *self = ref->soundEffect();
|
|
ref->release();
|
|
if (!self)
|
|
return;
|
|
|
|
if (!success)
|
|
qWarning("QSoundEffect(pulseaudio): faild to reset buffer attribute");
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << self << "stream_reset_buffer_callback";
|
|
#endif
|
|
const pa_buffer_attr *bufferAttr = pa_stream_get_buffer_attr(self->m_pulseStream);
|
|
self->m_pulseBufferSize = bufferAttr->tlength;
|
|
if (bufferAttr->prebuf > uint32_t(self->m_sample->data().size())) {
|
|
pa_buffer_attr newBufferAttr;
|
|
newBufferAttr = *bufferAttr;
|
|
newBufferAttr.prebuf = self->m_sample->data().size();
|
|
pa_stream_set_buffer_attr(self->m_pulseStream, &newBufferAttr, stream_adjust_prebuffer_callback, userdata);
|
|
} else {
|
|
QMetaObject::invokeMethod(self, "streamReady", Qt::QueuedConnection);
|
|
}
|
|
}
|
|
|
|
void QSoundEffectPrivate::stream_adjust_prebuffer_callback(pa_stream *s, int success, void *userdata)
|
|
{
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << "stream_adjust_prebuffer_callback";
|
|
#endif
|
|
Q_UNUSED(s);
|
|
QSoundEffectRef *ref = reinterpret_cast<QSoundEffectRef*>(userdata);
|
|
QSoundEffectPrivate *self = ref->soundEffect();
|
|
ref->release();
|
|
if (!self)
|
|
return;
|
|
|
|
if (!success)
|
|
qWarning("QSoundEffect(pulseaudio): faild to adjust pre-buffer attribute");
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << self << "stream_adjust_prebuffer_callback";
|
|
#endif
|
|
QMetaObject::invokeMethod(self, "streamReady", Qt::QueuedConnection);
|
|
}
|
|
|
|
void QSoundEffectPrivate::setvolume_callback(pa_context *c, int success, void *userdata)
|
|
{
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << "setvolume_callback";
|
|
#endif
|
|
Q_UNUSED(c);
|
|
Q_UNUSED(userdata);
|
|
QSoundEffectRef *ref = reinterpret_cast<QSoundEffectRef*>(userdata);
|
|
QSoundEffectPrivate *self = ref->soundEffect();
|
|
ref->release();
|
|
if (!self)
|
|
return;
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << self << "setvolume_callback";
|
|
#endif
|
|
if (!success) {
|
|
qWarning("QSoundEffect(pulseaudio): faild to set volume");
|
|
}
|
|
}
|
|
|
|
void QSoundEffectPrivate::setmuted_callback(pa_context *c, int success, void *userdata)
|
|
{
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << "setmuted_callback";
|
|
#endif
|
|
Q_UNUSED(c);
|
|
Q_UNUSED(userdata);
|
|
QSoundEffectRef *ref = reinterpret_cast<QSoundEffectRef*>(userdata);
|
|
QSoundEffectPrivate *self = ref->soundEffect();
|
|
ref->release();
|
|
if (!self)
|
|
return;
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << self << "setmuted_callback";
|
|
#endif
|
|
if (!success) {
|
|
qWarning("QSoundEffect(pulseaudio): faild to set muted");
|
|
}
|
|
}
|
|
|
|
void QSoundEffectPrivate::stream_underrun_callback(pa_stream *s, void *userdata)
|
|
{
|
|
Q_UNUSED(s);
|
|
QSoundEffectPrivate *self = reinterpret_cast<QSoundEffectPrivate*>(userdata);
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << self << "stream_underrun_callback";
|
|
#endif
|
|
if (self->m_runningCount == 0 && !self->m_playQueued)
|
|
QMetaObject::invokeMethod(self, "underRun", Qt::QueuedConnection);
|
|
#ifdef QT_PA_DEBUG
|
|
else
|
|
qDebug() << "underun corked =" << pa_stream_is_corked(s);
|
|
#endif
|
|
}
|
|
|
|
void QSoundEffectPrivate::stream_cork_callback(pa_stream *s, int success, void *userdata)
|
|
{
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << "stream_cork_callback";
|
|
#endif
|
|
Q_UNUSED(s);
|
|
QSoundEffectRef *ref = reinterpret_cast<QSoundEffectRef*>(userdata);
|
|
QSoundEffectPrivate *self = ref->soundEffect();
|
|
ref->release();
|
|
if (!self)
|
|
return;
|
|
|
|
if (!success)
|
|
qWarning("QSoundEffect(pulseaudio): faild to stop");
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << self << "stream_cork_callback";
|
|
#endif
|
|
QMetaObject::invokeMethod(self, "prepare", Qt::QueuedConnection);
|
|
}
|
|
|
|
void QSoundEffectPrivate::stream_flush_callback(pa_stream *s, int success, void *userdata)
|
|
{
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << "stream_flush_callback";
|
|
#endif
|
|
Q_UNUSED(s);
|
|
QSoundEffectRef *ref = reinterpret_cast<QSoundEffectRef*>(userdata);
|
|
QSoundEffectPrivate *self = ref->soundEffect();
|
|
ref->release();
|
|
if (!self)
|
|
return;
|
|
|
|
if (!success)
|
|
qWarning("QSoundEffect(pulseaudio): faild to drain");
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << self << "stream_flush_callback";
|
|
#endif
|
|
QMetaObject::invokeMethod(self, "emptyComplete", Qt::QueuedConnection, Q_ARG(void*, s));
|
|
}
|
|
|
|
void QSoundEffectPrivate::stream_write_done_callback(void *p)
|
|
{
|
|
Q_UNUSED(p);
|
|
#ifdef QT_PA_DEBUG
|
|
qDebug() << "stream_write_done_callback";
|
|
#endif
|
|
}
|
|
|
|
QT_END_NAMESPACE
|
|
|
|
#include "moc_qsoundeffect_pulse_p.cpp"
|
|
#include "qsoundeffect_pulse_p.moc"
|