Replace QAudioDeviceInfo with CAudioDeviceInfo where possible

QAudioDeviceInfo is a low level technical class, which shouldn't be used in higher level code. Remove it from all APIs
where possible and just create it in order to interface with QAudio
This commit is contained in:
Roland Rossgotterer
2019-10-02 14:50:37 +02:00
committed by Mat Sutcliffe
parent 8656131eb1
commit a2e3700739
18 changed files with 141 additions and 863 deletions

View File

@@ -15,6 +15,7 @@
#include <QtGlobal>
#include <QStringBuilder>
#include <QDebug>
#include <QAudioDeviceInfo>
#include <cmath>
using namespace BlackMisc;
@@ -90,21 +91,40 @@ namespace BlackCore
m_encoder.setBitRate(16 * 1024);
}
void CInput::start(const QAudioDeviceInfo &inputDevice)
void CInput::start(const BlackMisc::Audio::CAudioDeviceInfo &inputDevice)
{
if (m_started) { return; }
m_device = inputDevice;
QAudioDeviceInfo selectedDevice;
if (inputDevice.isDefault())
{
selectedDevice = QAudioDeviceInfo::defaultInputDevice();
}
else
{
// TODO: Add smart algorithm to find the device with lowest latency
const QList<QAudioDeviceInfo> inputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
for (const QAudioDeviceInfo &d : inputDevices)
{
if (d.deviceName() == inputDevice.getName())
{
selectedDevice = d;
}
}
}
m_inputFormat.setSampleRate(m_sampleRate);
m_inputFormat.setChannelCount(1);
m_inputFormat.setSampleSize(16);
m_inputFormat.setSampleType(QAudioFormat::SignedInt);
m_inputFormat.setByteOrder(QAudioFormat::LittleEndian);
m_inputFormat.setCodec("audio/pcm");
if (!inputDevice.isFormatSupported(m_inputFormat))
if (!selectedDevice.isFormatSupported(m_inputFormat))
{
m_inputFormat = inputDevice.nearestFormat(m_inputFormat);
m_inputFormat = selectedDevice.nearestFormat(m_inputFormat);
const QString w =
inputDevice.deviceName() %
selectedDevice.deviceName() %
": Default INPUT format not supported - trying to use nearest" %
" Sample rate: " % QString::number(m_inputFormat.sampleRate()) %
" Sample size: " % QString::number(m_inputFormat.sampleSize()) %
@@ -115,7 +135,7 @@ namespace BlackCore
CLogMessage(this).warning(w);
}
m_audioInput.reset(new QAudioInput(inputDevice, m_inputFormat));
m_audioInput.reset(new QAudioInput(selectedDevice, m_inputFormat));
m_audioInputBuffer.start();
m_audioInput->start(&m_audioInputBuffer);

View File

@@ -13,8 +13,8 @@
#include "blacksound/sampleprovider/bufferedwaveprovider.h"
#include "blacksound/codecs/opusencoder.h"
#include "blackmisc/audio/audiodeviceinfo.h"
#include <QAudioDeviceInfo>
#include <QAudioInput>
#include <QString>
#include <QDateTime>
@@ -70,7 +70,6 @@ namespace BlackCore
struct InputVolumeStreamArgs
{
QAudioDeviceInfo DeviceNumber;
double PeakRaw = 0.0;
double PeakDB = -1.0 * std::numeric_limits<double>::infinity();
double PeakVU = 0.0;
@@ -101,13 +100,13 @@ namespace BlackCore
bool started() const { return m_started; }
//! Start
void start(const QAudioDeviceInfo &inputDevice);
void start(const BlackMisc::Audio::CAudioDeviceInfo &inputDevice);
//! Stop
void stop();
//! Corresponding device
const QAudioDeviceInfo &device() const { return m_device; }
const BlackMisc::Audio::CAudioDeviceInfo &device() const { return m_device; }
signals:
//! Volume stream data
@@ -124,7 +123,7 @@ namespace BlackCore
BlackSound::Codecs::COpusEncoder m_encoder;
QScopedPointer<QAudioInput> m_audioInput;
QAudioDeviceInfo m_device;
BlackMisc::Audio::CAudioDeviceInfo m_device;
QAudioFormat m_inputFormat;
bool m_started = false;

View File

@@ -17,6 +17,7 @@
#include <cmath>
using namespace BlackMisc;
using namespace BlackMisc::Audio;
using namespace BlackSound;
using namespace BlackSound::SampleProvider;
@@ -81,11 +82,32 @@ namespace BlackCore
Output::Output(QObject *parent) : QObject(parent)
{ }
void Output::start(const QAudioDeviceInfo &outputDevice, ISampleProvider *sampleProvider)
void Output::start(const CAudioDeviceInfo &outputDevice, ISampleProvider *sampleProvider)
{
if (m_started) { return; }
m_audioOutputBuffer = new CAudioOutputBuffer(sampleProvider, this);
connect(m_audioOutputBuffer, &CAudioOutputBuffer::outputVolumeStream, this, &Output::outputVolumeStream);
m_device = outputDevice;
QAudioDeviceInfo selectedDevice;
if (outputDevice.isDefault())
{
selectedDevice = QAudioDeviceInfo::defaultOutputDevice();
}
else
{
// TODO: Add smart algorithm to find the device with lowest latency
const QList<QAudioDeviceInfo> outputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput);
for (const QAudioDeviceInfo &d : outputDevices)
{
if (d.deviceName() == outputDevice.getName())
{
selectedDevice = d;
}
}
}
QAudioFormat outputFormat;
outputFormat.setSampleRate(48000);
outputFormat.setChannelCount(1);
@@ -94,11 +116,11 @@ namespace BlackCore
outputFormat.setByteOrder(QAudioFormat::LittleEndian);
outputFormat.setCodec("audio/pcm");
if (!outputDevice.isFormatSupported(outputFormat))
if (!selectedDevice.isFormatSupported(outputFormat))
{
outputFormat = outputDevice.nearestFormat(outputFormat);
outputFormat = selectedDevice.nearestFormat(outputFormat);
const QString w =
outputDevice.deviceName() %
selectedDevice.deviceName() %
": Default OUTPUT format not supported - trying to use nearest" %
" Sample rate: " % QString::number(outputFormat.sampleRate()) %
" Sample size: " % QString::number(outputFormat.sampleSize()) %
@@ -109,7 +131,7 @@ namespace BlackCore
CLogMessage(this).warning(w);
}
m_audioOutputCom.reset(new QAudioOutput(outputDevice, outputFormat));
m_audioOutputCom.reset(new QAudioOutput(selectedDevice, outputFormat));
// m_audioOutput->setBufferSize(bufferSize);
m_audioOutputBuffer->open(QIODevice::ReadWrite | QIODevice::Unbuffered);
m_audioOutputBuffer->setAudioFormat(outputFormat);

View File

@@ -12,9 +12,9 @@
#define BLACKCORE_AFV_AUDIO_OUTPUT_H
#include "blacksound/sampleprovider/sampleprovider.h"
#include "blackmisc/audio/audiodeviceinfo.h"
#include <QObject>
#include <QAudioDeviceInfo>
#include <QAudioOutput>
namespace BlackCore
@@ -26,7 +26,6 @@ namespace BlackCore
//! Stream args
struct OutputVolumeStreamArgs
{
QAudioDeviceInfo DeviceNumber;
double PeakRaw = 0.0;
double PeakDB = -1 * std::numeric_limits<double>::infinity();
double PeakVU = 0.0;
@@ -82,13 +81,13 @@ namespace BlackCore
}
//! Start output
void start(const QAudioDeviceInfo &outputDevice, BlackSound::SampleProvider::ISampleProvider *sampleProvider);
void start(const BlackMisc::Audio::CAudioDeviceInfo &outputDevice, BlackSound::SampleProvider::ISampleProvider *sampleProvider);
//! Stop output
void stop();
//! Corresponding device
const QAudioDeviceInfo &device() const { return m_device; }
const BlackMisc::Audio::CAudioDeviceInfo &device() const { return m_device; }
signals:
//! Streaming data
@@ -97,7 +96,7 @@ namespace BlackCore
private:
bool m_started = false;
QAudioDeviceInfo m_device;
BlackMisc::Audio::CAudioDeviceInfo m_device;
QScopedPointer<QAudioOutput> m_audioOutputCom;
CAudioOutputBuffer *m_audioOutputBuffer = nullptr;
};

View File

@@ -106,12 +106,12 @@ namespace BlackCore
QStringList CAfvClient::availableInputDevices() const
{
return CAudioDeviceInfoList::allQtInputDevices().getDeviceNames();
return CAudioDeviceInfoList::allInputDevices().getDeviceNames();
}
QStringList CAfvClient::availableOutputDevices() const
{
return CAudioDeviceInfoList::allQtOutputDevices().getDeviceNames();
return CAudioDeviceInfoList::allOutputDevices().getDeviceNames();
}
void CAfvClient::setBypassEffects(bool value)
@@ -132,14 +132,14 @@ namespace BlackCore
Q_UNUSED(mute)
}
bool CAfvClient::restartWithNewDevices(const QAudioDeviceInfo &inputDevice, const QAudioDeviceInfo &outputDevice)
bool CAfvClient::restartWithNewDevices(const CAudioDeviceInfo &inputDevice, const CAudioDeviceInfo &outputDevice)
{
this->stop();
this->start(inputDevice, outputDevice, allTransceiverIds());
return true;
}
void CAfvClient::start(const QAudioDeviceInfo &inputDevice, const QAudioDeviceInfo &outputDevice, const QVector<quint16> &transceiverIDs)
void CAfvClient::start(const CAudioDeviceInfo &inputDevice, const CAudioDeviceInfo &outputDevice, const QVector<quint16> &transceiverIDs)
{
if (m_isStarted)
{
@@ -154,21 +154,27 @@ namespace BlackCore
outputSampleProvider = new CVolumeSampleProvider(soundcardSampleProvider, this);
outputSampleProvider->setVolume(m_outputVolume);
m_output->start(outputDevice.isNull() ? QAudioDeviceInfo::defaultOutputDevice() : outputDevice, outputSampleProvider);
m_input->start(inputDevice.isNull() ? QAudioDeviceInfo::defaultInputDevice() : inputDevice);
m_output->start(outputDevice, outputSampleProvider);
m_input->start(inputDevice);
m_startDateTimeUtc = QDateTime::currentDateTimeUtc();
m_connection->setReceiveAudio(true);
m_voiceServerPositionTimer->start(5000);
this->onSettingsChanged(); // make sure all settings are applied
m_isStarted = true;
CLogMessage(this).info(u"Started [Input: %1] [Output: %2]") << inputDevice.deviceName() << outputDevice.deviceName();
CLogMessage(this).info(u"Started [Input: %1] [Output: %2]") << inputDevice.getName() << outputDevice.getName();
}
void CAfvClient::start(const QString &inputDeviceName, const QString &outputDeviceName)
{
const QAudioDeviceInfo i = CAudioDeviceInfoList::allQtInputDevices().findByName(inputDeviceName).toAudioDeviceInfo();
const QAudioDeviceInfo o = CAudioDeviceInfoList::allQtOutputDevices().findByName(outputDeviceName).toAudioDeviceInfo();
if (QThread::currentThread() != this->thread())
{
QMetaObject::invokeMethod(this, "start", Q_ARG(QString, inputDeviceName), Q_ARG(QString, outputDeviceName));
return;
}
const CAudioDeviceInfo i(CAudioDeviceInfo::InputDevice, inputDeviceName);
const CAudioDeviceInfo o(CAudioDeviceInfo::OutputDevice, outputDeviceName);
this->start(i, o, allTransceiverIds());
}
@@ -581,17 +587,17 @@ namespace BlackCore
}
}
const QAudioDeviceInfo &CAfvClient::getInputDevice() const
const CAudioDeviceInfo &CAfvClient::getInputDevice() const
{
if (m_input) { return m_input->device(); }
static const QAudioDeviceInfo null = QAudioDeviceInfo();
return null;
static const CAudioDeviceInfo nullDevice;
return nullDevice;
}
const QAudioDeviceInfo &CAfvClient::getOutputDevice() const
const CAudioDeviceInfo &CAfvClient::getOutputDevice() const
{
if (m_output) { return m_output->device(); }
static const QAudioDeviceInfo nullDevice = QAudioDeviceInfo();
static const CAudioDeviceInfo nullDevice;
return nullDevice;
}

