Add a volume (gain) property to QAudioInput.

Only implemented for PulseAudio so far, but the API does explain that
it's optional.

Change-Id: I4543a1c81d810fe92bb08f1ed13f3a3534a371e4
Reviewed-by: Ling Hu <ling.hu@nokia.com>
This commit is contained in:
Michael Goddard
2012-01-25 13:50:37 +10:00
committed by Qt by Nokia
parent 8aef60c1cf
commit 3b00730eca
8 changed files with 181 additions and 0 deletions

View File

@@ -235,6 +235,12 @@ void InputTest::initializeWindow()
connect(m_deviceBox, SIGNAL(activated(int)), SLOT(deviceChanged(int))); connect(m_deviceBox, SIGNAL(activated(int)), SLOT(deviceChanged(int)));
layout->addWidget(m_deviceBox); layout->addWidget(m_deviceBox);
m_volumeSlider = new QSlider(Qt::Horizontal, this);
m_volumeSlider->setRange(0, 100);
m_volumeSlider->setValue(100);
connect(m_volumeSlider, SIGNAL(valueChanged(int)), SLOT(sliderChanged(int)));
layout->addWidget(m_volumeSlider);
m_modeButton = new QPushButton(this); m_modeButton = new QPushButton(this);
m_modeButton->setText(PushModeLabel); m_modeButton->setText(PushModeLabel);
connect(m_modeButton, SIGNAL(clicked()), SLOT(toggleMode())); connect(m_modeButton, SIGNAL(clicked()), SLOT(toggleMode()));
@@ -281,6 +287,7 @@ void InputTest::createAudioInput()
m_audioInput = new QAudioInput(m_device, m_format, this); m_audioInput = new QAudioInput(m_device, m_format, this);
connect(m_audioInput, SIGNAL(notify()), SLOT(notified())); connect(m_audioInput, SIGNAL(notify()), SLOT(notified()));
connect(m_audioInput, SIGNAL(stateChanged(QAudio::State)), SLOT(stateChanged(QAudio::State))); connect(m_audioInput, SIGNAL(stateChanged(QAudio::State)), SLOT(stateChanged(QAudio::State)));
m_volumeSlider->setValue(m_audioInput->volume() * 100);
m_audioInfo->start(); m_audioInfo->start();
m_audioInput->start(m_audioInfo); m_audioInput->start(m_audioInfo);
} }
@@ -364,3 +371,9 @@ void InputTest::deviceChanged(int index)
m_device = m_deviceBox->itemData(index).value<QAudioDeviceInfo>(); m_device = m_deviceBox->itemData(index).value<QAudioDeviceInfo>();
createAudioInput(); createAudioInput();
} }
void InputTest::sliderChanged(int value)
{
if (m_audioInput)
m_audioInput->setVolume(qreal(value) / 100);
}

View File

@@ -48,6 +48,7 @@
#include <QPushButton> #include <QPushButton>
#include <QComboBox> #include <QComboBox>
#include <QByteArray> #include <QByteArray>
#include <QSlider>
#include <qaudioinput.h> #include <qaudioinput.h>
@@ -113,6 +114,7 @@ private slots:
void toggleSuspend(); void toggleSuspend();
void stateChanged(QAudio::State state); void stateChanged(QAudio::State state);
void deviceChanged(int index); void deviceChanged(int index);
void sliderChanged(int value);
private: private:
// Owned by layout // Owned by layout
@@ -120,6 +122,7 @@ private:
QPushButton *m_modeButton; QPushButton *m_modeButton;
QPushButton *m_suspendResumeButton; QPushButton *m_suspendResumeButton;
QComboBox *m_deviceBox; QComboBox *m_deviceBox;
QSlider *m_volumeSlider;
QAudioDeviceInfo m_device; QAudioDeviceInfo m_device;
AudioInfo *m_audioInfo; AudioInfo *m_audioInfo;

View File

