migrated the sound player driver to QSoundEffect to prevent clicking noises
This commit is contained in:
parent
eb1328642d
commit
2aaaf92964
2 changed files with 50 additions and 95 deletions
|
@ -26,6 +26,7 @@
|
||||||
#include <QEventLoop>
|
#include <QEventLoop>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
|
#include <QSoundEffect>
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief The ScStwSoundPlayer class is used for ultra low latency sound playback of the speed clibing start tones and commands
|
* \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
|
* 0: AtYourMarksSound
|
||||||
* 1: ReadySound
|
* 1: ReadySound
|
||||||
* 2: StartSound
|
* 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;
|
QSoundEffect *soundEffect;
|
||||||
|
|
||||||
/*!
|
|
||||||
* \brief The audio output object
|
|
||||||
*/
|
|
||||||
QAudioOutput *audioOutput;
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief The QEventLoop used to wait for the sound to finish
|
* \brief The QEventLoop used to wait for the sound to finish
|
||||||
|
@ -74,6 +71,11 @@ private:
|
||||||
*/
|
*/
|
||||||
int currentlyPlayingAction;
|
int currentlyPlayingAction;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Holds the time the playback started at
|
||||||
|
*/
|
||||||
|
double playingStartedAt;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -104,12 +106,6 @@ public slots:
|
||||||
|
|
||||||
private 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:
|
signals:
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
|
|
@ -23,66 +23,57 @@ ScStwSoundPlayer::ScStwSoundPlayer(QObject *parent) : QObject(parent)
|
||||||
this->waitLoop = new QEventLoop(this);
|
this->waitLoop = new QEventLoop(this);
|
||||||
this->waitTimer = new QTimer(this);
|
this->waitTimer = new QTimer(this);
|
||||||
|
|
||||||
this->soundFiles.insert(0, new QFile(":/sound/AtYourMarksSound.wav", this));
|
this->soundFiles.insert(0, {{"path","qrc:/sound/AtYourMarksSound.wav"}, {"duration", 1000}});
|
||||||
this->soundFiles.insert(1, new QFile(":/sound/ReadySound.wav", this));
|
this->soundFiles.insert(1, {{"path","qrc:/sound/ReadySound.wav"}, {"duration", 570}});
|
||||||
this->soundFiles.insert(2, new QFile(":/sound/StartsignalSound.wav", this));
|
this->soundFiles.insert(2, {{"path","qrc:/sound/StartsignalSound.wav"}, {"duration", 3100}});
|
||||||
this->falseStartSoundFile = new QFile(":/sound/FalseStartSound.wav", this);
|
this->soundFiles.insert(3, {{"path","qrc:/sound/FalseStartSound.wav"}, {"duration", 2000}});
|
||||||
|
|
||||||
// init sound format
|
this->soundEffect = new QSoundEffect(this);
|
||||||
QAudioFormat format;
|
this->soundEffect->setLoopCount(1);
|
||||||
format.setSampleRate(44100);
|
|
||||||
format.setChannelCount(2);
|
|
||||||
format.setSampleSize(16);
|
|
||||||
format.setCodec("audio/pcm");
|
|
||||||
format.setByteOrder(QAudioFormat::LittleEndian);
|
|
||||||
format.setSampleType(QAudioFormat::SignedInt);
|
|
||||||
|
|
||||||
// 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, &ScStwSoundPlayer::playbackStarted, this->waitLoop, &QEventLoop::quit);
|
||||||
|
connect(this->soundEffect, &QSoundEffect::playingChanged, this->waitLoop, &QEventLoop::quit);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ScStwSoundPlayer::play(int action, double volume, double *timeOfStart) {
|
bool ScStwSoundPlayer::play(int action, double volume, double *timeOfStart) {
|
||||||
if(!this->soundFiles.contains(action))
|
if(!this->soundFiles.contains(action))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
if(action > 2 || action < 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
// stop playback
|
// stop playback
|
||||||
if(this->audioOutput->state() == QAudio::ActiveState)
|
if(this->soundEffect->isPlaying())
|
||||||
this->audioOutput->stop();
|
this->soundEffect->stop();
|
||||||
|
|
||||||
// update currently playing action
|
// update currently playing action
|
||||||
this->currentlyPlayingAction = action;
|
this->currentlyPlayingAction = action;
|
||||||
|
|
||||||
QFile *playbackFile = this->soundFiles[action];
|
// update volume
|
||||||
|
this->soundEffect->setVolume(volume);
|
||||||
|
|
||||||
// close soundfile
|
// load
|
||||||
if(playbackFile->isOpen())
|
this->soundEffect->setSource(this->soundFiles[action]["path"].toString());
|
||||||
playbackFile->close();
|
|
||||||
|
|
||||||
// open soundfile
|
// wait for the effect to load
|
||||||
if(!playbackFile->open(QFile::ReadOnly)) {
|
QEventLoop loop;
|
||||||
qWarning() << "[ERROR][SOUNDS] Could not open sound file " << action << " !!";
|
while(this->soundEffect->status() != QSoundEffect::Ready) {
|
||||||
return false;
|
QObject::connect(this->soundEffect, &QSoundEffect::statusChanged, &loop, &QEventLoop::quit);
|
||||||
|
loop.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
// update volume
|
|
||||||
this->audioOutput->setVolume(volume);
|
|
||||||
|
|
||||||
// start
|
// 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
|
// pass the time of start if requested
|
||||||
if(timeOfStart != nullptr) {
|
if(timeOfStart != nullptr) {
|
||||||
*timeOfStart = QDateTime::currentMSecsSinceEpoch() - (this->audioOutput->elapsedUSecs() / 1000);
|
*timeOfStart = this->playingStartedAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(action < 2)
|
if(action < 2)
|
||||||
|
@ -92,37 +83,30 @@ bool ScStwSoundPlayer::play(int action, double volume, double *timeOfStart) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ScStwSoundPlayer::cancel(double volume) {
|
bool ScStwSoundPlayer::cancel(double volume) {
|
||||||
if(!QList<QAudio::State>{QAudio::ActiveState}.contains(this->audioOutput->state()) )
|
if(!this->soundEffect->isPlaying() )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// stop playback
|
// stop playback
|
||||||
this->audioOutput->stop();
|
this->soundEffect->stop();
|
||||||
this->waitLoop->quit();
|
this->waitLoop->quit();
|
||||||
|
|
||||||
if(this->currentlyPlayingAction != 2)
|
if(this->currentlyPlayingAction != 2)
|
||||||
return true;
|
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
|
// update volume
|
||||||
this->audioOutput->setVolume(volume);
|
this->soundEffect->setVolume(volume);
|
||||||
|
|
||||||
// start
|
// load
|
||||||
this->audioOutput->start(this->falseStartSoundFile);
|
this->soundEffect->setSource(this->soundFiles[3]["path"].toString());
|
||||||
|
|
||||||
|
// play
|
||||||
|
this->soundEffect->play();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ScStwSoundPlayer::waitForSoundFinish(double *timeOfStop) {
|
bool ScStwSoundPlayer::waitForSoundFinish(double *timeOfStop) {
|
||||||
if(this->audioOutput->state() != QAudio::ActiveState)
|
if(!this->soundEffect->isPlaying())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// wait until the audio output reports the sound is over
|
// 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
|
// wait until the sound is actually over
|
||||||
// the timeOffset is the buffer time before the audio started!
|
// 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) {
|
if(timeOffset > 0) {
|
||||||
QTimer timer;
|
QTimer timer;
|
||||||
|
@ -140,34 +124,9 @@ bool ScStwSoundPlayer::waitForSoundFinish(double *timeOfStop) {
|
||||||
|
|
||||||
// calculate the point in time where the sound playback actually ended
|
// calculate the point in time where the sound playback actually ended
|
||||||
if(timeOfStop != nullptr) {
|
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;
|
*timeOfStop = QDateTime::currentMSecsSinceEpoch() - latency;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Reference in a new issue