From afacb5999932b8bdea36eec1404e2cab259d001e Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Wed, 4 Jan 2017 15:38:26 -0800 Subject: [PATCH] Use logarithmic quantization of injector "volume" over the wire. Allow gains > 1.0 (up to +30dB) Better quantization at low levels (uniform steps in dB) --- libraries/audio/src/AudioInjector.cpp | 5 +- libraries/audio/src/AudioInjector.h | 2 +- libraries/audio/src/InjectedAudioStream.cpp | 5 +- libraries/shared/src/AudioHelpers.h | 94 +++++++++++++++++++++ 4 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 libraries/shared/src/AudioHelpers.h diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 23e37d911e..48b44bd3d3 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -25,6 +25,7 @@ #include "AudioLogging.h" #include "SoundCache.h" #include "AudioSRC.h" +#include "AudioHelpers.h" int audioInjectorPtrMetaTypeId = qRegisterMetaType(); @@ -187,7 +188,7 @@ bool AudioInjector::injectLocally() { return success; } -const uchar MAX_INJECTOR_VOLUME = 0xFF; +const uchar MAX_INJECTOR_VOLUME = packFloatGainToByte(1.0f); static const int64_t NEXT_FRAME_DELTA_ERROR_OR_FINISHED = -1; static const int64_t NEXT_FRAME_DELTA_IMMEDIATELY = 0; @@ -333,7 +334,7 @@ int64_t AudioInjector::injectNextFrame() { _currentPacket->writePrimitive(_options.position); _currentPacket->writePrimitive(_options.orientation); - quint8 volume = MAX_INJECTOR_VOLUME * _options.volume; + quint8 volume = packFloatGainToByte(_options.volume); _currentPacket->seek(volumeOptionOffset); _currentPacket->writePrimitive(volume); diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index c5e741365a..2abc445034 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -63,7 +63,7 @@ public: AudioFOA& getLocalFOA() { return _localFOA; } bool isLocalOnly() const { return _options.localOnly; } - float getVolume() const { return glm::clamp(_options.volume, 0.0f, 1.0f); } + float getVolume() const { return _options.volume; } glm::vec3 getPosition() const { return _options.position; } glm::quat getOrientation() const { return _options.orientation; } bool isStereo() const { return _options.stereo; } diff --git a/libraries/audio/src/InjectedAudioStream.cpp b/libraries/audio/src/InjectedAudioStream.cpp index 519a3e2459..a06dba5389 100644 --- a/libraries/audio/src/InjectedAudioStream.cpp +++ b/libraries/audio/src/InjectedAudioStream.cpp @@ -18,6 +18,7 @@ #include #include "InjectedAudioStream.h" +#include "AudioHelpers.h" InjectedAudioStream::InjectedAudioStream(const QUuid& streamIdentifier, bool isStereo, int numStaticJitterFrames) : PositionalAudioStream(PositionalAudioStream::Injector, isStereo, numStaticJitterFrames), @@ -25,8 +26,6 @@ InjectedAudioStream::InjectedAudioStream(const QUuid& streamIdentifier, bool isS _radius(0.0f), _attenuationRatio(0) {} -const uchar MAX_INJECTOR_VOLUME = 255; - int InjectedAudioStream::parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples) { @@ -62,7 +61,7 @@ int InjectedAudioStream::parseStreamProperties(PacketType type, quint8 attenuationByte = 0; packetStream >> attenuationByte; - _attenuationRatio = attenuationByte / (float)MAX_INJECTOR_VOLUME; + _attenuationRatio = unpackFloatGainFromByte(attenuationByte); packetStream >> _ignorePenumbra; diff --git a/libraries/shared/src/AudioHelpers.h b/libraries/shared/src/AudioHelpers.h new file mode 100644 index 0000000000..b43764ef5d --- /dev/null +++ b/libraries/shared/src/AudioHelpers.h @@ -0,0 +1,94 @@ +// +// AudioHelpers.h +// libraries/shared/src +// +// Created by Ken Cooke on 1/4/17. +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_AudioHelpers_h +#define hifi_AudioHelpers_h + +#include + +const int IEEE754_MANT_BITS = 23; +const int IEEE754_EXPN_BIAS = 127; + +// +// for x > 0.0f, returns log2(x) +// for x <= 0.0f, returns large negative value +// +// abs |error| < 2e-4, smooth (exact for x=2^N) +// rel |error| < 0.4 from precision loss very close to 1.0f +// +static inline float fastLog2f(float x) { + + union { float f; int32_t i; } mant, bits = { x }; + + // split into mantissa and exponent + mant.i = (bits.i & ((1 << IEEE754_MANT_BITS) - 1)) | (IEEE754_EXPN_BIAS << IEEE754_MANT_BITS); + int32_t expn = (bits.i >> IEEE754_MANT_BITS) - IEEE754_EXPN_BIAS; + + mant.f -= 1.0f; + + // polynomial for log2(1+x) over x=[0,1] + x = (((-0.0821307180f * mant.f + 0.321188984f) * mant.f - 0.677784014f) * mant.f + 1.43872575f) * mant.f; + + return x + expn; +} + +// +// for -127 <= x < 128, returns exp2(x) +// otherwise, returns undefined +// +// rel |error| < 9e-6, smooth (exact for x=N) +// +static inline float fastExp2f(float x) { + + union { float f; int32_t i; } xi; + + // bias such that x > 0 + x += IEEE754_EXPN_BIAS; + + // split into integer and fraction + xi.i = (int32_t)x; + x -= xi.i; + + // construct exp2(xi) as a float + xi.i <<= IEEE754_MANT_BITS; + + // polynomial for exp2(x) over x=[0,1] + x = (((0.0135557472f * x + 0.0520323690f) * x + 0.241379763f) * x + 0.693032121f) * x + 1.0f; + + return x * xi.f; +} + +// +// Quantize a non-negative gain value to the nearest 0.5dB, and pack to a byte. +// +// Values above +30dB are clamped to +30dB +// Values below -97dB are clamped to -inf +// Value of 1.0 (+0dB) is reconstructed exactly +// +const float GAIN_CONVERSION_RATIO = 2.0f * 6.02059991f; // scale log2 to 0.5dB +const float GAIN_CONVERSION_OFFSET = 255 - 60.0f; // translate +30dB to max + +static inline uint8_t packFloatGainToByte(float gain) { + + float f = fastLog2f(gain) * GAIN_CONVERSION_RATIO + GAIN_CONVERSION_OFFSET; + int32_t i = (int32_t)(f + 0.5f); // quantize + + uint8_t byte = (i < 0) ? 0 : ((i > 255) ? 255 : i); // clamp + return byte; +} + +static inline float unpackFloatGainFromByte(uint8_t byte) { + + float gain = (byte == 0) ? 0.0f : fastExp2f((byte - GAIN_CONVERSION_OFFSET) * (1.0f/GAIN_CONVERSION_RATIO)); + return gain; +} + +#endif // hifi_AudioHelpers_h