View File

@@ -23,11 +23,11 @@
#include "blackmisc/aviation/comsystem.h"
#include "blackmisc/audio/audiosettings.h"
#include "blackmisc/audio/ptt.h"
#include "blackmisc/audio/audiodeviceinfo.h"
#include "blackmisc/logcategorylist.h"
#include "blackmisc/identifiable.h"
#include "blackmisc/settingscache.h"
#include <QAudioDeviceInfo>
#include <QDateTime>
#include <QAudioInput>
#include <QAudioOutput>
@@ -90,8 +90,8 @@ namespace BlackCore
void setMuted(bool mute);
//! @}
bool restartWithNewDevices(const QAudioDeviceInfo &inputDevice, const QAudioDeviceInfo &outputDevice);
void start(const QAudioDeviceInfo &inputDevice, const QAudioDeviceInfo &outputDevice, const QVector<quint16> &transceiverIDs);
bool restartWithNewDevices(const BlackMisc::Audio::CAudioDeviceInfo &inputDevice, const BlackMisc::Audio::CAudioDeviceInfo &outputDevice);
void start(const BlackMisc::Audio::CAudioDeviceInfo &inputDevice, const BlackMisc::Audio::CAudioDeviceInfo &outputDevice, const QVector<quint16> &transceiverIDs);
Q_INVOKABLE void start(const QString &inputDeviceName, const QString &outputDeviceName);
void stop();
@@ -155,10 +155,13 @@ namespace BlackCore
//! @}
//! Recently used device @{
const QAudioDeviceInfo &getInputDevice() const;
const QAudioDeviceInfo &getOutputDevice() const;
const BlackMisc::Audio::CAudioDeviceInfo &getInputDevice() const;
const BlackMisc::Audio::CAudioDeviceInfo &getOutputDevice() const;
//! @}
QString getReceivingCallsignsCom1();
QString getReceivingCallsignsCom2();
signals:
//! Receiving callsigns have been changed
//! \remark callsigns I do receive
@@ -183,8 +186,7 @@ namespace BlackCore
void audioOutDataAvailable(const AudioRxOnTransceiversDto &dto);
void inputVolumeStream(const Audio::InputVolumeStreamArgs &args);
void outputVolumeStream(const Audio::OutputVolumeStreamArgs &args);
QString getReceivingCallsignsCom1();
QString getReceivingCallsignsCom2();
void inputOpusDataAvailable();

