Commit cd39b372 authored by VaL Doroshchuk's avatar VaL Doroshchuk
Browse files

Modernize the Audio Output example


Changed initialization style.
Recoded toggleMode.
Removed unneeded defines, function members.
Changed signal/slot connection style.
Changed foreach to c++11 style.
Fixed bug with wrong duration seconds.

Task-number: QTBUG-60627
Change-Id: Ib62f7979f2a32d629482026e0d954612b2665d66
Reviewed-by: default avatarChristian Stromme <christian.stromme@qt.io>
parent 1a8b30c1
No related merge requests found
Showing with 113 additions and 165 deletions
...@@ -38,6 +38,8 @@ ...@@ -38,6 +38,8 @@
** **
****************************************************************************/ ****************************************************************************/
#include "audiooutput.h"
#include <QAudioDeviceInfo> #include <QAudioDeviceInfo>
#include <QAudioOutput> #include <QAudioOutput>
#include <QDebug> #include <QDebug>
...@@ -45,36 +47,14 @@ ...@@ -45,36 +47,14 @@
#include <qmath.h> #include <qmath.h>
#include <qendian.h> #include <qendian.h>
#include "audiooutput.h" Generator::Generator(const QAudioFormat &format
, qint64 durationUs
#define PUSH_MODE_LABEL "Enable push mode" , int sampleRate)
#define PULL_MODE_LABEL "Enable pull mode"
#define SUSPEND_LABEL "Suspend playback"
#define RESUME_LABEL "Resume playback"
#define VOLUME_LABEL "Volume:"
const int DurationSeconds = 1;
const int ToneSampleRateHz = 600;
const int DataSampleRateHz = 44100;
const int BufferSize = 32768;
Generator::Generator(const QAudioFormat &format,
qint64 durationUs,
int sampleRate,
QObject *parent)
: QIODevice(parent)
, m_pos(0)
{ {
if (format.isValid()) if (format.isValid())
generateData(format, durationUs, sampleRate); generateData(format, durationUs, sampleRate);
} }
Generator::~Generator()
{
}
void Generator::start() void Generator::start()
{ {
open(QIODevice::ReadOnly); open(QIODevice::ReadOnly);
...@@ -90,10 +70,8 @@ void Generator::generateData(const QAudioFormat &format, qint64 durationUs, int ...@@ -90,10 +70,8 @@ void Generator::generateData(const QAudioFormat &format, qint64 durationUs, int
{ {
const int channelBytes = format.sampleSize() / 8; const int channelBytes = format.sampleSize() / 8;
const int sampleBytes = format.channelCount() * channelBytes; const int sampleBytes = format.channelCount() * channelBytes;
qint64 length = (format.sampleRate() * format.channelCount() * (format.sampleSize() / 8)) qint64 length = (format.sampleRate() * format.channelCount() * (format.sampleSize() / 8))
* durationUs / 100000; * durationUs / 1000000;
Q_ASSERT(length % sampleBytes == 0); Q_ASSERT(length % sampleBytes == 0);
Q_UNUSED(sampleBytes) // suppress warning in release builds Q_UNUSED(sampleBytes) // suppress warning in release builds
...@@ -102,32 +80,36 @@ void Generator::generateData(const QAudioFormat &format, qint64 durationUs, int ...@@ -102,32 +80,36 @@ void Generator::generateData(const QAudioFormat &format, qint64 durationUs, int
int sampleIndex = 0; int sampleIndex = 0;
while (length) { while (length) {
const qreal x = qSin(2 * M_PI * sampleRate * qreal(sampleIndex % format.sampleRate()) / format.sampleRate()); // Produces value (-1..1)
const qreal x = qSin(2 * M_PI * sampleRate * qreal(sampleIndex++ % format.sampleRate()) / format.sampleRate());
for (int i=0; i<format.channelCount(); ++i) { for (int i=0; i<format.channelCount(); ++i) {
if (format.sampleSize() == 8 && format.sampleType() == QAudioFormat::UnSignedInt) { if (format.sampleSize() == 8) {
const quint8 value = static_cast<quint8>((1.0 + x) / 2 * 255); if (format.sampleType() == QAudioFormat::UnSignedInt) {
*reinterpret_cast<quint8*>(ptr) = value; const quint8 value = static_cast<quint8>((1.0 + x) / 2 * 255);
} else if (format.sampleSize() == 8 && format.sampleType() == QAudioFormat::SignedInt) { *reinterpret_cast<quint8 *>(ptr) = value;
const qint8 value = static_cast<qint8>(x * 127); } else if (format.sampleType() == QAudioFormat::SignedInt) {
*reinterpret_cast<quint8*>(ptr) = value; const qint8 value = static_cast<qint8>(x * 127);
} else if (format.sampleSize() == 16 && format.sampleType() == QAudioFormat::UnSignedInt) { *reinterpret_cast<qint8 *>(ptr) = value;
quint16 value = static_cast<quint16>((1.0 + x) / 2 * 65535); }
if (format.byteOrder() == QAudioFormat::LittleEndian) } else if (format.sampleSize() == 16) {
qToLittleEndian<quint16>(value, ptr); if (format.sampleType() == QAudioFormat::UnSignedInt) {
else quint16 value = static_cast<quint16>((1.0 + x) / 2 * 65535);
qToBigEndian<quint16>(value, ptr); if (format.byteOrder() == QAudioFormat::LittleEndian)
} else if (format.sampleSize() == 16 && format.sampleType() == QAudioFormat::SignedInt) { qToLittleEndian<quint16>(value, ptr);
qint16 value = static_cast<qint16>(x * 32767); else
if (format.byteOrder() == QAudioFormat::LittleEndian) qToBigEndian<quint16>(value, ptr);
qToLittleEndian<qint16>(value, ptr); } else if (format.sampleType() == QAudioFormat::SignedInt) {
else qint16 value = static_cast<qint16>(x * 32767);
qToBigEndian<qint16>(value, ptr); if (format.byteOrder() == QAudioFormat::LittleEndian)
qToLittleEndian<qint16>(value, ptr);
else
qToBigEndian<qint16>(value, ptr);
}
} }
ptr += channelBytes; ptr += channelBytes;
length -= channelBytes; length -= channelBytes;
} }
++sampleIndex;
} }
} }
...@@ -159,177 +141,149 @@ qint64 Generator::bytesAvailable() const ...@@ -159,177 +141,149 @@ qint64 Generator::bytesAvailable() const
} }
AudioTest::AudioTest() AudioTest::AudioTest()
: m_pushTimer(new QTimer(this)) : m_pushTimer(new QTimer(this))
, m_modeButton(0)
, m_suspendResumeButton(0)
, m_deviceBox(0)
, m_device(QAudioDeviceInfo::defaultOutputDevice())
, m_generator(0)
, m_audioOutput(0)
, m_output(0)
, m_pullMode(true)
, m_buffer(BufferSize, 0)
{ {
initializeWindow(); initializeWindow();
initializeAudio(); initializeAudio(QAudioDeviceInfo::defaultOutputDevice());
}
AudioTest::~AudioTest()
{
m_pushTimer->stop();
} }
void AudioTest::initializeWindow() void AudioTest::initializeWindow()
{ {
QScopedPointer<QWidget> window(new QWidget); QWidget *window = new QWidget;
QScopedPointer<QVBoxLayout> layout(new QVBoxLayout); QVBoxLayout *layout = new QVBoxLayout;
m_deviceBox = new QComboBox(this); m_deviceBox = new QComboBox(this);
const QAudioDeviceInfo &defaultDeviceInfo = QAudioDeviceInfo::defaultOutputDevice(); const QAudioDeviceInfo &defaultDeviceInfo = QAudioDeviceInfo::defaultOutputDevice();
m_deviceBox->addItem(defaultDeviceInfo.deviceName(), qVariantFromValue(defaultDeviceInfo)); m_deviceBox->addItem(defaultDeviceInfo.deviceName(), qVariantFromValue(defaultDeviceInfo));
foreach (const QAudioDeviceInfo &deviceInfo, QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) { for (auto &deviceInfo: QAudioDeviceInfo::availableDevices(QAudio::AudioOutput)) {
if (deviceInfo != defaultDeviceInfo) if (deviceInfo != defaultDeviceInfo)
m_deviceBox->addItem(deviceInfo.deviceName(), qVariantFromValue(deviceInfo)); m_deviceBox->addItem(deviceInfo.deviceName(), qVariantFromValue(deviceInfo));
} }
connect(m_deviceBox,SIGNAL(activated(int)),SLOT(deviceChanged(int))); connect(m_deviceBox, QOverload<int>::of(&QComboBox::activated), this, &AudioTest::deviceChanged);
layout->addWidget(m_deviceBox); layout->addWidget(m_deviceBox);
m_modeButton = new QPushButton(this); m_modeButton = new QPushButton(this);
m_modeButton->setText(tr(PUSH_MODE_LABEL)); connect(m_modeButton, &QPushButton::clicked, this, &AudioTest::toggleMode);
connect(m_modeButton, SIGNAL(clicked()), SLOT(toggleMode()));
layout->addWidget(m_modeButton); layout->addWidget(m_modeButton);
m_suspendResumeButton = new QPushButton(this); m_suspendResumeButton = new QPushButton(this);
m_suspendResumeButton->setText(tr(SUSPEND_LABEL)); connect(m_suspendResumeButton, &QPushButton::clicked, this, &AudioTest::toggleSuspendResume);
connect(m_suspendResumeButton, SIGNAL(clicked()), SLOT(toggleSuspendResume()));
layout->addWidget(m_suspendResumeButton); layout->addWidget(m_suspendResumeButton);
QHBoxLayout *volumeBox = new QHBoxLayout; QHBoxLayout *volumeBox = new QHBoxLayout;
m_volumeLabel = new QLabel; m_volumeLabel = new QLabel;
m_volumeLabel->setText(tr(VOLUME_LABEL)); m_volumeLabel->setText(tr("Volume:"));
m_volumeSlider = new QSlider(Qt::Horizontal); m_volumeSlider = new QSlider(Qt::Horizontal);
m_volumeSlider->setMinimum(0); m_volumeSlider->setMinimum(0);
m_volumeSlider->setMaximum(100); m_volumeSlider->setMaximum(100);
m_volumeSlider->setSingleStep(10); m_volumeSlider->setSingleStep(10);
connect(m_volumeSlider, SIGNAL(valueChanged(int)), this, SLOT(volumeChanged(int))); connect(m_volumeSlider, &QSlider::valueChanged, this, &AudioTest::volumeChanged);
volumeBox->addWidget(m_volumeLabel); volumeBox->addWidget(m_volumeLabel);
volumeBox->addWidget(m_volumeSlider); volumeBox->addWidget(m_volumeSlider);
layout->addLayout(volumeBox); layout->addLayout(volumeBox);
window->setLayout(layout.data()); window->setLayout(layout);
layout.take(); // ownership transferred
setCentralWidget(window.data()); setCentralWidget(window);
QWidget *const windowPtr = window.take(); // ownership transferred window->show();
windowPtr->show();
} }
void AudioTest::initializeAudio() void AudioTest::initializeAudio(const QAudioDeviceInfo &deviceInfo)
{ {
connect(m_pushTimer, SIGNAL(timeout()), SLOT(pushTimerExpired())); QAudioFormat format;
format.setSampleRate(44100);
m_format.setSampleRate(DataSampleRateHz); format.setChannelCount(1);
m_format.setChannelCount(1); format.setSampleSize(16);
m_format.setSampleSize(16); format.setCodec("audio/pcm");
m_format.setCodec("audio/pcm"); format.setByteOrder(QAudioFormat::LittleEndian);
m_format.setByteOrder(QAudioFormat::LittleEndian); format.setSampleType(QAudioFormat::SignedInt);
m_format.setSampleType(QAudioFormat::SignedInt);
if (!deviceInfo.isFormatSupported(format)) {
QAudioDeviceInfo info(m_device);
if (!info.isFormatSupported(m_format)) {
qWarning() << "Default format not supported - trying to use nearest"; qWarning() << "Default format not supported - trying to use nearest";
m_format = info.nearestFormat(m_format); format = deviceInfo.nearestFormat(format);
} }
if (m_generator) const int durationSeconds = 1;
delete m_generator; const int toneSampleRateHz = 600;
m_generator = new Generator(m_format, DurationSeconds*1000000, ToneSampleRateHz, this); m_generator.reset(new Generator(format, durationSeconds * 1000000, toneSampleRateHz));
m_audioOutput.reset(new QAudioOutput(deviceInfo, format));
createAudioOutput();
}
void AudioTest::createAudioOutput()
{
delete m_audioOutput;
m_audioOutput = 0;
m_audioOutput = new QAudioOutput(m_device, m_format, this);
m_generator->start(); m_generator->start();
m_audioOutput->start(m_generator);
qreal initialVolume = QAudio::convertVolume(m_audioOutput->volume(), qreal initialVolume = QAudio::convertVolume(m_audioOutput->volume(),
QAudio::LinearVolumeScale, QAudio::LinearVolumeScale,
QAudio::LogarithmicVolumeScale); QAudio::LogarithmicVolumeScale);
m_volumeSlider->setValue(qRound(initialVolume * 100)); m_volumeSlider->setValue(qRound(initialVolume * 100));
} toggleMode();
AudioTest::~AudioTest()
{
} }
void AudioTest::deviceChanged(int index) void AudioTest::deviceChanged(int index)
{ {
m_pushTimer->stop();
m_generator->stop(); m_generator->stop();
m_audioOutput->stop(); m_audioOutput->stop();
m_audioOutput->disconnect(this); m_audioOutput->disconnect(this);
m_device = m_deviceBox->itemData(index).value<QAudioDeviceInfo>(); initializeAudio(m_deviceBox->itemData(index).value<QAudioDeviceInfo>());
initializeAudio();
} }
void AudioTest::volumeChanged(int value) void AudioTest::volumeChanged(int value)
{ {
if (m_audioOutput) { qreal linearVolume = QAudio::convertVolume(value / qreal(100),
qreal linearVolume = QAudio::convertVolume(value / qreal(100), QAudio::LogarithmicVolumeScale,
QAudio::LogarithmicVolumeScale, QAudio::LinearVolumeScale);
QAudio::LinearVolumeScale);
m_audioOutput->setVolume(linearVolume); m_audioOutput->setVolume(linearVolume);
}
}
void AudioTest::pushTimerExpired()
{
if (m_audioOutput && m_audioOutput->state() != QAudio::StoppedState) {
int chunks = m_audioOutput->bytesFree()/m_audioOutput->periodSize();
while (chunks) {
const qint64 len = m_generator->read(m_buffer.data(), m_audioOutput->periodSize());
if (len)
m_output->write(m_buffer.data(), len);
if (len != m_audioOutput->periodSize())
break;
--chunks;
}
}
} }
void AudioTest::toggleMode() void AudioTest::toggleMode()
{ {
m_pushTimer->stop(); m_pushTimer->stop();
m_audioOutput->stop(); m_audioOutput->stop();
toggleSuspendResume();
if (m_pullMode) { if (m_pullMode) {
//switch to pull mode (QAudioOutput pulls from Generator as needed)
m_modeButton->setText(tr("Enable push mode"));
m_audioOutput->start(m_generator.data());
} else {
//switch to push mode (periodically push to QAudioOutput using a timer) //switch to push mode (periodically push to QAudioOutput using a timer)
m_modeButton->setText(tr(PULL_MODE_LABEL)); m_modeButton->setText(tr("Enable pull mode"));
m_output = m_audioOutput->start(); auto io = m_audioOutput->start();
m_pullMode = false; m_pushTimer->disconnect();
connect(m_pushTimer, &QTimer::timeout, [this, io]() {
if (m_audioOutput->state() == QAudio::StoppedState)
return;
QByteArray buffer(32768, 0);
int chunks = m_audioOutput->bytesFree() / m_audioOutput->periodSize();
while (chunks) {
const qint64 len = m_generator->read(buffer.data(), m_audioOutput->periodSize());
if (len)
io->write(buffer.data(), len);
if (len != m_audioOutput->periodSize())
break;
--chunks;
}
});
m_pushTimer->start(20); m_pushTimer->start(20);
} else {
//switch to pull mode (QAudioOutput pulls from Generator as needed)
m_modeButton->setText(tr(PUSH_MODE_LABEL));
m_pullMode = true;
m_audioOutput->start(m_generator);
} }
m_suspendResumeButton->setText(tr(SUSPEND_LABEL)); m_pullMode = !m_pullMode;
} }
void AudioTest::toggleSuspendResume() void AudioTest::toggleSuspendResume()
{ {
if (m_audioOutput->state() == QAudio::SuspendedState) { if (m_audioOutput->state() == QAudio::SuspendedState || m_audioOutput->state() == QAudio::StoppedState) {
m_audioOutput->resume(); m_audioOutput->resume();
m_suspendResumeButton->setText(tr(SUSPEND_LABEL)); m_suspendResumeButton->setText(tr("Suspend recording"));
} else if (m_audioOutput->state() == QAudio::ActiveState) { } else if (m_audioOutput->state() == QAudio::ActiveState) {
m_audioOutput->suspend(); m_audioOutput->suspend();
m_suspendResumeButton->setText(tr(RESUME_LABEL)); m_suspendResumeButton->setText(tr("Resume playback"));
} else if (m_audioOutput->state() == QAudio::StoppedState) {
m_audioOutput->resume();
m_suspendResumeButton->setText(tr(SUSPEND_LABEL));
} else if (m_audioOutput->state() == QAudio::IdleState) { } else if (m_audioOutput->state() == QAudio::IdleState) {
// no-op // no-op
} }
......
...@@ -53,14 +53,14 @@ ...@@ -53,14 +53,14 @@
#include <QPushButton> #include <QPushButton>
#include <QSlider> #include <QSlider>
#include <QTimer> #include <QTimer>
#include <QScopedPointer>
class Generator : public QIODevice class Generator : public QIODevice
{ {
Q_OBJECT Q_OBJECT
public: public:
Generator(const QAudioFormat &format, qint64 durationUs, int sampleRate, QObject *parent); Generator(const QAudioFormat &format, qint64 durationUs, int sampleRate);
~Generator();
void start(); void start();
void stop(); void stop();
...@@ -73,7 +73,7 @@ private: ...@@ -73,7 +73,7 @@ private:
void generateData(const QAudioFormat &format, qint64 durationUs, int sampleRate); void generateData(const QAudioFormat &format, qint64 durationUs, int sampleRate);
private: private:
qint64 m_pos; qint64 m_pos = 0;
QByteArray m_buffer; QByteArray m_buffer;
}; };
...@@ -87,30 +87,24 @@ public: ...@@ -87,30 +87,24 @@ public:
private: private:
void initializeWindow(); void initializeWindow();
void initializeAudio(); void initializeAudio(const QAudioDeviceInfo &deviceInfo);
void createAudioOutput();
private: private:
QTimer *m_pushTimer; QTimer *m_pushTimer = nullptr;
// Owned by layout // Owned by layout
QPushButton *m_modeButton; QPushButton *m_modeButton = nullptr;
QPushButton *m_suspendResumeButton; QPushButton *m_suspendResumeButton = nullptr;
QComboBox *m_deviceBox; QComboBox *m_deviceBox = nullptr;
QLabel *m_volumeLabel; QLabel *m_volumeLabel = nullptr;
QSlider *m_volumeSlider; QSlider *m_volumeSlider = nullptr;
QAudioDeviceInfo m_device; QScopedPointer<Generator> m_generator;
Generator *m_generator; QScopedPointer<QAudioOutput> m_audioOutput;
QAudioOutput *m_audioOutput;
QIODevice *m_output; // not owned bool m_pullMode = true;
QAudioFormat m_format;
bool m_pullMode;
QByteArray m_buffer;
private slots: private slots:
void pushTimerExpired();
void toggleMode(); void toggleMode();
void toggleSuspendResume(); void toggleSuspendResume();
void deviceChanged(int index); void deviceChanged(int index);
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment