PulseAudio: change the way volume is applied.

We used to change the PulseAudio sink input volume. Doing so had
some potential unwanted side effects depending on the PulseAudio server
configuration. When flat volumes were enabled, it would affect the
global system volume. It could also affect the volume of other streams
having the same audio role.
Volumes in Qt Multimedia are supposed to be relative to the application
volume and should not affect anything else than the object on which it
was changed. To guarantee that, PulseAudio volume APIs are not used
anymore. Instead, software-based volume attenuation is applied on the
audio samples before being passed to PulseAudio.

Applies to QSoundEffect, QAudioOutput and QAudioInput.

Task-number: QTBUG-40823
Task-number: QTBUG-49461
Change-Id: I690716976bda8fe666969ca2cbdf6d8d0b419733
Reviewed-by: Christian Stromme <christian.stromme@theqtcompany.com>
This commit is contained in:
Yoann Lopes
2016-02-05 14:07:20 +01:00
parent 399ec97f10
commit b17e0cd5dd
6 changed files with 113 additions and 307 deletions

View File

@@ -49,10 +49,7 @@
#include "qsoundeffect_pulse_p.h"
#if defined(Q_WS_MAEMO_6) || defined(NEMO_AUDIO)
#include <pulse/ext-stream-restore.h>
#endif
#include <private/qaudiohelpers_p.h>
#include <private/qmediaresourcepolicy_p.h>
#include <private/qmediaresourceset_p.h>
@@ -124,26 +121,9 @@ public:
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()
@@ -158,8 +138,6 @@ private Q_SLOTS:
void prepare()
{
m_vol = PA_VOLUME_NORM;
m_context = 0;
m_mainLoop = pa_threaded_mainloop_new();
if (m_mainLoop == 0) {
@@ -232,11 +210,6 @@ private:
case PA_CONTEXT_SETTING_NAME:
break;
case PA_CONTEXT_READY:
#if defined(Q_WS_MAEMO_6) || defined(NEMO_AUDIO)
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:
@@ -247,37 +220,6 @@ private:
}
}
#if defined(Q_WS_MAEMO_6) || defined(NEMO_AUDIO)
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;
@@ -385,9 +327,6 @@ QSoundEffectPrivate::QSoundEffectPrivate(QObject* parent):
m_sample(0),
m_position(0),
m_resourcesAvailable(false)
#if defined(Q_WS_MAEMO_6) || defined(NEMO_AUDIO)
, m_customVolume(false)
#endif
{
m_ref = new QSoundEffectRef(this);
pa_sample_spec_init(&m_pulseSpec);
@@ -538,60 +477,32 @@ void QSoundEffectPrivate::setLoopCount(int loopCount)
qreal QSoundEffectPrivate::volume() const
{
QReadLocker locker(&m_volumeLock);
return m_volume;
}
void QSoundEffectPrivate::setVolume(qreal volume)
{
#if defined(Q_WS_MAEMO_6) || defined(NEMO_AUDIO)
m_customVolume = true;
#endif
m_volume = volume;
emit volumeChanged();
updateVolume();
}
QWriteLocker locker(&m_volumeLock);
void QSoundEffectPrivate::updateVolume()
{
if (m_sinkInputId < 0)
if (qFuzzyCompare(m_volume, volume))
return;
#if defined(Q_WS_MAEMO_6) || defined(NEMO_AUDIO)
if (!m_customVolume)
return;
#endif
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
m_volume = qBound(qreal(0), volume, qreal(1));
emit volumeChanged();
}
bool QSoundEffectPrivate::isMuted() const
{
QReadLocker locker(&m_volumeLock);
return m_muted;
}
void QSoundEffectPrivate::setMuted(bool muted)
{
QWriteLocker locker(&m_volumeLock);
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
@@ -801,7 +712,6 @@ void QSoundEffectPrivate::unloadPulseStream()
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
@@ -822,11 +732,8 @@ void QSoundEffectPrivate::prepare()
<< "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())));
}
m_position = writeToStream(m_sample->data().data(), writeBytes);
if (m_playQueued) {
m_playQueued = false;
setLoopsRemaining(m_loopCount);
@@ -854,15 +761,13 @@ void QSoundEffectPrivate::uploadSample()
}
}
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;
int writtenBytes = writeToStream(m_sample->data().data() + m_position,
firstPartLength);
m_position += writtenBytes;
if (m_position == m_sample->data().size()) {
m_position = 0;
if (m_runningCount > 0)
@@ -871,11 +776,8 @@ void QSoundEffectPrivate::uploadSample()
{
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;
writtenBytes += writeToStream(m_sample->data().data(), writeSize);
if (writeSize < m_sample->data().size()) {
m_position = writeSize;
break;
@@ -893,6 +795,39 @@ void QSoundEffectPrivate::uploadSample()
#endif
}
int QSoundEffectPrivate::writeToStream(const void *data, int size)
{
m_volumeLock.lockForRead();
qreal volume = m_muted ? 0 : m_volume;
m_volumeLock.unlock();
pa_free_cb_t writeDoneCb = stream_write_done_callback;
if (volume < 1.0f) {
// Don't use PulseAudio volume, as it might affect all other streams of the same category
// or even affect the system volume if flat volumes are enabled
void *dest = NULL;
size_t nbytes = size;
if (pa_stream_begin_write(m_pulseStream, &dest, &nbytes) < 0) {
qWarning("QSoundEffect(pulseaudio): pa_stream_begin_write, error = %s",
pa_strerror(pa_context_errno(pulseDaemon()->context())));
return 0;
}
size = int(nbytes);
QAudioHelperInternal::qMultiplySamples(volume, m_sample->format(), data, dest, size);
data = dest;
writeDoneCb = NULL;
}
if (pa_stream_write(m_pulseStream, data, size, writeDoneCb, 0, PA_SEEK_RELATIVE) < 0) {
qWarning("QSoundEffect(pulseaudio): pa_stream_write, error = %s",
pa_strerror(pa_context_errno(pulseDaemon()->context())));
return 0;
}
return size;
}
void QSoundEffectPrivate::playSample()
{
#ifdef QT_PA_DEBUG
@@ -939,8 +874,6 @@ void QSoundEffectPrivate::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
@@ -966,7 +899,6 @@ void QSoundEffectPrivate::createPulseStream()
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) {
@@ -994,9 +926,7 @@ void QSoundEffectPrivate::createPulseStream()
#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) {
PA_STREAM_START_CORKED, 0, 0) < 0) {
qWarning("QSoundEffect(pulseaudio): Failed to connect stream, error = %s",
pa_strerror(pa_context_errno(pulseDaemon()->context())));
}
@@ -1115,46 +1045,6 @@ void QSoundEffectPrivate::stream_adjust_prebuffer_callback(pa_stream *s, int suc
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);

View File

@@ -50,6 +50,7 @@
#include <QtCore/qobject.h>
#include <QtCore/qdatetime.h>
#include <QtCore/qreadwritelock.h>
#include <qmediaplayer.h>
#include <pulse/pulseaudio.h>
#include "qsamplecache_p.h"
@@ -111,8 +112,6 @@ private Q_SLOTS:
void prepare();
void streamReady();
void emptyComplete(void *stream);
void updateVolume();
void updateMuted();
void handleAvailabilityChanged(bool available);
@@ -124,6 +123,8 @@ private:
void createPulseStream();
void unloadPulseStream();
int writeToStream(const void *data, int size);
void setPlaying(bool playing);
void setStatus(QSoundEffect::Status status);
void setLoopsRemaining(int loopsRemaining);
@@ -136,8 +137,6 @@ private:
static void stream_write_done_callback(void *p);
static void stream_adjust_prebuffer_callback(pa_stream *s, int success, void *userdata);
static void stream_reset_buffer_callback(pa_stream *s, int success, void *userdata);
static void setvolume_callback(pa_context *c, int success, void *userdata);
static void setmuted_callback(pa_context *c, int success, void *userdata);
pa_stream *m_pulseStream;
int m_sinkInputId;
@@ -165,11 +164,9 @@ private:
bool m_resourcesAvailable;
QMediaPlayerResourceSetInterface *m_resources;
mutable QReadWriteLock m_volumeLock;
#if defined(Q_WS_MAEMO_6) || defined(NEMO_AUDIO)
bool m_customVolume;
#endif
QMediaPlayerResourceSetInterface *m_resources;
};
QT_END_NAMESPACE

