[AFV] Change output format and processing to 32 bit float

32 bit float is what is used in C# reference and has a much higher
dynamic range. 16 bit integer was clipping very often with all the VHF
simulation applied.
This commit is contained in:
Roland Rossgotterer
2019-10-01 11:58:02 +02:00
committed by Mat Sutcliffe
parent fbb126370c
commit 240df93406
29 changed files with 77 additions and 80 deletions

View File

@@ -11,6 +11,7 @@
#include "callsignsampleprovider.h"
#include "callsigndelaycache.h"
#include "blacksound/sampleprovider/samples.h"
#include "blacksound/audioutilities.h"
#include "blackcore/afv/audio/receiversampleprovider.h"
#include <QtMath>
#include <QDebug>
@@ -60,7 +61,7 @@ namespace BlackCore
connect(&m_timer, &QTimer::timeout, this, &CallsignSampleProvider::timerElapsed);
}
int CallsignSampleProvider::readSamples(QVector<qint16> &samples, qint64 count)
int CallsignSampleProvider::readSamples(QVector<float> &samples, qint64 count)
{
int noOfSamples = m_mixer->readSamples(samples, count);
@@ -103,7 +104,7 @@ namespace BlackCore
if (delayMs > 0)
{
int phaseDelayLength = (m_audioFormat.sampleRate() / 1000) * delayMs;
QVector<qint16> phaseDelay(phaseDelayLength * 2, 0);
QVector<float> phaseDelay(phaseDelayLength * 2, 0);
m_audioInput->addSamples(phaseDelay);
}
}
@@ -131,7 +132,7 @@ namespace BlackCore
setEffects();
QVector<qint16> audio = decodeOpus(audioDto.audio);
m_audioInput->addSamples(audio);
m_audioInput->addSamples(BlackSound::convertFromShortToFloat(audio));
m_lastPacketLatch = audioDto.lastPacket;
if (audioDto.lastPacket && !m_underflow) { CallsignDelayCache::instance().success(m_callsign); }
m_lastSamplesAddedUtc = QDateTime::currentDateTimeUtc();

View File

@@ -44,7 +44,7 @@ namespace BlackCore
//! Ctor
CallsignSampleProvider(const QAudioFormat &audioFormat, const BlackCore::Afv::Audio::CReceiverSampleProvider *receiver, QObject *parent = nullptr);
int readSamples(QVector<qint16> &samples, qint64 count) override;
int readSamples(QVector<float> &samples, qint64 count) override;
//! The callsign
const QString &callsign() const { return m_callsign; }

View File

@@ -36,22 +36,20 @@ namespace BlackCore
int sampleBytes = m_outputFormat.sampleSize() / 8;
int channelCount = m_outputFormat.channelCount();
qint64 count = maxlen / (sampleBytes * channelCount);
QVector<qint16> buffer;
QVector<float> buffer;
m_sampleProvider->readSamples(buffer, count);
for (const qint16 sample : buffer)
for (float sample : buffer)
{
qint16 sampleInput = sample;
sampleInput = qAbs(sampleInput);
if (sampleInput > m_maxSampleOutput) { m_maxSampleOutput = sampleInput; }
float absSample = qAbs(sample);
if (absSample > m_maxSampleOutput) { m_maxSampleOutput = absSample; }
}
m_sampleCount += buffer.size();
if (m_sampleCount >= SampleCountPerEvent)
{
OutputVolumeStreamArgs outputVolumeStreamArgs;
qint16 maxInt = std::numeric_limits<qint16>::max();
outputVolumeStreamArgs.PeakRaw = m_maxSampleOutput / maxInt;
outputVolumeStreamArgs.PeakRaw = m_maxSampleOutput / 1.0;
outputVolumeStreamArgs.PeakDB = static_cast<float>(20 * std::log10(outputVolumeStreamArgs.PeakRaw));
const double db = qBound(m_minDb, outputVolumeStreamArgs.PeakDB, m_maxDb);
double ratio = (db - m_minDb) / (m_maxDb - m_minDb);
@@ -91,8 +89,8 @@ namespace BlackCore
QAudioFormat outputFormat;
outputFormat.setSampleRate(48000);
outputFormat.setChannelCount(1);
outputFormat.setSampleSize(16);
outputFormat.setSampleType(QAudioFormat::SignedInt);
outputFormat.setSampleSize(32);
outputFormat.setSampleType(QAudioFormat::Float);
outputFormat.setByteOrder(QAudioFormat::LittleEndian);
outputFormat.setCodec("audio/pcm");

View File

@@ -60,7 +60,7 @@ namespace BlackCore
static constexpr int SampleCountPerEvent = 4800;
QAudioFormat m_outputFormat;
double m_maxSampleOutput = 0;
float m_maxSampleOutput = 0.0;
int m_sampleCount = 0;
const double m_maxDb = 0;
const double m_minDb = -40;

View File

@@ -82,7 +82,7 @@ namespace BlackCore
}
}
int CReceiverSampleProvider::readSamples(QVector<qint16> &samples, qint64 count)
int CReceiverSampleProvider::readSamples(QVector<float> &samples, qint64 count)
{
int numberOfInUseInputs = activeCallsigns();

View File

@@ -57,7 +57,7 @@ namespace BlackCore
//! @}
//! \copydoc BlackSound::SampleProvider::ISampleProvider::readSamples
virtual int readSamples(QVector<qint16> &samples, qint64 count) override;
virtual int readSamples(QVector<float> &samples, qint64 count) override;
void addOpusSamples(const IAudioDto &audioDto, uint frequency, float distanceRatio);
void addSilentSamples(const IAudioDto &audioDto, uint frequency, float distanceRatio);

View File

@@ -85,7 +85,7 @@ namespace BlackCore
}
}
int CSoundcardSampleProvider::readSamples(QVector<qint16> &samples, qint64 count)
int CSoundcardSampleProvider::readSamples(QVector<float> &samples, qint64 count)
{
return m_mixer->readSamples(samples, count);
}

