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:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -135,8 +135,6 @@ private:
|
||||
QString m_category;
|
||||
|
||||
qreal m_volume;
|
||||
bool m_customVolumeRequired;
|
||||
pa_cvolume m_chVolume;
|
||||
pa_sample_spec m_spec;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user