Added volume control for QAudioOutput & QAudioInput (alsa)

QTBUG-25454

- Added update to docs on volume control.
- Added internal volume adjustment for alsa implementation.
- Enabled float sample option in QAudioDeviceInfo (alsa).

Change-Id: I6b89fc8beb457d71be9ad71b538c86a008570f07
Reviewed-by: Michael Goddard <michael.goddard@nokia.com>
Reviewed-by: Kurt Korbatits  <kurt.korbatits@nokia.com>
This commit is contained in:
Kurt Korbatits
2012-07-06 09:56:38 +10:00
committed by Qt by Nokia
parent 8c12864361
commit 1ac9318645
10 changed files with 252 additions and 10 deletions

View File

@@ -19,6 +19,7 @@ PRIVATE_HEADERS += \
audio/qaudiodevicefactory_p.h \ audio/qaudiodevicefactory_p.h \
audio/qwavedecoder_p.h \ audio/qwavedecoder_p.h \
audio/qsamplecache_p.h \ audio/qsamplecache_p.h \
audio/qaudiohelpers_p.h
SOURCES += \ SOURCES += \
audio/qaudio.cpp \ audio/qaudio.cpp \
@@ -35,7 +36,8 @@ SOURCES += \
audio/qsound.cpp \ audio/qsound.cpp \
audio/qaudiobuffer.cpp \ audio/qaudiobuffer.cpp \
audio/qaudioprobe.cpp \ audio/qaudioprobe.cpp \
audio/qaudiodecoder.cpp audio/qaudiodecoder.cpp \
audio/qaudiohelpers.cpp
mac { mac {
PRIVATE_HEADERS += audio/qaudioinput_mac_p.h \ PRIVATE_HEADERS += audio/qaudioinput_mac_p.h \

View File

@@ -286,6 +286,11 @@ bool QAudioDeviceInfoInternal::testSettings(const QAudioFormat& format) const
err = snd_pcm_hw_params_set_format(handle,params,SND_PCM_FORMAT_U32_LE); err = snd_pcm_hw_params_set_format(handle,params,SND_PCM_FORMAT_U32_LE);
else if(format.byteOrder() == QAudioFormat::BigEndian) else if(format.byteOrder() == QAudioFormat::BigEndian)
err = snd_pcm_hw_params_set_format(handle,params,SND_PCM_FORMAT_U32_BE); err = snd_pcm_hw_params_set_format(handle,params,SND_PCM_FORMAT_U32_BE);
} else if (format.sampleType() == QAudioFormat::Float) {
if (format.byteOrder() == QAudioFormat::LittleEndian)
err = snd_pcm_hw_params_set_format(handle,params,SND_PCM_FORMAT_FLOAT_LE);
else if (format.byteOrder() == QAudioFormat::BigEndian)
err = snd_pcm_hw_params_set_format(handle,params,SND_PCM_FORMAT_FLOAT_BE);
} }
} }
@@ -344,6 +349,11 @@ bool QAudioDeviceInfoInternal::testSettings(const QAudioFormat& format) const
err = snd_pcm_hw_params_set_format(handle,params,SND_PCM_FORMAT_U32_LE); err = snd_pcm_hw_params_set_format(handle,params,SND_PCM_FORMAT_U32_LE);
else if(format.byteOrder() == QAudioFormat::BigEndian) else if(format.byteOrder() == QAudioFormat::BigEndian)
err = snd_pcm_hw_params_set_format(handle,params,SND_PCM_FORMAT_U32_BE); err = snd_pcm_hw_params_set_format(handle,params,SND_PCM_FORMAT_U32_BE);
} else if (format.sampleType() == QAudioFormat::Float) {
if (format.byteOrder() == QAudioFormat::LittleEndian)
err = snd_pcm_hw_params_set_format(handle,params,SND_PCM_FORMAT_FLOAT_LE);
else if (format.byteOrder() == QAudioFormat::BigEndian)
err = snd_pcm_hw_params_set_format(handle,params,SND_PCM_FORMAT_FLOAT_BE);
} }
} }
if(err>=0) { if(err>=0) {

View File

@@ -0,0 +1,117 @@
/****************************************************************************
**
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/
**
** This file is part of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** 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, Nokia gives you certain additional
** rights. These rights are described in the Nokia 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.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include "qaudiohelpers_p.h"
#include <QDebug>
QT_BEGIN_NAMESPACE
namespace QAudioHelperInternal
{
template<class T> void adjustSamples(qreal factor, const void *src, void *dst, int samples)
{
const T *pSrc = (const T *)src;
T *pDst = (T*)dst;
for ( int i = 0; i < samples; i++ )
pDst[i] = pSrc[i] * factor;
}
// Unsigned samples are biased around 0x80/0x8000 :/
// This makes a pure template solution a bit unwieldy but possible
template<class T> struct signedVersion {};
template<> struct signedVersion<quint8>
{
typedef qint8 TS;
enum {offset = 0x80};
};
template<> struct signedVersion<quint16>
{
typedef qint16 TS;
enum {offset = 0x8000};
};
template<> struct signedVersion<quint32>
{
typedef qint32 TS;
enum {offset = 0x80000000};
};
template<class T> void adjustUnsignedSamples(qreal factor, const void *src, void *dst, int samples)
{
const T *pSrc = (const T *)src;
T *pDst = (T*)dst;
for ( int i = 0; i < samples; i++ ) {
pDst[i] = signedVersion<T>::offset + ((typename signedVersion<T>::TS)(pSrc[i] - signedVersion<T>::offset) * factor);
}
}
void qMultiplySamples(qreal factor, const QAudioFormat &format, const void* src, void* dest, int len)
{
int samplesCount = len / (format.sampleSize()/8);
switch ( format.sampleSize() ) {
case 8:
if (format.sampleType() == QAudioFormat::SignedInt)
QAudioHelperInternal::adjustSamples<qint8>(factor,src,dest,samplesCount);
else if (format.sampleType() == QAudioFormat::UnSignedInt)
QAudioHelperInternal::adjustUnsignedSamples<quint8>(factor,src,dest,samplesCount);
break;
case 16:
if (format.sampleType() == QAudioFormat::SignedInt)
QAudioHelperInternal::adjustSamples<qint16>(factor,src,dest,samplesCount);
else if (format.sampleType() == QAudioFormat::UnSignedInt)
QAudioHelperInternal::adjustUnsignedSamples<quint16>(factor,src,dest,samplesCount);
break;
default:
if (format.sampleType() == QAudioFormat::SignedInt)
QAudioHelperInternal::adjustSamples<qint32>(factor,src,dest,samplesCount);
else if (format.sampleType() == QAudioFormat::UnSignedInt)
QAudioHelperInternal::adjustUnsignedSamples<quint32>(factor,src,dest,samplesCount);
else if (format.sampleType() == QAudioFormat::Float)
QAudioHelperInternal::adjustSamples<float>(factor,src,dest,samplesCount);
}
}
}
QT_END_NAMESPACE

View File

@@ -0,0 +1,67 @@
/****************************************************************************
**
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/
**
** This file is part of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** GNU Lesser General Public License Usage
** 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, Nokia gives you certain additional
** rights. These rights are described in the Nokia 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.
**
** Other Usage
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
**
**
**
**
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QAUDIOHELPERS_H
#define QAUDIOHELPERS_H
//
// 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.
//
// We mean it.
//
#include <qaudioformat.h>
QT_BEGIN_NAMESPACE
namespace QAudioHelperInternal
{
void qMultiplySamples(qreal factor, const QAudioFormat& format, const void *src, void* dest, int len);
}
QT_END_NAMESPACE
#endif

View File

@@ -279,7 +279,7 @@ int QAudioInput::bufferSize() const
/*! /*!
Returns the amount of audio data available to read in bytes. Returns the amount of audio data available to read in bytes.
NOTE: returned value is only valid while in QAudio::ActiveState or QAudio::IdleState Note: returned value is only valid while in QAudio::ActiveState or QAudio::IdleState
state, otherwise returns zero. state, otherwise returns zero.
*/ */
@@ -332,6 +332,8 @@ int QAudioInput::notifyInterval() const
If the device does not support adjusting the input If the device does not support adjusting the input
volume then \a volume will be ignored and the input volume then \a volume will be ignored and the input
volume will remain at 1.0. volume will remain at 1.0.
Note: Adjustments to the volume will change the volume of this audio stream, not the global volume.
*/ */
void QAudioInput::setVolume(qreal volume) void QAudioInput::setVolume(qreal volume)
{ {

View File

@@ -53,6 +53,7 @@
#include <QtCore/qcoreapplication.h> #include <QtCore/qcoreapplication.h>
#include "qaudioinput_alsa_p.h" #include "qaudioinput_alsa_p.h"
#include "qaudiodeviceinfo_alsa_p.h" #include "qaudiodeviceinfo_alsa_p.h"
#include "qaudiohelpers_p.h"
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@@ -77,6 +78,8 @@ QAudioInputPrivate::QAudioInputPrivate(const QByteArray &device)
pullMode = true; pullMode = true;
resuming = false; resuming = false;
m_volume = 1.0f;
m_device = device; m_device = device;
timer = new QTimer(this); timer = new QTimer(this);
@@ -91,6 +94,16 @@ QAudioInputPrivate::~QAudioInputPrivate()
delete timer; delete timer;
} }
void QAudioInputPrivate::setVolume(qreal vol)
{
m_volume = vol;
}
qreal QAudioInputPrivate::volume() const
{
return m_volume;
}
QAudio::Error QAudioInputPrivate::error() const QAudio::Error QAudioInputPrivate::error() const
{ {
return errorState; return errorState;
@@ -537,9 +550,11 @@ qint64 QAudioInputPrivate::read(char* data, qint64 len)
frames = buffer_frames; frames = buffer_frames;
int readFrames = snd_pcm_readi(handle, buffer, frames); int readFrames = snd_pcm_readi(handle, buffer, frames);
bytesRead = snd_pcm_frames_to_bytes(handle, readFrames);
if (m_volume < 1.0f)
QAudioHelperInternal::qMultiplySamples(m_volume, settings, buffer, buffer, bytesRead);
if (readFrames >= 0) { if (readFrames >= 0) {
bytesRead = snd_pcm_frames_to_bytes(handle, readFrames);
ringBuffer.write(buffer, bytesRead); ringBuffer.write(buffer, bytesRead);
#ifdef DEBUG_AUDIO #ifdef DEBUG_AUDIO
qDebug() << QString::fromLatin1("read in bytes = %1 (frames=%2)").arg(bytesRead).arg(readFrames).toLatin1().constData(); qDebug() << QString::fromLatin1("read in bytes = %1 (frames=%2)").arg(bytesRead).arg(readFrames).toLatin1().constData();

View File

@@ -126,6 +126,8 @@ public:
QAudio::State state() const; QAudio::State state() const;
void setFormat(const QAudioFormat& fmt); void setFormat(const QAudioFormat& fmt);
QAudioFormat format() const; QAudioFormat format() const;
void setVolume(qreal);
qreal volume() const;
bool resuming; bool resuming;
snd_pcm_t* handle; snd_pcm_t* handle;
qint64 totalTimeValue; qint64 totalTimeValue;
@@ -166,6 +168,7 @@ private:
snd_pcm_format_t pcmformat; snd_pcm_format_t pcmformat;
snd_timestamp_t* timestamp; snd_timestamp_t* timestamp;
snd_pcm_hw_params_t *hwparams; snd_pcm_hw_params_t *hwparams;
qreal m_volume;
}; };
class InputPrivate : public QIODevice class InputPrivate : public QIODevice

View File

@@ -349,6 +349,7 @@ QAudio::State QAudioOutput::state() const
/*! /*!
Sets the volume. Sets the volume.
Where \a volume is between 0.0 and 1.0 inclusive. Where \a volume is between 0.0 and 1.0 inclusive.
Note: Adjustments to the volume will change the volume of this audio stream, not the global volume.
*/ */
void QAudioOutput::setVolume(qreal volume) void QAudioOutput::setVolume(qreal volume)
{ {

View File

@@ -53,6 +53,7 @@
#include <QtCore/qcoreapplication.h> #include <QtCore/qcoreapplication.h>
#include "qaudiooutput_alsa_p.h" #include "qaudiooutput_alsa_p.h"
#include "qaudiodeviceinfo_alsa_p.h" #include "qaudiodeviceinfo_alsa_p.h"
#include "qaudiohelpers_p.h"
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
@@ -81,6 +82,8 @@ QAudioOutputPrivate::QAudioOutputPrivate(const QByteArray &device)
resuming = false; resuming = false;
opened = false; opened = false;
m_volume = 1.0f;
m_device = device; m_device = device;
timer = new QTimer(this); timer = new QTimer(this);
@@ -95,6 +98,16 @@ QAudioOutputPrivate::~QAudioOutputPrivate()
delete timer; delete timer;
} }
void QAudioOutputPrivate::setVolume(qreal vol)
{
m_volume = vol;
}
qreal QAudioOutputPrivate::volume() const
{
return m_volume;
}
QAudio::Error QAudioOutputPrivate::error() const QAudio::Error QAudioOutputPrivate::error() const
{ {
return errorState; return errorState;
@@ -571,15 +584,23 @@ qint64 QAudioOutputPrivate::write( const char *data, qint64 len )
#endif #endif
int frames, err; int frames, err;
int space = bytesFree(); int space = bytesFree();
if(len < space) {
// Just write it if (!space)
frames = snd_pcm_bytes_to_frames( handle, (int)len ); return 0;
err = snd_pcm_writei( handle, data, frames );
if (len < space)
space = len;
frames = snd_pcm_bytes_to_frames(handle, space);
if (m_volume < 1.0f) {
char out[space];
QAudioHelperInternal::qMultiplySamples(m_volume, settings, data, out, space);
err = snd_pcm_writei(handle, out, frames);
} else { } else {
// Only write space worth
frames = snd_pcm_bytes_to_frames( handle, (int)space );
err = snd_pcm_writei(handle, data, frames); err = snd_pcm_writei(handle, data, frames);
} }
if(err > 0) { if(err > 0) {
totalTimeValue += err; totalTimeValue += err;
resuming = false; resuming = false;

View File

@@ -103,6 +103,9 @@ public:
QAudio::State state() const; QAudio::State state() const;
void setFormat(const QAudioFormat& fmt); void setFormat(const QAudioFormat& fmt);
QAudioFormat format() const; QAudioFormat format() const;
void setVolume(qreal);
qreal volume() const;
QIODevice* audioSource; QIODevice* audioSource;
QAudioFormat settings; QAudioFormat settings;
@@ -150,6 +153,7 @@ private:
snd_pcm_format_t pcmformat; snd_pcm_format_t pcmformat;
snd_timestamp_t* timestamp; snd_timestamp_t* timestamp;
snd_pcm_hw_params_t *hwparams; snd_pcm_hw_params_t *hwparams;
qreal m_volume;
}; };
class OutputPrivate : public QIODevice class OutputPrivate : public QIODevice