migrated the sound player driver to QSoundEffect to prevent clicking noises

This commit is contained in:
Dorian Zedler 2020-06-18 11:53:25 +02:00
parent eb1328642d
commit 2aaaf92964
Signed by: dorian
GPG key ID: 989DE36109AFA354
2 changed files with 50 additions and 95 deletions

View file

@ -26,6 +26,7 @@
#include <QEventLoop>
#include <QTimer>
#include <QDateTime>
#include <QSoundEffect>
/*!
* \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<int, QFile*> soundFiles;
QMap<int, QVariantMap> 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:
/*!

View file

@ -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::State>{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;
}
}