From 7177d535521e843cc0400d1849468fb78a7bd0d1 Mon Sep 17 00:00:00 2001 From: Klaus Basan Date: Thu, 20 Feb 2014 00:00:06 +0100 Subject: [PATCH] refs #144 , sounds now can be generated, saved in a temp wav file, then played --- src/blacksound/soundgenerator.cpp | 93 +++++++++++++++++++++++++++++-- src/blacksound/soundgenerator.h | 76 +++++++++++++++++++++++-- 2 files changed, 159 insertions(+), 10 deletions(-) diff --git a/src/blacksound/soundgenerator.cpp b/src/blacksound/soundgenerator.cpp index a4caa3814..40c09eb89 100644 --- a/src/blacksound/soundgenerator.cpp +++ b/src/blacksound/soundgenerator.cpp @@ -1,4 +1,5 @@ #include "soundgenerator.h" +#include #include #include #include @@ -8,7 +9,8 @@ #include #include #include - +#include +#include using namespace BlackMisc::Aviation; using namespace BlackMisc::PhysicalQuantities; @@ -16,7 +18,8 @@ using namespace BlackMisc::Voice; namespace BlackSound { - QDateTime CSoundGenerator::selcalStarted = QDateTime::currentDateTimeUtc(); + QDateTime CSoundGenerator::s_selcalStarted = QDateTime::currentDateTimeUtc(); + BlackMisc::CFileDeleter CSoundGenerator::s_fileDeleter = BlackMisc::CFileDeleter(); CSoundGenerator::CSoundGenerator(const QAudioDeviceInfo &device, const QAudioFormat &format, const QList &tones, PlayMode mode, QObject *parent) : QIODevice(parent), @@ -211,6 +214,54 @@ namespace BlackSound } } + bool CSoundGenerator::saveToWavFile(const QString &fileName) const + { + QFile file(fileName); + bool success = file.open(QIODevice::WriteOnly); + if (!success) return false; + + CombinedHeader header; + size_t headerLength = sizeof(CombinedHeader); + memset(&header, 0, headerLength); + + // RIFF header + if (m_audioFormat.byteOrder() == QAudioFormat::LittleEndian) + memcpy(&header.riff.descriptor.id[0], "RIFF", 4); + else + memcpy(&header.riff.descriptor.id[0], "RIFX", 4); + + qToLittleEndian(quint32(m_buffer.size() + headerLength - 8), + reinterpret_cast(&header.riff.descriptor.size)); + memcpy(&header.riff.type[0], "WAVE", 4); + + // WAVE header + memcpy(&header.wave.descriptor.id[0], "fmt ", 4); + qToLittleEndian(quint32(16), + reinterpret_cast(&header.wave.descriptor.size)); + qToLittleEndian(quint16(1), + reinterpret_cast(&header.wave.audioFormat)); + qToLittleEndian(quint16(m_audioFormat.channelCount()), + reinterpret_cast(&header.wave.numChannels)); + qToLittleEndian(quint32(m_audioFormat.sampleRate()), + reinterpret_cast(&header.wave.sampleRate)); + qToLittleEndian(quint32(m_audioFormat.sampleRate() * m_audioFormat.channelCount() * m_audioFormat.sampleSize() / 8), + reinterpret_cast(&header.wave.byteRate)); + qToLittleEndian(quint16(m_audioFormat.channelCount() * m_audioFormat.sampleSize() / 8), + reinterpret_cast(&header.wave.blockAlign)); + qToLittleEndian(quint16(m_audioFormat.sampleSize()), + reinterpret_cast(&header.wave.bitsPerSample)); + + // DATA header + memcpy(&header.data.descriptor.id[0], "data", 4); + qToLittleEndian(quint32(this->m_buffer.size()), + reinterpret_cast(&header.data.descriptor.size)); + + success = file.write(reinterpret_cast(&header), headerLength) == headerLength; + success = success && file.write(this->m_buffer) == this->m_buffer.size(); + file.close(); + return success; + } + qint64 CSoundGenerator::calculateDurationMs(const QList &tones) { if (tones.isEmpty()) return 0; @@ -331,6 +382,24 @@ namespace BlackSound return generator; } + void CSoundGenerator::playSignalRecorded(qint32 volume, const QList &tones, QAudioDeviceInfo device) + { + if (tones.isEmpty()) return; // that was easy + if (volume < 1) return; + + CSoundGenerator *generator = new CSoundGenerator(device, CSoundGenerator::defaultAudioFormat(), tones, CSoundGenerator::SingleWithAutomaticDeletion); + if (generator->singleCyleDurationMs() > 10) + { + // play, and maybe clean up when done + QString fileName = QString("blacksound").append(QString::number(QDateTime::currentMSecsSinceEpoch())).append(".wav"); + fileName = QDir::temp().filePath(fileName); + generator->generateData(); + generator->saveToWavFile(fileName); + CSoundGenerator::playFile(volume, fileName, true); + } + generator->deleteLater(); + } + void CSoundGenerator::playSelcal(qint32 volume, const BlackMisc::Aviation::CSelcal &selcal, QAudioDeviceInfo device) { QList tones; @@ -345,18 +414,19 @@ namespace BlackSound tones << t1 << t2 << t3; } CSoundGenerator::playSignalInBackground(volume, tones, device); + // CSoundGenerator::playSignalRecorded(volume, tones, device); } void CSoundGenerator::playSelcal(qint32 volume, const CSelcal &selcal, const CAudioDevice &audioDevice) { - if (CSoundGenerator::selcalStarted.msecsTo(QDateTime::currentDateTimeUtc()) < 2500) return; // simple check not to play 2 SELCAL at the same time - CSoundGenerator::selcalStarted = QDateTime::currentDateTimeUtc(); + if (CSoundGenerator::s_selcalStarted.msecsTo(QDateTime::currentDateTimeUtc()) < 2500) return; // simple check not to play 2 SELCAL at the same time + CSoundGenerator::s_selcalStarted = QDateTime::currentDateTimeUtc(); CSoundGenerator::playSelcal(volume, selcal, CSoundGenerator::findClosestOutputDevice(audioDevice)); } void CSoundGenerator::playNotificationSound(qint32 volume, CSoundGenerator::Notification notification) { - static QMediaPlayer *mediaPlayer = new QMediaPlayer(); + QMediaPlayer *mediaPlayer = CSoundGenerator::mediaPlayer(); if (mediaPlayer->state() == QMediaPlayer::PlayingState) return; QMediaPlaylist *playlist = mediaPlayer->playlist(); if (!playlist || playlist->isEmpty()) @@ -378,4 +448,17 @@ namespace BlackSound mediaPlayer->play(); } + void CSoundGenerator::playFile(qint32 volume, const QString &file, bool removeFileAfterPlaying) + { + if (!QFile::exists(file)) return; + + QMediaPlayer *mediaPlayer = CSoundGenerator::mediaPlayer(); + QMediaResource mediaResource(QUrl(file), "audio"); + QMediaContent media(mediaResource); + mediaPlayer->setMedia(media); + mediaPlayer->setVolume(volume); // 0-100 + mediaPlayer->play(); + // I cannot delete the file here, only after it has been played + if (removeFileAfterPlaying) CSoundGenerator::s_fileDeleter.addFileForDeletion(file); + } } // namespace diff --git a/src/blacksound/soundgenerator.h b/src/blacksound/soundgenerator.h index cc8c02d92..5166bf46e 100644 --- a/src/blacksound/soundgenerator.h +++ b/src/blacksound/soundgenerator.h @@ -9,13 +9,14 @@ #include "blackmisc/avselcal.h" #include "blackmisc/vaudiodevice.h" #include "blackmisc/pqtime.h" - +#include "blackmisc/filedeleter.h" #include #include #include #include #include #include +#include namespace BlackSound { @@ -187,6 +188,14 @@ namespace BlackSound */ static CSoundGenerator *playSignalInBackground(qint32 volume, const QList &tones, QAudioDeviceInfo device); + /*! + * \brief Record the tones to a wav file, then play the wav file + * \param volume 0-100 + * \param tones list of tones + * \param device device to be used + */ + static void playSignalRecorded(qint32 volume, const QList &tones, QAudioDeviceInfo device); + /*! * \brief Play SELCAL tone * \param volume 0-100 @@ -220,6 +229,14 @@ namespace BlackSound return BlackMisc::PhysicalQuantities::CTime(this->m_oneCycleDurationMs, BlackMisc::PhysicalQuantities::CTimeUnit::ms()); } + /*! + * \brief Play given file + * \param volume 0-100 + * \param file + * \param removeFileAfterPlaying delete the file, after it has been played + */ + static void playFile(qint32 volume, const QString &file, bool removeFileAfterPlaying); + signals: /*! * \brief Device was closed @@ -272,13 +289,62 @@ namespace BlackSound QTimer *m_pushTimer; /*!< Push mode timer */ QIODevice *m_pushModeIODevice; /*!< IO device when used in push mode */ QThread *m_ownThread; - static QDateTime selcalStarted; + static QDateTime s_selcalStarted; + static BlackMisc::CFileDeleter s_fileDeleter; - /*! - * \brief Duration of these tones - */ + //! \brief Header for saving .wav files + struct chunk + { + char id[4]; + quint32 size; + }; + + //! \brief Header for saving .wav files + struct RiffHeader + { + chunk descriptor; // "RIFF" + char type[4]; // "WAVE" + }; + + //! \brief Header for saving .wav files + struct WaveHeader + { + chunk descriptor; + quint16 audioFormat; + quint16 numChannels; + quint32 sampleRate; + quint32 byteRate; + quint16 blockAlign; + quint16 bitsPerSample; + }; + + //! \brief Header for saving .wav files + struct DataHeader + { + chunk descriptor; + }; + + //! \brief Header for saving .wav files + struct CombinedHeader + { + RiffHeader riff; + WaveHeader wave; + DataHeader data; + }; + + //! \brief "My" media player + static QMediaPlayer *mediaPlayer() + { + static QMediaPlayer *mediaPlayer = new QMediaPlayer(); + return mediaPlayer; + } + + //! \brief Duration of these tones static qint64 calculateDurationMs(const QList &tones); + //! \brief save buffer to wav file + bool saveToWavFile(const QString &fileName) const; + }; } //namespace