refs #144 , sounds now can be generated, saved in a temp wav file, then played

This commit is contained in:
Klaus Basan
2014-02-20 00:00:06 +01:00
parent 7b1fa5617d
commit 7177d53552
2 changed files with 159 additions and 10 deletions

View File

@@ -1,4 +1,5 @@
#include "soundgenerator.h" #include "soundgenerator.h"
#include <QtCore/qendian.h>
#include <math.h> #include <math.h>
#include <qmath.h> #include <qmath.h>
#include <qendian.h> #include <qendian.h>
@@ -8,7 +9,8 @@
#include <QMediaPlaylist> #include <QMediaPlaylist>
#include <QTimer> #include <QTimer>
#include <QUrl> #include <QUrl>
#include <QFile>
#include <QDir>
using namespace BlackMisc::Aviation; using namespace BlackMisc::Aviation;
using namespace BlackMisc::PhysicalQuantities; using namespace BlackMisc::PhysicalQuantities;
@@ -16,7 +18,8 @@ using namespace BlackMisc::Voice;
namespace BlackSound 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<Tone> &tones, PlayMode mode, QObject *parent) CSoundGenerator::CSoundGenerator(const QAudioDeviceInfo &device, const QAudioFormat &format, const QList<Tone> &tones, PlayMode mode, QObject *parent)
: QIODevice(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>(quint32(m_buffer.size() + headerLength - 8),
reinterpret_cast<unsigned char *>(&header.riff.descriptor.size));
memcpy(&header.riff.type[0], "WAVE", 4);
// WAVE header
memcpy(&header.wave.descriptor.id[0], "fmt ", 4);
qToLittleEndian<quint32>(quint32(16),
reinterpret_cast<unsigned char *>(&header.wave.descriptor.size));
qToLittleEndian<quint16>(quint16(1),
reinterpret_cast<unsigned char *>(&header.wave.audioFormat));
qToLittleEndian<quint16>(quint16(m_audioFormat.channelCount()),
reinterpret_cast<unsigned char *>(&header.wave.numChannels));
qToLittleEndian<quint32>(quint32(m_audioFormat.sampleRate()),
reinterpret_cast<unsigned char *>(&header.wave.sampleRate));
qToLittleEndian<quint32>(quint32(m_audioFormat.sampleRate() * m_audioFormat.channelCount() * m_audioFormat.sampleSize() / 8),
reinterpret_cast<unsigned char *>(&header.wave.byteRate));
qToLittleEndian<quint16>(quint16(m_audioFormat.channelCount() * m_audioFormat.sampleSize() / 8),
reinterpret_cast<unsigned char *>(&header.wave.blockAlign));
qToLittleEndian<quint16>(quint16(m_audioFormat.sampleSize()),
reinterpret_cast<unsigned char *>(&header.wave.bitsPerSample));
// DATA header
memcpy(&header.data.descriptor.id[0], "data", 4);
qToLittleEndian<quint32>(quint32(this->m_buffer.size()),
reinterpret_cast<unsigned char *>(&header.data.descriptor.size));
success = file.write(reinterpret_cast<const char *>(&header), headerLength) == headerLength;
success = success && file.write(this->m_buffer) == this->m_buffer.size();
file.close();
return success;
}
qint64 CSoundGenerator::calculateDurationMs(const QList<CSoundGenerator::Tone> &tones) qint64 CSoundGenerator::calculateDurationMs(const QList<CSoundGenerator::Tone> &tones)
{ {
if (tones.isEmpty()) return 0; if (tones.isEmpty()) return 0;
@@ -331,6 +382,24 @@ namespace BlackSound
return generator; return generator;
} }
void CSoundGenerator::playSignalRecorded(qint32 volume, const QList<CSoundGenerator::Tone> &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) void CSoundGenerator::playSelcal(qint32 volume, const BlackMisc::Aviation::CSelcal &selcal, QAudioDeviceInfo device)
{ {
QList<CSoundGenerator::Tone> tones; QList<CSoundGenerator::Tone> tones;
@@ -345,18 +414,19 @@ namespace BlackSound
tones << t1 << t2 << t3; tones << t1 << t2 << t3;
} }
CSoundGenerator::playSignalInBackground(volume, tones, device); CSoundGenerator::playSignalInBackground(volume, tones, device);
// CSoundGenerator::playSignalRecorded(volume, tones, device);
} }
void CSoundGenerator::playSelcal(qint32 volume, const CSelcal &selcal, const CAudioDevice &audioDevice) 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 if (CSoundGenerator::s_selcalStarted.msecsTo(QDateTime::currentDateTimeUtc()) < 2500) return; // simple check not to play 2 SELCAL at the same time
CSoundGenerator::selcalStarted = QDateTime::currentDateTimeUtc(); CSoundGenerator::s_selcalStarted = QDateTime::currentDateTimeUtc();
CSoundGenerator::playSelcal(volume, selcal, CSoundGenerator::findClosestOutputDevice(audioDevice)); CSoundGenerator::playSelcal(volume, selcal, CSoundGenerator::findClosestOutputDevice(audioDevice));
} }
void CSoundGenerator::playNotificationSound(qint32 volume, CSoundGenerator::Notification notification) void CSoundGenerator::playNotificationSound(qint32 volume, CSoundGenerator::Notification notification)
{ {
static QMediaPlayer *mediaPlayer = new QMediaPlayer(); QMediaPlayer *mediaPlayer = CSoundGenerator::mediaPlayer();
if (mediaPlayer->state() == QMediaPlayer::PlayingState) return; if (mediaPlayer->state() == QMediaPlayer::PlayingState) return;
QMediaPlaylist *playlist = mediaPlayer->playlist(); QMediaPlaylist *playlist = mediaPlayer->playlist();
if (!playlist || playlist->isEmpty()) if (!playlist || playlist->isEmpty())
@@ -378,4 +448,17 @@ namespace BlackSound
mediaPlayer->play(); 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 } // namespace

View File

@@ -9,13 +9,14 @@
#include "blackmisc/avselcal.h" #include "blackmisc/avselcal.h"
#include "blackmisc/vaudiodevice.h" #include "blackmisc/vaudiodevice.h"
#include "blackmisc/pqtime.h" #include "blackmisc/pqtime.h"
#include "blackmisc/filedeleter.h"
#include <QIODevice> #include <QIODevice>
#include <QThread> #include <QThread>
#include <QDateTime> #include <QDateTime>
#include <QAudioFormat> #include <QAudioFormat>
#include <QAudioOutput> #include <QAudioOutput>
#include <QAudioDeviceInfo> #include <QAudioDeviceInfo>
#include <QMediaPlayer>
namespace BlackSound namespace BlackSound
{ {
@@ -187,6 +188,14 @@ namespace BlackSound
*/ */
static CSoundGenerator *playSignalInBackground(qint32 volume, const QList<CSoundGenerator::Tone> &tones, QAudioDeviceInfo device); static CSoundGenerator *playSignalInBackground(qint32 volume, const QList<CSoundGenerator::Tone> &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<CSoundGenerator::Tone> &tones, QAudioDeviceInfo device);
/*! /*!
* \brief Play SELCAL tone * \brief Play SELCAL tone
* \param volume 0-100 * \param volume 0-100
@@ -220,6 +229,14 @@ namespace BlackSound
return BlackMisc::PhysicalQuantities::CTime(this->m_oneCycleDurationMs, BlackMisc::PhysicalQuantities::CTimeUnit::ms()); 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: signals:
/*! /*!
* \brief Device was closed * \brief Device was closed
@@ -272,13 +289,62 @@ namespace BlackSound
QTimer *m_pushTimer; /*!< Push mode timer */ QTimer *m_pushTimer; /*!< Push mode timer */
QIODevice *m_pushModeIODevice; /*!< IO device when used in push mode */ QIODevice *m_pushModeIODevice; /*!< IO device when used in push mode */
QThread *m_ownThread; QThread *m_ownThread;
static QDateTime selcalStarted; static QDateTime s_selcalStarted;
static BlackMisc::CFileDeleter s_fileDeleter;
/*! //! \brief Header for saving .wav files
* \brief Duration of these tones 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<Tone> &tones); static qint64 calculateDurationMs(const QList<Tone> &tones);
//! \brief save buffer to wav file
bool saveToWavFile(const QString &fileName) const;
}; };
} //namespace } //namespace