@@ -326,6 +326,29 @@ int QAudioInput::notifyInterval() const
return d->notifyInterval(); return d->notifyInterval();
} }
/*!
Sets the input volume to \a volume.
If the device does not support adjusting the input
volume then \a volume will be ignored and the input
volume will remain at 1.0.
*/
void QAudioInput::setVolume(qreal volume)
{
d->setVolume(volume);
}
/*!
Returns the input volume (gain).
If the device does not support adjusting the input volume
the returned value will be 1.0.
*/
qreal QAudioInput::volume() const
{
return d->volume();
}
/*! /*!
Returns the amount of audio data processed since start() Returns the amount of audio data processed since start()
was called in microseconds. was called in microseconds.

View File

@@ -91,6 +91,9 @@ public:
void setNotifyInterval(int milliSeconds); void setNotifyInterval(int milliSeconds);
int notifyInterval() const; int notifyInterval() const;
void setVolume(qreal volume);
qreal volume() const;
qint64 processedUSecs() const; qint64 processedUSecs() const;
qint64 elapsedUSecs() const; qint64 elapsedUSecs() const;

View File

@@ -127,6 +127,8 @@ public:
virtual QAudio::State state() const = 0; virtual QAudio::State state() const = 0;
virtual void setFormat(const QAudioFormat& fmt) = 0; virtual void setFormat(const QAudioFormat& fmt) = 0;
virtual QAudioFormat format() const = 0; virtual QAudioFormat format() const = 0;
virtual void setVolume(qreal) {}
virtual qreal volume() const { return 1.0; }
Q_SIGNALS: Q_SIGNALS:
void errorChanged(QAudio::Error); void errorChanged(QAudio::Error);

View File

@@ -41,6 +41,7 @@
#include <QtCore/qcoreapplication.h> #include <QtCore/qcoreapplication.h>
#include <QtCore/qdebug.h> #include <QtCore/qdebug.h>
#include <QtCore/qmath.h>
#include "qaudioinput_pulse.h" #include "qaudioinput_pulse.h"
#include "qaudiodeviceinfo_pulse.h" #include "qaudiodeviceinfo_pulse.h"
@@ -51,6 +52,10 @@ QT_BEGIN_NAMESPACE
const int PeriodTimeMs = 50; const int PeriodTimeMs = 50;
// Map from void* (for userdata) to QPulseAudioInput instance
// protected by pulse mainloop lock
QMap<void *, QPulseAudioInput*> QPulseAudioInput::s_inputsMap;
static void inputStreamReadCallback(pa_stream *stream, size_t length, void *userdata) static void inputStreamReadCallback(pa_stream *stream, size_t length, void *userdata)
{ {
Q_UNUSED(userdata); Q_UNUSED(userdata);
@@ -123,11 +128,39 @@ static void inputStreamSuccessCallback(pa_stream *stream, int success, void *use
pa_threaded_mainloop_signal(pulseEngine->mainloop(), 0); 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);
QPulseAudioInput *that = QPulseAudioInput::s_inputsMap.value(userdata);
if (that && i) {
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 = QPulseAudioInput::s_inputsMap.value(userdata);
// Regardless of success or failure, we update the volume property
if (that && 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) QPulseAudioInput::QPulseAudioInput(const QByteArray &device)
: m_totalTimeValue(0) : m_totalTimeValue(0)
, m_audioSource(0) , m_audioSource(0)
, m_errorState(QAudio::NoError) , m_errorState(QAudio::NoError)
, m_deviceState(QAudio::StoppedState) , m_deviceState(QAudio::StoppedState)
, m_volume(qreal(1.0f))
, m_pullMode(true) , m_pullMode(true)
, m_opened(false) , m_opened(false)
, m_bytesAvailable(0) , m_bytesAvailable(0)
@@ -139,10 +172,20 @@ QPulseAudioInput::QPulseAudioInput(const QByteArray &device)
{ {
m_timer = new QTimer(this); m_timer = new QTimer(this);
connect(m_timer, SIGNAL(timeout()), SLOT(userFeed())); connect(m_timer, SIGNAL(timeout()), SLOT(userFeed()));
QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
pa_threaded_mainloop_lock(pulseEngine->mainloop());
s_inputsMap.insert(this, this);
pa_threaded_mainloop_unlock(pulseEngine->mainloop());
} }
QPulseAudioInput::~QPulseAudioInput() QPulseAudioInput::~QPulseAudioInput()
{ {
QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
pa_threaded_mainloop_lock(pulseEngine->mainloop());
s_inputsMap.remove(this);
pa_threaded_mainloop_unlock(pulseEngine->mainloop());
close(); close();
disconnect(m_timer, SIGNAL(timeout())); disconnect(m_timer, SIGNAL(timeout()));
QCoreApplication::processEvents(); QCoreApplication::processEvents();
@@ -248,6 +291,8 @@ bool QPulseAudioInput::open()
return false; return false;
} }
m_spec = spec;
#ifdef DEBUG_PULSE #ifdef DEBUG_PULSE
qDebug() << "Format: " << QPulseAudioInternal::sampleFormatToQString(spec.format); qDebug() << "Format: " << QPulseAudioInternal::sampleFormatToQString(spec.format);
qDebug() << "Rate: " << spec.rate; qDebug() << "Rate: " << spec.rate;
@@ -297,6 +342,9 @@ bool QPulseAudioInput::open()
while (pa_stream_get_state(m_stream) != PA_STREAM_READY) { while (pa_stream_get_state(m_stream) != PA_STREAM_READY) {
pa_threaded_mainloop_wait(pulseEngine->mainloop()); pa_threaded_mainloop_wait(pulseEngine->mainloop());
} }
setPulseVolume();
pa_threaded_mainloop_unlock(pulseEngine->mainloop()); pa_threaded_mainloop_unlock(pulseEngine->mainloop());
m_opened = true; m_opened = true;
@@ -333,6 +381,31 @@ void QPulseAudioInput::close()
m_opened = false; m_opened = false;
} }
/* Call this with the stream opened and the mainloop locked */
void QPulseAudioInput::setPulseVolume()
{
QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
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() int QPulseAudioInput::checkBytesReady()
{ {
if (m_deviceState != QAudio::ActiveState && m_deviceState != QAudio::IdleState) { if (m_deviceState != QAudio::ActiveState && m_deviceState != QAudio::IdleState) {
@@ -466,6 +539,31 @@ void QPulseAudioInput::resume()
} }
} }
void QPulseAudioInput::setVolume(qreal vol)
{
if (vol >= 0.0 && vol <= 1.0) {
QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
pa_threaded_mainloop_lock(pulseEngine->mainloop());
if (!qFuzzyCompare(m_volume, vol)) {
m_volume = vol;
if (m_opened) {
setPulseVolume();
}
}
pa_threaded_mainloop_unlock(pulseEngine->mainloop());
}
}
qreal QPulseAudioInput::volume() const
{
QPulseAudioEngine *pulseEngine = QPulseAudioEngine::instance();
pa_threaded_mainloop_lock(pulseEngine->mainloop());
qreal vol = m_volume;
pa_threaded_mainloop_unlock(pulseEngine->mainloop());
return vol;
}
void QPulseAudioInput::setBufferSize(int value) void QPulseAudioInput::setBufferSize(int value)
{ {
m_bufferSize = value; m_bufferSize = value;

View File

@@ -98,11 +98,16 @@ public:
void setFormat(const QAudioFormat &format); void setFormat(const QAudioFormat &format);
QAudioFormat format() const; QAudioFormat format() const;
void setVolume(qreal volume);
qreal volume() const;
qint64 m_totalTimeValue; qint64 m_totalTimeValue;
QIODevice *m_audioSource; QIODevice *m_audioSource;
QAudioFormat m_format; QAudioFormat m_format;
QAudio::Error m_errorState; QAudio::Error m_errorState;
QAudio::State m_deviceState; QAudio::State m_deviceState;
qreal m_volume;
pa_cvolume m_chVolume;
private slots: private slots:
void userFeed(); void userFeed();
@@ -112,6 +117,12 @@ private:
int checkBytesReady(); int checkBytesReady();
bool open(); bool open();
void close(); void close();
void setPulseVolume();
static QMap<void *, QPulseAudioInput*> s_inputsMap;
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_pullMode;
bool m_opened; bool m_opened;
@@ -130,6 +141,7 @@ private:
QByteArray m_streamName; QByteArray m_streamName;
QByteArray m_device; QByteArray m_device;
QByteArray m_tempBuffer; QByteArray m_tempBuffer;
pa_sample_spec m_spec;
}; };
class InputPrivate : public QIODevice class InputPrivate : public QIODevice

View File

@@ -101,6 +101,8 @@ private slots:
void reset(); void reset();
void volume();
private: private:
typedef QSharedPointer<QFile> FilePtr; typedef QSharedPointer<QFile> FilePtr;
@@ -840,6 +842,31 @@ void tst_QAudioInput::reset()
} }
} }
void tst_QAudioInput::volume()
{
const qreal half(0.5f);
const qreal one(1.0f);
// Hard to automatically test, but we can test the get/set a little
for (int i=0; i < testFormats.count(); i++) {
QAudioInput audioInput(testFormats.at(i), this);
qreal volume = audioInput.volume();
audioInput.setVolume(half);
QVERIFY(qFuzzyCompare(audioInput.volume(), half) || qFuzzyCompare(audioInput.volume(), one));
// Wait a while to see if this changes
QTest::qWait(500);
QVERIFY(qFuzzyCompare(audioInput.volume(), half) || qFuzzyCompare(audioInput.volume(), one));
audioInput.setVolume(volume);
QVERIFY(qFuzzyCompare(audioInput.volume(), volume));
// Wait a while to see if this changes
QTest::qWait(500);
QVERIFY(qFuzzyCompare(audioInput.volume(), volume));
}
}
QTEST_MAIN(tst_QAudioInput) QTEST_MAIN(tst_QAudioInput)
#include "tst_qaudioinput.moc" #include "tst_qaudioinput.moc"