From 2aaaf9296459ce9de558bdbff91844dca31bf687 Mon Sep 17 00:00:00 2001 From: Dorian Zedler Date: Thu, 18 Jun 2020 11:53:25 +0200 Subject: [PATCH] migrated the sound player driver to QSoundEffect to prevent clicking noises --- ScStwLibraries/headers/scstwsoundplayer.h | 24 ++-- ScStwLibraries/sources/scstwsoundplayer.cpp | 121 +++++++------------- 2 files changed, 50 insertions(+), 95 deletions(-) diff --git a/ScStwLibraries/headers/scstwsoundplayer.h b/ScStwLibraries/headers/scstwsoundplayer.h index 91c17cb..d158725 100644 --- a/ScStwLibraries/headers/scstwsoundplayer.h +++ b/ScStwLibraries/headers/scstwsoundplayer.h @@ -26,6 +26,7 @@ #include #include #include +#include /*! * \brief The ScStwSoundPlayer class is used for ultra low latency sound playback of the speed clibing start tones and commands @@ -46,18 +47,14 @@ private: * 0: AtYourMarksSound * 1: ReadySound * 2: StartSound + * 3: FalseStartSound */ - QMap soundFiles; + QMap soundFiles; /*! - * \brief Containng the false start sound file + * \brief The sound effect object */ - QFile *falseStartSoundFile; - - /*! - * \brief The audio output object - */ - QAudioOutput *audioOutput; + QSoundEffect *soundEffect; /*! * \brief The QEventLoop used to wait for the sound to finish @@ -74,6 +71,11 @@ private: */ int currentlyPlayingAction; + /*! + * \brief Holds the time the playback started at + */ + double playingStartedAt; + public slots: /*! @@ -104,12 +106,6 @@ public slots: private slots: - /*! - * \brief Handle a state change of the audio output - * \param newState the new state of the audio output - */ - void handleStateChanged(QAudio::State newState); - signals: /*! diff --git a/ScStwLibraries/sources/scstwsoundplayer.cpp b/ScStwLibraries/sources/scstwsoundplayer.cpp index be5473c..bf8f5be 100644 --- a/ScStwLibraries/sources/scstwsoundplayer.cpp +++ b/ScStwLibraries/sources/scstwsoundplayer.cpp @@ -23,66 +23,57 @@ ScStwSoundPlayer::ScStwSoundPlayer(QObject *parent) : QObject(parent) this->waitLoop = new QEventLoop(this); this->waitTimer = new QTimer(this); - this->soundFiles.insert(0, new QFile(":/sound/AtYourMarksSound.wav", this)); - this->soundFiles.insert(1, new QFile(":/sound/ReadySound.wav", this)); - this->soundFiles.insert(2, new QFile(":/sound/StartsignalSound.wav", this)); - this->falseStartSoundFile = new QFile(":/sound/FalseStartSound.wav", this); + this->soundFiles.insert(0, {{"path","qrc:/sound/AtYourMarksSound.wav"}, {"duration", 1000}}); + this->soundFiles.insert(1, {{"path","qrc:/sound/ReadySound.wav"}, {"duration", 570}}); + this->soundFiles.insert(2, {{"path","qrc:/sound/StartsignalSound.wav"}, {"duration", 3100}}); + this->soundFiles.insert(3, {{"path","qrc:/sound/FalseStartSound.wav"}, {"duration", 2000}}); - // init sound format - QAudioFormat format; - format.setSampleRate(44100); - format.setChannelCount(2); - format.setSampleSize(16); - format.setCodec("audio/pcm"); - format.setByteOrder(QAudioFormat::LittleEndian); - format.setSampleType(QAudioFormat::SignedInt); + this->soundEffect = new QSoundEffect(this); + this->soundEffect->setLoopCount(1); - // check if format is valid - QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice()); - if (!info.isFormatSupported(format)) { - qWarning() << "Raw audio format not supported by backend, cannot play audio."; - return; - } - - this->audioOutput = new QAudioOutput(format, this); - this->audioOutput->setCategory("media"); - - connect(this->audioOutput, SIGNAL(stateChanged(QAudio::State)), this, SLOT(handleStateChanged(QAudio::State))); connect(this, &ScStwSoundPlayer::playbackStarted, this->waitLoop, &QEventLoop::quit); + connect(this->soundEffect, &QSoundEffect::playingChanged, this->waitLoop, &QEventLoop::quit); } bool ScStwSoundPlayer::play(int action, double volume, double *timeOfStart) { if(!this->soundFiles.contains(action)) return false; + if(action > 2 || action < 0) + return false; + // stop playback - if(this->audioOutput->state() == QAudio::ActiveState) - this->audioOutput->stop(); + if(this->soundEffect->isPlaying()) + this->soundEffect->stop(); // update currently playing action this->currentlyPlayingAction = action; - QFile *playbackFile = this->soundFiles[action]; + // update volume + this->soundEffect->setVolume(volume); - // close soundfile - if(playbackFile->isOpen()) - playbackFile->close(); + // load + this->soundEffect->setSource(this->soundFiles[action]["path"].toString()); - // open soundfile - if(!playbackFile->open(QFile::ReadOnly)) { - qWarning() << "[ERROR][SOUNDS] Could not open sound file " << action << " !!"; - return false; + // wait for the effect to load + QEventLoop loop; + while(this->soundEffect->status() != QSoundEffect::Ready) { + QObject::connect(this->soundEffect, &QSoundEffect::statusChanged, &loop, &QEventLoop::quit); + loop.exec(); } - // update volume - this->audioOutput->setVolume(volume); - // start - this->audioOutput->start(playbackFile); + this->soundEffect->play(); + + // emit a playback start + emit this->playbackStarted(); + + // save started at + this->playingStartedAt = QDateTime::currentMSecsSinceEpoch(); // pass the time of start if requested if(timeOfStart != nullptr) { - *timeOfStart = QDateTime::currentMSecsSinceEpoch() - (this->audioOutput->elapsedUSecs() / 1000); + *timeOfStart = this->playingStartedAt; } if(action < 2) @@ -92,37 +83,30 @@ bool ScStwSoundPlayer::play(int action, double volume, double *timeOfStart) { } bool ScStwSoundPlayer::cancel(double volume) { - if(!QList{QAudio::ActiveState}.contains(this->audioOutput->state()) ) + if(!this->soundEffect->isPlaying() ) return false; // stop playback - this->audioOutput->stop(); + this->soundEffect->stop(); this->waitLoop->quit(); if(this->currentlyPlayingAction != 2) return true; - // close soundfile - if(this->falseStartSoundFile->isOpen()) - this->falseStartSoundFile->close(); - - // open sound file - if(!this->falseStartSoundFile->open(QFile::ReadOnly)) { - qWarning() << "[ERROR][SOUNDS] Could not open false start sound file!!"; - return false; - } - // update volume - this->audioOutput->setVolume(volume); + this->soundEffect->setVolume(volume); - // start - this->audioOutput->start(this->falseStartSoundFile); + // load + this->soundEffect->setSource(this->soundFiles[3]["path"].toString()); + + // play + this->soundEffect->play(); return true; } bool ScStwSoundPlayer::waitForSoundFinish(double *timeOfStop) { - if(this->audioOutput->state() != QAudio::ActiveState) + if(!this->soundEffect->isPlaying()) return false; // wait until the audio output reports the sound is over @@ -130,7 +114,7 @@ bool ScStwSoundPlayer::waitForSoundFinish(double *timeOfStop) { // wait until the sound is actually over // the timeOffset is the buffer time before the audio started! - int timeOffset = ((this->audioOutput->processedUSecs() - this->audioOutput->elapsedUSecs()) / 1000); + int timeOffset = this->soundFiles[this->currentlyPlayingAction]["duration"].toDouble() - (QDateTime::currentMSecsSinceEpoch() - playingStartedAt); if(timeOffset > 0) { QTimer timer; @@ -140,34 +124,9 @@ bool ScStwSoundPlayer::waitForSoundFinish(double *timeOfStop) { // calculate the point in time where the sound playback actually ended if(timeOfStop != nullptr) { - int latency = (this->audioOutput->processedUSecs() - this->audioOutput->elapsedUSecs()) / 1000; + int latency = this->playingStartedAt + this->soundFiles[this->currentlyPlayingAction]["duration"].toDouble(); *timeOfStop = QDateTime::currentMSecsSinceEpoch() - latency; } return true; } - -void ScStwSoundPlayer::handleStateChanged(QAudio::State newState) -{ - switch (newState) { - case QAudio::IdleState: - // Finished playing (no more data) - waitLoop->exit(); - break; - - case QAudio::StoppedState: - // Stopped for other reasons - if (this->audioOutput->error() != QAudio::NoError) { - // Error handling - } - break; - - case QAudio::ActiveState: - emit this->playbackStarted(); - break; - - default: - // ... other cases as appropriate - break; - } -}