diff --git a/src/blackcore/afv/audio/input.cpp b/src/blackcore/afv/audio/input.cpp index dec1d9c6e..e301cb1a8 100644 --- a/src/blackcore/afv/audio/input.cpp +++ b/src/blackcore/afv/audio/input.cpp @@ -19,6 +19,7 @@ #include using namespace BlackMisc; +using namespace BlackMisc::Audio; using namespace BlackSound; namespace BlackCore @@ -27,6 +28,7 @@ namespace BlackCore { namespace Audio { + CAudioInputBuffer::CAudioInputBuffer(QObject *parent) : QIODevice(parent) {} @@ -34,7 +36,6 @@ namespace BlackCore void CAudioInputBuffer::start() { open(QIODevice::WriteOnly | QIODevice::Unbuffered); - // m_timerId = startTimer(5, Qt::PreciseTimer); } void CAudioInputBuffer::stop() @@ -91,28 +92,11 @@ namespace BlackCore m_encoder.setBitRate(16 * 1024); } - void CInput::start(const BlackMisc::Audio::CAudioDeviceInfo &inputDevice) + void CInput::start(const 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); @@ -120,21 +104,8 @@ namespace BlackCore m_inputFormat.setSampleType(QAudioFormat::SignedInt); m_inputFormat.setByteOrder(QAudioFormat::LittleEndian); m_inputFormat.setCodec("audio/pcm"); - if (!selectedDevice.isFormatSupported(m_inputFormat)) - { - m_inputFormat = selectedDevice.nearestFormat(m_inputFormat); - const QString w = - 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()) % - " Sample type: " % QString::number(m_inputFormat.sampleType()) % - " Byte order: " % QString::number(m_inputFormat.byteOrder()) % - " Codec: " % m_inputFormat.codec() % - " Channel count: " % QString::number(m_inputFormat.channelCount()); - CLogMessage(this).warning(w); - } + QAudioDeviceInfo selectedDevice = getLowestLatencyDevice(inputDevice, m_inputFormat); m_audioInput.reset(new QAudioInput(selectedDevice, m_inputFormat)); m_audioInputBuffer.start(); diff --git a/src/blackcore/afv/audio/output.cpp b/src/blackcore/afv/audio/output.cpp index 5abdbaaf6..dd04af9a2 100644 --- a/src/blackcore/afv/audio/output.cpp +++ b/src/blackcore/afv/audio/output.cpp @@ -90,23 +90,6 @@ namespace BlackCore 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); @@ -116,23 +99,8 @@ namespace BlackCore outputFormat.setByteOrder(QAudioFormat::LittleEndian); outputFormat.setCodec("audio/pcm"); - if (!selectedDevice.isFormatSupported(outputFormat)) - { - outputFormat = selectedDevice.nearestFormat(outputFormat); - const QString w = - selectedDevice.deviceName() % - ": Default OUTPUT format not supported - trying to use nearest" % - " Sample rate: " % QString::number(outputFormat.sampleRate()) % - " Sample size: " % QString::number(outputFormat.sampleSize()) % - " Sample type: " % QString::number(outputFormat.sampleType()) % - " Byte order: " % QString::number(outputFormat.byteOrder()) % - " Codec: " % outputFormat.codec() % - " Channel count: " % QString::number(outputFormat.channelCount()); - CLogMessage(this).warning(w); - } - + QAudioDeviceInfo selectedDevice = getLowestLatencyDevice(outputDevice, outputFormat); m_audioOutputCom.reset(new QAudioOutput(selectedDevice, outputFormat)); - // m_audioOutput->setBufferSize(bufferSize); m_audioOutputBuffer->open(QIODevice::ReadWrite | QIODevice::Unbuffered); m_audioOutputBuffer->setAudioFormat(outputFormat); m_audioOutputCom->start(m_audioOutputBuffer); diff --git a/src/blacksound/audioutilities.cpp b/src/blacksound/audioutilities.cpp index 1e4d1aab9..f718a530e 100644 --- a/src/blacksound/audioutilities.cpp +++ b/src/blacksound/audioutilities.cpp @@ -7,6 +7,10 @@ */ #include "audioutilities.h" +#include +#include + +using namespace BlackMisc::Audio; namespace BlackSound { @@ -76,4 +80,98 @@ namespace BlackSound return output; } + QAudioDeviceInfo getLowestLatencyDevice(const CAudioDeviceInfo &device, QAudioFormat &format) + { + if (device.isDefault()) + { + if (device.getType() == CAudioDeviceInfo::InputDevice) { return QAudioDeviceInfo::defaultInputDevice(); } + else { return QAudioDeviceInfo::defaultOutputDevice(); } + } + + QAudio::Mode mode = device.getType() == CAudioDeviceInfo::InputDevice ? QAudio::AudioInput : QAudio::AudioOutput; + const QList allQtDevices = QAudioDeviceInfo::availableDevices(mode); + + // Find the one with lowest latency. + QList supportedDevices; + for (const QAudioDeviceInfo &d : allQtDevices) + { + if (d.deviceName() == device.getName()) + { + if (! d.isFormatSupported(format)) + { + // Check whether the nearest format is acceptable for our needs + QAudioFormat nearestFormat = d.nearestFormat(format); + if (nearestFormat.sampleRate() != format.sampleRate() || + nearestFormat.sampleSize() != format.sampleSize() || + nearestFormat.sampleType() != format.sampleType() || + nearestFormat.byteOrder() != format.byteOrder() || + nearestFormat.codec() != format.codec()) + { + continue; + } + } + supportedDevices.push_back(d); + } + } + + if (supportedDevices.empty()) { return {}; } + + QAudioDeviceInfo deviceWithLowestLatency = supportedDevices.at(0); + + if (supportedDevices.size() > 1) + { + QAudioFormat nearestFormat = format; + int lowestBufferSize = std::numeric_limits::max(); + for (const QAudioDeviceInfo &d : supportedDevices) + { + int bufferSize = 0; + if (device.getType() == CAudioDeviceInfo::InputDevice) + { + QAudioInput input(d, d.nearestFormat(format)); + input.start(); + input.stop(); + bufferSize = input.bufferSize(); + } + else + { + QAudioOutput output(d, d.nearestFormat(format)); + output.start(); + output.stop(); + bufferSize = output.bufferSize(); + } + + if (bufferSize < lowestBufferSize) + { + deviceWithLowestLatency = d; + nearestFormat = d.nearestFormat(format); + lowestBufferSize = bufferSize; + } + } + format = nearestFormat; + } + return deviceWithLowestLatency; + } + + QAudioDeviceInfo getHighestCompatibleOutputDevice(const CAudioDeviceInfo &device, QAudioFormat &format) + { + if (device.isDefault()) { return QAudioDeviceInfo::defaultOutputDevice(); } + + const QList allQtDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput); + + QList supportedDevices; + for (const QAudioDeviceInfo &d : allQtDevices) + { + if (d.deviceName() == device.getName()) + { + if (d.isFormatSupported(format)) + { + supportedDevices.push_back(d); + } + } + + if (supportedDevices.size() > 0) { return supportedDevices.at(0); } + } + return {}; + } + } // ns diff --git a/src/blacksound/audioutilities.h b/src/blacksound/audioutilities.h index 3854116b1..e0dc4c36d 100644 --- a/src/blacksound/audioutilities.h +++ b/src/blacksound/audioutilities.h @@ -12,6 +12,9 @@ #define BLACKSOUND_AUDIOUTILITIES_H #include "blacksound/blacksoundexport.h" +#include "blackmisc/audio/audiodeviceinfo.h" + +#include #include #include @@ -24,6 +27,10 @@ namespace BlackSound BLACKSOUND_EXPORT QVector convertFromMonoToStereo(const QVector &mono); BLACKSOUND_EXPORT QVector convertFromStereoToMono(const QVector &stereo); BLACKSOUND_EXPORT QVector convertFromShortToFloat(const QVector &input); + + BLACKSOUND_EXPORT QAudioDeviceInfo getLowestLatencyDevice(const BlackMisc::Audio::CAudioDeviceInfo &device, QAudioFormat &format); + BLACKSOUND_EXPORT QAudioDeviceInfo getHighestCompatibleOutputDevice(const BlackMisc::Audio::CAudioDeviceInfo &device, QAudioFormat &format); + //! @} } // ns diff --git a/src/blacksound/threadedtonepairplayer.cpp b/src/blacksound/threadedtonepairplayer.cpp index b52dfc984..daa9f8a7a 100644 --- a/src/blacksound/threadedtonepairplayer.cpp +++ b/src/blacksound/threadedtonepairplayer.cpp @@ -8,11 +8,13 @@ #include "threadedtonepairplayer.h" #include "blackmisc/logmessage.h" +#include "blacksound/audioutilities.h" #include using namespace BlackMisc; using namespace BlackMisc::Audio; +using namespace BlackSound; namespace BlackSound { @@ -38,36 +40,14 @@ namespace BlackSound { 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; - } - } - } + m_audioFormat.setSampleRate(44100); + m_audioFormat.setChannelCount(1); + m_audioFormat.setSampleSize(16); // 8 or 16 works + m_audioFormat.setCodec("audio/pcm"); + m_audioFormat.setByteOrder(QAudioFormat::LittleEndian); + m_audioFormat.setSampleType(QAudioFormat::SignedInt); - 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); - if (!selectedDevice.isFormatSupported(format)) - { - format = selectedDevice.nearestFormat(format); - } - m_audioFormat = format; + QAudioDeviceInfo selectedDevice = getHighestCompatibleOutputDevice(m_deviceInfo, m_audioFormat); m_audioOutput = new QAudioOutput(selectedDevice, m_audioFormat, this); connect(m_audioOutput, &QAudioOutput::stateChanged, this, &CThreadedTonePairPlayer::handleStateChanged); }