mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-05-03 16:25:54 +08:00
refs #144 , sounds now can be generated, saved in a temp wav file, then played
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user