Reduce jitter delay for low fps streams.
Enabled by finch flag. BUG= R=stefan@webrtc.org Review URL: https://webrtc-codereview.appspot.com/31389005 git-svn-id: http://webrtc.googlecode.com/svn/trunk@7288 4adac7df-926f-26a2-2b94-8c16560cd09d
This commit is contained in:
parent
275dac2c1d
commit
70e2d11ea8
@ -237,6 +237,7 @@
|
||||
'video_coding/main/interface/mock/mock_vcm_callbacks.h',
|
||||
'video_coding/main/source/decoding_state_unittest.cc',
|
||||
'video_coding/main/source/jitter_buffer_unittest.cc',
|
||||
'video_coding/main/source/jitter_estimator_tests.cc',
|
||||
'video_coding/main/source/media_optimization_unittest.cc',
|
||||
'video_coding/main/source/receiver_unittest.cc',
|
||||
'video_coding/main/source/session_info_unittest.cc',
|
||||
|
@ -122,8 +122,7 @@ void FrameList::Reset(UnorderedFrameList* free_frames) {
|
||||
}
|
||||
}
|
||||
|
||||
VCMJitterBuffer::VCMJitterBuffer(Clock* clock,
|
||||
EventFactory* event_factory)
|
||||
VCMJitterBuffer::VCMJitterBuffer(Clock* clock, EventFactory* event_factory)
|
||||
: clock_(clock),
|
||||
running_(false),
|
||||
crit_sect_(CriticalSectionWrapper::CreateCriticalSection()),
|
||||
@ -145,7 +144,7 @@ VCMJitterBuffer::VCMJitterBuffer(Clock* clock,
|
||||
num_consecutive_old_frames_(0),
|
||||
num_consecutive_old_packets_(0),
|
||||
num_discarded_packets_(0),
|
||||
jitter_estimate_(),
|
||||
jitter_estimate_(clock),
|
||||
inter_frame_delay_(clock_->TimeInMilliseconds()),
|
||||
rtt_ms_(kDefaultRtt),
|
||||
nack_mode_(kNoNack),
|
||||
|
@ -11,6 +11,8 @@
|
||||
#include "webrtc/modules/video_coding/main/source/internal_defines.h"
|
||||
#include "webrtc/modules/video_coding/main/source/jitter_estimator.h"
|
||||
#include "webrtc/modules/video_coding/main/source/rtt_filter.h"
|
||||
#include "webrtc/system_wrappers/interface/clock.h"
|
||||
#include "webrtc/system_wrappers/interface/field_trial.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
@ -19,7 +21,13 @@
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
VCMJitterEstimator::VCMJitterEstimator(int32_t vcmId, int32_t receiverId)
|
||||
enum { kStartupDelaySamples = 30 };
|
||||
enum { kFsAccuStartupSamples = 5 };
|
||||
enum { kMaxFramerateEstimate = 200 };
|
||||
|
||||
VCMJitterEstimator::VCMJitterEstimator(const Clock* clock,
|
||||
int32_t vcmId,
|
||||
int32_t receiverId)
|
||||
: _vcmId(vcmId),
|
||||
_receiverId(receiverId),
|
||||
_phi(0.97),
|
||||
@ -32,8 +40,15 @@ VCMJitterEstimator::VCMJitterEstimator(int32_t vcmId, int32_t receiverId)
|
||||
_noiseStdDevs(2.33), // ~Less than 1% chance
|
||||
// (look up in normal distribution table)...
|
||||
_noiseStdDevOffset(30.0), // ...of getting 30 ms freezes
|
||||
_rttFilter() {
|
||||
Reset();
|
||||
_rttFilter(),
|
||||
fps_counter_(30), // TODO(sprang): Use an estimator with limit based on
|
||||
// time, rather than number of samples.
|
||||
low_rate_experiment_(kInit),
|
||||
clock_(clock) {
|
||||
Reset();
|
||||
}
|
||||
|
||||
VCMJitterEstimator::~VCMJitterEstimator() {
|
||||
}
|
||||
|
||||
VCMJitterEstimator&
|
||||
@ -94,6 +109,7 @@ VCMJitterEstimator::Reset()
|
||||
_fsCount = 0;
|
||||
_startupCount = 0;
|
||||
_rttFilter.Reset();
|
||||
fps_counter_.Reset();
|
||||
}
|
||||
|
||||
void
|
||||
@ -297,35 +313,54 @@ VCMJitterEstimator::DeviationFromExpectedDelay(int64_t frameDelayMS,
|
||||
|
||||
// Estimates the random jitter by calculating the variance of the
|
||||
// sample distance from the line given by theta.
|
||||
void
|
||||
VCMJitterEstimator::EstimateRandomJitter(double d_dT, bool incompleteFrame)
|
||||
{
|
||||
double alpha;
|
||||
if (_alphaCount == 0)
|
||||
{
|
||||
assert(_alphaCount > 0);
|
||||
return;
|
||||
}
|
||||
alpha = static_cast<double>(_alphaCount - 1) / static_cast<double>(_alphaCount);
|
||||
_alphaCount++;
|
||||
if (_alphaCount > _alphaCountMax)
|
||||
{
|
||||
_alphaCount = _alphaCountMax;
|
||||
}
|
||||
double avgNoise = alpha * _avgNoise + (1 - alpha) * d_dT;
|
||||
double varNoise = alpha * _varNoise +
|
||||
(1 - alpha) * (d_dT - _avgNoise) * (d_dT - _avgNoise);
|
||||
if (!incompleteFrame || varNoise > _varNoise)
|
||||
{
|
||||
_avgNoise = avgNoise;
|
||||
_varNoise = varNoise;
|
||||
}
|
||||
if (_varNoise < 1.0)
|
||||
{
|
||||
// The variance should never be zero, since we might get
|
||||
// stuck and consider all samples as outliers.
|
||||
_varNoise = 1.0;
|
||||
void VCMJitterEstimator::EstimateRandomJitter(double d_dT,
|
||||
bool incompleteFrame) {
|
||||
uint64_t now = clock_->TimeInMicroseconds();
|
||||
if (_lastUpdateT != -1) {
|
||||
fps_counter_.AddSample(now - _lastUpdateT);
|
||||
}
|
||||
_lastUpdateT = now;
|
||||
|
||||
if (_alphaCount == 0) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
double alpha =
|
||||
static_cast<double>(_alphaCount - 1) / static_cast<double>(_alphaCount);
|
||||
_alphaCount++;
|
||||
if (_alphaCount > _alphaCountMax)
|
||||
_alphaCount = _alphaCountMax;
|
||||
|
||||
if (LowRateExperimentEnabled()) {
|
||||
// In order to avoid a low frame rate stream to react slower to changes,
|
||||
// scale the alpha weight relative a 30 fps stream.
|
||||
double fps = GetFrameRate();
|
||||
if (fps > 0.0) {
|
||||
double rate_scale = 30.0 / fps;
|
||||
// At startup, there can be a lot of noise in the fps estimate.
|
||||
// Interpolate rate_scale linearly, from 1.0 at sample #1, to 30.0 / fps
|
||||
// at sample #kStartupDelaySamples.
|
||||
if (_alphaCount < kStartupDelaySamples) {
|
||||
rate_scale =
|
||||
(_alphaCount * rate_scale + (kStartupDelaySamples - _alphaCount)) /
|
||||
kStartupDelaySamples;
|
||||
}
|
||||
alpha = pow(alpha, rate_scale);
|
||||
}
|
||||
}
|
||||
|
||||
double avgNoise = alpha * _avgNoise + (1 - alpha) * d_dT;
|
||||
double varNoise =
|
||||
alpha * _varNoise + (1 - alpha) * (d_dT - _avgNoise) * (d_dT - _avgNoise);
|
||||
if (!incompleteFrame || varNoise > _varNoise) {
|
||||
_avgNoise = avgNoise;
|
||||
_varNoise = varNoise;
|
||||
}
|
||||
if (_varNoise < 1.0) {
|
||||
// The variance should never be zero, since we might get
|
||||
// stuck and consider all samples as outliers.
|
||||
_varNoise = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
double
|
||||
@ -387,19 +422,61 @@ VCMJitterEstimator::UpdateMaxFrameSize(uint32_t frameSizeBytes)
|
||||
|
||||
// Returns the current filtered estimate if available,
|
||||
// otherwise tries to calculate an estimate.
|
||||
int
|
||||
VCMJitterEstimator::GetJitterEstimate(double rttMultiplier)
|
||||
{
|
||||
double jitterMS = CalculateEstimate() + OPERATING_SYSTEM_JITTER;
|
||||
if (_filterJitterEstimate > jitterMS)
|
||||
{
|
||||
jitterMS = _filterJitterEstimate;
|
||||
int VCMJitterEstimator::GetJitterEstimate(double rttMultiplier) {
|
||||
double jitterMS = CalculateEstimate() + OPERATING_SYSTEM_JITTER;
|
||||
if (_filterJitterEstimate > jitterMS)
|
||||
jitterMS = _filterJitterEstimate;
|
||||
if (_nackCount >= _nackLimit)
|
||||
jitterMS += _rttFilter.RttMs() * rttMultiplier;
|
||||
|
||||
if (LowRateExperimentEnabled()) {
|
||||
static const double kJitterScaleLowThreshold = 5.0;
|
||||
static const double kJitterScaleHighThreshold = 10.0;
|
||||
double fps = GetFrameRate();
|
||||
// Ignore jitter for very low fps streams.
|
||||
if (fps < kJitterScaleLowThreshold) {
|
||||
if (fps == 0.0) {
|
||||
return jitterMS;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (_nackCount >= _nackLimit)
|
||||
{
|
||||
jitterMS += _rttFilter.RttMs() * rttMultiplier;
|
||||
|
||||
// Semi-low frame rate; scale by factor linearly interpolated from 0.0 at
|
||||
// kJitterScaleLowThreshold to 1.0 at kJitterScaleHighThreshold.
|
||||
if (fps < kJitterScaleHighThreshold) {
|
||||
jitterMS =
|
||||
(1.0 / (kJitterScaleHighThreshold - kJitterScaleLowThreshold)) *
|
||||
(fps - kJitterScaleLowThreshold) * jitterMS;
|
||||
}
|
||||
return static_cast<uint32_t>(jitterMS + 0.5);
|
||||
}
|
||||
|
||||
return static_cast<uint32_t>(jitterMS + 0.5);
|
||||
}
|
||||
|
||||
bool VCMJitterEstimator::LowRateExperimentEnabled() {
|
||||
if (low_rate_experiment_ == kInit) {
|
||||
std::string group =
|
||||
webrtc::field_trial::FindFullName("WebRTC-ReducedJitterDelay");
|
||||
if (group == "Disabled") {
|
||||
low_rate_experiment_ = kDisabled;
|
||||
} else {
|
||||
low_rate_experiment_ = kEnabled;
|
||||
}
|
||||
}
|
||||
return low_rate_experiment_ == kEnabled ? true : false;
|
||||
}
|
||||
|
||||
double VCMJitterEstimator::GetFrameRate() const {
|
||||
if (fps_counter_.count() == 0)
|
||||
return 0;
|
||||
|
||||
double fps = 1000000.0 / fps_counter_.ComputeMean();
|
||||
// Sanity check.
|
||||
assert(fps >= 0.0);
|
||||
if (fps > kMaxFramerateEstimate) {
|
||||
fps = kMaxFramerateEstimate;
|
||||
}
|
||||
return fps;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -11,17 +11,22 @@
|
||||
#ifndef WEBRTC_MODULES_VIDEO_CODING_JITTER_ESTIMATOR_H_
|
||||
#define WEBRTC_MODULES_VIDEO_CODING_JITTER_ESTIMATOR_H_
|
||||
|
||||
#include "webrtc/base/rollingaccumulator.h"
|
||||
#include "webrtc/modules/video_coding/main/source/rtt_filter.h"
|
||||
#include "webrtc/typedefs.h"
|
||||
|
||||
namespace webrtc
|
||||
{
|
||||
|
||||
class Clock;
|
||||
|
||||
class VCMJitterEstimator
|
||||
{
|
||||
public:
|
||||
VCMJitterEstimator(int32_t vcmId = 0, int32_t receiverId = 0);
|
||||
|
||||
VCMJitterEstimator(const Clock* clock,
|
||||
int32_t vcmId = 0,
|
||||
int32_t receiverId = 0);
|
||||
virtual ~VCMJitterEstimator();
|
||||
VCMJitterEstimator& operator=(const VCMJitterEstimator& rhs);
|
||||
|
||||
// Resets the estimate to the initial state
|
||||
@ -68,6 +73,8 @@ protected:
|
||||
double _theta[2]; // Estimated line parameters (slope, offset)
|
||||
double _varNoise; // Variance of the time-deviation from the line
|
||||
|
||||
virtual bool LowRateExperimentEnabled();
|
||||
|
||||
private:
|
||||
// Updates the Kalman filter for the line describing
|
||||
// the frame size dependent jitter.
|
||||
@ -109,6 +116,8 @@ private:
|
||||
double DeviationFromExpectedDelay(int64_t frameDelayMS,
|
||||
int32_t deltaFSBytes) const;
|
||||
|
||||
double GetFrameRate() const;
|
||||
|
||||
// Constants, filter parameters
|
||||
int32_t _vcmId;
|
||||
int32_t _receiverId;
|
||||
@ -145,8 +154,10 @@ private:
|
||||
// but never goes above _nackLimit
|
||||
VCMRttFilter _rttFilter;
|
||||
|
||||
enum { kStartupDelaySamples = 30 };
|
||||
enum { kFsAccuStartupSamples = 5 };
|
||||
rtc::RollingAccumulator<uint64_t> fps_counter_;
|
||||
enum ExperimentFlag { kInit, kEnabled, kDisabled };
|
||||
ExperimentFlag low_rate_experiment_;
|
||||
const Clock* clock_;
|
||||
};
|
||||
|
||||
} // namespace webrtc
|
||||
|
@ -0,0 +1,160 @@
|
||||
/* Copyright (c) 2014 The WebRTC project authors. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by a BSD-style license
|
||||
* that can be found in the LICENSE file in the root of the source
|
||||
* tree. An additional intellectual property rights grant can be found
|
||||
* in the file PATENTS. All contributing project authors may
|
||||
* be found in the AUTHORS file in the root of the source tree.
|
||||
*/
|
||||
|
||||
#include "webrtc/modules/video_coding/main/source/jitter_estimator.h"
|
||||
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "webrtc/system_wrappers/interface/clock.h"
|
||||
|
||||
namespace webrtc {
|
||||
|
||||
class TestEstimator : public VCMJitterEstimator {
|
||||
public:
|
||||
explicit TestEstimator(bool exp_enabled)
|
||||
: VCMJitterEstimator(&fake_clock_, 0, 0),
|
||||
fake_clock_(0),
|
||||
exp_enabled_(exp_enabled) {}
|
||||
|
||||
virtual bool LowRateExperimentEnabled() { return exp_enabled_; }
|
||||
|
||||
void AdvanceClock(int64_t microseconds) {
|
||||
fake_clock_.AdvanceTimeMicroseconds(microseconds);
|
||||
}
|
||||
|
||||
private:
|
||||
SimulatedClock fake_clock_;
|
||||
const bool exp_enabled_;
|
||||
};
|
||||
|
||||
class TestVCMJitterEstimator : public ::testing::Test {
|
||||
protected:
|
||||
TestVCMJitterEstimator()
|
||||
: regular_estimator_(false), low_rate_estimator_(true) {}
|
||||
|
||||
virtual void SetUp() { regular_estimator_.Reset(); }
|
||||
|
||||
TestEstimator regular_estimator_;
|
||||
TestEstimator low_rate_estimator_;
|
||||
};
|
||||
|
||||
// Generates some simple test data in the form of a sawtooth wave.
|
||||
class ValueGenerator {
|
||||
public:
|
||||
ValueGenerator(int32_t amplitude) : amplitude_(amplitude), counter_(0) {}
|
||||
virtual ~ValueGenerator() {}
|
||||
|
||||
int64_t Delay() { return ((counter_ % 11) - 5) * amplitude_; }
|
||||
|
||||
uint32_t FrameSize() { return 1000 + Delay(); }
|
||||
|
||||
void Advance() { ++counter_; }
|
||||
|
||||
private:
|
||||
const int32_t amplitude_;
|
||||
int64_t counter_;
|
||||
};
|
||||
|
||||
// 5 fps, disable jitter delay altogether.
|
||||
TEST_F(TestVCMJitterEstimator, TestLowRate) {
|
||||
ValueGenerator gen(10);
|
||||
uint64_t time_delta = 1000000 / 5;
|
||||
for (int i = 0; i < 60; ++i) {
|
||||
regular_estimator_.UpdateEstimate(gen.Delay(), gen.FrameSize());
|
||||
regular_estimator_.AdvanceClock(time_delta);
|
||||
low_rate_estimator_.UpdateEstimate(gen.Delay(), gen.FrameSize());
|
||||
low_rate_estimator_.AdvanceClock(time_delta);
|
||||
EXPECT_GT(regular_estimator_.GetJitterEstimate(0), 0);
|
||||
if (i > 2)
|
||||
EXPECT_EQ(low_rate_estimator_.GetJitterEstimate(0), 0);
|
||||
gen.Advance();
|
||||
}
|
||||
}
|
||||
|
||||
// 8 fps, steady state estimate should be in interpolated interval between 0
|
||||
// and value of previous method.
|
||||
TEST_F(TestVCMJitterEstimator, TestMidRate) {
|
||||
ValueGenerator gen(10);
|
||||
uint64_t time_delta = 1000000 / 8;
|
||||
for (int i = 0; i < 60; ++i) {
|
||||
regular_estimator_.UpdateEstimate(gen.Delay(), gen.FrameSize());
|
||||
regular_estimator_.AdvanceClock(time_delta);
|
||||
low_rate_estimator_.UpdateEstimate(gen.Delay(), gen.FrameSize());
|
||||
low_rate_estimator_.AdvanceClock(time_delta);
|
||||
EXPECT_GT(regular_estimator_.GetJitterEstimate(0), 0);
|
||||
EXPECT_GT(low_rate_estimator_.GetJitterEstimate(0), 0);
|
||||
EXPECT_GE(regular_estimator_.GetJitterEstimate(0),
|
||||
low_rate_estimator_.GetJitterEstimate(0));
|
||||
gen.Advance();
|
||||
}
|
||||
}
|
||||
|
||||
// 30 fps, steady state estimate should be same as previous method.
|
||||
TEST_F(TestVCMJitterEstimator, TestHighRate) {
|
||||
ValueGenerator gen(10);
|
||||
uint64_t time_delta = 1000000 / 30;
|
||||
for (int i = 0; i < 60; ++i) {
|
||||
regular_estimator_.UpdateEstimate(gen.Delay(), gen.FrameSize());
|
||||
regular_estimator_.AdvanceClock(time_delta);
|
||||
low_rate_estimator_.UpdateEstimate(gen.Delay(), gen.FrameSize());
|
||||
low_rate_estimator_.AdvanceClock(time_delta);
|
||||
EXPECT_EQ(regular_estimator_.GetJitterEstimate(0),
|
||||
low_rate_estimator_.GetJitterEstimate(0));
|
||||
gen.Advance();
|
||||
}
|
||||
}
|
||||
|
||||
// 10 fps, high jitter then low jitter. Low rate estimator should converge
|
||||
// faster to low noise estimate.
|
||||
TEST_F(TestVCMJitterEstimator, TestConvergence) {
|
||||
// Reach a steady state with high noise.
|
||||
ValueGenerator gen(50);
|
||||
uint64_t time_delta = 1000000 / 10;
|
||||
for (int i = 0; i < 100; ++i) {
|
||||
regular_estimator_.UpdateEstimate(gen.Delay(), gen.FrameSize());
|
||||
regular_estimator_.AdvanceClock(time_delta * 2);
|
||||
low_rate_estimator_.UpdateEstimate(gen.Delay(), gen.FrameSize());
|
||||
low_rate_estimator_.AdvanceClock(time_delta * 2);
|
||||
gen.Advance();
|
||||
}
|
||||
|
||||
int threshold = regular_estimator_.GetJitterEstimate(0) / 2;
|
||||
|
||||
// New generator with zero noise.
|
||||
ValueGenerator low_gen(0);
|
||||
int regular_iterations = 0;
|
||||
int low_rate_iterations = 0;
|
||||
for (int i = 0; i < 500; ++i) {
|
||||
if (regular_iterations == 0) {
|
||||
regular_estimator_.UpdateEstimate(low_gen.Delay(), low_gen.FrameSize());
|
||||
regular_estimator_.AdvanceClock(time_delta);
|
||||
if (regular_estimator_.GetJitterEstimate(0) < threshold) {
|
||||
regular_iterations = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (low_rate_iterations == 0) {
|
||||
low_rate_estimator_.UpdateEstimate(low_gen.Delay(), low_gen.FrameSize());
|
||||
low_rate_estimator_.AdvanceClock(time_delta);
|
||||
if (low_rate_estimator_.GetJitterEstimate(0) < threshold) {
|
||||
low_rate_iterations = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (regular_iterations != 0 && low_rate_iterations != 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
gen.Advance();
|
||||
}
|
||||
|
||||
EXPECT_NE(regular_iterations, 0);
|
||||
EXPECT_NE(low_rate_iterations, 0);
|
||||
EXPECT_LE(low_rate_iterations, regular_iterations);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user