[AFV] Select audio device based on situational need

For AFV itself, a low latency device is required (which on Windows most likely will pick WASAPI).
For notifications and effects, the most compatible device is required (which on Windows will most likely fall back to QWindowsAudio).
This commit is contained in:
Roland Rossgotterer
2019-10-02 17:09:15 +02:00
committed by Mat Sutcliffe
parent 18ec101391
commit 90e87835fc
5 changed files with 119 additions and 95 deletions

View File

@@ -19,6 +19,7 @@
#include <cmath> #include <cmath>
using namespace BlackMisc; using namespace BlackMisc;
using namespace BlackMisc::Audio;
using namespace BlackSound; using namespace BlackSound;
namespace BlackCore namespace BlackCore
@@ -27,6 +28,7 @@ namespace BlackCore
{ {
namespace Audio namespace Audio
{ {
CAudioInputBuffer::CAudioInputBuffer(QObject *parent) : CAudioInputBuffer::CAudioInputBuffer(QObject *parent) :
QIODevice(parent) QIODevice(parent)
{} {}
@@ -34,7 +36,6 @@ namespace BlackCore
void CAudioInputBuffer::start() void CAudioInputBuffer::start()
{ {
open(QIODevice::WriteOnly | QIODevice::Unbuffered); open(QIODevice::WriteOnly | QIODevice::Unbuffered);
// m_timerId = startTimer(5, Qt::PreciseTimer);
} }
void CAudioInputBuffer::stop() void CAudioInputBuffer::stop()
@@ -91,28 +92,11 @@ namespace BlackCore
m_encoder.setBitRate(16 * 1024); m_encoder.setBitRate(16 * 1024);
} }
void CInput::start(const BlackMisc::Audio::CAudioDeviceInfo &inputDevice) void CInput::start(const CAudioDeviceInfo &inputDevice)
{ {
if (m_started) { return; } if (m_started) { return; }
m_device = inputDevice; 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.setSampleRate(m_sampleRate);
m_inputFormat.setChannelCount(1); m_inputFormat.setChannelCount(1);
@@ -120,21 +104,8 @@ namespace BlackCore
m_inputFormat.setSampleType(QAudioFormat::SignedInt); m_inputFormat.setSampleType(QAudioFormat::SignedInt);
m_inputFormat.setByteOrder(QAudioFormat::LittleEndian); m_inputFormat.setByteOrder(QAudioFormat::LittleEndian);
m_inputFormat.setCodec("audio/pcm"); 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_audioInput.reset(new QAudioInput(selectedDevice, m_inputFormat));
m_audioInputBuffer.start(); m_audioInputBuffer.start();

View File

@@ -90,23 +90,6 @@ namespace BlackCore
connect(m_audioOutputBuffer, &CAudioOutputBuffer::outputVolumeStream, this, &Output::outputVolumeStream); connect(m_audioOutputBuffer, &CAudioOutputBuffer::outputVolumeStream, this, &Output::outputVolumeStream);
m_device = outputDevice; 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; QAudioFormat outputFormat;
outputFormat.setSampleRate(48000); outputFormat.setSampleRate(48000);
@@ -116,23 +99,8 @@ namespace BlackCore
outputFormat.setByteOrder(QAudioFormat::LittleEndian); outputFormat.setByteOrder(QAudioFormat::LittleEndian);
outputFormat.setCodec("audio/pcm"); outputFormat.setCodec("audio/pcm");
if (!selectedDevice.isFormatSupported(outputFormat)) QAudioDeviceInfo selectedDevice = getLowestLatencyDevice(outputDevice, 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);
}
m_audioOutputCom.reset(new QAudioOutput(selectedDevice, outputFormat)); m_audioOutputCom.reset(new QAudioOutput(selectedDevice, outputFormat));
// m_audioOutput->setBufferSize(bufferSize);
m_audioOutputBuffer->open(QIODevice::ReadWrite | QIODevice::Unbuffered); m_audioOutputBuffer->open(QIODevice::ReadWrite | QIODevice::Unbuffered);
m_audioOutputBuffer->setAudioFormat(outputFormat); m_audioOutputBuffer->setAudioFormat(outputFormat);
m_audioOutputCom->start(m_audioOutputBuffer); m_audioOutputCom->start(m_audioOutputBuffer);

View File

@@ -7,6 +7,10 @@
*/ */
#include "audioutilities.h" #include "audioutilities.h"
#include <QAudioInput>
#include <QAudioOutput>
using namespace BlackMisc::Audio;
namespace BlackSound namespace BlackSound
{ {
@@ -76,4 +80,98 @@ namespace BlackSound
return output; 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<QAudioDeviceInfo> allQtDevices = QAudioDeviceInfo::availableDevices(mode);
// Find the one with lowest latency.
QList<QAudioDeviceInfo> 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<int>::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<QAudioDeviceInfo> allQtDevices = QAudioDeviceInfo::availableDevices(QAudio::AudioOutput);
QList<QAudioDeviceInfo> 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 } // ns

View File

@@ -12,6 +12,9 @@
#define BLACKSOUND_AUDIOUTILITIES_H #define BLACKSOUND_AUDIOUTILITIES_H
#include "blacksound/blacksoundexport.h" #include "blacksound/blacksoundexport.h"
#include "blackmisc/audio/audiodeviceinfo.h"
#include <QAudioDeviceInfo>
#include <QByteArray> #include <QByteArray>
#include <QVector> #include <QVector>
@@ -24,6 +27,10 @@ namespace BlackSound
BLACKSOUND_EXPORT QVector<float> convertFromMonoToStereo(const QVector<float> &mono); BLACKSOUND_EXPORT QVector<float> convertFromMonoToStereo(const QVector<float> &mono);
BLACKSOUND_EXPORT QVector<qint16> convertFromStereoToMono(const QVector<qint16> &stereo); BLACKSOUND_EXPORT QVector<qint16> convertFromStereoToMono(const QVector<qint16> &stereo);
BLACKSOUND_EXPORT QVector<float> convertFromShortToFloat(const QVector<qint16> &input); BLACKSOUND_EXPORT QVector<float> convertFromShortToFloat(const QVector<qint16> &input);
BLACKSOUND_EXPORT QAudioDeviceInfo getLowestLatencyDevice(const BlackMisc::Audio::CAudioDeviceInfo &device, QAudioFormat &format);
BLACKSOUND_EXPORT QAudioDeviceInfo getHighestCompatibleOutputDevice(const BlackMisc::Audio::CAudioDeviceInfo &device, QAudioFormat &format);
//! @} //! @}
} // ns } // ns

View File

@@ -8,11 +8,13 @@
#include "threadedtonepairplayer.h" #include "threadedtonepairplayer.h"
#include "blackmisc/logmessage.h" #include "blackmisc/logmessage.h"
#include "blacksound/audioutilities.h"
#include <QTimer> #include <QTimer>
using namespace BlackMisc; using namespace BlackMisc;
using namespace BlackMisc::Audio; using namespace BlackMisc::Audio;
using namespace BlackSound;
namespace BlackSound namespace BlackSound
{ {
@@ -38,36 +40,14 @@ namespace BlackSound
{ {
CLogMessage(this).info(u"CThreadedTonePairPlayer for device '%1'") << m_deviceInfo.getName(); CLogMessage(this).info(u"CThreadedTonePairPlayer for device '%1'") << m_deviceInfo.getName();
QAudioDeviceInfo selectedDevice; m_audioFormat.setSampleRate(44100);
if (m_deviceInfo.isDefault()) m_audioFormat.setChannelCount(1);
{ m_audioFormat.setSampleSize(16); // 8 or 16 works
selectedDevice = QAudioDeviceInfo::defaultOutputDevice(); m_audioFormat.setCodec("audio/pcm");
} m_audioFormat.setByteOrder(QAudioFormat::LittleEndian);
else m_audioFormat.setSampleType(QAudioFormat::SignedInt);
{
// 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; QAudioDeviceInfo selectedDevice = getHighestCompatibleOutputDevice(m_deviceInfo, m_audioFormat);
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;
m_audioOutput = new QAudioOutput(selectedDevice, m_audioFormat, this); m_audioOutput = new QAudioOutput(selectedDevice, m_audioFormat, this);
connect(m_audioOutput, &QAudioOutput::stateChanged, this, &CThreadedTonePairPlayer::handleStateChanged); connect(m_audioOutput, &QAudioOutput::stateChanged, this, &CThreadedTonePairPlayer::handleStateChanged);
} }