706 lines
24 KiB
C++
706 lines
24 KiB
C++
|
/*
|
||
|
* Copyright (c) 2012 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 "modules/video_coding/media_opt_util.h"
|
||
|
|
||
|
#include <assert.h>
|
||
|
#include <math.h>
|
||
|
|
||
|
#include <algorithm>
|
||
|
|
||
|
#include "modules/video_coding/fec_rate_table.h"
|
||
|
#include "modules/video_coding/internal_defines.h"
|
||
|
#include "modules/video_coding/utility/simulcast_rate_allocator.h"
|
||
|
#include "rtc_base/checks.h"
|
||
|
#include "rtc_base/experiments/rate_control_settings.h"
|
||
|
#include "rtc_base/numerics/safe_conversions.h"
|
||
|
|
||
|
namespace webrtc {
|
||
|
// Max value of loss rates in off-line model
|
||
|
static const int kPacketLossMax = 129;
|
||
|
|
||
|
namespace media_optimization {
|
||
|
|
||
|
VCMProtectionParameters::VCMProtectionParameters()
|
||
|
: rtt(0),
|
||
|
lossPr(0.0f),
|
||
|
bitRate(0.0f),
|
||
|
packetsPerFrame(0.0f),
|
||
|
packetsPerFrameKey(0.0f),
|
||
|
frameRate(0.0f),
|
||
|
keyFrameSize(0.0f),
|
||
|
fecRateDelta(0),
|
||
|
fecRateKey(0),
|
||
|
codecWidth(0),
|
||
|
codecHeight(0),
|
||
|
numLayers(1) {}
|
||
|
|
||
|
VCMProtectionMethod::VCMProtectionMethod()
|
||
|
: _effectivePacketLoss(0),
|
||
|
_protectionFactorK(0),
|
||
|
_protectionFactorD(0),
|
||
|
_scaleProtKey(2.0f),
|
||
|
_maxPayloadSize(1460),
|
||
|
_corrFecCost(1.0),
|
||
|
_type(kNone) {}
|
||
|
|
||
|
VCMProtectionMethod::~VCMProtectionMethod() {}
|
||
|
|
||
|
enum VCMProtectionMethodEnum VCMProtectionMethod::Type() const {
|
||
|
return _type;
|
||
|
}
|
||
|
|
||
|
uint8_t VCMProtectionMethod::RequiredPacketLossER() {
|
||
|
return _effectivePacketLoss;
|
||
|
}
|
||
|
|
||
|
uint8_t VCMProtectionMethod::RequiredProtectionFactorK() {
|
||
|
return _protectionFactorK;
|
||
|
}
|
||
|
|
||
|
uint8_t VCMProtectionMethod::RequiredProtectionFactorD() {
|
||
|
return _protectionFactorD;
|
||
|
}
|
||
|
|
||
|
bool VCMProtectionMethod::RequiredUepProtectionK() {
|
||
|
return _useUepProtectionK;
|
||
|
}
|
||
|
|
||
|
bool VCMProtectionMethod::RequiredUepProtectionD() {
|
||
|
return _useUepProtectionD;
|
||
|
}
|
||
|
|
||
|
int VCMProtectionMethod::MaxFramesFec() const {
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
VCMNackFecMethod::VCMNackFecMethod(int64_t lowRttNackThresholdMs,
|
||
|
int64_t highRttNackThresholdMs)
|
||
|
: VCMFecMethod(),
|
||
|
_lowRttNackMs(lowRttNackThresholdMs),
|
||
|
_highRttNackMs(highRttNackThresholdMs),
|
||
|
_maxFramesFec(1) {
|
||
|
assert(lowRttNackThresholdMs >= -1 && highRttNackThresholdMs >= -1);
|
||
|
assert(highRttNackThresholdMs == -1 ||
|
||
|
lowRttNackThresholdMs <= highRttNackThresholdMs);
|
||
|
assert(lowRttNackThresholdMs > -1 || highRttNackThresholdMs == -1);
|
||
|
_type = kNackFec;
|
||
|
}
|
||
|
|
||
|
VCMNackFecMethod::~VCMNackFecMethod() {
|
||
|
//
|
||
|
}
|
||
|
bool VCMNackFecMethod::ProtectionFactor(
|
||
|
const VCMProtectionParameters* parameters) {
|
||
|
// Hybrid Nack FEC has three operational modes:
|
||
|
// 1. Low RTT (below kLowRttNackMs) - Nack only: Set FEC rate
|
||
|
// (_protectionFactorD) to zero. -1 means no FEC.
|
||
|
// 2. High RTT (above _highRttNackMs) - FEC Only: Keep FEC factors.
|
||
|
// -1 means always allow NACK.
|
||
|
// 3. Medium RTT values - Hybrid mode: We will only nack the
|
||
|
// residual following the decoding of the FEC (refer to JB logic). FEC
|
||
|
// delta protection factor will be adjusted based on the RTT.
|
||
|
|
||
|
// Otherwise: we count on FEC; if the RTT is below a threshold, then we
|
||
|
// nack the residual, based on a decision made in the JB.
|
||
|
|
||
|
// Compute the protection factors
|
||
|
VCMFecMethod::ProtectionFactor(parameters);
|
||
|
if (_lowRttNackMs == -1 || parameters->rtt < _lowRttNackMs) {
|
||
|
_protectionFactorD = 0;
|
||
|
VCMFecMethod::UpdateProtectionFactorD(_protectionFactorD);
|
||
|
|
||
|
// When in Hybrid mode (RTT range), adjust FEC rates based on the
|
||
|
// RTT (NACK effectiveness) - adjustment factor is in the range [0,1].
|
||
|
} else if (_highRttNackMs == -1 || parameters->rtt < _highRttNackMs) {
|
||
|
// TODO(mikhal): Disabling adjustment temporarily.
|
||
|
// uint16_t rttIndex = (uint16_t) parameters->rtt;
|
||
|
float adjustRtt = 1.0f; // (float)VCMNackFecTable[rttIndex] / 100.0f;
|
||
|
|
||
|
// Adjust FEC with NACK on (for delta frame only)
|
||
|
// table depends on RTT relative to rttMax (NACK Threshold)
|
||
|
_protectionFactorD = rtc::saturated_cast<uint8_t>(
|
||
|
adjustRtt * rtc::saturated_cast<float>(_protectionFactorD));
|
||
|
// update FEC rates after applying adjustment
|
||
|
VCMFecMethod::UpdateProtectionFactorD(_protectionFactorD);
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
int VCMNackFecMethod::ComputeMaxFramesFec(
|
||
|
const VCMProtectionParameters* parameters) {
|
||
|
if (parameters->numLayers > 2) {
|
||
|
// For more than 2 temporal layers we will only have FEC on the base layer,
|
||
|
// and the base layers will be pretty far apart. Therefore we force one
|
||
|
// frame FEC.
|
||
|
return 1;
|
||
|
}
|
||
|
// We set the max number of frames to base the FEC on so that on average
|
||
|
// we will have complete frames in one RTT. Note that this is an upper
|
||
|
// bound, and that the actual number of frames used for FEC is decided by the
|
||
|
// RTP module based on the actual number of packets and the protection factor.
|
||
|
float base_layer_framerate =
|
||
|
parameters->frameRate /
|
||
|
rtc::saturated_cast<float>(1 << (parameters->numLayers - 1));
|
||
|
int max_frames_fec = std::max(
|
||
|
rtc::saturated_cast<int>(
|
||
|
2.0f * base_layer_framerate * parameters->rtt / 1000.0f + 0.5f),
|
||
|
1);
|
||
|
// |kUpperLimitFramesFec| is the upper limit on how many frames we
|
||
|
// allow any FEC to be based on.
|
||
|
if (max_frames_fec > kUpperLimitFramesFec) {
|
||
|
max_frames_fec = kUpperLimitFramesFec;
|
||
|
}
|
||
|
return max_frames_fec;
|
||
|
}
|
||
|
|
||
|
int VCMNackFecMethod::MaxFramesFec() const {
|
||
|
return _maxFramesFec;
|
||
|
}
|
||
|
|
||
|
bool VCMNackFecMethod::BitRateTooLowForFec(
|
||
|
const VCMProtectionParameters* parameters) {
|
||
|
// Bitrate below which we turn off FEC, regardless of reported packet loss.
|
||
|
// The condition should depend on resolution and content. For now, use
|
||
|
// threshold on bytes per frame, with some effect for the frame size.
|
||
|
// The condition for turning off FEC is also based on other factors,
|
||
|
// such as |_numLayers|, |_maxFramesFec|, and |_rtt|.
|
||
|
int estimate_bytes_per_frame = 1000 * BitsPerFrame(parameters) / 8;
|
||
|
int max_bytes_per_frame = kMaxBytesPerFrameForFec;
|
||
|
int num_pixels = parameters->codecWidth * parameters->codecHeight;
|
||
|
if (num_pixels <= 352 * 288) {
|
||
|
max_bytes_per_frame = kMaxBytesPerFrameForFecLow;
|
||
|
} else if (num_pixels > 640 * 480) {
|
||
|
max_bytes_per_frame = kMaxBytesPerFrameForFecHigh;
|
||
|
}
|
||
|
// TODO(marpan): add condition based on maximum frames used for FEC,
|
||
|
// and expand condition based on frame size.
|
||
|
// Max round trip time threshold in ms.
|
||
|
const int64_t kMaxRttTurnOffFec = 200;
|
||
|
if (estimate_bytes_per_frame < max_bytes_per_frame &&
|
||
|
parameters->numLayers < 3 && parameters->rtt < kMaxRttTurnOffFec) {
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool VCMNackFecMethod::EffectivePacketLoss(
|
||
|
const VCMProtectionParameters* parameters) {
|
||
|
// Set the effective packet loss for encoder (based on FEC code).
|
||
|
// Compute the effective packet loss and residual packet loss due to FEC.
|
||
|
VCMFecMethod::EffectivePacketLoss(parameters);
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool VCMNackFecMethod::UpdateParameters(
|
||
|
const VCMProtectionParameters* parameters) {
|
||
|
ProtectionFactor(parameters);
|
||
|
EffectivePacketLoss(parameters);
|
||
|
_maxFramesFec = ComputeMaxFramesFec(parameters);
|
||
|
if (BitRateTooLowForFec(parameters)) {
|
||
|
_protectionFactorK = 0;
|
||
|
_protectionFactorD = 0;
|
||
|
}
|
||
|
|
||
|
// Protection/fec rates obtained above are defined relative to total number
|
||
|
// of packets (total rate: source + fec) FEC in RTP module assumes
|
||
|
// protection factor is defined relative to source number of packets so we
|
||
|
// should convert the factor to reduce mismatch between mediaOpt's rate and
|
||
|
// the actual one
|
||
|
_protectionFactorK = VCMFecMethod::ConvertFECRate(_protectionFactorK);
|
||
|
_protectionFactorD = VCMFecMethod::ConvertFECRate(_protectionFactorD);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
VCMNackMethod::VCMNackMethod() : VCMProtectionMethod() {
|
||
|
_type = kNack;
|
||
|
}
|
||
|
|
||
|
VCMNackMethod::~VCMNackMethod() {
|
||
|
//
|
||
|
}
|
||
|
|
||
|
bool VCMNackMethod::EffectivePacketLoss(
|
||
|
const VCMProtectionParameters* parameter) {
|
||
|
// Effective Packet Loss, NA in current version.
|
||
|
_effectivePacketLoss = 0;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool VCMNackMethod::UpdateParameters(
|
||
|
const VCMProtectionParameters* parameters) {
|
||
|
// Compute the effective packet loss
|
||
|
EffectivePacketLoss(parameters);
|
||
|
|
||
|
// nackCost = (bitRate - nackCost) * (lossPr)
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
VCMFecMethod::VCMFecMethod()
|
||
|
: VCMProtectionMethod(),
|
||
|
rate_control_settings_(RateControlSettings::ParseFromFieldTrials()) {
|
||
|
_type = kFec;
|
||
|
}
|
||
|
|
||
|
VCMFecMethod::~VCMFecMethod() = default;
|
||
|
|
||
|
uint8_t VCMFecMethod::BoostCodeRateKey(uint8_t packetFrameDelta,
|
||
|
uint8_t packetFrameKey) const {
|
||
|
uint8_t boostRateKey = 2;
|
||
|
// Default: ratio scales the FEC protection up for I frames
|
||
|
uint8_t ratio = 1;
|
||
|
|
||
|
if (packetFrameDelta > 0) {
|
||
|
ratio = (int8_t)(packetFrameKey / packetFrameDelta);
|
||
|
}
|
||
|
ratio = VCM_MAX(boostRateKey, ratio);
|
||
|
|
||
|
return ratio;
|
||
|
}
|
||
|
|
||
|
uint8_t VCMFecMethod::ConvertFECRate(uint8_t codeRateRTP) const {
|
||
|
return rtc::saturated_cast<uint8_t>(
|
||
|
VCM_MIN(255, (0.5 + 255.0 * codeRateRTP /
|
||
|
rtc::saturated_cast<float>(255 - codeRateRTP))));
|
||
|
}
|
||
|
|
||
|
// Update FEC with protectionFactorD
|
||
|
void VCMFecMethod::UpdateProtectionFactorD(uint8_t protectionFactorD) {
|
||
|
_protectionFactorD = protectionFactorD;
|
||
|
}
|
||
|
|
||
|
// Update FEC with protectionFactorK
|
||
|
void VCMFecMethod::UpdateProtectionFactorK(uint8_t protectionFactorK) {
|
||
|
_protectionFactorK = protectionFactorK;
|
||
|
}
|
||
|
|
||
|
bool VCMFecMethod::ProtectionFactor(const VCMProtectionParameters* parameters) {
|
||
|
// FEC PROTECTION SETTINGS: varies with packet loss and bitrate
|
||
|
|
||
|
// No protection if (filtered) packetLoss is 0
|
||
|
uint8_t packetLoss = rtc::saturated_cast<uint8_t>(255 * parameters->lossPr);
|
||
|
if (packetLoss == 0) {
|
||
|
_protectionFactorK = 0;
|
||
|
_protectionFactorD = 0;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Parameters for FEC setting:
|
||
|
// first partition size, thresholds, table pars, spatial resoln fac.
|
||
|
|
||
|
// First partition protection: ~ 20%
|
||
|
uint8_t firstPartitionProt = rtc::saturated_cast<uint8_t>(255 * 0.20);
|
||
|
|
||
|
// Minimum protection level needed to generate one FEC packet for one
|
||
|
// source packet/frame (in RTP sender)
|
||
|
uint8_t minProtLevelFec = 85;
|
||
|
|
||
|
// Threshold on packetLoss and bitRrate/frameRate (=average #packets),
|
||
|
// above which we allocate protection to cover at least first partition.
|
||
|
uint8_t lossThr = 0;
|
||
|
uint8_t packetNumThr = 1;
|
||
|
|
||
|
// Parameters for range of rate index of table.
|
||
|
const uint8_t ratePar1 = 5;
|
||
|
const uint8_t ratePar2 = 49;
|
||
|
|
||
|
// Spatial resolution size, relative to a reference size.
|
||
|
float spatialSizeToRef = rtc::saturated_cast<float>(parameters->codecWidth *
|
||
|
parameters->codecHeight) /
|
||
|
(rtc::saturated_cast<float>(704 * 576));
|
||
|
// resolnFac: This parameter will generally increase/decrease the FEC rate
|
||
|
// (for fixed bitRate and packetLoss) based on system size.
|
||
|
// Use a smaller exponent (< 1) to control/soften system size effect.
|
||
|
const float resolnFac = 1.0 / powf(spatialSizeToRef, 0.3f);
|
||
|
|
||
|
const int bitRatePerFrame = BitsPerFrame(parameters);
|
||
|
|
||
|
// Average number of packets per frame (source and fec):
|
||
|
const uint8_t avgTotPackets = rtc::saturated_cast<uint8_t>(
|
||
|
1.5f + rtc::saturated_cast<float>(bitRatePerFrame) * 1000.0f /
|
||
|
rtc::saturated_cast<float>(8.0 * _maxPayloadSize));
|
||
|
|
||
|
// FEC rate parameters: for P and I frame
|
||
|
uint8_t codeRateDelta = 0;
|
||
|
uint8_t codeRateKey = 0;
|
||
|
|
||
|
// Get index for table: the FEC protection depends on an effective rate.
|
||
|
// The range on the rate index corresponds to rates (bps)
|
||
|
// from ~200k to ~8000k, for 30fps
|
||
|
const uint16_t effRateFecTable =
|
||
|
rtc::saturated_cast<uint16_t>(resolnFac * bitRatePerFrame);
|
||
|
uint8_t rateIndexTable = rtc::saturated_cast<uint8_t>(
|
||
|
VCM_MAX(VCM_MIN((effRateFecTable - ratePar1) / ratePar1, ratePar2), 0));
|
||
|
|
||
|
// Restrict packet loss range to 50:
|
||
|
// current tables defined only up to 50%
|
||
|
if (packetLoss >= kPacketLossMax) {
|
||
|
packetLoss = kPacketLossMax - 1;
|
||
|
}
|
||
|
uint16_t indexTable = rateIndexTable * kPacketLossMax + packetLoss;
|
||
|
|
||
|
// Check on table index
|
||
|
RTC_DCHECK_LT(indexTable, kFecRateTableSize);
|
||
|
|
||
|
// Protection factor for P frame
|
||
|
codeRateDelta = kFecRateTable[indexTable];
|
||
|
|
||
|
if (packetLoss > lossThr && avgTotPackets > packetNumThr) {
|
||
|
// Set a minimum based on first partition size.
|
||
|
if (codeRateDelta < firstPartitionProt) {
|
||
|
codeRateDelta = firstPartitionProt;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Check limit on amount of protection for P frame; 50% is max.
|
||
|
if (codeRateDelta >= kPacketLossMax) {
|
||
|
codeRateDelta = kPacketLossMax - 1;
|
||
|
}
|
||
|
|
||
|
// For Key frame:
|
||
|
// Effectively at a higher rate, so we scale/boost the rate
|
||
|
// The boost factor may depend on several factors: ratio of packet
|
||
|
// number of I to P frames, how much protection placed on P frames, etc.
|
||
|
const uint8_t packetFrameDelta =
|
||
|
rtc::saturated_cast<uint8_t>(0.5 + parameters->packetsPerFrame);
|
||
|
const uint8_t packetFrameKey =
|
||
|
rtc::saturated_cast<uint8_t>(0.5 + parameters->packetsPerFrameKey);
|
||
|
const uint8_t boostKey = BoostCodeRateKey(packetFrameDelta, packetFrameKey);
|
||
|
|
||
|
rateIndexTable = rtc::saturated_cast<uint8_t>(VCM_MAX(
|
||
|
VCM_MIN(1 + (boostKey * effRateFecTable - ratePar1) / ratePar1, ratePar2),
|
||
|
0));
|
||
|
uint16_t indexTableKey = rateIndexTable * kPacketLossMax + packetLoss;
|
||
|
|
||
|
indexTableKey = VCM_MIN(indexTableKey, kFecRateTableSize);
|
||
|
|
||
|
// Check on table index
|
||
|
assert(indexTableKey < kFecRateTableSize);
|
||
|
|
||
|
// Protection factor for I frame
|
||
|
codeRateKey = kFecRateTable[indexTableKey];
|
||
|
|
||
|
// Boosting for Key frame.
|
||
|
int boostKeyProt = _scaleProtKey * codeRateDelta;
|
||
|
if (boostKeyProt >= kPacketLossMax) {
|
||
|
boostKeyProt = kPacketLossMax - 1;
|
||
|
}
|
||
|
|
||
|
// Make sure I frame protection is at least larger than P frame protection,
|
||
|
// and at least as high as filtered packet loss.
|
||
|
codeRateKey = rtc::saturated_cast<uint8_t>(
|
||
|
VCM_MAX(packetLoss, VCM_MAX(boostKeyProt, codeRateKey)));
|
||
|
|
||
|
// Check limit on amount of protection for I frame: 50% is max.
|
||
|
if (codeRateKey >= kPacketLossMax) {
|
||
|
codeRateKey = kPacketLossMax - 1;
|
||
|
}
|
||
|
|
||
|
_protectionFactorK = codeRateKey;
|
||
|
_protectionFactorD = codeRateDelta;
|
||
|
|
||
|
// Generally there is a rate mis-match between the FEC cost estimated
|
||
|
// in mediaOpt and the actual FEC cost sent out in RTP module.
|
||
|
// This is more significant at low rates (small # of source packets), where
|
||
|
// the granularity of the FEC decreases. In this case, non-zero protection
|
||
|
// in mediaOpt may generate 0 FEC packets in RTP sender (since actual #FEC
|
||
|
// is based on rounding off protectionFactor on actual source packet number).
|
||
|
// The correction factor (_corrFecCost) attempts to corrects this, at least
|
||
|
// for cases of low rates (small #packets) and low protection levels.
|
||
|
|
||
|
float numPacketsFl =
|
||
|
1.0f + (rtc::saturated_cast<float>(bitRatePerFrame) * 1000.0 /
|
||
|
rtc::saturated_cast<float>(8.0 * _maxPayloadSize) +
|
||
|
0.5);
|
||
|
|
||
|
const float estNumFecGen =
|
||
|
0.5f +
|
||
|
rtc::saturated_cast<float>(_protectionFactorD * numPacketsFl / 255.0f);
|
||
|
|
||
|
// We reduce cost factor (which will reduce overhead for FEC and
|
||
|
// hybrid method) and not the protectionFactor.
|
||
|
_corrFecCost = 1.0f;
|
||
|
if (estNumFecGen < 1.1f && _protectionFactorD < minProtLevelFec) {
|
||
|
_corrFecCost = 0.5f;
|
||
|
}
|
||
|
if (estNumFecGen < 0.9f && _protectionFactorD < minProtLevelFec) {
|
||
|
_corrFecCost = 0.0f;
|
||
|
}
|
||
|
|
||
|
// DONE WITH FEC PROTECTION SETTINGS
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
int VCMFecMethod::BitsPerFrame(const VCMProtectionParameters* parameters) {
|
||
|
// When temporal layers are available FEC will only be applied on the base
|
||
|
// layer.
|
||
|
const float bitRateRatio =
|
||
|
webrtc::SimulcastRateAllocator::GetTemporalRateAllocation(
|
||
|
parameters->numLayers, 0,
|
||
|
rate_control_settings_.Vp8BaseHeavyTl3RateAllocation());
|
||
|
float frameRateRatio = powf(1 / 2.0, parameters->numLayers - 1);
|
||
|
float bitRate = parameters->bitRate * bitRateRatio;
|
||
|
float frameRate = parameters->frameRate * frameRateRatio;
|
||
|
|
||
|
// TODO(mikhal): Update factor following testing.
|
||
|
float adjustmentFactor = 1;
|
||
|
|
||
|
if (frameRate < 1.0f)
|
||
|
frameRate = 1.0f;
|
||
|
// Average bits per frame (units of kbits)
|
||
|
return rtc::saturated_cast<int>(adjustmentFactor * bitRate / frameRate);
|
||
|
}
|
||
|
|
||
|
bool VCMFecMethod::EffectivePacketLoss(
|
||
|
const VCMProtectionParameters* parameters) {
|
||
|
// Effective packet loss to encoder is based on RPL (residual packet loss)
|
||
|
// this is a soft setting based on degree of FEC protection
|
||
|
// RPL = received/input packet loss - average_FEC_recovery
|
||
|
// note: received/input packet loss may be filtered based on FilteredLoss
|
||
|
|
||
|
// Effective Packet Loss, NA in current version.
|
||
|
_effectivePacketLoss = 0;
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool VCMFecMethod::UpdateParameters(const VCMProtectionParameters* parameters) {
|
||
|
// Compute the protection factor
|
||
|
ProtectionFactor(parameters);
|
||
|
|
||
|
// Compute the effective packet loss
|
||
|
EffectivePacketLoss(parameters);
|
||
|
|
||
|
// Protection/fec rates obtained above is defined relative to total number
|
||
|
// of packets (total rate: source+fec) FEC in RTP module assumes protection
|
||
|
// factor is defined relative to source number of packets so we should
|
||
|
// convert the factor to reduce mismatch between mediaOpt suggested rate and
|
||
|
// the actual rate
|
||
|
_protectionFactorK = ConvertFECRate(_protectionFactorK);
|
||
|
_protectionFactorD = ConvertFECRate(_protectionFactorD);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
VCMLossProtectionLogic::VCMLossProtectionLogic(int64_t nowMs)
|
||
|
: _currentParameters(),
|
||
|
_rtt(0),
|
||
|
_lossPr(0.0f),
|
||
|
_bitRate(0.0f),
|
||
|
_frameRate(0.0f),
|
||
|
_keyFrameSize(0.0f),
|
||
|
_fecRateKey(0),
|
||
|
_fecRateDelta(0),
|
||
|
_lastPrUpdateT(0),
|
||
|
_lossPr255(0.9999f),
|
||
|
_lossPrHistory(),
|
||
|
_shortMaxLossPr255(0),
|
||
|
_packetsPerFrame(0.9999f),
|
||
|
_packetsPerFrameKey(0.9999f),
|
||
|
_codecWidth(704),
|
||
|
_codecHeight(576),
|
||
|
_numLayers(1) {
|
||
|
Reset(nowMs);
|
||
|
}
|
||
|
|
||
|
VCMLossProtectionLogic::~VCMLossProtectionLogic() {
|
||
|
Release();
|
||
|
}
|
||
|
|
||
|
void VCMLossProtectionLogic::SetMethod(
|
||
|
enum VCMProtectionMethodEnum newMethodType) {
|
||
|
if (_selectedMethod && _selectedMethod->Type() == newMethodType)
|
||
|
return;
|
||
|
|
||
|
switch (newMethodType) {
|
||
|
case kNack:
|
||
|
_selectedMethod.reset(new VCMNackMethod());
|
||
|
break;
|
||
|
case kFec:
|
||
|
_selectedMethod.reset(new VCMFecMethod());
|
||
|
break;
|
||
|
case kNackFec:
|
||
|
_selectedMethod.reset(new VCMNackFecMethod(kLowRttNackMs, -1));
|
||
|
break;
|
||
|
case kNone:
|
||
|
_selectedMethod.reset();
|
||
|
break;
|
||
|
}
|
||
|
UpdateMethod();
|
||
|
}
|
||
|
|
||
|
void VCMLossProtectionLogic::UpdateRtt(int64_t rtt) {
|
||
|
_rtt = rtt;
|
||
|
}
|
||
|
|
||
|
void VCMLossProtectionLogic::UpdateMaxLossHistory(uint8_t lossPr255,
|
||
|
int64_t now) {
|
||
|
if (_lossPrHistory[0].timeMs >= 0 &&
|
||
|
now - _lossPrHistory[0].timeMs < kLossPrShortFilterWinMs) {
|
||
|
if (lossPr255 > _shortMaxLossPr255) {
|
||
|
_shortMaxLossPr255 = lossPr255;
|
||
|
}
|
||
|
} else {
|
||
|
// Only add a new value to the history once a second
|
||
|
if (_lossPrHistory[0].timeMs == -1) {
|
||
|
// First, no shift
|
||
|
_shortMaxLossPr255 = lossPr255;
|
||
|
} else {
|
||
|
// Shift
|
||
|
for (int32_t i = (kLossPrHistorySize - 2); i >= 0; i--) {
|
||
|
_lossPrHistory[i + 1].lossPr255 = _lossPrHistory[i].lossPr255;
|
||
|
_lossPrHistory[i + 1].timeMs = _lossPrHistory[i].timeMs;
|
||
|
}
|
||
|
}
|
||
|
if (_shortMaxLossPr255 == 0) {
|
||
|
_shortMaxLossPr255 = lossPr255;
|
||
|
}
|
||
|
|
||
|
_lossPrHistory[0].lossPr255 = _shortMaxLossPr255;
|
||
|
_lossPrHistory[0].timeMs = now;
|
||
|
_shortMaxLossPr255 = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
uint8_t VCMLossProtectionLogic::MaxFilteredLossPr(int64_t nowMs) const {
|
||
|
uint8_t maxFound = _shortMaxLossPr255;
|
||
|
if (_lossPrHistory[0].timeMs == -1) {
|
||
|
return maxFound;
|
||
|
}
|
||
|
for (int32_t i = 0; i < kLossPrHistorySize; i++) {
|
||
|
if (_lossPrHistory[i].timeMs == -1) {
|
||
|
break;
|
||
|
}
|
||
|
if (nowMs - _lossPrHistory[i].timeMs >
|
||
|
kLossPrHistorySize * kLossPrShortFilterWinMs) {
|
||
|
// This sample (and all samples after this) is too old
|
||
|
break;
|
||
|
}
|
||
|
if (_lossPrHistory[i].lossPr255 > maxFound) {
|
||
|
// This sample is the largest one this far into the history
|
||
|
maxFound = _lossPrHistory[i].lossPr255;
|
||
|
}
|
||
|
}
|
||
|
return maxFound;
|
||
|
}
|
||
|
|
||
|
uint8_t VCMLossProtectionLogic::FilteredLoss(int64_t nowMs,
|
||
|
FilterPacketLossMode filter_mode,
|
||
|
uint8_t lossPr255) {
|
||
|
// Update the max window filter.
|
||
|
UpdateMaxLossHistory(lossPr255, nowMs);
|
||
|
|
||
|
// Update the recursive average filter.
|
||
|
_lossPr255.Apply(rtc::saturated_cast<float>(nowMs - _lastPrUpdateT),
|
||
|
rtc::saturated_cast<float>(lossPr255));
|
||
|
_lastPrUpdateT = nowMs;
|
||
|
|
||
|
// Filtered loss: default is received loss (no filtering).
|
||
|
uint8_t filtered_loss = lossPr255;
|
||
|
|
||
|
switch (filter_mode) {
|
||
|
case kNoFilter:
|
||
|
break;
|
||
|
case kAvgFilter:
|
||
|
filtered_loss = rtc::saturated_cast<uint8_t>(_lossPr255.filtered() + 0.5);
|
||
|
break;
|
||
|
case kMaxFilter:
|
||
|
filtered_loss = MaxFilteredLossPr(nowMs);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return filtered_loss;
|
||
|
}
|
||
|
|
||
|
void VCMLossProtectionLogic::UpdateFilteredLossPr(uint8_t packetLossEnc) {
|
||
|
_lossPr = rtc::saturated_cast<float>(packetLossEnc) / 255.0;
|
||
|
}
|
||
|
|
||
|
void VCMLossProtectionLogic::UpdateBitRate(float bitRate) {
|
||
|
_bitRate = bitRate;
|
||
|
}
|
||
|
|
||
|
void VCMLossProtectionLogic::UpdatePacketsPerFrame(float nPackets,
|
||
|
int64_t nowMs) {
|
||
|
_packetsPerFrame.Apply(
|
||
|
rtc::saturated_cast<float>(nowMs - _lastPacketPerFrameUpdateT), nPackets);
|
||
|
_lastPacketPerFrameUpdateT = nowMs;
|
||
|
}
|
||
|
|
||
|
void VCMLossProtectionLogic::UpdatePacketsPerFrameKey(float nPackets,
|
||
|
int64_t nowMs) {
|
||
|
_packetsPerFrameKey.Apply(
|
||
|
rtc::saturated_cast<float>(nowMs - _lastPacketPerFrameUpdateTKey),
|
||
|
nPackets);
|
||
|
_lastPacketPerFrameUpdateTKey = nowMs;
|
||
|
}
|
||
|
|
||
|
void VCMLossProtectionLogic::UpdateKeyFrameSize(float keyFrameSize) {
|
||
|
_keyFrameSize = keyFrameSize;
|
||
|
}
|
||
|
|
||
|
void VCMLossProtectionLogic::UpdateFrameSize(size_t width, size_t height) {
|
||
|
_codecWidth = width;
|
||
|
_codecHeight = height;
|
||
|
}
|
||
|
|
||
|
void VCMLossProtectionLogic::UpdateNumLayers(int numLayers) {
|
||
|
_numLayers = (numLayers == 0) ? 1 : numLayers;
|
||
|
}
|
||
|
|
||
|
bool VCMLossProtectionLogic::UpdateMethod() {
|
||
|
if (!_selectedMethod)
|
||
|
return false;
|
||
|
_currentParameters.rtt = _rtt;
|
||
|
_currentParameters.lossPr = _lossPr;
|
||
|
_currentParameters.bitRate = _bitRate;
|
||
|
_currentParameters.frameRate = _frameRate; // rename actual frame rate?
|
||
|
_currentParameters.keyFrameSize = _keyFrameSize;
|
||
|
_currentParameters.fecRateDelta = _fecRateDelta;
|
||
|
_currentParameters.fecRateKey = _fecRateKey;
|
||
|
_currentParameters.packetsPerFrame = _packetsPerFrame.filtered();
|
||
|
_currentParameters.packetsPerFrameKey = _packetsPerFrameKey.filtered();
|
||
|
_currentParameters.codecWidth = _codecWidth;
|
||
|
_currentParameters.codecHeight = _codecHeight;
|
||
|
_currentParameters.numLayers = _numLayers;
|
||
|
return _selectedMethod->UpdateParameters(&_currentParameters);
|
||
|
}
|
||
|
|
||
|
VCMProtectionMethod* VCMLossProtectionLogic::SelectedMethod() const {
|
||
|
return _selectedMethod.get();
|
||
|
}
|
||
|
|
||
|
VCMProtectionMethodEnum VCMLossProtectionLogic::SelectedType() const {
|
||
|
return _selectedMethod ? _selectedMethod->Type() : kNone;
|
||
|
}
|
||
|
|
||
|
void VCMLossProtectionLogic::Reset(int64_t nowMs) {
|
||
|
_lastPrUpdateT = nowMs;
|
||
|
_lastPacketPerFrameUpdateT = nowMs;
|
||
|
_lastPacketPerFrameUpdateTKey = nowMs;
|
||
|
_lossPr255.Reset(0.9999f);
|
||
|
_packetsPerFrame.Reset(0.9999f);
|
||
|
_fecRateDelta = _fecRateKey = 0;
|
||
|
for (int32_t i = 0; i < kLossPrHistorySize; i++) {
|
||
|
_lossPrHistory[i].lossPr255 = 0;
|
||
|
_lossPrHistory[i].timeMs = -1;
|
||
|
}
|
||
|
_shortMaxLossPr255 = 0;
|
||
|
Release();
|
||
|
}
|
||
|
|
||
|
void VCMLossProtectionLogic::Release() {
|
||
|
_selectedMethod.reset();
|
||
|
}
|
||
|
|
||
|
} // namespace media_optimization
|
||
|
} // namespace webrtc
|