View File

@@ -137,7 +137,6 @@ namespace BlackCore
virtual void playSelcalTone(const BlackMisc::Aviation::CSelcal &selcal) = 0;
//! Play notification sound
//! \param notification CSoundGenerator::Notification
//! \param considerSettings consider settings (notification on/off), false means settings ignored
//! \param volume 0..100
virtual void playNotification(BlackMisc::Audio::CNotificationSounds::NotificationFlag notification, bool considerSettings, int volume = -1) = 0;

View File

@@ -26,7 +26,6 @@
#include "blackmisc/simplecommandparser.h"
#include "blackmisc/statusmessage.h"
#include "blackmisc/verify.h"
#include "blacksound/soundgenerator.h"
#include <QTimer>
#include <QtGlobal>
@@ -58,7 +57,7 @@ namespace BlackCore
{
const CSettings as = m_audioSettings.getThreadLocal();
this->setVoiceOutputVolume(as.getOutVolume());
m_selcalPlayer = new CSelcalPlayer(QAudioDeviceInfo::defaultOutputDevice(), this);
m_selcalPlayer = new CSelcalPlayer(CAudioDeviceInfo::getDefaultOutputDevice(), this);
connect(&m_voiceClient, &CAfvClient::ptt, this, &CContextAudio::ptt);
@@ -94,7 +93,7 @@ namespace BlackCore
CAudioDeviceInfoList CContextAudio::getAudioDevices() const
{
if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; }
return CAudioDeviceInfoList::allQtDevices();
return CAudioDeviceInfoList::allDevices();
}
CAudioDeviceInfoList CContextAudio::getCurrentAudioDevices() const
@@ -102,13 +101,11 @@ namespace BlackCore
if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO; }
// either the devices really used, or settings
CAudioDeviceInfo inputDevice = CAudioDeviceInfoList::fromQtInputDevice(m_voiceClient.getInputDevice());
if (!inputDevice.isValid()) { inputDevice = CAudioDeviceInfoList::allQtInputDevices().findByName(m_inputDeviceSetting.get()); }
if (!inputDevice.isValid()) { inputDevice = CAudioDeviceInfoList::qtDefaultInputDevice(); }
CAudioDeviceInfo inputDevice = m_voiceClient.getInputDevice();
if (!inputDevice.isValid()) { inputDevice = CAudioDeviceInfo::getDefaultInputDevice(); }
CAudioDeviceInfo outputDevice = CAudioDeviceInfoList::fromQtOutputDevice(m_voiceClient.getOutputDevice());
if (!outputDevice.isValid()) { outputDevice = CAudioDeviceInfoList::allQtOutputDevices().findByName(m_outputDeviceSetting.get()); }
if (!outputDevice.isValid()) { outputDevice = CAudioDeviceInfoList::qtDefaultOutputDevice(); }
CAudioDeviceInfo outputDevice = m_voiceClient.getOutputDevice();
if (!outputDevice.isValid()) { outputDevice = CAudioDeviceInfo::getDefaultOutputDevice(); }
CAudioDeviceInfoList devices;
devices.push_back(inputDevice);
@@ -121,7 +118,7 @@ namespace BlackCore
if (m_debugEnabled) { CLogMessage(this, CLogCategory::contextSlot()).debug() << Q_FUNC_INFO << inputDevice << outputDevice; }
if (!inputDevice.getName().isEmpty()) { m_inputDeviceSetting.setAndSave(inputDevice.getName()); }
if (!outputDevice.getName().isEmpty()) { m_outputDeviceSetting.setAndSave(outputDevice.getName()); }
const bool changed = m_voiceClient.restartWithNewDevices(inputDevice.toAudioDeviceInfo(), outputDevice.toAudioDeviceInfo());
const bool changed = m_voiceClient.restartWithNewDevices(inputDevice, outputDevice);
if (changed)
{
emit this->changedSelectedAudioDevices(this->getCurrentAudioDevices());
@@ -392,7 +389,7 @@ namespace BlackCore
{
const CUser connectedUser = this->getIContextNetwork()->getConnectedServer().getUser();
m_voiceClient.connectTo(connectedUser.getId(), connectedUser.getPassword(), connectedUser.getCallsign().asString());
m_voiceClient.start(QAudioDeviceInfo::defaultInputDevice(), QAudioDeviceInfo::defaultOutputDevice(), {0, 1});
m_voiceClient.start(CAudioDeviceInfo::getDefaultInputDevice(), CAudioDeviceInfo::getDefaultOutputDevice(), {0, 1});
}
else if (to.isDisconnected())
{

View File

@@ -26,27 +26,6 @@ namespace BlackMisc
m_deviceName(name), m_hostName(QHostInfo::localHostName())
{ }
QAudioDeviceInfo CAudioDeviceInfo::toAudioDeviceInfo() const
{
QList<QAudioDeviceInfo> devices;
switch (this->getType())
{
case InputDevice: devices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); break;
case OutputDevice: devices = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); break;
default:
devices = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput);
devices += QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
break;
}
for (const QAudioDeviceInfo &d : as_const(devices))
{
if (d.deviceName() == this->getName()) { return d; }
}
return QAudioDeviceInfo();
}
CAudioDeviceInfo::DeviceType CAudioDeviceInfo::fromQtMode(QAudio::Mode m)
{
switch (m)

View File

@@ -44,7 +44,7 @@ namespace BlackMisc
CAudioDeviceInfo();
//! Constructor.
CAudioDeviceInfo(DeviceType type, const QString &getName);
CAudioDeviceInfo(DeviceType type, const QString &name);
//! Get the device name
const QString &getName() const { return m_deviceName; }
@@ -58,8 +58,8 @@ namespace BlackMisc
//! Valid audio device object?
bool isValid() const { return !m_deviceName.isEmpty(); }
//! To QAudioDeviceInfo
QAudioDeviceInfo toAudioDeviceInfo() const;
//! Is this a default device?
bool isDefault() const { return m_deviceName == "default"; }
//! Convert the Qt type
static DeviceType fromQtMode(QAudio::Mode m);

View File

@@ -71,7 +71,7 @@ namespace BlackMisc
return names;
}
CAudioDeviceInfoList CAudioDeviceInfoList::allQtInputDevices()
CAudioDeviceInfoList CAudioDeviceInfoList::allInputDevices()
{
const QList<QAudioDeviceInfo> inputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput);
CAudioDeviceInfoList devices;
@@ -84,7 +84,7 @@ namespace BlackMisc
return devices;
}
CAudioDeviceInfoList CAudioDeviceInfoList::allQtOutputDevices()
CAudioDeviceInfoList CAudioDeviceInfoList::allOutputDevices()
{
const QList<QAudioDeviceInfo> outputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput);
CAudioDeviceInfoList devices;
@@ -97,42 +97,12 @@ namespace BlackMisc
return devices;
}
CAudioDeviceInfoList CAudioDeviceInfoList::allQtDevices()
CAudioDeviceInfoList CAudioDeviceInfoList::allDevices()
{
CAudioDeviceInfoList i = allQtInputDevices();
i.push_back(allQtOutputDevices());
CAudioDeviceInfoList i = allInputDevices();
i.push_back(allOutputDevices());
return i;
}
CAudioDeviceInfo CAudioDeviceInfoList::fromQtInputDevice(const QAudioDeviceInfo &device)
{
const CAudioDeviceInfoList dl = CAudioDeviceInfoList::allQtInputDevices();
for (const CAudioDeviceInfo &d : dl)
{
if (d.getName() == device.deviceName()) { return d; }
}
return CAudioDeviceInfo();
}
CAudioDeviceInfo CAudioDeviceInfoList::fromQtOutputDevice(const QAudioDeviceInfo &device)
{
const CAudioDeviceInfoList dl = CAudioDeviceInfoList::allQtOutputDevices();
for (const CAudioDeviceInfo &d : dl)
{
if (d.getName() == device.deviceName()) { return d; }
}
return CAudioDeviceInfo();
}
CAudioDeviceInfo CAudioDeviceInfoList::qtDefaultInputDevice()
{
return fromQtInputDevice(QAudioDeviceInfo::defaultInputDevice());
}
CAudioDeviceInfo CAudioDeviceInfoList::qtDefaultOutputDevice()
{
return fromQtOutputDevice(QAudioDeviceInfo::defaultOutputDevice());
}
} // namespace
} // namespace