View File

@@ -34,6 +34,7 @@
#include <QtCore/qcoreapplication.h>
#include <QtCore/qdebug.h>
#include <QtCore/qmath.h>
#include <private/qaudiohelpers_p.h>
#include "qaudioinput_pulse.h"
#include "qaudiodeviceinfo_pulse.h"
@@ -118,39 +119,12 @@ static void inputStreamSuccessCallback(pa_stream *stream, int success, void *use
pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0);
}
void QPulseAudioInput::sourceInfoCallback(pa_context *context, const pa_source_info *i, int eol, void *userdata)
{
Q_UNUSED(context);
Q_UNUSED(eol);
Q_ASSERT(userdata);
if (i) {
QPulseAudioInput *that = reinterpret_cast<QPulseAudioInput*>(userdata);
that->m_volume = pa_sw_volume_to_linear(pa_cvolume_avg(&i->volume));
}
}
void QPulseAudioInput::inputVolumeCallback(pa_context *context, int success, void *userdata)
{
Q_UNUSED(success);
if (!success)
qWarning() << "QAudioInput: failed to set input volume";
QPulseAudioInput *that = reinterpret_cast<QPulseAudioInput*>(userdata);
// Regardless of success or failure, we update the volume property
if (that->m_stream)
pa_context_get_source_info_by_index(context, pa_stream_get_device_index(that->m_stream), sourceInfoCallback, userdata);
}
QPulseAudioInput::QPulseAudioInput(const QByteArray &device)
: m_totalTimeValue(0)
, m_audioSource(0)
, m_errorState(QAudio::NoError)
, m_deviceState(QAudio::StoppedState)
, m_volume(qreal(1.0f))
, m_customVolumeRequired(false)
, m_pullMode(true)
, m_opened(false)
, m_bytesAvailable(0)
@@ -356,9 +330,6 @@ bool QPulseAudioInput::open()
if (actualBufferAttr->tlength != (uint32_t)-1)
m_bufferSize = actualBufferAttr->tlength;
if (m_customVolumeRequired)
setPulseVolume();
pulseEngine->unlock();
connect(pulseEngine, &QPulseAudioEngine::contextFailed, this, &QPulseAudioInput::onPulseContextFailed);
@@ -407,32 +378,6 @@ void QPulseAudioInput::close()
m_opened = false;
}
/* Call this with the stream opened and the mainloop locked */
void QPulseAudioInput::setPulseVolume()
{
QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
Q_ASSERT(pulseEngine->context() != 0);
pa_cvolume cvolume;
if (qFuzzyCompare(m_volume, 0.0)) {
pa_cvolume_mute(&cvolume, m_spec.channels);
} else {
pa_cvolume_set(&cvolume, m_spec.channels, pa_sw_volume_from_linear(m_volume));
}
pa_operation *op = pa_context_set_source_volume_by_index(pulseEngine->context(),
pa_stream_get_device_index(m_stream),
&cvolume,
inputVolumeCallback,
this);
if (op == NULL)
qWarning() << "QAudioInput: Failed to set volume";
else
pa_operation_unref(op);
}
int QPulseAudioInput::checkBytesReady()
{
if (m_deviceState != QAudio::ActiveState && m_deviceState != QAudio::IdleState) {
@@ -494,7 +439,9 @@ qint64 QPulseAudioInput::read(char *data, qint64 len)
qint64 actualLength = 0;
if (m_pullMode) {
actualLength = m_audioSource->write(static_cast<const char *>(audioBuffer), readLength);
QByteArray adjusted(readLength, Qt::Uninitialized);
applyVolume(audioBuffer, adjusted.data(), readLength);
actualLength = m_audioSource->write(adjusted);
if (actualLength < qint64(readLength)) {
pulseEngine->unlock();
@@ -506,7 +453,7 @@ qint64 QPulseAudioInput::read(char *data, qint64 len)
}
} else {
actualLength = qMin(static_cast<int>(len - readBytes), static_cast<int>(readLength));
memcpy(data + readBytes, audioBuffer, actualLength);
applyVolume(audioBuffer, data + readBytes, actualLength);
}
#ifdef DEBUG_PULSE
@@ -517,7 +464,10 @@ qint64 QPulseAudioInput::read(char *data, qint64 len)
#ifdef DEBUG_PULSE
qDebug() << "QPulseAudioInput::read -- appending " << readLength - actualLength << " bytes of data to temp buffer";
#endif
m_tempBuffer.append(static_cast<const char *>(audioBuffer) + actualLength, readLength - actualLength);
int diff = readLength - actualLength;
int oldSize = m_tempBuffer.size();
m_tempBuffer.resize(m_tempBuffer.size() + diff);
applyVolume(static_cast<const char *>(audioBuffer) + actualLength, m_tempBuffer.data() + oldSize, diff);
QMetaObject::invokeMethod(this, "userFeed", Qt::QueuedConnection);
}
@@ -544,6 +494,14 @@ qint64 QPulseAudioInput::read(char *data, qint64 len)
return readBytes;
}
void QPulseAudioInput::applyVolume(const void *src, void *dest, int len)
{
if (m_volume < 1.f)
QAudioHelperInternal::qMultiplySamples(m_volume, m_format, src, dest, len);
else
memcpy(dest, src, len);
}
void QPulseAudioInput::resume()
{
if (m_deviceState == QAudio::SuspendedState || m_deviceState == QAudio::IdleState) {
@@ -567,30 +525,17 @@ void QPulseAudioInput::resume()
void QPulseAudioInput::setVolume(qreal vol)
{
if (vol >= 0.0 && vol <= 1.0) {
QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
pulseEngine->lock();
m_customVolumeRequired = true;
if (!qFuzzyCompare(m_volume, vol)) {
m_volume = vol;
if (m_opened) {
setPulseVolume();
}
}
pulseEngine->unlock();
}
if (qFuzzyCompare(m_volume, vol))
return;
m_volume = qBound(qreal(0), vol, qreal(1));
}
qreal QPulseAudioInput::volume() const
{
QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
pulseEngine->lock();
qreal vol = m_volume;
pulseEngine->unlock();
return vol;
return m_volume;
}
void QPulseAudioInput::setBufferSize(int value)
{
m_bufferSize = value;

View File

@@ -100,8 +100,6 @@ public:
QAudio::Error m_errorState;
QAudio::State m_deviceState;
qreal m_volume;
bool m_customVolumeRequired;
pa_cvolume m_chVolume;
private slots:
void userFeed();
@@ -112,13 +110,11 @@ private:
void setState(QAudio::State state);
void setError(QAudio::Error error);
void applyVolume(const void *src, void *dest, int len);
int checkBytesReady();
bool open();
void close();
void setPulseVolume();
static void sourceInfoCallback(pa_context *c, const pa_source_info *i, int eol, void *userdata);
static void inputVolumeCallback(pa_context *context, int success, void *userdata);
bool m_pullMode;
bool m_opened;

View File

@@ -34,6 +34,7 @@
#include <QtCore/qcoreapplication.h>
#include <QtCore/qdebug.h>
#include <QtCore/qmath.h>
#include <private/qaudiohelpers_p.h>
#include "qaudiooutput_pulse.h"
#include "qaudiodeviceinfo_pulse.h"
@@ -162,7 +163,6 @@ QPulseAudioOutput::QPulseAudioOutput(const QByteArray &device)
, m_audioBuffer(0)
, m_resuming(false)
, m_volume(1.0)
, m_customVolumeRequired(false)
{
connect(m_tickTimer, SIGNAL(timeout()), SLOT(userFeed()));
}
@@ -312,27 +312,6 @@ bool QPulseAudioOutput::open()
pa_stream_set_overflow_callback(m_stream, outputStreamOverflowCallback, this);
pa_stream_set_latency_update_callback(m_stream, outputStreamLatencyCallback, this);
pa_volume_t paVolume;
/* streams without a custom volume set are expected to already have a
* sensible volume set by Pulse, so we don't set it explicitly.
*
* explicit setting also breaks volume handling on sailfish, where each
* stream's volume is set separately inside pulseaudio, with the
* exception of streams that already have a volume set (i.e. if we set
* it here, we'd ignore system volume).
*/
if (m_customVolumeRequired) {
if (qFuzzyCompare(m_volume, 0.0)) {
paVolume = PA_VOLUME_MUTED;
m_volume = 0.0;
} else {
paVolume = qFloor(m_volume * PA_VOLUME_NORM + 0.5);
}
pa_cvolume_set(&m_chVolume, m_spec.channels, paVolume);
}
if (m_bufferSize <= 0 && m_category == LOW_LATENCY_CATEGORY_NAME) {
m_bufferSize = bytesPerSecond * LowLatencyBufferSizeMs / qint64(1000);
}
@@ -344,7 +323,7 @@ bool QPulseAudioOutput::open()
requestedBuffer.prebuf = (uint32_t)-1;
requestedBuffer.tlength = m_bufferSize;
if (pa_stream_connect_playback(m_stream, m_device.data(), (m_bufferSize > 0) ? &requestedBuffer : NULL, (pa_stream_flags_t)0, m_customVolumeRequired ? &m_chVolume : NULL, NULL) < 0) {
if (pa_stream_connect_playback(m_stream, m_device.data(), (m_bufferSize > 0) ? &requestedBuffer : NULL, (pa_stream_flags_t)0, NULL, NULL) < 0) {
qWarning() << "pa_stream_connect_playback() failed!";
pa_stream_unref(m_stream);
m_stream = 0;
@@ -491,8 +470,33 @@ qint64 QPulseAudioOutput::write(const char *data, qint64 len)
QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
pulseEngine->lock();
len = qMin(len, static_cast<qint64>(pa_stream_writable_size(m_stream)));
pa_stream_write(m_stream, data, len, 0, 0, PA_SEEK_RELATIVE);
if (m_volume < 1.0f) {
// Don't use PulseAudio volume, as it might affect all other streams of the same category
// or even affect the system volume if flat volumes are enabled
void *dest = NULL;
size_t nbytes = len;
if (pa_stream_begin_write(m_stream, &dest, &nbytes) < 0) {
qWarning("QAudioOutput(pulseaudio): pa_stream_begin_write, error = %s",
pa_strerror(pa_context_errno(pulseEngine->context())));
setError(QAudio::IOError);
return 0;
}
len = int(nbytes);
QAudioHelperInternal::qMultiplySamples(m_volume, m_format, data, dest, len);
data = reinterpret_cast<char *>(dest);
}
if (pa_stream_write(m_stream, data, len, NULL, 0, PA_SEEK_RELATIVE) < 0) {
qWarning("QAudioOutput(pulseaudio): pa_stream_write, error = %s",
pa_strerror(pa_context_errno(pulseEngine->context())));
setError(QAudio::IOError);
return 0;
}
pulseEngine->unlock();
m_totalTimeValue += len;
@@ -664,34 +668,10 @@ qint64 PulseOutputPrivate::writeData(const char *data, qint64 len)
void QPulseAudioOutput::setVolume(qreal vol)
{
if (vol >= 0.0 && vol <= 1.0) {
if (!qFuzzyCompare(m_volume, vol)) {
m_customVolumeRequired = true;
m_volume = vol;
if (m_opened) {
QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
pulseEngine->lock();
pa_volume_t paVolume;
if (qFuzzyCompare(vol, 0.0)) {
pa_cvolume_mute(&m_chVolume, m_spec.channels);
m_volume = 0.0;
} else {
paVolume = qFloor(m_volume * PA_VOLUME_NORM + 0.5);
pa_cvolume_set(&m_chVolume, m_spec.channels, paVolume);
}
pa_operation *op = pa_context_set_sink_input_volume(pulseEngine->context(),
pa_stream_get_index(m_stream),
&m_chVolume,
NULL,
NULL);
if (op == NULL)
qWarning()<<"QAudioOutput: Failed to set volume";
else
pa_operation_unref(op);
pulseEngine->unlock();
}
}
}
if (qFuzzyCompare(m_volume, vol))
return;
m_volume = qBound(qreal(0), vol, qreal(1));
}
qreal QPulseAudioOutput::volume() const

View File

@@ -135,8 +135,6 @@ private:
QString m_category;
qreal m_volume;
bool m_customVolumeRequired;
pa_cvolume m_chVolume;
pa_sample_spec m_spec;
};