webrtc/modules/rtp_rtcp/source/overuse_detector.cc

540 lines
15 KiB
C++
Raw Normal View History

/*
* Copyright (c) 2011 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 "tick_util.h"
#include "trace.h"
#include "overuse_detector.h"
#include "remote_rate_control.h"
#include <math.h>
#include <stdlib.h> //abs
#ifdef MATLAB
extern MatlabEngine eng; // global variable defined elsewhere
#endif
#define INIT_CAPACITY_SLOPE 8.0/512.0
#define DETECTOR_THRESHOLD 25.0
#define OVER_USING_TIME_THRESHOLD 100
#define MIN_FRAME_PERIOD_HISTORY_LEN 60
namespace webrtc {
OverUseDetector::OverUseDetector()
:
_firstPacket(true),
_currentFrame(),
_prevFrame(),
_numOfDeltas(0),
_slope(INIT_CAPACITY_SLOPE),
_offset(0),
_E(),
_processNoise(),
_avgNoise(0.0),
_varNoise(500),
_threshold(DETECTOR_THRESHOLD),
_tsDeltaHist(),
_prevOffset(0.0),
_timeOverUsing(-1),
_overUseCounter(0),
_hypothesis(kBwNormal)
#ifdef DEBUG_FILE
,_debugFile(NULL)
#endif
#ifdef MATLAB
,_plot1(NULL),
_plot2(NULL),
_plot3(NULL),
_plot4(NULL)
#endif
{
_E[0][0] = 100;
_E[1][1] = 1e-1;
_E[0][1] = _E[1][0] = 0;
_processNoise[0] = 1e-10;
_processNoise[1] = 1e-2;
#ifdef DEBUG_FILE
_debugFile = fopen("detectorData.txt", "w");
if (_debugFile)
fprintf(_debugFile, "data = [\n");
#endif
}
OverUseDetector::~OverUseDetector()
{
#ifdef DEBUG_FILE
if (_debugFile) {
fprintf(_debugFile, "];\n");
fclose(_debugFile);
}
#endif
#ifdef MATLAB
if (_plot1)
{
eng.DeletePlot(_plot1);
_plot1 = NULL;
}
if (_plot2)
{
eng.DeletePlot(_plot2);
_plot2 = NULL;
}
if (_plot3)
{
eng.DeletePlot(_plot3);
_plot3 = NULL;
}
if (_plot4)
{
eng.DeletePlot(_plot4);
_plot4 = NULL;
}
#endif
while (!_tsDeltaHist.Empty())
{
ListItem* item = _tsDeltaHist.First();
delete static_cast<double*>(item->GetItem());
_tsDeltaHist.Erase(item);
}
}
void OverUseDetector::Reset()
{
_firstPacket = true;
_currentFrame._size = 0;
_currentFrame._completeTimeMs = -1;
_currentFrame._timestamp = -1;
_prevFrame._size = 0;
_prevFrame._completeTimeMs = -1;
_prevFrame._timestamp = -1;
_numOfDeltas = 0;
_slope = INIT_CAPACITY_SLOPE;
_offset = 0;
_E[0][0] = 100;
_E[1][1] = 1e-1;
_E[0][1] = _E[1][0] = 0;
_processNoise[0] = 1e-10;
_processNoise[1] = 1e-2;
_avgNoise = 0.0;
_varNoise = 500;
_threshold = DETECTOR_THRESHOLD;
_prevOffset = 0.0;
_timeOverUsing = -1;
_overUseCounter = 0;
_hypothesis = kBwNormal;
while (!_tsDeltaHist.Empty())
{
ListItem* item = _tsDeltaHist.First();
delete static_cast<double*>(item->GetItem());
_tsDeltaHist.Erase(item);
}
}
bool OverUseDetector::Update(const WebRtcRTPHeader& rtpHeader, const WebRtc_UWord16 packetSize)
{
#ifdef MATLAB
// Create plots
const WebRtc_Word64 startTimeMs = TickTime::MillisecondTimestamp();
if (_plot1 == NULL)
{
_plot1 = eng.NewPlot(new MatlabPlot());
_plot1->AddLine(1000, "b.", "scatter");
}
if (_plot2 == NULL)
{
_plot2 = eng.NewPlot(new MatlabPlot());
_plot2->AddTimeLine(30, "b", "offset", startTimeMs);
_plot2->AddTimeLine(30, "r--", "limitPos", startTimeMs);
_plot2->AddTimeLine(30, "k.", "trigger", startTimeMs);
_plot2->AddTimeLine(30, "ko", "detection", startTimeMs);
//_plot2->AddTimeLine(30, "g", "slowMean", startTimeMs);
}
if (_plot3 == NULL)
{
_plot3 = eng.NewPlot(new MatlabPlot());
_plot3->AddTimeLine(30, "b", "noiseVar", startTimeMs);
}
if (_plot4 == NULL)
{
_plot4 = eng.NewPlot(new MatlabPlot());
//_plot4->AddTimeLine(60, "b", "p11", startTimeMs);
//_plot4->AddTimeLine(60, "r", "p12", startTimeMs);
_plot4->AddTimeLine(60, "g", "p22", startTimeMs);
//_plot4->AddTimeLine(60, "g--", "p22_hat", startTimeMs);
//_plot4->AddTimeLine(30, "b.-", "deltaFs", startTimeMs);
}
#endif
bool wrapped = false;
bool completeFrame = false;
const WebRtc_Word64 nowMs = TickTime::MillisecondTimestamp();
if (_currentFrame._timestamp == -1)
{
_currentFrame._timestamp = rtpHeader.header.timestamp;
}
else if (OldTimestamp(rtpHeader.header.timestamp, static_cast<WebRtc_UWord32>(_currentFrame._timestamp), wrapped))
{
// Don't update with old data
return completeFrame;
}
else if (rtpHeader.header.timestamp != _currentFrame._timestamp)
{
// First packet of a later frame, the previous frame sample is ready
WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, -1, "Frame complete at %I64i", _currentFrame._completeTimeMs);
if (_prevFrame._completeTimeMs >= 0) // This is our second frame
{
WebRtc_Word64 tDelta = 0;
double tsDelta = 0;
// Check for wrap
OldTimestamp(static_cast<WebRtc_UWord32>(_prevFrame._timestamp), static_cast<WebRtc_UWord32>(_currentFrame._timestamp), wrapped);
CompensatedTimeDelta(_currentFrame, _prevFrame, tDelta, tsDelta, wrapped);
UpdateKalman(tDelta, tsDelta, _currentFrame._size, _prevFrame._size);
}
// The new timestamp is now the current frame,
// and the old timestamp becomes the previous frame.
_prevFrame = _currentFrame;
_currentFrame._timestamp = rtpHeader.header.timestamp;
_currentFrame._size = 0;
_currentFrame._completeTimeMs = -1;
completeFrame = true;
}
// Accumulate the frame size
_currentFrame._size += packetSize;
_currentFrame._completeTimeMs = nowMs;
return completeFrame;
}
BandwidthUsage OverUseDetector::State() const
{
#ifdef _DEBUG
char logStr[256];
static BandwidthUsage oldState = kBwNormal;
if (_hypothesis != oldState)
{
switch(_hypothesis)
{
case kBwOverusing:
{
#ifdef _WIN32
_snprintf(logStr,256, "State: OVER-USING\n");
#else
snprintf(logStr,256, "State: OVER-USING\n");
#endif
break;
}
case kBwUnderUsing:
{
#ifdef _WIN32
_snprintf(logStr,256, "State: UNDER-USING\n");
#else
snprintf(logStr,256, "State: UNDER-USING\n");
#endif
break;
}
case kBwNormal:
{
#ifdef _WIN32
_snprintf(logStr,256, "State: NORMAL\n");
#else
snprintf(logStr,256, "State: NORMAL\n");
#endif
break;
}
}
#ifdef _WIN32
OutputDebugStringA(logStr);
#else
//TODO
#endif
oldState = _hypothesis;
}
#endif
return _hypothesis;
}
double OverUseDetector::NoiseVar() const
{
return _varNoise;
}
void OverUseDetector::SetRateControlRegion(RateControlRegion region)
{
switch (region)
{
case kRcMaxUnknown:
{
_threshold = DETECTOR_THRESHOLD;
break;
}
case kRcAboveMax:
case kRcNearMax:
{
_threshold = DETECTOR_THRESHOLD / 2;
break;
}
}
}
void OverUseDetector::CompensatedTimeDelta(const FrameSample& currentFrame, const FrameSample& prevFrame, WebRtc_Word64& tDelta,
double& tsDelta, bool wrapped)
{
_numOfDeltas++;
if (_numOfDeltas > 1000)
{
_numOfDeltas = 1000;
}
// Add wrap-around compensation
WebRtc_Word64 wrapCompensation = 0;
if (wrapped)
{
wrapCompensation = static_cast<WebRtc_Word64>(1)<<32;
}
tsDelta = (currentFrame._timestamp + wrapCompensation - prevFrame._timestamp) / 90.0;
tDelta = currentFrame._completeTimeMs - prevFrame._completeTimeMs;
assert(tsDelta > 0);
}
double OverUseDetector::CurrentDrift()
{
return 1.0;
}
void OverUseDetector::UpdateKalman(WebRtc_Word64 tDelta, double tsDelta, WebRtc_UWord32 frameSize, WebRtc_UWord32 prevFrameSize)
{
const double minFramePeriod = UpdateMinFramePeriod(tsDelta);
const double drift = CurrentDrift();
// Compensate for drift
const double tTsDelta = tDelta - tsDelta / drift;
double fsDelta = static_cast<double>(frameSize) - prevFrameSize;
#ifdef DEBUG_FILE
if (_debugFile) {
fprintf(_debugFile, "%I64i %f %I64i %lu;\n", tDelta, tsDelta, static_cast<WebRtc_Word64>(fsDelta), frameSize);
fflush(_debugFile);
}
#endif
// Update the Kalman filter
const double scaleFactor = minFramePeriod / (1000.0 / 30.0);
_E[0][0] += _processNoise[0] * scaleFactor;
_E[1][1] += _processNoise[1] * scaleFactor;
if ((_hypothesis == kBwOverusing && _offset < _prevOffset) ||
(_hypothesis == kBwUnderUsing && _offset > _prevOffset))
{
_E[1][1] += 10 * _processNoise[1] * scaleFactor;
}
const double h[2] = {fsDelta, 1.0};
const double Eh[2] = {_E[0][0]*h[0] + _E[0][1]*h[1],
_E[1][0]*h[0] + _E[1][1]*h[1]};
const double residual = tTsDelta - _slope*h[0] - _offset;
const bool stableState = (BWE_MIN(_numOfDeltas, 60) * abs(_offset) < _threshold);
// We try to filter out very late frames. For instance periodic key
// frames doesn't fit the Gaussian model well.
if (abs(residual) < 3 * sqrt(_varNoise))
{
UpdateNoiseEstimate(residual, minFramePeriod, stableState);
}
else
{
UpdateNoiseEstimate(3 * sqrt(_varNoise), minFramePeriod, stableState);
}
const double denom = _varNoise + h[0]*Eh[0] + h[1]*Eh[1];
const double K[2] = {Eh[0] / denom,
Eh[1] / denom};
const double IKh[2][2] = {{1.0 - K[0]*h[0], -K[0]*h[1]},
{-K[1]*h[0], 1.0 - K[1]*h[1]}};
const double e00 = _E[0][0];
const double e01 = _E[0][1];
// Update state
_E[0][0] = e00 * IKh[0][0] + _E[1][0] * IKh[0][1];
_E[0][1] = e01 * IKh[0][0] + _E[1][1] * IKh[0][1];
_E[1][0] = e00 * IKh[1][0] + _E[1][0] * IKh[1][1];
_E[1][1] = e01 * IKh[1][0] + _E[1][1] * IKh[1][1];
// Covariance matrix, must be positive semi-definite
assert(_E[0][0] + _E[1][1] >= 0 &&
_E[0][0] * _E[1][1] - _E[0][1] * _E[1][0] >= 0 &&
_E[0][0] >= 0);
#ifdef MATLAB
//_plot4->Append("p11",_E[0][0]);
//_plot4->Append("p12",_E[0][1]);
_plot4->Append("p22",_E[1][1]);
//_plot4->Append("p22_hat", 0.5*(_processNoise[1] +
// sqrt(_processNoise[1]*(_processNoise[1] + 4*_varNoise))));
//_plot4->Append("deltaFs", fsDelta);
_plot4->Plot();
#endif
_slope = _slope + K[0] * residual;
_prevOffset = _offset;
_offset = _offset + K[1] * residual;
Detect(tsDelta);
#ifdef MATLAB
_plot1->Append("scatter", static_cast<double>(_currentFrame._size) - _prevFrame._size,
static_cast<double>(tDelta-tsDelta));
_plot1->MakeTrend("scatter", "slope", _slope, _offset, "k-");
_plot1->MakeTrend("scatter", "thresholdPos", _slope, _offset + 2 * sqrt(_varNoise), "r-");
_plot1->MakeTrend("scatter", "thresholdNeg", _slope, _offset - 2 * sqrt(_varNoise), "r-");
_plot1->Plot();
_plot2->Append("offset", _offset);
_plot2->Append("limitPos", _threshold/BWE_MIN(_numOfDeltas, 60));
_plot2->Plot();
_plot3->Append("noiseVar", _varNoise);
_plot3->Plot();
#endif
}
double OverUseDetector::UpdateMinFramePeriod(double tsDelta)
{
double minFramePeriod = tsDelta;
if (_tsDeltaHist.GetSize() >= MIN_FRAME_PERIOD_HISTORY_LEN)
{
ListItem* firstItem = _tsDeltaHist.First();
delete static_cast<double*>(firstItem->GetItem());
_tsDeltaHist.Erase(firstItem);
}
for (ListItem* item = _tsDeltaHist.First();
item != NULL;
item = _tsDeltaHist.Next(item))
{
const double* histDelta = static_cast<double*>(item->GetItem());
minFramePeriod = BWE_MIN(*histDelta, minFramePeriod);
}
_tsDeltaHist.PushBack(new double(tsDelta));
return minFramePeriod;
}
void OverUseDetector::UpdateNoiseEstimate(double residual, double tsDelta, bool stableState)
{
if (!stableState)
{
return;
}
// Faster filter during startup to faster adapt to the jitter level of the network
// alpha is tuned for 30 frames per second, but
double alpha = 0.01;
if (_numOfDeltas > 10*30)
{
alpha = 0.002;
}
// Only update the noise estimate if we're not over-using
// beta is a function of alpha and the time delta since
// the previous update.
const double beta = pow(1 - alpha, tsDelta * 30.0 / 1000.0);
_avgNoise = beta * _avgNoise + (1 - beta) * residual;
_varNoise = beta * _varNoise + (1 - beta) * (_avgNoise - residual) * (_avgNoise - residual);
if (_varNoise < 1e-7)
{
_varNoise = 1e-7;
}
}
BandwidthUsage OverUseDetector::Detect(double tsDelta)
{
if (_numOfDeltas < 2)
{
return kBwNormal;
}
const double T = BWE_MIN(_numOfDeltas, 60) * _offset;
if (abs(T) > _threshold)
{
if (_offset > 0)
{
if (_timeOverUsing == -1)
{
// Initialize the timer. Assume that we've been
// over-using half of the time since the previous
// sample.
_timeOverUsing = tsDelta / 2;
}
else
{
// Increment timer
_timeOverUsing += tsDelta;
}
_overUseCounter++;
if (_timeOverUsing > OVER_USING_TIME_THRESHOLD && _overUseCounter > 1)
{
if (_offset >= _prevOffset)
{
#ifdef _DEBUG
if (_hypothesis != kBwOverusing)
WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, -1, "BWE: kBwOverusing");
#endif
_timeOverUsing = 0;
_overUseCounter = 0;
_hypothesis = kBwOverusing;
#ifdef MATLAB
_plot2->Append("detection",_offset); // plot it later
#endif
}
}
#ifdef MATLAB
_plot2->Append("trigger",_offset); // plot it later
#endif
}
else
{
#ifdef _DEBUG
if (_hypothesis != kBwUnderUsing)
WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, -1, "BWE: kBwUnderUsing");
#endif
_timeOverUsing = -1;
_overUseCounter = 0;
_hypothesis = kBwUnderUsing;
}
}
else
{
#ifdef _DEBUG
if (_hypothesis != kBwNormal)
WEBRTC_TRACE(kTraceStream, kTraceRtpRtcp, -1, "BWE: kBwNormal");
#endif
_timeOverUsing = -1;
_overUseCounter = 0;
_hypothesis = kBwNormal;
}
return _hypothesis;
}
bool OverUseDetector::OldTimestamp(WebRtc_UWord32 newTimestamp, WebRtc_UWord32 existingTimestamp, bool& wrapped)
{
wrapped = (newTimestamp < 0x0000ffff && existingTimestamp > 0xffff0000) ||
(newTimestamp > 0xffff0000 && existingTimestamp < 0x0000ffff);
if (existingTimestamp > newTimestamp && !wrapped)
{
return true;
}
else if (existingTimestamp <= newTimestamp && !wrapped)
{
return false;
}
else if (existingTimestamp < newTimestamp && wrapped)
{
return true;
}
else
{
return false;
}
}
} // namespace webrtc