audioinput.cpp Example File
multimedia/audioinput/audioinput.cpp
/****************************************************************************
**
** Copyright (C) 2017 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <stdlib.h>
#include <math.h>
#include <QDateTime>
#include <QDebug>
#include <QPainter>
#include <QVBoxLayout>
#include <QAudioDeviceInfo>
#include <QAudioInput>
#include <qendian.h>
#include "audioinput.h"
#define PUSH_MODE_LABEL "Enable push mode"
#define PULL_MODE_LABEL "Enable pull mode"
#define SUSPEND_LABEL "Suspend recording"
#define RESUME_LABEL "Resume recording"
const int BufferSize = 4096 ;
AudioInfo:: AudioInfo(const QAudioFormat & format, QObject * parent)
: QIODevice (parent)
, m_format(format)
, m_maxAmplitude(0 )
, m_level(0.0 )
{
switch (m_format. sampleSize()) {
case 8 :
switch (m_format. sampleType()) {
case QAudioFormat :: UnSignedInt:
m_maxAmplitude = 255 ;
break ;
case QAudioFormat :: SignedInt:
m_maxAmplitude = 127 ;
break ;
default :
break ;
}
break ;
case 16 :
switch (m_format. sampleType()) {
case QAudioFormat :: UnSignedInt:
m_maxAmplitude = 65535 ;
break ;
case QAudioFormat :: SignedInt:
m_maxAmplitude = 32767 ;
break ;
default :
break ;
}
break ;
case 32 :
switch (m_format. sampleType()) {
case QAudioFormat :: UnSignedInt:
m_maxAmplitude = 0xffffffff ;
break ;
case QAudioFormat :: SignedInt:
m_maxAmplitude = 0x7fffffff ;
break ;
case QAudioFormat :: Float:
m_maxAmplitude = 0x7fffffff ; // Kind of
default :
break ;
}
break ;
default :
break ;
}
}
AudioInfo:: ~ AudioInfo()
{
}
void AudioInfo:: start()
{
open(QIODevice :: WriteOnly);
}
void AudioInfo:: stop()
{
close();
}
qint64 AudioInfo:: readData(char * data, qint64 maxlen)
{
Q_UNUSED(data)
Q_UNUSED(maxlen)
return 0 ;
}
qint64 AudioInfo:: writeData(const char * data, qint64 len)
{
if (m_maxAmplitude) {
Q_ASSERT(m_format. sampleSize() % 8 = = 0 );
const int channelBytes = m_format. sampleSize() / 8 ;
const int sampleBytes = m_format. channelCount() * channelBytes;
Q_ASSERT(len % sampleBytes = = 0 );
const int numSamples = len / sampleBytes;
quint32 maxValue = 0 ;
const unsigned char * ptr = reinterpret_cast < const unsigned char * > (data);
for (int i = 0 ; i < numSamples; + + i) {
for (int j = 0 ; j < m_format. channelCount(); + + j) {
quint32 value = 0 ;
if (m_format. sampleSize() = = 8 & & m_format. sampleType() = = QAudioFormat :: UnSignedInt) {
value = * reinterpret_cast < const quint8 * > (ptr);
} else if (m_format. sampleSize() = = 8 & & m_format. sampleType() = = QAudioFormat :: SignedInt) {
value = qAbs (* reinterpret_cast < const qint8 * > (ptr));
} else if (m_format. sampleSize() = = 16 & & m_format. sampleType() = = QAudioFormat :: UnSignedInt) {
if (m_format. byteOrder() = = QAudioFormat :: LittleEndian)
value = qFromLittleEndian < quint16 > (ptr);
else
value = qFromBigEndian < quint16 > (ptr);
} else if (m_format. sampleSize() = = 16 & & m_format. sampleType() = = QAudioFormat :: SignedInt) {
if (m_format. byteOrder() = = QAudioFormat :: LittleEndian)
value = qAbs (qFromLittleEndian < qint16 > (ptr));
else
value = qAbs (qFromBigEndian < qint16 > (ptr));
} else if (m_format. sampleSize() = = 32 & & m_format. sampleType() = = QAudioFormat :: UnSignedInt) {
if (m_format. byteOrder() = = QAudioFormat :: LittleEndian)
value = qFromLittleEndian < quint32 > (ptr);
else
value = qFromBigEndian < quint32 > (ptr);
} else if (m_format. sampleSize() = = 32 & & m_format. sampleType() = = QAudioFormat :: SignedInt) {
if (m_format. byteOrder() = = QAudioFormat :: LittleEndian)
value = qAbs (qFromLittleEndian < qint32 > (ptr));
else
value = qAbs (qFromBigEndian < qint32 > (ptr));
} else if (m_format. sampleSize() = = 32 & & m_format. sampleType() = = QAudioFormat :: Float) {
value = qAbs (* reinterpret_cast < const float * > (ptr) * 0x7fffffff ); // assumes 0-1.0
}
maxValue = qMax (value, maxValue);
ptr + = channelBytes;
}
}
maxValue = qMin (maxValue, m_maxAmplitude);
m_level = qreal (maxValue) / m_maxAmplitude;
}
emit update();
return len;
}
RenderArea:: RenderArea(QWidget * parent)
: QWidget (parent)
{
setBackgroundRole(QPalette :: Base);
setAutoFillBackground(true );
m_level = 0 ;
setMinimumHeight(30 );
setMinimumWidth(200 );
}
void RenderArea:: paintEvent(QPaintEvent * /* event */ )
{
QPainter painter(this );
painter. setPen(Qt :: black);
painter. drawRect(QRect (painter. viewport(). left()+ 10 ,
painter. viewport(). top()+ 10 ,
painter. viewport(). right()- 20 ,
painter. viewport(). bottom()- 20 ));
if (m_level = = 0.0 )
return ;
int pos = ((painter. viewport(). right()- 20 )- (painter. viewport(). left()+ 11 ))* m_level;
painter. fillRect(painter. viewport(). left()+ 11 ,
painter. viewport(). top()+ 10 ,
pos,
painter. viewport(). height()- 21 ,
Qt :: red);
}
void RenderArea:: setLevel(qreal value)
{
m_level = value;
update();
}
InputTest:: InputTest()
: m_canvas(0 )
, m_modeButton(0 )
, m_suspendResumeButton(0 )
, m_deviceBox(0 )
, m_device(QAudioDeviceInfo :: defaultInputDevice())
, m_audioInfo(0 )
, m_audioInput(0 )
, m_input(0 )
, m_pullMode(true )
, m_buffer(BufferSize, 0 )
{
initializeWindow();
initializeAudio();
}
InputTest:: ~ InputTest() {}
void InputTest:: initializeWindow()
{
QScopedPointer < QWidget > window(new QWidget );
QScopedPointer < QVBoxLayout > layout(new QVBoxLayout );
m_canvas = new RenderArea(this );
layout- > addWidget(m_canvas);
m_deviceBox = new QComboBox (this );
const QAudioDeviceInfo & defaultDeviceInfo = QAudioDeviceInfo :: defaultInputDevice();
m_deviceBox- > addItem(defaultDeviceInfo. deviceName(), qVariantFromValue (defaultDeviceInfo));
foreach (const QAudioDeviceInfo & deviceInfo, QAudioDeviceInfo :: availableDevices(QAudio :: AudioInput)) {
if (deviceInfo ! = defaultDeviceInfo)
m_deviceBox- > addItem(deviceInfo. deviceName(), qVariantFromValue (deviceInfo));
}
connect(m_deviceBox, SIGNAL(activated(int )), SLOT(deviceChanged(int )));
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- > setText(tr(PUSH_MODE_LABEL));
connect(m_modeButton, SIGNAL(clicked()), SLOT(toggleMode()));
layout- > addWidget(m_modeButton);
m_suspendResumeButton = new QPushButton (this );
m_suspendResumeButton- > setText(tr(SUSPEND_LABEL));
connect(m_suspendResumeButton, SIGNAL(clicked()), SLOT(toggleSuspend()));
layout- > addWidget(m_suspendResumeButton);
window- > setLayout(layout. data());
layout. take(); // ownership transferred
setCentralWidget(window. data());
QWidget * const windowPtr = window. take(); // ownership transferred
windowPtr- > show();
}
void InputTest:: initializeAudio()
{
m_format. setSampleRate(8000 );
m_format. setChannelCount(1 );
m_format. setSampleSize(16 );
m_format. setSampleType(QAudioFormat :: SignedInt);
m_format. setByteOrder(QAudioFormat :: LittleEndian);
m_format. setCodec("audio/pcm" );
QAudioDeviceInfo info(m_device);
if (! info. isFormatSupported(m_format)) {
qWarning () < < "Default format not supported - trying to use nearest" ;
m_format = info. nearestFormat(m_format);
}
if (m_audioInfo)
delete m_audioInfo;
m_audioInfo = new AudioInfo(m_format, this );
connect(m_audioInfo, SIGNAL(update()), SLOT(refreshDisplay()));
createAudioInput();
}
void InputTest:: createAudioInput()
{
m_audioInput = new QAudioInput (m_device, m_format, this );
qreal initialVolume = QAudio :: convertVolume(m_audioInput- > volume(),
QAudio :: LinearVolumeScale,
QAudio :: LogarithmicVolumeScale);
m_volumeSlider- > setValue(qRound (initialVolume * 100 ));
m_audioInfo- > start();
m_audioInput- > start(m_audioInfo);
}
void InputTest:: readMore()
{
if (! m_audioInput)
return ;
qint64 len = m_audioInput- > bytesReady();
if (len > BufferSize)
len = BufferSize;
qint64 l = m_input- > read(m_buffer. data(), len);
if (l > 0 )
m_audioInfo- > write(m_buffer. constData(), l);
}
void InputTest:: toggleMode()
{
// Change bewteen pull and push modes
m_audioInput- > stop();
if (m_pullMode) {
m_modeButton- > setText(tr(PULL_MODE_LABEL));
m_input = m_audioInput- > start();
connect(m_input, SIGNAL(readyRead()), SLOT(readMore()));
m_pullMode = false ;
} else {
m_modeButton- > setText(tr(PUSH_MODE_LABEL));
m_pullMode = true ;
m_audioInput- > start(m_audioInfo);
}
m_suspendResumeButton- > setText(tr(SUSPEND_LABEL));
}
void InputTest:: toggleSuspend()
{
// toggle suspend/resume
if (m_audioInput- > state() = = QAudio :: SuspendedState) {
m_audioInput- > resume();
m_suspendResumeButton- > setText(tr(SUSPEND_LABEL));
} else if (m_audioInput- > state() = = QAudio :: ActiveState) {
m_audioInput- > suspend();
m_suspendResumeButton- > setText(tr(RESUME_LABEL));
} else if (m_audioInput- > state() = = QAudio :: StoppedState) {
m_audioInput- > resume();
m_suspendResumeButton- > setText(tr(SUSPEND_LABEL));
} else if (m_audioInput- > state() = = QAudio :: IdleState) {
// no-op
}
}
void InputTest:: refreshDisplay()
{
m_canvas- > setLevel(m_audioInfo- > level());
}
void InputTest:: deviceChanged(int index)
{
m_audioInfo- > stop();
m_audioInput- > stop();
m_audioInput- > disconnect(this );
delete m_audioInput;
m_device = m_deviceBox- > itemData(index). value< QAudioDeviceInfo > ();
initializeAudio();
}
void InputTest:: sliderChanged(int value)
{
if (m_audioInput) {
qreal linearVolume = QAudio :: convertVolume(value / qreal (100 ),
QAudio :: LogarithmicVolumeScale,
QAudio :: LinearVolumeScale);
m_audioInput- > setVolume(linearVolume);
}
}