mirror of
https://github.com/swift-project/pilotclient.git
synced 2026-03-22 14:55:36 +08:00
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:
committed by
Mat Sutcliffe
parent
8656131eb1
commit
a2e3700739
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user