View File

@@ -43,7 +43,7 @@ namespace BlackCore
void pttUpdate(bool active, const QVector<TxTransceiverDto> &txTransceivers);
//! \copydoc ISampleProvider::readSamples
virtual int readSamples(QVector<qint16> &samples, qint64 count) override;
virtual int readSamples(QVector<float> &samples, qint64 count) override;
//! Add OPUS samples
void addOpusSamples(const IAudioDto &audioDto, const QVector<RxTransceiverDto> &rxTransceivers);

View File

@@ -10,12 +10,25 @@
namespace BlackSound
{
QVector<float> convertBytesTo32BitFloatPCM(const QByteArray input)
{
int inputSamples = input.size() / 2; // 16 bit input, so 2 bytes per sample
QVector<float> output;
output.fill(0, inputSamples);
for (int n = 0; n < inputSamples; n++)
{
output[n] = *reinterpret_cast<const qint16 *>(input.data() + n * 2);
output[n] /= 32767.0;
}
return output;
}
QVector<qint16> convertBytesTo16BitPCM(const QByteArray input)
{
int inputSamples = input.size() / 2; // 16 bit input, so 2 bytes per sample
QVector<qint16> output;
output.fill(0, inputSamples);
for (int n = 0; n < inputSamples; n++)
{
output[n] = *reinterpret_cast<const qint16 *>(input.data() + n * 2);
@@ -30,11 +43,11 @@ namespace BlackSound
return {};
}
QVector<qint16> convertFromMonoToStereo(const QVector<qint16> &mono)
QVector<float> convertFromMonoToStereo(const QVector<float> &mono)
{
QVector<qint16> stereo;
QVector<float> stereo;
stereo.reserve(mono.size() * 2);
for (qint16 sample : mono)
for (float sample : mono)
{
stereo << sample;
stereo << sample;
@@ -53,9 +66,9 @@ namespace BlackSound
return mono;
}
QVector<double> convertFromShortToDouble(const QVector<qint16> &input)
QVector<float> convertFromShortToFloat(const QVector<qint16> &input)
{
QVector<double> output;
QVector<float> output;
for (auto sample : input)
{
output.push_back(sample / 32768.0);
@@ -63,14 +76,4 @@ namespace BlackSound
return output;
}
QVector<qint16> convertFromDoubleToShort(const QVector<double> &input)
{
QVector<qint16> output;
for (auto sample : input)
{
output.push_back(sample * 32768);
}
return output;
}
} // ns

View File

@@ -18,12 +18,12 @@
namespace BlackSound
{
//! Conversion functions @{
BLACKSOUND_EXPORT QVector<float> convertBytesTo32BitFloatPCM(const QByteArray input);
BLACKSOUND_EXPORT QVector<qint16> convertBytesTo16BitPCM(const QByteArray input);
BLACKSOUND_EXPORT QVector<qint16> convertFloatBytesTo16BitPCM(const QByteArray input);
BLACKSOUND_EXPORT QVector<qint16> convertFromMonoToStereo(const QVector<qint16> &mono);
BLACKSOUND_EXPORT QVector<float> convertFromMonoToStereo(const QVector<float> &mono);
BLACKSOUND_EXPORT QVector<qint16> convertFromStereoToMono(const QVector<qint16> &stereo);
BLACKSOUND_EXPORT QVector<double> convertFromShortToDouble(const QVector<qint16> &input);
BLACKSOUND_EXPORT QVector<qint16> convertFromDoubleToShort(const QVector<double> &input);
BLACKSOUND_EXPORT QVector<float> convertFromShortToFloat(const QVector<qint16> &input);
//! @}
} // ns

View File

@@ -13,7 +13,7 @@ namespace BlackSound
m_maxBufferSize = format.bytesForDuration(10 * 1000 * 1000);
}
void CBufferedWaveProvider::addSamples(const QVector<qint16> &samples)
void CBufferedWaveProvider::addSamples(const QVector<float> &samples)
{
int delta = m_audioBuffer.size() + samples.size() - m_maxBufferSize;
if (delta > 0)
@@ -23,7 +23,7 @@ namespace BlackSound
m_audioBuffer.append(samples);
}
int CBufferedWaveProvider::readSamples(QVector<qint16> &samples, qint64 count)
int CBufferedWaveProvider::readSamples(QVector<float> &samples, qint64 count)
{
qint64 len = qMin(count, static_cast<qint64>(m_audioBuffer.size()));
samples = m_audioBuffer.mid(0, len);

View File

@@ -32,10 +32,10 @@ namespace BlackSound
CBufferedWaveProvider(const QAudioFormat &format, QObject *parent = nullptr);
//! Add samples
void addSamples(const QVector<qint16> &samples);
void addSamples(const QVector<float> &samples);
//! ISampleProvider::readSamples
virtual int readSamples(QVector<qint16> &samples, qint64 count) override;
virtual int readSamples(QVector<float> &samples, qint64 count) override;
//! Bytes from buffer
int getBufferedBytes() const { return m_audioBuffer.size(); }
@@ -44,7 +44,7 @@ namespace BlackSound
void clearBuffer();
private:
QVector<qint16> m_audioBuffer;
QVector<float> m_audioBuffer;
qint32 m_maxBufferSize;
};
} // ns

View File

@@ -13,25 +13,19 @@ namespace BlackSound
setupPreset(preset);
}
int CEqualizerSampleProvider::readSamples(QVector<qint16> &samples, qint64 count)
int CEqualizerSampleProvider::readSamples(QVector<float> &samples, qint64 count)
{
int samplesRead = m_sourceProvider->readSamples(samples, count);
if (m_bypass) return samplesRead;
QVector<double> doubleSamples = convertFromShortToDouble(samples);
for (int n = 0; n < samplesRead; n++)
{
// TODO stereo implementation
for (int band = 0; band < m_filters.size(); band++)
{
doubleSamples[n] = m_filters[band].transform(doubleSamples[n]);
samples[n] = m_filters[band].transform(samples[n]);
}
doubleSamples[n] *= m_outputGain;
samples[n] *= m_outputGain;
}
samples = convertFromDoubleToShort(doubleSamples);
return samplesRead;
}

