From a2e37007398f46752979b2bcc8b32a92e18bf1db Mon Sep 17 00:00:00 2001 From: Roland Rossgotterer Date: Wed, 2 Oct 2019 14:50:37 +0200 Subject: [PATCH] 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 --- src/blackcore/afv/audio/input.cpp | 30 +- src/blackcore/afv/audio/input.h | 9 +- src/blackcore/afv/audio/output.cpp | 32 +- src/blackcore/afv/audio/output.h | 9 +- src/blackcore/afv/clients/afvclient.cpp | 34 +- src/blackcore/afv/clients/afvclient.h | 16 +- src/blackcore/context/contextaudio.h | 1 - src/blackcore/context/contextaudioimpl.cpp | 19 +- src/blackmisc/audio/audiodeviceinfo.cpp | 21 - src/blackmisc/audio/audiodeviceinfo.h | 6 +- src/blackmisc/audio/audiodeviceinfolist.cpp | 40 +- src/blackmisc/audio/audiodeviceinfolist.h | 12 +- src/blacksound/selcalplayer.cpp | 3 +- src/blacksound/selcalplayer.h | 5 +- src/blacksound/soundgenerator.cpp | 477 -------------------- src/blacksound/soundgenerator.h | 254 ----------- src/blacksound/threadedtonepairplayer.cpp | 30 +- src/blacksound/threadedtonepairplayer.h | 6 +- 18 files changed, 141 insertions(+), 863 deletions(-) delete mode 100644 src/blacksound/soundgenerator.cpp delete mode 100644 src/blacksound/soundgenerator.h diff --git a/src/blackcore/afv/audio/input.cpp b/src/blackcore/afv/audio/input.cpp index bca3ed718..dec1d9c6e 100644 --- a/src/blackcore/afv/audio/input.cpp +++ b/src/blackcore/afv/audio/input.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include 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 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); diff --git a/src/blackcore/afv/audio/input.h b/src/blackcore/afv/audio/input.h index f3cea02d5..16338402e 100644 --- a/src/blackcore/afv/audio/input.h +++ b/src/blackcore/afv/audio/input.h @@ -13,8 +13,8 @@ #include "blacksound/sampleprovider/bufferedwaveprovider.h" #include "blacksound/codecs/opusencoder.h" +#include "blackmisc/audio/audiodeviceinfo.h" -#include #include #include #include @@ -70,7 +70,6 @@ namespace BlackCore struct InputVolumeStreamArgs { - QAudioDeviceInfo DeviceNumber; double PeakRaw = 0.0; double PeakDB = -1.0 * std::numeric_limits::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 m_audioInput; - QAudioDeviceInfo m_device; + BlackMisc::Audio::CAudioDeviceInfo m_device; QAudioFormat m_inputFormat; bool m_started = false; diff --git a/src/blackcore/afv/audio/output.cpp b/src/blackcore/afv/audio/output.cpp index 50219ce9e..5abdbaaf6 100644 --- a/src/blackcore/afv/audio/output.cpp +++ b/src/blackcore/afv/audio/output.cpp @@ -17,6 +17,7 @@ #include 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 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); diff --git a/src/blackcore/afv/audio/output.h b/src/blackcore/afv/audio/output.h index d55b6a77d..fb4c28699 100644 --- a/src/blackcore/afv/audio/output.h +++ b/src/blackcore/afv/audio/output.h @@ -12,9 +12,9 @@ #define BLACKCORE_AFV_AUDIO_OUTPUT_H #include "blacksound/sampleprovider/sampleprovider.h" +#include "blackmisc/audio/audiodeviceinfo.h" #include -#include #include namespace BlackCore @@ -26,7 +26,6 @@ namespace BlackCore //! Stream args struct OutputVolumeStreamArgs { - QAudioDeviceInfo DeviceNumber; double PeakRaw = 0.0; double PeakDB = -1 * std::numeric_limits::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 m_audioOutputCom; CAudioOutputBuffer *m_audioOutputBuffer = nullptr; }; diff --git a/src/blackcore/afv/clients/afvclient.cpp b/src/blackcore/afv/clients/afvclient.cpp index 78699759c..4f1ba6518 100644 --- a/src/blackcore/afv/clients/afvclient.cpp +++ b/src/blackcore/afv/clients/afvclient.cpp @@ -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 &transceiverIDs) + void CAfvClient::start(const CAudioDeviceInfo &inputDevice, const CAudioDeviceInfo &outputDevice, const QVector &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; } diff --git a/src/blackcore/afv/clients/afvclient.h b/src/blackcore/afv/clients/afvclient.h index f35c33aef..b15444458 100644 --- a/src/blackcore/afv/clients/afvclient.h +++ b/src/blackcore/afv/clients/afvclient.h @@ -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 #include #include #include @@ -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 &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 &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(); diff --git a/src/blackcore/context/contextaudio.h b/src/blackcore/context/contextaudio.h index 9d0e16bf7..0eb9e885d 100644 --- a/src/blackcore/context/contextaudio.h +++ b/src/blackcore/context/contextaudio.h @@ -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; diff --git a/src/blackcore/context/contextaudioimpl.cpp b/src/blackcore/context/contextaudioimpl.cpp index 5fa8c12fa..acab6ef7f 100644 --- a/src/blackcore/context/contextaudioimpl.cpp +++ b/src/blackcore/context/contextaudioimpl.cpp @@ -26,7 +26,6 @@ #include "blackmisc/simplecommandparser.h" #include "blackmisc/statusmessage.h" #include "blackmisc/verify.h" -#include "blacksound/soundgenerator.h" #include #include @@ -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()) { diff --git a/src/blackmisc/audio/audiodeviceinfo.cpp b/src/blackmisc/audio/audiodeviceinfo.cpp index f1bfd3224..a370b702c 100644 --- a/src/blackmisc/audio/audiodeviceinfo.cpp +++ b/src/blackmisc/audio/audiodeviceinfo.cpp @@ -26,27 +26,6 @@ namespace BlackMisc m_deviceName(name), m_hostName(QHostInfo::localHostName()) { } - QAudioDeviceInfo CAudioDeviceInfo::toAudioDeviceInfo() const - { - QList 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) diff --git a/src/blackmisc/audio/audiodeviceinfo.h b/src/blackmisc/audio/audiodeviceinfo.h index f4091f9bc..be820dfb7 100644 --- a/src/blackmisc/audio/audiodeviceinfo.h +++ b/src/blackmisc/audio/audiodeviceinfo.h @@ -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); diff --git a/src/blackmisc/audio/audiodeviceinfolist.cpp b/src/blackmisc/audio/audiodeviceinfolist.cpp index 7ae47d9c6..efe94d52b 100644 --- a/src/blackmisc/audio/audiodeviceinfolist.cpp +++ b/src/blackmisc/audio/audiodeviceinfolist.cpp @@ -71,7 +71,7 @@ namespace BlackMisc return names; } - CAudioDeviceInfoList CAudioDeviceInfoList::allQtInputDevices() + CAudioDeviceInfoList CAudioDeviceInfoList::allInputDevices() { const QList inputDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioInput); CAudioDeviceInfoList devices; @@ -84,7 +84,7 @@ namespace BlackMisc return devices; } - CAudioDeviceInfoList CAudioDeviceInfoList::allQtOutputDevices() + CAudioDeviceInfoList CAudioDeviceInfoList::allOutputDevices() { const QList 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 diff --git a/src/blackmisc/audio/audiodeviceinfolist.h b/src/blackmisc/audio/audiodeviceinfolist.h index 2ea3c34f9..063291413 100644 --- a/src/blackmisc/audio/audiodeviceinfolist.h +++ b/src/blackmisc/audio/audiodeviceinfolist.h @@ -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 diff --git a/src/blacksound/selcalplayer.cpp b/src/blacksound/selcalplayer.cpp index 563b74b70..f5febd199 100644 --- a/src/blacksound/selcalplayer.cpp +++ b/src/blacksound/selcalplayer.cpp @@ -10,12 +10,13 @@ #include 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) { diff --git a/src/blacksound/selcalplayer.h b/src/blacksound/selcalplayer.h index b7ea0c581..4715c2394 100644 --- a/src/blacksound/selcalplayer.h +++ b/src/blacksound/selcalplayer.h @@ -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 - 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; diff --git a/src/blacksound/soundgenerator.cpp b/src/blacksound/soundgenerator.cpp deleted file mode 100644 index d5d6ac510..000000000 --- a/src/blacksound/soundgenerator.cpp +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include -#include -#include - -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 &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 &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(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(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((1.0 + amplitude) / 2 * 255); - *reinterpret_cast(bufferPointer) = value; - } - else if (m_audioFormat.sampleSize() == 8 && m_audioFormat.sampleType() == QAudioFormat::SignedInt) - { - const qint8 value = static_cast(amplitude * 127); - *reinterpret_cast(bufferPointer) = value; - } - else if (m_audioFormat.sampleSize() == 16 && m_audioFormat.sampleType() == QAudioFormat::UnSignedInt) - { - quint16 value = static_cast((1.0 + amplitude) / 2 * 65535); - if (m_audioFormat.byteOrder() == QAudioFormat::LittleEndian) - { - qToLittleEndian(value, bufferPointer); - } - else - { - qToBigEndian(value, bufferPointer); - } - } - else if (m_audioFormat.sampleSize() == 16 && m_audioFormat.sampleType() == QAudioFormat::SignedInt) - { - qint16 value = static_cast(amplitude * 32767); - if (m_audioFormat.byteOrder() == QAudioFormat::LittleEndian) - { - qToLittleEndian(value, bufferPointer); - } - else - { - qToBigEndian(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(static_cast(m_buffer.size()) + headerLength - 8), - reinterpret_cast(&header.riff.descriptor.size)); - memcpy(&header.riff.type[0], "WAVE", 4); - - // WAVE header - memcpy(&header.wave.descriptor.id[0], "fmt ", 4); - qToLittleEndian(quint32(16), reinterpret_cast(&header.wave.descriptor.size)); - qToLittleEndian(quint16(1), reinterpret_cast(&header.wave.audioFormat)); - qToLittleEndian(quint16(m_audioFormat.channelCount()), reinterpret_cast(&header.wave.numChannels)); - qToLittleEndian(quint32(m_audioFormat.sampleRate()), reinterpret_cast(&header.wave.sampleRate)); - qToLittleEndian(quint32(m_audioFormat.sampleRate() * m_audioFormat.channelCount() * m_audioFormat.sampleSize() / 8), reinterpret_cast(&header.wave.byteRate)); - qToLittleEndian(quint16(m_audioFormat.channelCount() * m_audioFormat.sampleSize() / 8), reinterpret_cast(&header.wave.blockAlign)); - qToLittleEndian(quint16(m_audioFormat.sampleSize()), reinterpret_cast(&header.wave.bitsPerSample)); - - // DATA header - memcpy(&header.data.descriptor.id[0], "data", 4); - qToLittleEndian(quint32(m_buffer.size()), reinterpret_cast(&header.data.descriptor.size)); - - success = file.write(reinterpret_cast(&header), headerLength) == headerLength; - success = success && file.write(m_buffer) == m_buffer.size(); - file.close(); - return success; - } - - qint64 CSoundGenerator::calculateDurationMs(const QList &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(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 &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 &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 tones; - if (selcal.isValid()) - { - QList 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 diff --git a/src/blacksound/soundgenerator.h b/src/blacksound/soundgenerator.h deleted file mode 100644 index d8a783763..000000000 --- a/src/blacksound/soundgenerator.h +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include - -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(frequency.valueRounded(BlackMisc::PhysicalQuantities::CFrequencyUnit::Hz()))), - m_secondaryFrequencyHz(0), - m_durationMs(static_cast(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(frequency.valueRounded(BlackMisc::PhysicalQuantities::CFrequencyUnit::Hz()))), - m_secondaryFrequencyHz(static_cast(secondaryFrequency.valueRounded(BlackMisc::PhysicalQuantities::CFrequencyUnit::Hz()))), - m_durationMs(static_cast(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 &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 &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 &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 &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 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 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 &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 diff --git a/src/blacksound/threadedtonepairplayer.cpp b/src/blacksound/threadedtonepairplayer.cpp index 190fa5b2e..b52dfc984 100644 --- a/src/blacksound/threadedtonepairplayer.cpp +++ b/src/blacksound/threadedtonepairplayer.cpp @@ -12,10 +12,11 @@ #include 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 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); } diff --git a/src/blacksound/threadedtonepairplayer.h b/src/blacksound/threadedtonepairplayer.h index 73d24b040..e70ef9c22 100644 --- a/src/blacksound/threadedtonepairplayer.h +++ b/src/blacksound/threadedtonepairplayer.h @@ -13,9 +13,9 @@ #include "blacksoundexport.h" #include "blacksound/tonepair.h" +#include "blackmisc/audio/audiodeviceinfo.h" #include "blackmisc/worker.h" -#include #include #include #include @@ -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;