mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-04-14 00:25:35 +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 <QtCore/qendian.h>
|
||||
#include <math.h>
|
||||
#include <qmath.h>
|
||||
#include <qendian.h>
|
||||
@@ -8,7 +9,8 @@
|
||||
#include <QMediaPlaylist>
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
|
||||
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<Tone> &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>(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)
|
||||
{
|
||||
if (tones.isEmpty()) return 0;
|
||||
@@ -331,6 +382,24 @@ namespace BlackSound
|
||||
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)
|
||||
{
|
||||
QList<CSoundGenerator::Tone> 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
|
||||
|
||||
@@ -9,13 +9,14 @@
|
||||
#include "blackmisc/avselcal.h"
|
||||
#include "blackmisc/vaudiodevice.h"
|
||||
#include "blackmisc/pqtime.h"
|
||||
|
||||
#include "blackmisc/filedeleter.h"
|
||||
#include <QIODevice>
|
||||
#include <QThread>
|
||||
#include <QDateTime>
|
||||
#include <QAudioFormat>
|
||||
#include <QAudioOutput>
|
||||
#include <QAudioDeviceInfo>
|
||||
#include <QMediaPlayer>
|
||||
|
||||
namespace BlackSound
|
||||
{
|
||||
@@ -187,6 +188,14 @@ namespace BlackSound
|
||||
*/
|
||||
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
|
||||
* \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<Tone> &tones);
|
||||
|
||||
//! \brief save buffer to wav file
|
||||
bool saveToWavFile(const QString &fileName) const;
|
||||
|
||||
};
|
||||
} //namespace
|
||||
|
||||
|
||||
Reference in New Issue
Block a user