diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 2a0094de29..ab9cc45740 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -278,12 +278,18 @@ void setupPreferences() { preferences->addPreference(preference); } #if DEV_BUILD || PR_BUILD + { + auto getter = []()->bool { return DependencyManager::get()->isSimulatingJitter(); }; + auto setter = [](bool value) { return DependencyManager::get()->setIsSimulatingJitter(value); }; + auto preference = new CheckPreference(AUDIO, "Packet jitter simulator", getter, setter); + preferences->addPreference(preference); + } { auto getter = []()->float { return DependencyManager::get()->getGateThreshold(); }; auto setter = [](float value) { return DependencyManager::get()->setGateThreshold(value); }; - auto preference = new SpinnerPreference(AUDIO, "Debug gate threshold", getter, setter); + auto preference = new SpinnerPreference(AUDIO, "Packet throttle threshold", getter, setter); preference->setMin(1); - preference->setMax((float)100); + preference->setMax(200); preference->setStep(1); preferences->addPreference(preference); } diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index a37a208072..b03672063f 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -56,8 +56,6 @@ static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 100; static const auto DEFAULT_POSITION_GETTER = []{ return Vectors::ZERO; }; static const auto DEFAULT_ORIENTATION_GETTER = [] { return Quaternions::IDENTITY; }; -static const int DEFAULT_AUDIO_OUTPUT_GATE_THRESHOLD = 1; - Setting::Handle dynamicJitterBuffers("dynamicJitterBuffers", DEFAULT_DYNAMIC_JITTER_BUFFERS); Setting::Handle maxFramesOverDesired("maxFramesOverDesired", DEFAULT_MAX_FRAMES_OVER_DESIRED); Setting::Handle staticDesiredJitterBufferFrames("staticDesiredJitterBufferFrames", @@ -102,8 +100,7 @@ private: AudioClient::AudioClient() : AbstractAudioInterface(), - _gateThreshold("audioOutputGateThreshold", DEFAULT_AUDIO_OUTPUT_GATE_THRESHOLD), - _gate(this, _gateThreshold.get()), + _gate(this), _audioInput(NULL), _desiredInputFormat(), _inputFormat(), @@ -551,31 +548,53 @@ void AudioClient::handleAudioDataPacket(QSharedPointer message) } } -AudioClient::Gate::Gate(AudioClient* audioClient, int threshold) : - _audioClient(audioClient), - _threshold(threshold) {} +AudioClient::Gate::Gate(AudioClient* audioClient) : + _audioClient(audioClient) {} + +void AudioClient::Gate::setIsSimulatingJitter(bool enable) { + std::lock_guard lock(_mutex); + flush(); + _isSimulatingJitter = enable; +} void AudioClient::Gate::setThreshold(int threshold) { + std::lock_guard lock(_mutex); flush(); _threshold = std::max(threshold, 1); } void AudioClient::Gate::insert(QSharedPointer message) { + std::lock_guard lock(_mutex); + // Short-circuit for normal behavior - if (_threshold == 1) { + if (_threshold == 1 && !_isSimulatingJitter) { _audioClient->_receivedAudioStream.parseData(*message); return; } + // Throttle the current packet until the next flush _queue.push(message); _index++; - if (_index % _threshold == 0) { + // When appropriate, flush all held packets to the received audio stream + if (_isSimulatingJitter) { + // The JITTER_FLUSH_CHANCE defines the discrete probability density function of jitter (ms), + // where f(t) = pow(1 - JITTER_FLUSH_CHANCE, (t / 10) * JITTER_FLUSH_CHANCE + // for t (ms) = 10, 20, ... (because typical packet timegap is 10ms), + // because there is a JITTER_FLUSH_CHANCE of any packet instigating a flush of all held packets. + static const float JITTER_FLUSH_CHANCE = 0.6f; + // It is set at 0.6 to give a low chance of spikes (>30ms, 2.56%) so that they are obvious, + // but settled within the measured 5s window in audio network stats. + if (randFloat() < JITTER_FLUSH_CHANCE) { + flush(); + } + } else if (!(_index % _threshold)) { flush(); } } void AudioClient::Gate::flush() { + // Send all held packets to the received audio stream to be (eventually) played while (!_queue.empty()) { _audioClient->_receivedAudioStream.parseData(*_queue.front()); _queue.pop(); diff --git a/libraries/audio-client/src/AudioClient.h b/libraries/audio-client/src/AudioClient.h index c4d32e8694..926212cf47 100644 --- a/libraries/audio-client/src/AudioClient.h +++ b/libraries/audio-client/src/AudioClient.h @@ -132,6 +132,9 @@ public: int getOutputStarveDetectionThreshold() { return _outputStarveDetectionThreshold.get(); } void setOutputStarveDetectionThreshold(int threshold) { _outputStarveDetectionThreshold.set(threshold); } + bool isSimulatingJitter() { return _gate.isSimulatingJitter(); } + void setIsSimulatingJitter(bool enable) { _gate.setIsSimulatingJitter(enable); } + int getGateThreshold() { return _gate.getThreshold(); } void setGateThreshold(int threshold) { _gate.setThreshold(threshold); } @@ -230,7 +233,10 @@ private: class Gate { public: - Gate(AudioClient* audioClient, int threshold); + Gate(AudioClient* audioClient); + + bool isSimulatingJitter() { return _isSimulatingJitter; } + void setIsSimulatingJitter(bool enable); int getThreshold() { return _threshold; } void setThreshold(int threshold); @@ -242,11 +248,13 @@ private: AudioClient* _audioClient; std::queue> _queue; + std::mutex _mutex; + int _index{ 0 }; - int _threshold; + int _threshold{ 1 }; + bool _isSimulatingJitter{ false }; }; - Setting::Handle _gateThreshold; Gate _gate; Mutex _injectorsMutex;