From 1ac931864550bae89baed7575785b339fb2b1465 Mon Sep 17 00:00:00 2001 From: Kurt Korbatits Date: Fri, 6 Jul 2012 09:56:38 +1000 Subject: [PATCH] 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 Reviewed-by: Kurt Korbatits --- src/multimedia/audio/audio.pri | 4 +- src/multimedia/audio/qaudiodeviceinfo_alsa_p.cpp | 10 ++ src/multimedia/audio/qaudiohelpers.cpp | 117 +++++++++++++++++++++++ src/multimedia/audio/qaudiohelpers_p.h | 67 +++++++++++++ src/multimedia/audio/qaudioinput.cpp | 4 +- src/multimedia/audio/qaudioinput_alsa_p.cpp | 17 +++- src/multimedia/audio/qaudioinput_alsa_p.h | 3 + src/multimedia/audio/qaudiooutput.cpp | 1 + src/multimedia/audio/qaudiooutput_alsa_p.cpp | 35 +++++-- src/multimedia/audio/qaudiooutput_alsa_p.h | 4 + 10 files changed, 252 insertions(+), 10 deletions(-) create mode 100644 src/multimedia/audio/qaudiohelpers.cpp create mode 100644 src/multimedia/audio/qaudiohelpers_p.h diff --git a/src/multimedia/audio/audio.pri b/src/multimedia/audio/audio.pri index 6351bec..6fc99f9 100644 --- a/src/multimedia/audio/audio.pri +++ b/src/multimedia/audio/audio.pri @@ -19,6 +19,7 @@ PRIVATE_HEADERS += \ audio/qaudiodevicefactory_p.h \ audio/qwavedecoder_p.h \ audio/qsamplecache_p.h \ + audio/qaudiohelpers_p.h SOURCES += \ audio/qaudio.cpp \ @@ -35,7 +36,8 @@ SOURCES += \ audio/qsound.cpp \ audio/qaudiobuffer.cpp \ audio/qaudioprobe.cpp \ - audio/qaudiodecoder.cpp + audio/qaudiodecoder.cpp \ + audio/qaudiohelpers.cpp mac { PRIVATE_HEADERS += audio/qaudioinput_mac_p.h \ diff --git a/src/multimedia/audio/qaudiodeviceinfo_alsa_p.cpp b/src/multimedia/audio/qaudiodeviceinfo_alsa_p.cpp index 4ef96c7..0735041 100644 --- a/src/multimedia/audio/qaudiodeviceinfo_alsa_p.cpp +++ b/src/multimedia/audio/qaudiodeviceinfo_alsa_p.cpp @@ -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); else if(format.byteOrder() == QAudioFormat::BigEndian) 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); else if(format.byteOrder() == QAudioFormat::BigEndian) 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) { diff --git a/src/multimedia/audio/qaudiohelpers.cpp b/src/multimedia/audio/qaudiohelpers.cpp new file mode 100644 index 0000000..9949507 --- /dev/null +++ b/src/multimedia/audio/qaudiohelpers.cpp @@ -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 + +QT_BEGIN_NAMESPACE + +namespace QAudioHelperInternal +{ + +template 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 struct signedVersion {}; +template<> struct signedVersion +{ + typedef qint8 TS; + enum {offset = 0x80}; +}; + +template<> struct signedVersion +{ + typedef qint16 TS; + enum {offset = 0x8000}; +}; + +template<> struct signedVersion +{ + typedef qint32 TS; + enum {offset = 0x80000000}; +}; + +template 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::offset + ((typename signedVersion::TS)(pSrc[i] - signedVersion::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(factor,src,dest,samplesCount); + else if (format.sampleType() == QAudioFormat::UnSignedInt) + QAudioHelperInternal::adjustUnsignedSamples(factor,src,dest,samplesCount); + break; + case 16: + if (format.sampleType() == QAudioFormat::SignedInt) + QAudioHelperInternal::adjustSamples(factor,src,dest,samplesCount); + else if (format.sampleType() == QAudioFormat::UnSignedInt) + QAudioHelperInternal::adjustUnsignedSamples(factor,src,dest,samplesCount); + break; + default: + if (format.sampleType() == QAudioFormat::SignedInt) + QAudioHelperInternal::adjustSamples(factor,src,dest,samplesCount); + else if (format.sampleType() == QAudioFormat::UnSignedInt) + QAudioHelperInternal::adjustUnsignedSamples(factor,src,dest,samplesCount); + else if (format.sampleType() == QAudioFormat::Float) + QAudioHelperInternal::adjustSamples(factor,src,dest,samplesCount); + } +} +} + +QT_END_NAMESPACE diff --git a/src/multimedia/audio/qaudiohelpers_p.h b/src/multimedia/audio/qaudiohelpers_p.h new file mode 100644 index 0000000..6a8f1a0 --- /dev/null +++ b/src/multimedia/audio/qaudiohelpers_p.h @@ -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 + +QT_BEGIN_NAMESPACE + +namespace QAudioHelperInternal +{ +void qMultiplySamples(qreal factor, const QAudioFormat& format, const void *src, void* dest, int len); +} + +QT_END_NAMESPACE + +#endif diff --git a/src/multimedia/audio/qaudioinput.cpp b/src/multimedia/audio/qaudioinput.cpp index af6dc6e..c5abb5a 100644 --- a/src/multimedia/audio/qaudioinput.cpp +++ b/src/multimedia/audio/qaudioinput.cpp @@ -279,7 +279,7 @@ int QAudioInput::bufferSize() const /*! 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. */ @@ -332,6 +332,8 @@ int QAudioInput::notifyInterval() const 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. + + Note: Adjustments to the volume will change the volume of this audio stream, not the global volume. */ void QAudioInput::setVolume(qreal volume) { diff --git a/src/multimedia/audio/qaudioinput_alsa_p.cpp b/src/multimedia/audio/qaudioinput_alsa_p.cpp index 5a34318..fdeb58d 100644 --- a/src/multimedia/audio/qaudioinput_alsa_p.cpp +++ b/src/multimedia/audio/qaudioinput_alsa_p.cpp @@ -53,6 +53,7 @@ #include #include "qaudioinput_alsa_p.h" #include "qaudiodeviceinfo_alsa_p.h" +#include "qaudiohelpers_p.h" QT_BEGIN_NAMESPACE @@ -77,6 +78,8 @@ QAudioInputPrivate::QAudioInputPrivate(const QByteArray &device) pullMode = true; resuming = false; + m_volume = 1.0f; + m_device = device; timer = new QTimer(this); @@ -91,6 +94,16 @@ QAudioInputPrivate::~QAudioInputPrivate() delete timer; } +void QAudioInputPrivate::setVolume(qreal vol) +{ + m_volume = vol; +} + +qreal QAudioInputPrivate::volume() const +{ + return m_volume; +} + QAudio::Error QAudioInputPrivate::error() const { return errorState; @@ -537,9 +550,11 @@ qint64 QAudioInputPrivate::read(char* data, qint64 len) frames = 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) { - bytesRead = snd_pcm_frames_to_bytes(handle, readFrames); ringBuffer.write(buffer, bytesRead); #ifdef DEBUG_AUDIO qDebug() << QString::fromLatin1("read in bytes = %1 (frames=%2)").arg(bytesRead).arg(readFrames).toLatin1().constData(); diff --git a/src/multimedia/audio/qaudioinput_alsa_p.h b/src/multimedia/audio/qaudioinput_alsa_p.h index eb8da07..b8ba81f 100644 --- a/src/multimedia/audio/qaudioinput_alsa_p.h +++ b/src/multimedia/audio/qaudioinput_alsa_p.h @@ -126,6 +126,8 @@ public: QAudio::State state() const; void setFormat(const QAudioFormat& fmt); QAudioFormat format() const; + void setVolume(qreal); + qreal volume() const; bool resuming; snd_pcm_t* handle; qint64 totalTimeValue; @@ -166,6 +168,7 @@ private: snd_pcm_format_t pcmformat; snd_timestamp_t* timestamp; snd_pcm_hw_params_t *hwparams; + qreal m_volume; }; class InputPrivate : public QIODevice diff --git a/src/multimedia/audio/qaudiooutput.cpp b/src/multimedia/audio/qaudiooutput.cpp index 4150e8c..bdd40e6 100644 --- a/src/multimedia/audio/qaudiooutput.cpp +++ b/src/multimedia/audio/qaudiooutput.cpp @@ -349,6 +349,7 @@ QAudio::State QAudioOutput::state() const /*! Sets the volume. 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) { diff --git a/src/multimedia/audio/qaudiooutput_alsa_p.cpp b/src/multimedia/audio/qaudiooutput_alsa_p.cpp index 8da76d9..e80cf15 100644 --- a/src/multimedia/audio/qaudiooutput_alsa_p.cpp +++ b/src/multimedia/audio/qaudiooutput_alsa_p.cpp @@ -53,6 +53,7 @@ #include #include "qaudiooutput_alsa_p.h" #include "qaudiodeviceinfo_alsa_p.h" +#include "qaudiohelpers_p.h" QT_BEGIN_NAMESPACE @@ -81,6 +82,8 @@ QAudioOutputPrivate::QAudioOutputPrivate(const QByteArray &device) resuming = false; opened = false; + m_volume = 1.0f; + m_device = device; timer = new QTimer(this); @@ -95,6 +98,16 @@ QAudioOutputPrivate::~QAudioOutputPrivate() delete timer; } +void QAudioOutputPrivate::setVolume(qreal vol) +{ + m_volume = vol; +} + +qreal QAudioOutputPrivate::volume() const +{ + return m_volume; +} + QAudio::Error QAudioOutputPrivate::error() const { return errorState; @@ -571,15 +584,23 @@ qint64 QAudioOutputPrivate::write( const char *data, qint64 len ) #endif int frames, err; int space = bytesFree(); - if(len < space) { - // Just write it - frames = snd_pcm_bytes_to_frames( handle, (int)len ); - err = snd_pcm_writei( handle, data, frames ); + + if (!space) + return 0; + + 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 { - // 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) { totalTimeValue += err; resuming = false; diff --git a/src/multimedia/audio/qaudiooutput_alsa_p.h b/src/multimedia/audio/qaudiooutput_alsa_p.h index 71d57e8..4bdb9ac 100644 --- a/src/multimedia/audio/qaudiooutput_alsa_p.h +++ b/src/multimedia/audio/qaudiooutput_alsa_p.h @@ -103,6 +103,9 @@ public: QAudio::State state() const; void setFormat(const QAudioFormat& fmt); QAudioFormat format() const; + void setVolume(qreal); + qreal volume() const; + QIODevice* audioSource; QAudioFormat settings; @@ -150,6 +153,7 @@ private: snd_pcm_format_t pcmformat; snd_timestamp_t* timestamp; snd_pcm_hw_params_t *hwparams; + qreal m_volume; }; class OutputPrivate : public QIODevice -- 2.7.4