View File

@@ -54,14 +54,10 @@ namespace BlackMisc
//! All names
QStringList getDeviceNames() const;
//! List based on the Qt devices @{
static CAudioDeviceInfoList allQtInputDevices();
static CAudioDeviceInfoList allQtOutputDevices();
static CAudioDeviceInfoList allQtDevices();
static CAudioDeviceInfo fromQtInputDevice(const QAudioDeviceInfo &device);
static CAudioDeviceInfo fromQtOutputDevice(const QAudioDeviceInfo &device);
static CAudioDeviceInfo qtDefaultInputDevice();
static CAudioDeviceInfo qtDefaultOutputDevice();
//! Lists of all available devices @{
static CAudioDeviceInfoList allInputDevices();
static CAudioDeviceInfoList allOutputDevices();
static CAudioDeviceInfoList allDevices();
//! @}
};
} //namespace

View File

@@ -10,12 +10,13 @@
#include <QTimer>
using namespace BlackMisc;
using namespace BlackMisc::Audio;
using namespace BlackMisc::Aviation;
using namespace BlackMisc::PhysicalQuantities;
namespace BlackSound
{
CSelcalPlayer::CSelcalPlayer(const QAudioDeviceInfo &device, QObject *parent)
CSelcalPlayer::CSelcalPlayer(const CAudioDeviceInfo &device, QObject *parent)
: QObject(parent),
m_threadedPlayer(this, "CSelcalPlayer", device)
{

View File

@@ -14,11 +14,10 @@
#include "blacksound/threadedtonepairplayer.h"
#include "blacksound/tonepair.h"
#include "blacksoundexport.h"
#include "blackmisc/audio/audiodeviceinfo.h"
#include "blackmisc/aviation/selcal.h"
#include "blackmisc/worker.h"
#include <QAudioDeviceInfo>
namespace BlackSound
{
//! SELCAL player
@@ -28,7 +27,7 @@ namespace BlackSound
public:
//! Constructor
CSelcalPlayer(const QAudioDeviceInfo &device = QAudioDeviceInfo::defaultOutputDevice(), QObject *parent = nullptr);
CSelcalPlayer(const BlackMisc::Audio::CAudioDeviceInfo &device, QObject *parent = nullptr);
//! Destructor
virtual ~CSelcalPlayer() override;

View File

@@ -1,477 +0,0 @@
/* Copyright (C) 2013
* swift project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated,
* or distributed except according to the terms contained in the LICENSE file.
*/
#include "blacksound/soundgenerator.h"
#include "blackmisc/directoryutils.h"
#include "blackmisc/filedeleter.h"
#include "blackmisc/worker.h"
#include <QtCore/qendian.h>
#include <math.h>
#include <qmath.h>
#include <qendian.h>
#include <QMultimedia>
#include <QAudioOutput>
#include <QTimer>
#include <QFile>
#include <QSound>
using namespace BlackMisc;
using namespace BlackMisc::Aviation;
using namespace BlackMisc::PhysicalQuantities;
using namespace BlackMisc::Audio;
namespace BlackSound
{
QDateTime CSoundGenerator::s_selcalStarted = QDateTime::currentDateTimeUtc();
CSoundGenerator::CSoundGenerator(const QAudioDeviceInfo &device, const QAudioFormat &format, const QList<Tone> &tones, CNotificationSounds::PlayMode mode, QObject *parent)
: QIODevice(parent),
m_tones(tones), m_position(0), m_playMode(mode), m_endReached(false), m_oneCycleDurationMs(calculateDurationMs(tones)),
m_device(device), m_audioFormat(format), m_audioOutput(new QAudioOutput(format))
{
Q_ASSERT_X(tones.size() > 0, Q_FUNC_INFO, "No tones");
}
CSoundGenerator::CSoundGenerator(const QList<Tone> &tones, CNotificationSounds::PlayMode mode, QObject *parent)
: QIODevice(parent),
m_tones(tones), m_position(0), m_playMode(mode), m_endReached(false), m_oneCycleDurationMs(calculateDurationMs(tones)),
m_device(QAudioDeviceInfo::defaultOutputDevice()), m_audioFormat(CSoundGenerator::defaultAudioFormat()),
m_audioOutput(new QAudioOutput(CSoundGenerator::defaultAudioFormat()))
{
Q_ASSERT_X(tones.size() > 0, Q_FUNC_INFO, "No tones");
}
CSoundGenerator::~CSoundGenerator()
{
this->stop(true);
if (m_ownThread) { m_ownThread->deleteLater(); }
}
void CSoundGenerator::start(int volume, bool pull)
{
if (m_buffer.isEmpty()) this->generateData();
this->open(QIODevice::ReadOnly);
m_audioOutput->setVolume(qreal(0.01 * volume));
if (pull)
{
// For an output device, the QAudioOutput class will pull data from the QIODevice
// (using QIODevice::read()) when more audio data is required.
m_audioOutput->start(this); // pull
}
else
{
// In push mode, the audio device provides a QIODevice instance that can be
// written or read to as needed. Typically this results in simpler code but more buffering, which may affect latency.
if (!m_pushTimer)
{
m_pushTimer = new QTimer(this);
bool ok = connect(m_pushTimer, &QTimer::timeout, this, &CSoundGenerator::pushTimerExpired);
Q_ASSERT(ok);
Q_UNUSED(ok) // suppress Clang warning in release build
m_pushTimer->start(20);
}
m_pushModeIODevice = m_audioOutput->start(); // push, IO device not owned
}
}
void CSoundGenerator::stop(bool destructor)
{
// m_audioOutput->setVolume(0); // Bug or feature, killing the applicaions volume?
if (this->isOpen())
{
// 1. isOpen avoids redundant signals
// 2. OK in destructor, see http://stackoverflow.com/a/14024955/356726
this->close(); // close IO Device
m_audioOutput->stop();
if (m_pushTimer) { m_pushTimer->stop(); }
emit this->stopped();
}
m_position = 0;
if (destructor) return;
// trigger own termination
if (m_playMode == CNotificationSounds::SingleWithAutomaticDeletion)
{
emit this->stopping();
if (!m_ownThread) this->deleteLater(); // with own thread, thread signal will call deleteLater
}
}
void CSoundGenerator::pushTimerExpired()
{
if (m_pushModeIODevice && !m_endReached && m_audioOutput->state() != QAudio::StoppedState)
{
int chunks = m_audioOutput->bytesFree() / m_audioOutput->periodSize();
while (chunks)
{
// periodSize-> Returns the period size in bytes.
//! \todo looks wrong: read() will memcpy from m_buffer.constData() to m_buffer.data()
const qint64 len = this->read(m_buffer.data(), m_audioOutput->periodSize());
if (len >= 0)
{
m_pushModeIODevice->write(m_buffer.constData(), len);
}
if (len != m_audioOutput->periodSize())
{
break; // not a complete period, so buffer is completely read
}
--chunks;
}
}
else
{
if (m_pushTimer)
{
m_pushTimer->stop();
m_pushTimer->disconnect(this);
}
if (m_playMode == CNotificationSounds::SingleWithAutomaticDeletion)
{
this->stop();
}
}
}
void CSoundGenerator::generateData()
{
Q_ASSERT(m_tones.size() > 0);
const int bytesPerSample = m_audioFormat.sampleSize() / 8;
const int bytesForAllChannels = m_audioFormat.channelCount() * bytesPerSample;
qint64 totalLength = 0;
for (const Tone &t : m_tones)
{
totalLength += m_audioFormat.sampleRate() * bytesForAllChannels * t.m_durationMs / 1000;
}
Q_ASSERT(totalLength % bytesForAllChannels == 0);
Q_UNUSED(bytesForAllChannels) // suppress warning in release builds
m_buffer.resize(static_cast<int>(totalLength)); // potentially dangerous cast, but I see no use case where the int range on any of our platforms is exceeded
unsigned char *bufferPointer = reinterpret_cast<unsigned char *>(m_buffer.data()); // clazy:exclude=detaching-member
for (const Tone &t : m_tones)
{
qint64 bytesPerTone = m_audioFormat.sampleRate() * bytesForAllChannels * t.m_durationMs / 1000;
qint64 last0AmplitudeSample = bytesPerTone; // last sample when amplitude was 0
int sampleIndexPerTone = 0;
while (bytesPerTone)
{
// http://hyperphysics.phy-astr.gsu.edu/hbase/audio/sumdif.html
// http://math.stackexchange.com/questions/164369/how-do-you-calculate-the-frequency-perceived-by-humans-of-two-sinusoidal-waves-a
const double pseudoTime = double(sampleIndexPerTone % m_audioFormat.sampleRate()) / m_audioFormat.sampleRate();
double amplitude = 0.0; // amplitude -1 -> +1 , 0 is silence
if (t.m_frequencyHz > 10)
{
// the combination of two frequencies actually would have 2*amplitude,
// but I have to normalize with amplitude -1 -> +1
amplitude = t.m_secondaryFrequencyHz == 0 ?
qSin(2 * M_PI * t.m_frequencyHz * pseudoTime) :
qSin(M_PI * (t.m_frequencyHz + t.m_secondaryFrequencyHz) * pseudoTime) *
qCos(M_PI * (t.m_frequencyHz - t.m_secondaryFrequencyHz) * pseudoTime);
}
// avoid overflow
Q_ASSERT(amplitude <= 1.0 && amplitude >= -1.0);
if (amplitude < -1.0)
{
amplitude = -1.0;
}
else if (amplitude > 1.0)
{
amplitude = 1.0;
}
else if (qAbs(amplitude) < double(1.0 / 65535))
{
amplitude = 0;
last0AmplitudeSample = bytesPerTone;
}
// generate this for all channels, usually 1 channel
for (int i = 0; i < m_audioFormat.channelCount(); ++i)
{
this->writeAmplitudeToBuffer(amplitude, bufferPointer);
bufferPointer += bytesPerSample;
bytesPerTone -= bytesPerSample;
}
++sampleIndexPerTone;
}
// fixes the range from the last 0 pass through
if (last0AmplitudeSample > 0)
{
bufferPointer -= last0AmplitudeSample;
while (last0AmplitudeSample)
{
double amplitude = 0.0; // amplitude -1 -> +1 , 0 is silence
// generate this for all channels, usually 1 channel
for (int i = 0; i < m_audioFormat.channelCount(); ++i)
{
this->writeAmplitudeToBuffer(amplitude, bufferPointer);
bufferPointer += bytesPerSample;
last0AmplitudeSample -= bytesPerSample;
}
}
}
}
}
void CSoundGenerator::writeAmplitudeToBuffer(const double amplitude, unsigned char *bufferPointer)
{
if (m_audioFormat.sampleSize() == 8 && m_audioFormat.sampleType() == QAudioFormat::UnSignedInt)
{
const quint8 value = static_cast<quint8>((1.0 + amplitude) / 2 * 255);
*reinterpret_cast<quint8 *>(bufferPointer) = value;
}
else if (m_audioFormat.sampleSize() == 8 && m_audioFormat.sampleType() == QAudioFormat::SignedInt)
{
const qint8 value = static_cast<qint8>(amplitude * 127);
*reinterpret_cast<qint8 *>(bufferPointer) = value;
}
else if (m_audioFormat.sampleSize() == 16 && m_audioFormat.sampleType() == QAudioFormat::UnSignedInt)
{
quint16 value = static_cast<quint16>((1.0 + amplitude) / 2 * 65535);
if (m_audioFormat.byteOrder() == QAudioFormat::LittleEndian)
{
qToLittleEndian<quint16>(value, bufferPointer);
}
else
{
qToBigEndian<quint16>(value, bufferPointer);
}
}
else if (m_audioFormat.sampleSize() == 16 && m_audioFormat.sampleType() == QAudioFormat::SignedInt)
{
qint16 value = static_cast<qint16>(amplitude * 32767);
if (m_audioFormat.byteOrder() == QAudioFormat::LittleEndian)
{
qToLittleEndian<qint16>(value, bufferPointer);
}
else
{
qToBigEndian<qint16>(value, bufferPointer);
}
}
}
bool CSoundGenerator::saveToWavFile(const QString &fileName) const
{
QFile file(fileName);
bool success = file.open(QIODevice::WriteOnly);
if (!success) return false;
CombinedHeader header;
constexpr auto 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(static_cast<uint>(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(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(m_buffer) == m_buffer.size();
file.close();
return success;
}
qint64 CSoundGenerator::calculateDurationMs(const QList<CSoundGenerator::Tone> &tones)
{
if (tones.isEmpty()) { return 0; }
qint64 d = 0;
for (const Tone &t : tones)
{
d += t.m_durationMs;
}
return d;
}
qint64 CSoundGenerator::readData(char *data, qint64 len)
{
if (len < 1) { return 0; }
if (m_endReached)
{
this->stop(); // all data read, we can stop output
return 0;
}
if (!this->isOpen()) return 0;
qint64 total = 0; // toal is used for the overflow when starting new wave again
while (len - total > 0)
{
const qint64 chunkSize = qMin((m_buffer.size() - m_position), (len - total));
memcpy(data + total, m_buffer.constData() + m_position, static_cast<size_t>(chunkSize));
m_position = (m_position + chunkSize) % m_buffer.size();
total += chunkSize;
if (m_position == 0 &&
(m_playMode == CNotificationSounds::Single || m_playMode == CNotificationSounds::SingleWithAutomaticDeletion))
{
m_endReached = true;
break;
}
}
return total;
}
qint64 CSoundGenerator::writeData(const char *data, qint64 len)
{
Q_UNUSED(data)
Q_UNUSED(len)
return 0;
}
qint64 CSoundGenerator::bytesAvailable() const
{
return m_buffer.size() + QIODevice::bytesAvailable();
}
QAudioFormat CSoundGenerator::defaultAudioFormat()
{
QAudioFormat format;
format.setSampleRate(44100);
format.setChannelCount(1);
format.setSampleSize(16); // 8 or 16 works
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
return format;
}
QAudioDeviceInfo CSoundGenerator::findClosestOutputDevice(const CAudioDeviceInfo &audioDevice)
{
Q_ASSERT(audioDevice.getType() == CAudioDeviceInfo::OutputDevice);
const QString lookFor = audioDevice.getName().toLower();
QAudioDeviceInfo qtDevice = QAudioDeviceInfo::defaultOutputDevice();
if (lookFor.startsWith("default")) { return qtDevice; }
int score = 0;
for (const QAudioDeviceInfo &qd : QAudioDeviceInfo::availableDevices(QAudio::AudioOutput))
{
const QString cn = qd.deviceName().toLower();
if (lookFor == cn) { return qd; } // exact match
if (cn.length() < lookFor.length())
{
if (lookFor.contains(cn) && cn.length() > score)
{
qtDevice = qd;
score = cn.length();
}
}
else
{
if (cn.contains(lookFor) && lookFor.length() > score)
{
qtDevice = qd;
score = lookFor.length();
}
}
}
return qtDevice;
}
CSoundGenerator *CSoundGenerator::playSignal(int volume, const QList<CSoundGenerator::Tone> &tones, const QAudioDeviceInfo &device)
{
CSoundGenerator *generator = new CSoundGenerator(device, CSoundGenerator::defaultAudioFormat(), tones, CNotificationSounds::SingleWithAutomaticDeletion);
if (tones.isEmpty()) { return generator; } // that was easy
if (volume < 1) { return generator; }
if (generator->singleCyleDurationMs() < 10) { return generator; } // unable to hear
// play, and maybe clean up when done
generator->start(volume);
return generator;
}
void CSoundGenerator::playSignalInBackground(int volume, const QList<CSoundGenerator::Tone> &tones, const QAudioDeviceInfo &device)
{
CSoundGenerator *generator = new CSoundGenerator(device, CSoundGenerator::defaultAudioFormat(), tones, CNotificationSounds::SingleWithAutomaticDeletion);
if (tones.isEmpty()) { return; } // that was easy
if (volume < 1) { return; }
if (generator->singleCyleDurationMs() < 10) { return; } // unable to hear
CWorker *worker = CWorker::fromTask(QCoreApplication::instance(), "CSoundGenerator::playSignalInBackground", [generator, volume]()
{
generator->start(volume, false);
});
worker->then([generator]()
{
generator->deleteLater();
});
}
void CSoundGenerator::playSelcal(int volume, const CSelcal &selcal, const QAudioDeviceInfo &device)
{
QList<CSoundGenerator::Tone> tones;
if (selcal.isValid())
{
QList<CFrequency> frequencies = selcal.getFrequencies();
Q_ASSERT(frequencies.size() == 4);
const CTime oneSec(1000.0, CTimeUnit::ms());
Tone t1(frequencies.at(0), frequencies.at(1), oneSec);
Tone t2(CFrequency(), oneSec / 5.0);
Tone t3(frequencies.at(2), frequencies.at(3), oneSec);
tones << t1 << t2 << t3;
}
CSoundGenerator::playSignalInBackground(volume, tones, device);
}
void CSoundGenerator::playSelcal(int volume, const CSelcal &selcal, const CAudioDeviceInfo &audioDevice)
{
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::playFile(int volume, const QString &file, bool removeFileAfterPlaying)
{
if (!QFile::exists(file)) { return; }
Q_UNUSED(volume)
QSound::play(file);
// I cannot delete the file here, only after it has been played
if (removeFileAfterPlaying) { new CTimedFileDeleter(file, 1000 * 60, QCoreApplication::instance()); }
}
void CSoundGenerator::printAllQtSoundDevices(QTextStream &out)
{
out << "output:" << endl;
for (const QAudioDeviceInfo &qd : QAudioDeviceInfo::availableDevices(QAudio::AudioOutput))
{
out << qd.deviceName() << endl;
}
out << "input:" << endl;
for (const QAudioDeviceInfo &qd : QAudioDeviceInfo::availableDevices(QAudio::AudioInput))
{
out << qd.deviceName() << endl;
}
}
} // namespace

View File

@@ -1,254 +0,0 @@
/* Copyright (C) 2013
* swift project Community / Contributors
*
* This file is part of swift project. It is subject to the license terms in the LICENSE file found in the top-level
* directory of this distribution. No part of swift project, including this file, may be copied, modified, propagated,
* or distributed except according to the terms contained in the LICENSE file.
*/
//! \file
#ifndef BLACKSOUND_SOUNDGENERATOR_H
#define BLACKSOUND_SOUNDGENERATOR_H
#include "blacksoundexport.h"
#include "blackmisc/aviation/selcal.h"
#include "blackmisc/audio/audiodeviceinfo.h"
#include "blackmisc/pq/time.h"
#include "blackmisc/pq/frequency.h"
#include "blackmisc/audio/notificationsounds.h"
#include <QIODevice>
#include <QThread>
#include <QDateTime>
#include <QAudioFormat>
#include <QAudioOutput>
#include <QAudioDeviceInfo>
#include <QMediaPlayer>
namespace BlackSound
{
//! Playing simple sounds
class BLACKSOUND_EXPORT CSoundGenerator : public QIODevice
{
Q_OBJECT
public:
//! Tone to be played
struct Tone
{
friend class CSoundGenerator;
public:
//! Play frequency f for t milliseconds
Tone(const BlackMisc::PhysicalQuantities::CFrequency &frequency, const BlackMisc::PhysicalQuantities::CTime &duration) :
m_frequencyHz(static_cast<int>(frequency.valueRounded(BlackMisc::PhysicalQuantities::CFrequencyUnit::Hz()))),
m_secondaryFrequencyHz(0),
m_durationMs(static_cast<qint64>(duration.valueRounded(BlackMisc::PhysicalQuantities::CTimeUnit::ms()))) {}
//! Play 2 frequencies f for t milliseconds
Tone(const BlackMisc::PhysicalQuantities::CFrequency &frequency, const BlackMisc::PhysicalQuantities::CFrequency &secondaryFrequency, const BlackMisc::PhysicalQuantities::CTime &duration) :
m_frequencyHz(static_cast<int>(frequency.valueRounded(BlackMisc::PhysicalQuantities::CFrequencyUnit::Hz()))),
m_secondaryFrequencyHz(static_cast<int>(secondaryFrequency.valueRounded(BlackMisc::PhysicalQuantities::CFrequencyUnit::Hz()))),
m_durationMs(static_cast<qint64>(duration.valueRounded(BlackMisc::PhysicalQuantities::CTimeUnit::ms()))) {}
private:
int m_frequencyHz; //!< first tone's frequency, use 0 for silence
int m_secondaryFrequencyHz; //!< second tone's frequency, or 0
qint64 m_durationMs; //!< How long to play (duration)
};
//! Constructor
//! \param device device
//! \param format audio format
//! \param tones list of Tones
//! \param mode play once?
//! \param parent
//! \see PlayMode
CSoundGenerator(const QAudioDeviceInfo &device, const QAudioFormat &format, const QList<Tone> &tones, BlackMisc::Audio::CNotificationSounds::PlayMode mode, QObject *parent = nullptr);
//! Constructor
//! \param tones list of Tones
//! \param mode play once?
//! \param parent
//! \see PlayMode
CSoundGenerator(const QList<Tone> &tones, BlackMisc::Audio::CNotificationSounds::PlayMode mode, QObject *parent = nullptr);
//! Destructor
virtual ~CSoundGenerator() override;
//! Set volume
//! \param volume 0..100
void setVolume(int volume)
{
m_audioOutput->setVolume(qreal(volume / 100.0));
}
//! Close device, buffer stays intact
void stop(bool destructor = false);
//! Duration of one cycle
qint64 singleCyleDurationMs() const { return calculateDurationMs(m_tones); }
//! \copydoc QIODevice::readData()
virtual qint64 readData(char *data, qint64 maxlen) override;
//! \copydoc QIODevice::writeData()
//! \remarks NOT(!) used here
virtual qint64 writeData(const char *data, qint64 len) override;
//! \copydoc QIODevice::bytesAvailable()
virtual qint64 bytesAvailable() const override;
//! \copydoc QIODevice::seek()
virtual bool seek(qint64 pos) override
{
return m_endReached ? false : QIODevice::seek(pos);
}
//! \copydoc QIODevice::atEnd()
virtual bool atEnd() const override
{
return m_endReached ? true : QIODevice::atEnd();
}
//! Default audio format fo play these sounds
static QAudioFormat defaultAudioFormat();
//! Find the closest Qt device to this audio device
//! \param audioDevice output audio device
//! \return
static QAudioDeviceInfo findClosestOutputDevice(const BlackMisc::Audio::CAudioDeviceInfo &audioDevice);
//! Play signal of tones once
//! \param volume 0-100
//! \param tones list of tones
//! \param device device to be used
//! \return generator used, important with SingleWithAutomaticDeletion automatically deleted
static CSoundGenerator *playSignal(int volume, const QList<Tone> &tones, const QAudioDeviceInfo &device = QAudioDeviceInfo::defaultOutputDevice());
//! Play signal of tones once
//! \param volume 0-100
//! \param tones list of tones
//! \param device device to be used
static void playSignalInBackground(int volume, const QList<CSoundGenerator::Tone> &tones, const QAudioDeviceInfo &device);
//! Play SELCAL tone
//! \param volume 0-100
//! \param selcal
//! \param device device to be used
//! \see BlackMisc::Aviation::CSelcal
static void playSelcal(int volume, const BlackMisc::Aviation::CSelcal &selcal, const QAudioDeviceInfo &device = QAudioDeviceInfo::defaultOutputDevice());
//! Play SELCAL tone
//! \param volume 0-100
//! \param selcal
//! \param audioDevice device to be used
//! \see BlackMisc::Aviation::CSelcal
static void playSelcal(int volume, const BlackMisc::Aviation::CSelcal &selcal, const BlackMisc::Audio::CAudioDeviceInfo &audioDevice);
//! One cycle of tones takes t milliseconds
BlackMisc::PhysicalQuantities::CTime oneCycleDurationMs() const
{
return BlackMisc::PhysicalQuantities::CTime(m_oneCycleDurationMs, BlackMisc::PhysicalQuantities::CTimeUnit::ms());
}
//! Play given file
//! \param volume 0-100
//! \param file
//! \param removeFileAfterPlaying delete the file, after it has been played
static void playFile(int volume, const QString &file, bool removeFileAfterPlaying);
//! For debugging purposes
static void printAllQtSoundDevices(QTextStream &qtout);
signals:
//! Device was closed
//! \remarks With singleShot the signal indicates that sound sequence has finished
void stopped();
//! Generator is stopping
void stopping();
public slots:
//! Play sound, open device
//! \param volume 0..100
//! \param pull pull/push, if false push mode
void start(int volume, bool pull = true);
private slots:
//! Push mode, timer expired
void pushTimerExpired();
private:
//! Generate tone data in internal buffer
void generateData();
private:
QList<Tone> m_tones; //!< tones to be played
qint64 m_position; //!< position in buffer
BlackMisc::Audio::CNotificationSounds::PlayMode m_playMode; //!< end data provisioning after playing all tones, play endless loop
bool m_endReached; //!< indicates end in combination with single play
qint64 m_oneCycleDurationMs; //!< how long is one cycle of tones
QByteArray m_buffer; //!< generated buffer for data
QAudioDeviceInfo m_device; //!< audio device
QAudioFormat m_audioFormat; //!< used format
QScopedPointer<QAudioOutput> m_audioOutput;
QTimer *m_pushTimer = nullptr; //!< Push mode timer
QIODevice *m_pushModeIODevice = nullptr; //!< IO device when used in push mode
QThread *m_ownThread = nullptr;
static QDateTime s_selcalStarted;
//! Header for saving .wav files
struct chunk
{
char id[4];
quint32 size;
};
//! Header for saving .wav files
struct RiffHeader
{
chunk descriptor; //!< "RIFF"
char type[4]; //!< "WAVE"
};
//! Header for saving .wav files
struct WaveHeader
{
chunk descriptor;
quint16 audioFormat;
quint16 numChannels;
quint32 sampleRate;
quint32 byteRate;
quint16 blockAlign;
quint16 bitsPerSample;
};
//! Header for saving .wav files
struct DataHeader
{
chunk descriptor;
};
//! Header for saving .wav files
struct CombinedHeader
{
RiffHeader riff;
WaveHeader wave;
DataHeader data;
};
//! Duration of these tones
static qint64 calculateDurationMs(const QList<Tone> &tones);
//! save buffer to wav file
bool saveToWavFile(const QString &fileName) const;
//! Write amplitude to buffer
//! \param amplitude value -1 .. 1
//! \param bufferPointer current buffer pointer
void writeAmplitudeToBuffer(const double amplitude, unsigned char *bufferPointer);
};
} //namespace
#endif // guard

View File

@@ -12,10 +12,11 @@
#include <QTimer>
using namespace BlackMisc;
using namespace BlackMisc::Audio;
namespace BlackSound
{
CThreadedTonePairPlayer::CThreadedTonePairPlayer(QObject *owner, const QString &name, const QAudioDeviceInfo &device)
CThreadedTonePairPlayer::CThreadedTonePairPlayer(QObject *owner, const QString &name, const CAudioDeviceInfo &device)
: CContinuousWorker(owner, name),
m_deviceInfo(device)
{ }
@@ -35,7 +36,26 @@ namespace BlackSound
void CThreadedTonePairPlayer::initialize()
{
CLogMessage(this).info(u"CThreadedTonePairPlayer for device '%1'") << m_deviceInfo.deviceName();
CLogMessage(this).info(u"CThreadedTonePairPlayer for device '%1'") << m_deviceInfo.getName();
QAudioDeviceInfo selectedDevice;
if (m_deviceInfo.isDefault())
{
selectedDevice = QAudioDeviceInfo::defaultOutputDevice();
}
else
{
// TODO: Add smart algorithm to find the device with exactly supports the audio format below
const QList<QAudioDeviceInfo> outputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput);
for (const QAudioDeviceInfo &d : outputDevices)
{
if (d.deviceName() == m_deviceInfo.getName())
{
selectedDevice = d;
}
}
}
QAudioFormat format;
format.setSampleRate(44100);
format.setChannelCount(1);
@@ -43,12 +63,12 @@ namespace BlackSound
format.setCodec("audio/pcm");
format.setByteOrder(QAudioFormat::LittleEndian);
format.setSampleType(QAudioFormat::SignedInt);
if (!m_deviceInfo.isFormatSupported(format))
if (!selectedDevice.isFormatSupported(format))
{
format = m_deviceInfo.nearestFormat(format);
format = selectedDevice.nearestFormat(format);
}
m_audioFormat = format;
m_audioOutput = new QAudioOutput(m_deviceInfo, m_audioFormat, this);
m_audioOutput = new QAudioOutput(selectedDevice, m_audioFormat, this);
connect(m_audioOutput, &QAudioOutput::stateChanged, this, &CThreadedTonePairPlayer::handleStateChanged);
}

View File

@@ -13,9 +13,9 @@
#include "blacksoundexport.h"
#include "blacksound/tonepair.h"
#include "blackmisc/audio/audiodeviceinfo.h"
#include "blackmisc/worker.h"
#include <QAudioDeviceInfo>
#include <QAudioOutput>
#include <QBuffer>
#include <QMap>
@@ -34,7 +34,7 @@ namespace BlackSound
public:
//! Constructor
CThreadedTonePairPlayer(QObject *owner, const QString &name, const QAudioDeviceInfo &device = QAudioDeviceInfo::defaultOutputDevice());
CThreadedTonePairPlayer(QObject *owner, const QString &name, const BlackMisc::Audio::CAudioDeviceInfo &device);
//! Destructor
virtual ~CThreadedTonePairPlayer() override;
@@ -61,7 +61,7 @@ namespace BlackSound
//! \li sample type == signed int
void writeAmplitudeToBuffer(double amplitude, unsigned char *bufferPointer);
QAudioDeviceInfo m_deviceInfo;
BlackMisc::Audio::CAudioDeviceInfo m_deviceInfo;
QAudioOutput *m_audioOutput = nullptr;
QByteArray m_bufferData;
QBuffer m_buffer;