View File

@@ -38,7 +38,7 @@ namespace BlackSound
CEqualizerSampleProvider(ISampleProvider *sourceProvider, EqualizerPresets preset, QObject *parent = nullptr);
//! \copydoc ISampleProvider::readSamples
virtual int readSamples(QVector<qint16> &samples, qint64 count) override;
virtual int readSamples(QVector<float> &samples, qint64 count) override;
//! Bypassing?
void setBypassEffects(bool value) { m_bypass = value; }

View File

@@ -12,7 +12,7 @@ namespace BlackSound
{
namespace SampleProvider
{
int CMixingSampleProvider::readSamples(QVector<qint16> &samples, qint64 count)
int CMixingSampleProvider::readSamples(QVector<float> &samples, qint64 count)
{
samples.clear();
samples.fill(0, count);
@@ -22,7 +22,7 @@ namespace BlackSound
for (int i = 0; i < m_sources.size(); i++)
{
ISampleProvider *sampleProvider = m_sources.at(i);
QVector<qint16> sourceBuffer;
QVector<float> sourceBuffer;
int len = sampleProvider->readSamples(sourceBuffer, count);
for (int n = 0; n < len; n++)

View File

@@ -30,7 +30,7 @@ namespace BlackSound
void addMixerInput(ISampleProvider *provider) { m_sources.append(provider); }
//! \copydoc ISampleProvider::readSamples
virtual int readSamples(QVector<qint16> &samples, qint64 count) override;
virtual int readSamples(QVector<float> &samples, qint64 count) override;
private:
QVector<ISampleProvider *> m_sources;

View File

@@ -14,7 +14,7 @@ namespace BlackSound
{
namespace SampleProvider
{
int CPinkNoiseGenerator::readSamples(QVector<qint16> &samples, qint64 count)
int CPinkNoiseGenerator::readSamples(QVector<float> &samples, qint64 count)
{
samples.clear();
samples.fill(0, count);
@@ -32,7 +32,7 @@ namespace BlackSound
double pink = pinkNoiseBuffer[0] + pinkNoiseBuffer[1] + pinkNoiseBuffer[2] + pinkNoiseBuffer[3] + pinkNoiseBuffer[4] + pinkNoiseBuffer[5] + pinkNoiseBuffer[6] + white * 0.5362;
pinkNoiseBuffer[6] = white * 0.115926;
double sampleValue = (m_gain * (pink / 5));
samples[sampleCount] = sampleValue * 32768;
samples[sampleCount] = sampleValue;
}
return count;
}

View File

@@ -33,7 +33,7 @@ namespace BlackSound
CPinkNoiseGenerator(QObject *parent = nullptr) : ISampleProvider(parent) {}
//! Read samples
virtual int readSamples(QVector<qint16> &samples, qint64 count) override;
virtual int readSamples(QVector<float> &samples, qint64 count) override;
//! Gain
void setGain(double gain) { m_gain = gain; }

View File

@@ -29,11 +29,12 @@ namespace BlackSound
{
if (wavFile.fileFormat().sampleType() == QAudioFormat::Float)
{
m_samples = convertFloatBytesTo16BitPCM(wavFile.audioData());
// Not implemented
// m_samples = convertFloatBytesTo16BitPCM(wavFile.audioData());
}
else
{
m_samples = convertBytesTo16BitPCM(wavFile.audioData());
m_samples = convertBytesTo32BitFloatPCM(wavFile.audioData());
}
m_fn = audioFileName;
}

View File

@@ -29,7 +29,7 @@ namespace BlackSound
CResourceSound(const QString &audioFileName);
//! Audio data
const QVector<qint16> &audioData() const { return m_samples; }
const QVector<float> &audioData() const { return m_samples; }
//! Corresponding file
const QString &getFileName() { return m_fn; }
@@ -39,7 +39,7 @@ namespace BlackSound
private:
QString m_fn; //!< file name
QVector<qint16> m_samples;
QVector<float> m_samples;
};
} // ns
} // ns

View File

@@ -13,7 +13,7 @@ namespace BlackSound
m_tempBuffer.resize(m_tempBufferSize);
}
int CResourceSoundSampleProvider::readSamples(QVector<qint16> &samples, qint64 count)
int CResourceSoundSampleProvider::readSamples(QVector<float> &samples, qint64 count)
{
if (count > m_tempBufferSize)
{
@@ -35,7 +35,7 @@ namespace BlackSound
{
for (int i = 0; i < samplesToCopy; i++)
{
m_tempBuffer[i] = static_cast<qint16>(qRound(m_gain * m_tempBuffer[i]));
m_tempBuffer[i] *= m_gain;
}
}

View File

@@ -27,7 +27,7 @@ namespace BlackSound
CResourceSoundSampleProvider(const CResourceSound &resourceSound, QObject *parent = nullptr);
//! copydoc ISampleProvider::readSamples
virtual int readSamples(QVector<qint16> &samples, qint64 count) override;
virtual int readSamples(QVector<float> &samples, qint64 count) override;
//! copydoc ISampleProvider::isFinished
virtual bool isFinished() const override { return m_isFinished; }
@@ -43,8 +43,8 @@ namespace BlackSound
//! @}
//! Temp buffer @{
QVector<qint16> getTempBuffer() const { return m_tempBuffer; }
void setTempBuffer(const QVector<qint16> &value) { m_tempBuffer = value; }
QVector<float> getTempBuffer() const { return m_tempBuffer; }
void setTempBuffer(const QVector<float> &value) { m_tempBuffer = value; }
//! @}
private:
@@ -54,7 +54,7 @@ namespace BlackSound
CResourceSound m_resourceSound;
qint64 m_position = 0;
const int m_tempBufferSize = 9600; //9600 = 200ms
QVector<qint16> m_tempBuffer;
QVector<float> m_tempBuffer;
bool m_isFinished = false;
};
} // ns

View File

@@ -32,7 +32,7 @@ namespace BlackSound
virtual ~ISampleProvider() override {}
//! Read samples
virtual int readSamples(QVector<qint16> &samples, qint64 count) = 0;
virtual int readSamples(QVector<float> &samples, qint64 count) = 0;
//! Finished?
virtual bool isFinished() const { return false; }

View File

@@ -18,7 +18,7 @@ namespace BlackSound
m_frequency(frequency)
{}
int CSawToothGenerator::readSamples(QVector<qint16> &samples, qint64 count)
int CSawToothGenerator::readSamples(QVector<float> &samples, qint64 count)
{
samples.clear();
samples.fill(0, static_cast<int>(count));
@@ -28,7 +28,7 @@ namespace BlackSound
double multiple = 2 * m_frequency / m_sampleRate;
double sampleSaw = std::fmod((m_nSample * multiple), 2) - 1;
double sampleValue = m_gain * sampleSaw;
samples[sampleCount] = static_cast<qint16>(qRound(sampleValue * 32768));
samples[sampleCount] = static_cast<float>(sampleValue);
m_nSample++;
}
return static_cast<int>(count);

View File

@@ -31,7 +31,7 @@ namespace BlackSound
CSawToothGenerator(double frequency, QObject *parent = nullptr);
//! \copydoc ISampleProvider::readSamples
virtual int readSamples(QVector<qint16> &samples, qint64 count) override;
virtual int readSamples(QVector<float> &samples, qint64 count) override;
//! Set the gain
void setGain(double gain) { m_gain = gain; }

View File

@@ -27,7 +27,7 @@ namespace BlackSound
m_timer.start(3000);
}
int CSimpleCompressorEffect::readSamples(QVector<qint16> &samples, qint64 count)
int CSimpleCompressorEffect::readSamples(QVector<float> &samples, qint64 count)
{
int samplesRead = m_sourceStream->readSamples(samples, count);
@@ -35,12 +35,12 @@ namespace BlackSound
{
for (int sample = 0; sample < samplesRead; sample += channels)
{
double in1 = samples.at(sample) / 32768.0;
double in1 = samples.at(sample);
double in2 = (channels == 1) ? 0 : samples.at(sample + 1);
m_simpleCompressor.process(in1, in2);
samples[sample] = in1 * 32768.0;
samples[sample] = in1;
if (channels > 1)
samples[sample + 1] = in2 * 32768.0f;
samples[sample + 1] = in2;
}
}
return samplesRead;

View File

@@ -32,7 +32,7 @@ namespace BlackSound
CSimpleCompressorEffect(ISampleProvider *source, QObject *parent = nullptr);
//! \copydoc ISampleProvider::readSamples
virtual int readSamples(QVector<qint16> &samples, qint64 count) override;
virtual int readSamples(QVector<float> &samples, qint64 count) override;
//! Enable
void setEnabled(bool enabled);

View File

@@ -20,7 +20,7 @@ namespace BlackSound
m_sourceProvider(sourceProvider)
{ }
int CVolumeSampleProvider::readSamples(QVector<qint16> &samples, qint64 count)
int CVolumeSampleProvider::readSamples(QVector<float> &samples, qint64 count)
{
int samplesRead = m_sourceProvider->readSamples(samples, count);
@@ -28,7 +28,7 @@ namespace BlackSound
{
for (int n = 0; n < samplesRead; n++)
{
samples[n] = static_cast<qint16>(qRound(samples[n] * m_volume));
samples[n] *= static_cast<float>(m_volume);
}
}
return samplesRead;

View File

@@ -28,7 +28,7 @@ namespace BlackSound
CVolumeSampleProvider(ISampleProvider *sourceProvider, QObject *parent = nullptr);
//! \copydoc ISampleProvider::readSamples
virtual int readSamples(QVector<qint16> &samples, qint64 count) override;
virtual int readSamples(QVector<float> &samples, qint64 count) override;
//! Volume @{
double volume() const { return m_volume; }