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 <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:
|
||||
|
||||
/*!
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Reference in a new issue